From b3cdc98c402e1415d36439695a069f778528f462 Mon Sep 17 00:00:00 2001 From: sanjay7178 Date: Sun, 10 Aug 2025 17:54:30 +0530 Subject: [PATCH 1/9] feat(bookinfo-mtls): Add Istio configuration for Bookinfo application with mTLS support Signed-off-by: sanjay7178 --- kind/bookinfo-istio/DEPLOYMENT.md | 290 +++++++++++++++++ kind/bookinfo-istio/README.md | 294 ++++++++++++++++++ kind/bookinfo-istio/bookinfo.sh | 172 ++++++++++ kind/bookinfo-istio/check-prerequisites.sh | 146 +++++++++ kind/bookinfo-istio/config_files/details.yaml | 63 ++++ kind/bookinfo-istio/config_files/gateway.yaml | 43 +++ .../config_files/ingress-gw-install.yaml | 15 + .../config_files/istio-allow-ingress.yaml | 12 + .../config_files/istio-authorization.yaml | 73 +++++ .../config_files/istio-base.yaml | 23 ++ .../config_files/istio-destination-rule.yml | 32 ++ .../config_files/istio-discovery.yaml | 22 ++ .../config_files/istio-rbac.yaml | 12 + .../config_files/peer-authentication.yaml | 62 ++++ .../config_files/productpage.yaml | 75 +++++ kind/bookinfo-istio/config_files/ratings.yaml | 63 ++++ kind/bookinfo-istio/config_files/reviews.yaml | 76 +++++ .../config_files/serviceexports.yaml | 53 ++++ .../config_files/slice-config.yaml | 54 ++++ kind/bookinfo-istio/demo.sh | 173 +++++++++++ kind/bookinfo-istio/install-istio.sh | 161 ++++++++++ kind/bookinfo-istio/utils/bookinfo_test.sh | 188 +++++++++++ kind/bookinfo-istio/utils/verify_mtls.sh | 176 +++++++++++ 23 files changed, 2278 insertions(+) create mode 100644 kind/bookinfo-istio/DEPLOYMENT.md create mode 100644 kind/bookinfo-istio/README.md create mode 100755 kind/bookinfo-istio/bookinfo.sh create mode 100755 kind/bookinfo-istio/check-prerequisites.sh create mode 100644 kind/bookinfo-istio/config_files/details.yaml create mode 100644 kind/bookinfo-istio/config_files/gateway.yaml create mode 100644 kind/bookinfo-istio/config_files/ingress-gw-install.yaml create mode 100644 kind/bookinfo-istio/config_files/istio-allow-ingress.yaml create mode 100644 kind/bookinfo-istio/config_files/istio-authorization.yaml create mode 100644 kind/bookinfo-istio/config_files/istio-base.yaml create mode 100644 kind/bookinfo-istio/config_files/istio-destination-rule.yml create mode 100644 kind/bookinfo-istio/config_files/istio-discovery.yaml create mode 100644 kind/bookinfo-istio/config_files/istio-rbac.yaml create mode 100644 kind/bookinfo-istio/config_files/peer-authentication.yaml create mode 100644 kind/bookinfo-istio/config_files/productpage.yaml create mode 100644 kind/bookinfo-istio/config_files/ratings.yaml create mode 100644 kind/bookinfo-istio/config_files/reviews.yaml create mode 100644 kind/bookinfo-istio/config_files/serviceexports.yaml create mode 100644 kind/bookinfo-istio/config_files/slice-config.yaml create mode 100755 kind/bookinfo-istio/demo.sh create mode 100755 kind/bookinfo-istio/install-istio.sh create mode 100755 kind/bookinfo-istio/utils/bookinfo_test.sh create mode 100755 kind/bookinfo-istio/utils/verify_mtls.sh diff --git a/kind/bookinfo-istio/DEPLOYMENT.md b/kind/bookinfo-istio/DEPLOYMENT.md new file mode 100644 index 0000000..7ac8d4d --- /dev/null +++ b/kind/bookinfo-istio/DEPLOYMENT.md @@ -0,0 +1,290 @@ +# Deployment Guide: Bookinfo with Istio and mTLS + +This guide provides step-by-step instructions for deploying the Istio-enabled Bookinfo application across KubeSlice clusters. + +## Overview + +The deployment creates a secure, service mesh-enabled microservices application with: +- Cross-cluster communication via KubeSlice +- End-to-end mTLS encryption +- Automatic certificate management +- Traffic monitoring and observability + +## Prerequisites Setup + +### 1. Verify Environment +```bash +cd kind/bookinfo-istio +./check-prerequisites.sh +``` + +Expected output should show: +- ✓ kubectl is available +- ✓ Can list namespaces +- ✓ Found kind.env configuration file +- ✓ Product cluster (kind-worker-1) is accessible +- ✓ Services cluster (kind-worker-2) is accessible + +### 2. Cluster Requirements +Each cluster should have: +- Kubernetes 1.21+ +- 2+ CPU cores available +- 4GB+ RAM available +- KubeSlice operator installed and configured + +## Installation Steps + +### Step 1: Install Istio (if needed) +```bash +# Check if Istio is already installed +kubectl get pods -n istio-system + +# If not installed, run: +./install-istio.sh +``` + +This script will: +- Install Istio base components +- Deploy Istio control plane (istiod) +- Configure mTLS policies +- Verify installation + +### Step 2: Deploy Bookinfo Application +```bash +./bookinfo.sh +``` + +The deployment process: +1. ✅ Checks Istio installation on both clusters +2. ✅ Creates bookinfo namespace with sidecar injection enabled +3. ✅ Deploys productpage service on cluster 1 +4. ✅ Deploys details, reviews, ratings services on cluster 2 +5. ✅ Configures KubeSlice ServiceExports for cross-cluster access +6. ✅ Applies mTLS policies +7. ✅ Sets up Istio Gateway for external access + +### Step 3: Verify Deployment +```bash +./utils/bookinfo_test.sh +``` + +This will test: +- Basic application functionality +- Istio sidecar injection +- mTLS configuration +- Cross-cluster connectivity +- Gateway configuration + +### Step 4: Verify mTLS Security +```bash +./utils/verify_mtls.sh +``` + +This validates: +- Certificate presence in Envoy proxies +- PeerAuthentication policies +- Encrypted service communication +- TLS configuration + +## Expected Results + +### Successful Deployment Indicators + +1. **Pods Running**: All pods should have 3 containers (app + netshoot + istio-proxy) +```bash +kubectl get pods -n bookinfo +NAME READY STATUS RESTARTS AGE +productpage-v1-xyz 3/3 Running 0 5m +``` + +2. **mTLS Enabled**: STRICT mode configured +```bash +kubectl get peerauthentication default -n bookinfo -o yaml +spec: + mtls: + mode: STRICT +``` + +3. **Cross-cluster Services**: ServiceImports available +```bash +kubectl get serviceimport -n bookinfo +NAME TYPE IP AGE +details ClusterSetIP 10.x.x.x 5m +reviews ClusterSetIP 10.x.x.x 5m +``` + +4. **Application Accessible**: Web interface responds +```bash +curl http:///productpage +# Should return HTML content with book information +``` + +## Troubleshooting + +### Common Issues and Solutions + +#### Issue: Pods stuck in Pending +```bash +kubectl describe pod -n bookinfo +``` +**Solutions**: +- Check resource availability +- Verify Istio sidecar injector is running +- Ensure namespace has injection label + +#### Issue: mTLS connection failures +```bash +kubectl logs -n bookinfo -c istio-proxy +``` +**Solutions**: +- Verify PeerAuthentication is applied +- Check certificate rotation +- Validate service account permissions + +#### Issue: Cross-cluster connectivity problems +```bash +kubectl get serviceexport -n bookinfo +kubectl get serviceimport -n bookinfo +``` +**Solutions**: +- Verify KubeSlice configuration +- Check slice connectivity +- Validate DNS resolution + +#### Issue: Istio installation problems +```bash +kubectl get pods -n istio-system +kubectl logs deployment/istiod -n istio-system +``` +**Solutions**: +- Ensure sufficient cluster resources +- Check image pull policies +- Verify cluster permissions + +### Debug Commands + +```bash +# Check Istio configuration +istioctl analyze -n bookinfo + +# View Envoy configuration +kubectl exec -n bookinfo -c istio-proxy -- curl localhost:15000/config_dump + +# Check service mesh traffic +kubectl exec -n bookinfo -c istio-proxy -- curl localhost:15000/stats | grep cluster + +# Verify mTLS certificates +kubectl exec -n bookinfo -c istio-proxy -- openssl s_client -connect details:9080 + +# View access logs +kubectl logs -n bookinfo -c istio-proxy +``` + +## Performance Tuning + +### Resource Allocation +Adjust resources based on load: +```yaml +resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 200m + memory: 256Mi +``` + +### Istio Configuration +Optimize for your environment: +```yaml +# Reduce proxy resources for testing +spec: + defaultConfig: + proxyStatsMatcher: + inclusionRegexps: + - ".*circuit_breakers.*" + - ".*upstream_rq_retry.*" +``` + +## Security Hardening + +### 1. Network Policies +Implement Kubernetes NetworkPolicies alongside Istio: +```yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: bookinfo-deny-all +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress +``` + +### 2. RBAC Configuration +Use minimal service account permissions: +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-minimal +automountServiceAccountToken: false +``` + +### 3. Certificate Management +Monitor certificate expiration: +```bash +kubectl exec -n bookinfo -c istio-proxy -- \ + openssl x509 -in /var/run/secrets/istio/root-cert.pem -text -noout +``` + +## Monitoring Setup + +### Enable Telemetry +```bash +kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.20/samples/addons/prometheus.yaml +kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.20/samples/addons/grafana.yaml +kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.20/samples/addons/kiali.yaml +``` + +### Access Dashboards +```bash +# Kiali (Service Mesh Visualization) +kubectl port-forward svc/kiali -n istio-system 20001:20001 + +# Grafana (Metrics) +kubectl port-forward svc/grafana -n istio-system 3000:3000 + +# Prometheus (Raw Metrics) +kubectl port-forward svc/prometheus -n istio-system 9090:9090 +``` + +## Cleanup + +### Remove Application +```bash +./bookinfo.sh --delete +``` + +### Remove Istio (if needed) +```bash +kubectl delete namespace istio-system +kubectl delete validatingwebhookconfiguration istiod-default-validator +kubectl delete mutatingwebhookconfiguration istio-sidecar-injector +``` + +## Next Steps + +1. **Extend with Observability**: Add Jaeger for distributed tracing +2. **Implement Canary Deployments**: Use Istio traffic routing +3. **Add Rate Limiting**: Configure Envoy rate limiting +4. **Security Policies**: Implement fine-grained authorization +5. **Multi-cluster Service Discovery**: Extend to additional clusters + +## References + +- [Istio Documentation](https://istio.io/latest/docs/) +- [KubeSlice Documentation](https://docs.avesha.io/) +- [Bookinfo Application Guide](https://istio.io/latest/docs/examples/bookinfo/) +- [mTLS Configuration](https://istio.io/latest/docs/concepts/security/#mutual-tls-authentication) \ No newline at end of file diff --git a/kind/bookinfo-istio/README.md b/kind/bookinfo-istio/README.md new file mode 100644 index 0000000..19988b3 --- /dev/null +++ b/kind/bookinfo-istio/README.md @@ -0,0 +1,294 @@ +# Bookinfo with Istio and mTLS on KubeSlice + +This example deploys the Istio Bookinfo application across two KubeSlice-connected clusters with Istio service mesh and mTLS enabled for secure service-to-service communication. + +## Architecture + +``` +┌─────────────────────────────────────┐ ┌─────────────────────────────────────┐ +│ Cluster 1 (Product) │ │ Cluster 2 (Services) │ +│ ┌─────────────────────────────────┐ │ │ ┌─────────────────────────────────┐ │ +│ │ productpage │ │ │ │ details │ │ +│ │ ┌─────────────────────┐ │ │ │ │ ┌─────────────────────┐ │ │ +│ │ │ Istio Proxy │ │ │ │ │ │ Istio Proxy │ │ │ +│ │ │ (Envoy) │◄─────┼─┼────┼─┼────► (Envoy) │ │ │ +│ │ └─────────────────────┘ │ │ │ │ └─────────────────────┘ │ │ +│ │ │ productpage │ │ │ │ │ │ details │ │ │ +│ │ │ container │ │ │ │ │ │ container │ │ │ +│ │ └─────────────────────┘ │ │ │ │ └─────────────────────┘ │ │ +│ └─────────────────────────────────┘ │ │ └─────────────────────────────────┘ │ +│ │ │ │ +│ ┌─────────────────────────────────┐ │ │ ┌─────────────────────────────────┐ │ +│ │ Istio Gateway │ │ │ │ reviews │ │ +│ │ ┌─────────────────────────────┐ │ │ │ │ ┌─────────────────────┐ │ │ +│ │ │ External Access │ │ │ │ │ │ Istio Proxy │ │ │ +│ │ │ (port 80/443) │ │ │ │ │ │ (Envoy) │◄─────┼─┼──┐ +│ │ └─────────────────────────────┘ │ │ │ │ └─────────────────────┘ │ │ │ +│ └─────────────────────────────────┘ │ │ │ │ reviews │ │ │ │ +│ │ │ │ │ container │ │ │ │ +│ KubeSlice │ │ │ └─────────────────────┘ │ │ │ +│ Service Mesh │ │ └─────────────────────────────────┘ │ │ +└─────────────────────────────────────┘ │ │ │ + │ ┌─────────────────────────────────┐ │ │ + │ │ ratings │ │ │ + │ │ ┌─────────────────────┐ │ │ │ + │ │ │ Istio Proxy │ │ │ │ + │ │ │ (Envoy) │◄─────┼─┼──┘ + │ │ └─────────────────────┘ │ │ + │ │ │ ratings │ │ │ + │ │ │ container │ │ │ + │ │ └─────────────────────┘ │ │ + │ └─────────────────────────────────┘ │ + └─────────────────────────────────────┘ +``` + +### Service Distribution +- **Cluster 1 (Product Cluster)**: Runs the productpage service with Istio Gateway for external access +- **Cluster 2 (Services Cluster)**: Runs details, ratings, and reviews services +- **Service Mesh**: Istio with automatic sidecar injection on both clusters +- **Security**: Strict mTLS enabled between all services +- **Cross-cluster connectivity**: KubeSlice ServiceExport/ServiceImport with service mesh overlay + +### Security Features +- **Mutual TLS (mTLS)**: All service-to-service communication is encrypted and authenticated +- **PeerAuthentication**: Enforces STRICT mTLS mode for the bookinfo namespace +- **AuthorizationPolicy**: Controls which services can communicate with each other +- **Certificate Management**: Automatic certificate rotation via Istio's Certificate Authority + +## Prerequisites + +Before deploying this example, ensure you have: + +1. **KubeSlice Environment**: Two kind clusters connected via KubeSlice +2. **Required Tools**: + - `kubectl` - Kubernetes command-line tool + - `kubectx` (optional) - For easier cluster switching + - `istioctl` (optional) - For advanced Istio operations + +3. **Cluster Requirements**: + - Kubernetes 1.21+ on both clusters + - Sufficient resources (2+ CPU cores, 4GB+ RAM per cluster) + - Network connectivity between clusters via KubeSlice + +Run the prerequisites check: +```bash +./check-prerequisites.sh +``` + +## Quick Start + +### 1. Check Prerequisites +```bash +./check-prerequisites.sh +``` + +### 2. Install Istio (if not already installed) +```bash +./install-istio.sh +``` + +### 3. Deploy Bookinfo with mTLS +```bash +./bookinfo.sh +``` + +### 4. Test the Deployment +```bash +./utils/bookinfo_test.sh +``` + +### 5. Verify mTLS Configuration +```bash +./utils/verify_mtls.sh +``` + +## Detailed Usage + +### Installation Scripts + +#### install-istio.sh +Installs Istio service mesh on both KubeSlice clusters: +- Downloads and applies Istio manifests +- Configures Istio discovery service (istiod) +- Sets up mTLS policies +- Verifies installation + +```bash +./install-istio.sh --help +``` + +#### bookinfo.sh +Main deployment script that: +- Verifies Istio installation +- Creates bookinfo namespace with sidecar injection enabled +- Deploys services across clusters +- Configures KubeSlice ServiceExports +- Applies mTLS policies +- Sets up Istio Gateway for external access + +```bash +# Deploy the application +./bookinfo.sh + +# Remove the application +./bookinfo.sh --delete + +# Show help +./bookinfo.sh --help +``` + +### Testing and Verification + +#### bookinfo_test.sh +Comprehensive test suite that verifies: +- Basic application functionality +- Istio sidecar injection +- mTLS configuration +- Service-to-service connectivity +- Istio networking components + +#### verify_mtls.sh +Specialized mTLS verification script that: +- Checks certificate presence in Envoy proxies +- Verifies PeerAuthentication policies +- Tests encrypted communication between services +- Validates TLS configuration + +### Configuration Files + +#### Service Configurations +- `productpage.yaml` - Frontend service with Istio sidecar +- `details.yaml` - Book details service with Istio sidecar +- `reviews.yaml` - Book reviews service with Istio sidecar +- `ratings.yaml` - Star ratings service with Istio sidecar + +#### Istio Configuration +- `gateway.yaml` - Istio Gateway for external access +- `peer-authentication.yaml` - mTLS enforcement policy +- `istio-base.yaml` - Basic Istio installation manifest +- `istio-discovery.yaml` - Istio control plane configuration + +#### KubeSlice Configuration +- `serviceexports.yaml` - Cross-cluster service exports + +## Troubleshooting + +### Common Issues + +1. **Istio not installed**: + ```bash + Error: Istio not found on cluster + ``` + **Solution**: Run `./install-istio.sh` first + +2. **Pods stuck in Pending**: + ```bash + kubectl describe pod -n bookinfo + ``` + Check for resource constraints or scheduling issues + +3. **mTLS connection failures**: + ```bash + ./utils/verify_mtls.sh + ``` + Verify certificates and PeerAuthentication policies + +4. **Cross-cluster connectivity issues**: + ```bash + kubectl get serviceimport -n bookinfo + kubectl get serviceexport -n bookinfo + ``` + Ensure KubeSlice is properly configured + +### Debug Commands + +```bash +# Check Istio installation +kubectl get pods -n istio-system + +# Verify sidecar injection +kubectl get pods -n bookinfo -o jsonpath='{.items[*].spec.containers[*].name}' + +# Check mTLS certificates +kubectl exec -n bookinfo -c istio-proxy -- openssl s_client -connect reviews:9080 + +# View Envoy configuration +kubectl exec -n bookinfo -c istio-proxy -- curl localhost:15000/config_dump + +# Check service mesh connectivity +istioctl proxy-config cluster -n bookinfo +``` + +## Architecture Details + +### mTLS Flow +1. Service A initiates connection to Service B +2. Istio Envoy proxy intercepts the connection +3. Mutual certificate exchange occurs +4. Connection is established with encryption +5. All subsequent traffic is encrypted + +### Cross-Cluster Communication +1. productpage (Cluster 1) calls reviews.bookinfo.svc.slice.local +2. KubeSlice routes traffic to Cluster 2 +3. Istio maintains mTLS encryption across cluster boundaries +4. reviews service responds through encrypted channel + +### Security Policies +- **PeerAuthentication**: Enforces STRICT mTLS for all services +- **AuthorizationPolicy**: Controls service-to-service access +- **Certificate Rotation**: Automatic every 24 hours via Istio CA + +## Customization + +### Adding New Services +1. Create service YAML with `sidecar.istio.io/inject: "true"` annotation +2. Add ServiceExport for cross-cluster access +3. Update AuthorizationPolicy if needed + +### Modifying mTLS Policy +Edit `peer-authentication.yaml` to change mTLS mode: +- `STRICT` - Always require mTLS +- `PERMISSIVE` - Allow both mTLS and plain text +- `DISABLE` - Disable mTLS + +### External Access +The Istio Gateway exposes the productpage service externally. To access: +1. Find the gateway external IP +2. Access http:///productpage + +## Performance Considerations + +- **Latency**: mTLS adds ~1-2ms latency per hop +- **CPU**: Envoy proxies consume additional CPU (~10-50m per service) +- **Memory**: Each sidecar uses ~50-100MB RAM +- **Network**: Certificate exchange adds startup time (~2-5 seconds) + +## Security Best Practices + +1. **Certificate Management**: Use short-lived certificates (default 24h) +2. **Network Policies**: Implement Kubernetes NetworkPolicies alongside Istio +3. **RBAC**: Configure proper service account permissions +4. **Monitoring**: Enable Istio telemetry for security monitoring +5. **Updates**: Keep Istio updated for security patches + +## Monitoring and Observability + +This example can be extended with: +- **Kiali** - Service mesh visualization +- **Jaeger** - Distributed tracing +- **Prometheus** - Metrics collection +- **Grafana** - Metrics visualization + +## Contributing + +To contribute improvements: +1. Test changes thoroughly +2. Update documentation +3. Verify mTLS functionality +4. Submit pull request with examples + +## Related Examples + +- [bookinfo](../bookinfo/) - Basic version without Istio +- [boutique](../boutique/) - Another microservices example \ No newline at end of file diff --git a/kind/bookinfo-istio/bookinfo.sh b/kind/bookinfo-istio/bookinfo.sh new file mode 100755 index 0000000..3963d87 --- /dev/null +++ b/kind/bookinfo-istio/bookinfo.sh @@ -0,0 +1,172 @@ +#!/usr/bin/env bash + +# Bookinfo deployment with Istio service mesh and mTLS across KubeSlice clusters + +BASE_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +CONFIG_DIR=${BASE_DIR}/config_files + +ENV_FILE=${BASE_DIR}/../kind.env + +if [[ ! -f $ENV_FILE ]]; then + echo "${ENV_FILE} file not found! Exiting" + exit 1 +fi + +source $ENV_FILE + +PRODUCT_CLUSTER="${PREFIX}${WORKERS[0]}" +SERVICES_CLUSTER="${PREFIX}${WORKERS[1]}" +BOOKINFO_NAMESPACE=bookinfo + +uninstall() { + echo "Uninstalling Bookinfo with Istio..." + for cluster in $SERVICES_CLUSTER $PRODUCT_CLUSTER; do + echo "Deleting namespace $BOOKINFO_NAMESPACE on cluster $cluster" + kubectx $cluster + [[ $(kubectl get namespaces | grep $BOOKINFO_NAMESPACE) ]] && kubectl delete namespace $BOOKINFO_NAMESPACE + done + echo "Uninstall completed" +} + +help() { + echo "Usage: bookinfo.sh [--delete] [--help]" + echo " Deploy Istio Bookinfo application with mTLS across KubeSlice clusters" + echo " --delete Uninstall the bookinfo application" + echo " --help Show this help message" +} + +check_istio() { + local cluster=$1 + echo "Checking Istio installation on cluster $cluster..." + kubectx $cluster + + if ! kubectl get namespace istio-system &> /dev/null; then + echo "Istio not found on $cluster. Please install Istio first:" + echo "kubectl apply -f https://github.com/istio/istio/releases/download/1.20.1/istio-base.yaml" + echo "kubectl apply -f https://github.com/istio/istio/releases/download/1.20.1/istio-discovery.yaml" + echo "Or run: ${BASE_DIR}/install-istio.sh" + return 1 + fi + + if ! kubectl get deployment istiod -n istio-system &> /dev/null; then + echo "Istio control plane not found on $cluster" + return 1 + fi + + echo "Istio verified on $cluster" + return 0 +} + +enable_injection() { + local cluster=$1 + echo "Enabling Istio sidecar injection for namespace $BOOKINFO_NAMESPACE on $cluster" + kubectx $cluster + kubectl label namespace $BOOKINFO_NAMESPACE istio-injection=enabled --overwrite +} + +# Get the options +while getopts ":d:delete:help:" option; do + case $option in + d | delete) # Uninstall + uninstall + exit;; + h |help) + help + exit;; + esac +done + +echo "=== Deploying Bookinfo with Istio and mTLS ===" + +# Check Istio installation on both clusters +check_istio $PRODUCT_CLUSTER || exit 1 +check_istio $SERVICES_CLUSTER || exit 1 + +# Create namespaces and enable injection +echo "Creating bookinfo namespace on product cluster..." +kubectx $PRODUCT_CLUSTER +kubectl create namespace $BOOKINFO_NAMESPACE || true +enable_injection $PRODUCT_CLUSTER + +echo "Creating bookinfo namespace on services cluster..." +kubectx $SERVICES_CLUSTER +kubectl create namespace $BOOKINFO_NAMESPACE || true +enable_injection $SERVICES_CLUSTER + +function wait_for_pods { + local cluster=$1 + echo "Waiting for pods to be ready on $cluster..." + kubectx $cluster + + for pod in $(kubectl get pods -n $BOOKINFO_NAMESPACE | grep -v NAME | awk '{ print $1 }'); do + counter=0 + + while [[ $(kubectl get pods $pod -o 'jsonpath={..status.conditions[?(@.type=="Ready")].status}' -n $BOOKINFO_NAMESPACE) != True ]]; do + sleep 1 + let counter=counter+1 + + if ((counter == 180)); then # Increased timeout for Istio sidecar injection + echo "POD $pod failed to start in 180 seconds" + kubectl describe pod $pod -n $BOOKINFO_NAMESPACE + echo "Exiting" + exit -1 + fi + done + done + echo "All pods ready on $cluster" +} + +echo "Installing productpage on $PRODUCT_CLUSTER..." +kubectx $PRODUCT_CLUSTER +kubectl apply -f ${CONFIG_DIR}/productpage.yaml -n $BOOKINFO_NAMESPACE + +echo "Waiting for productpage pods to be ready..." +wait_for_pods $PRODUCT_CLUSTER + +echo "Installing services on $SERVICES_CLUSTER..." +kubectx $SERVICES_CLUSTER +kubectl apply -f ${CONFIG_DIR}/details.yaml -n $BOOKINFO_NAMESPACE +kubectl apply -f ${CONFIG_DIR}/ratings.yaml -n $BOOKINFO_NAMESPACE +kubectl apply -f ${CONFIG_DIR}/reviews.yaml -n $BOOKINFO_NAMESPACE + +echo "Waiting for service pods to be ready..." +wait_for_pods $SERVICES_CLUSTER + +echo "Applying KubeSlice ServiceExports..." +kubectl apply -f ${CONFIG_DIR}/serviceexports.yaml -n $BOOKINFO_NAMESPACE +echo "Waiting for ServiceExports to be created..." +sleep 30 + +echo "Verifying ServiceExports..." +kubectl get serviceexport -n $BOOKINFO_NAMESPACE + +echo "Applying mTLS configuration on both clusters..." +kubectx $PRODUCT_CLUSTER +kubectl apply -f ${CONFIG_DIR}/peer-authentication.yaml +kubectx $SERVICES_CLUSTER +kubectl apply -f ${CONFIG_DIR}/peer-authentication.yaml + +echo "Applying Istio Gateway configuration..." +kubectx $PRODUCT_CLUSTER +kubectl apply -f ${CONFIG_DIR}/gateway.yaml + +echo "Verifying ServiceImports on product cluster..." +kubectl get serviceimport -n $BOOKINFO_NAMESPACE + +echo "Printing services on both clusters..." +echo "=== Product Cluster Services ===" +kubectl get services -n $BOOKINFO_NAMESPACE + +kubectx $SERVICES_CLUSTER +echo "=== Services Cluster Services ===" +kubectl get services -n $BOOKINFO_NAMESPACE + +echo "=== Testing bookinfo services with mTLS ===" +echo "Waiting for services to be available..." +sleep 40 + +echo "Deployment completed successfully!" +echo "Run: bash ${BASE_DIR}/utils/bookinfo_test.sh" +echo "To test the application." + +bash ${BASE_DIR}/utils/bookinfo_test.sh diff --git a/kind/bookinfo-istio/check-prerequisites.sh b/kind/bookinfo-istio/check-prerequisites.sh new file mode 100755 index 0000000..8d43be9 --- /dev/null +++ b/kind/bookinfo-istio/check-prerequisites.sh @@ -0,0 +1,146 @@ +#!/bin/bash + +# Prerequisites check script for bookinfo-istio deployment + +echo "=== Prerequisites Check for Bookinfo-Istio ===" + +check_command() { + local cmd=$1 + local package=$2 + + if command -v $cmd &> /dev/null; then + echo "✓ $cmd is available" + return 0 + else + echo "✗ $cmd is not available" + if [[ -n "$package" ]]; then + echo " Install with: $package" + fi + return 1 + fi +} + +check_kubernetes_connectivity() { + echo "Checking Kubernetes connectivity..." + + if kubectl cluster-info &> /dev/null; then + echo "✓ kubectl can connect to cluster" + + # Check current context + CURRENT_CONTEXT=$(kubectl config current-context) + echo " Current context: $CURRENT_CONTEXT" + + # Check if we can list namespaces + if kubectl get namespaces &> /dev/null; then + echo "✓ Can list namespaces" + else + echo "✗ Cannot list namespaces (insufficient permissions)" + fi + else + echo "✗ kubectl cannot connect to cluster" + echo " Please ensure your kubeconfig is set up correctly" + return 1 + fi +} + +check_kubeslice_clusters() { + echo "Checking KubeSlice cluster configuration..." + + BASE_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + ENV_FILE=${BASE_DIR}/../kind.env + + if [[ -f $ENV_FILE ]]; then + echo "✓ Found kind.env configuration file" + source $ENV_FILE + + PRODUCT_CLUSTER="${PREFIX}${WORKERS[0]}" + SERVICES_CLUSTER="${PREFIX}${WORKERS[1]}" + + echo " Product cluster: $PRODUCT_CLUSTER" + echo " Services cluster: $SERVICES_CLUSTER" + + # Check if kubectx is available + if command -v kubectx &> /dev/null; then + echo "✓ kubectx is available" + + # Check if clusters exist + if kubectx $PRODUCT_CLUSTER &> /dev/null; then + echo "✓ Product cluster ($PRODUCT_CLUSTER) is accessible" + else + echo "✗ Product cluster ($PRODUCT_CLUSTER) is not accessible" + fi + + if kubectx $SERVICES_CLUSTER &> /dev/null; then + echo "✓ Services cluster ($SERVICES_CLUSTER) is accessible" + else + echo "✗ Services cluster ($SERVICES_CLUSTER) is not accessible" + fi + else + echo "⚠ kubectx is not available - will use kubectl config use-context instead" + + # Check contexts manually + CONTEXTS=$(kubectl config get-contexts -o name) + if echo "$CONTEXTS" | grep -q "$PRODUCT_CLUSTER"; then + echo "✓ Product cluster context exists" + else + echo "✗ Product cluster context not found" + fi + + if echo "$CONTEXTS" | grep -q "$SERVICES_CLUSTER"; then + echo "✓ Services cluster context exists" + else + echo "✗ Services cluster context not found" + fi + fi + else + echo "✗ kind.env configuration file not found at $ENV_FILE" + echo " Please ensure you're running this from the correct directory" + return 1 + fi +} + +check_istio_availability() { + echo "Checking Istio availability..." + + if command -v istioctl &> /dev/null; then + echo "✓ istioctl is available" + ISTIO_VERSION=$(istioctl version --short 2>/dev/null || echo "unknown") + echo " Version: $ISTIO_VERSION" + else + echo "⚠ istioctl is not available" + echo " The deployment will use kubectl to install Istio manifests" + echo " For better control, consider installing istioctl:" + echo " curl -L https://istio.io/downloadIstio | sh -" + fi +} + +main() { + echo "Required tools:" + check_command "kubectl" "https://kubernetes.io/docs/tasks/tools/install-kubectl/" + echo "" + + echo "Optional tools:" + check_command "kubectx" "https://github.com/ahmetb/kubectx" + check_command "istioctl" "https://istio.io/latest/docs/setup/getting-started/" + echo "" + + check_kubernetes_connectivity + echo "" + + check_kubeslice_clusters + echo "" + + check_istio_availability + echo "" + + echo "=== Summary ===" + echo "If all checks pass, you're ready to deploy bookinfo with Istio!" + echo "" + echo "Deployment steps:" + echo "1. Install Istio (optional): ./install-istio.sh" + echo "2. Deploy bookinfo: ./bookinfo.sh" + echo "3. Test deployment: ./utils/bookinfo_test.sh" + echo "4. Verify mTLS: ./utils/verify_mtls.sh" +} + +main \ No newline at end of file diff --git a/kind/bookinfo-istio/config_files/details.yaml b/kind/bookinfo-istio/config_files/details.yaml new file mode 100644 index 0000000..76a21c4 --- /dev/null +++ b/kind/bookinfo-istio/config_files/details.yaml @@ -0,0 +1,63 @@ +################################################################################################## +# Details service with Istio sidecar injection +################################################################################################## +apiVersion: v1 +kind: Service +metadata: + name: details + labels: + app: details + service: details +spec: + ports: + - port: 9080 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-details + labels: + account: details +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details-v1 + labels: + app: details + version: v1 +spec: + replicas: 1 + selector: + matchLabels: + app: details + version: v1 + template: + metadata: + labels: + app: details + version: v1 + annotations: + sidecar.istio.io/inject: "true" + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 + - name: netshoot + image: nicolaka/netshoot + imagePullPolicy: IfNotPresent + command: ["/bin/sleep", "3650d"] + securityContext: + capabilities: + add: ["NET_ADMIN"] + allowPrivilegeEscalation: true + privileged: true diff --git a/kind/bookinfo-istio/config_files/gateway.yaml b/kind/bookinfo-istio/config_files/gateway.yaml new file mode 100644 index 0000000..803fdfa --- /dev/null +++ b/kind/bookinfo-istio/config_files/gateway.yaml @@ -0,0 +1,43 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: Gateway +metadata: + name: bookinfo-gateway + namespace: bookinfo +spec: + selector: + istio: ingressgateway # use istio default controller + servers: + - port: + number: 80 + name: http + protocol: HTTP + hosts: + - "*" +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: bookinfo + namespace: bookinfo +spec: + hosts: + - "*" + gateways: + - bookinfo-gateway + http: + - match: + - uri: + exact: /productpage + - uri: + prefix: /static + - uri: + exact: /login + - uri: + exact: /logout + - uri: + prefix: /api/v1/products + route: + - destination: + host: productpage + port: + number: 9080 \ No newline at end of file diff --git a/kind/bookinfo-istio/config_files/ingress-gw-install.yaml b/kind/bookinfo-istio/config_files/ingress-gw-install.yaml new file mode 100644 index 0000000..bba35e4 --- /dev/null +++ b/kind/bookinfo-istio/config_files/ingress-gw-install.yaml @@ -0,0 +1,15 @@ +apiVersion: install.istio.io/v1alpha1 +kind: IstioOperator +metadata: + name: ingress-gateway-install + namespace: istio-system +spec: + profile: empty + components: + ingressGateways: + - name: istio-ingressgateway + namespace: istio-system + enabled: true + label: + app: istio-ingressgateway + istio: ingressgateway \ No newline at end of file diff --git a/kind/bookinfo-istio/config_files/istio-allow-ingress.yaml b/kind/bookinfo-istio/config_files/istio-allow-ingress.yaml new file mode 100644 index 0000000..73155e9 --- /dev/null +++ b/kind/bookinfo-istio/config_files/istio-allow-ingress.yaml @@ -0,0 +1,12 @@ +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: allow-ingress-gateway + namespace: istio-system +spec: + selector: + matchLabels: + app: istio-ingressgateway + action: ALLOW + rules: + - {} # Empty rule means allow all traffic \ No newline at end of file diff --git a/kind/bookinfo-istio/config_files/istio-authorization.yaml b/kind/bookinfo-istio/config_files/istio-authorization.yaml new file mode 100644 index 0000000..9d59601 --- /dev/null +++ b/kind/bookinfo-istio/config_files/istio-authorization.yaml @@ -0,0 +1,73 @@ +# Apply authorization policy to Cluster 1 (Product Cluster) +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: bookinfo-policy + namespace: bookinfo +spec: + action: ALLOW + rules: + - from: + - source: + principals: ["cluster.local/ns/bookinfo/sa/bookinfo-productpage"] + to: + - operation: + methods: ["GET"] + - from: + - source: + principals: ["cluster.local/ns/bookinfo/sa/bookinfo-reviews"] + to: + - operation: + methods: ["GET"] + - from: + - source: + principals: ["cluster.local/ns/bookinfo/sa/bookinfo-details"] + to: + - operation: + methods: ["GET"] + - from: + - source: + principals: ["cluster.local/ns/bookinfo/sa/bookinfo-ratings"] + to: + - operation: + methods: ["GET"] + - from: + - source: + namespaces: ["istio-system"] +--- +# Apply same policy to Cluster 2 (Services Cluster) +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: bookinfo-policy + namespace: bookinfo +spec: + action: ALLOW + rules: + - from: + - source: + principals: ["cluster.local/ns/bookinfo/sa/bookinfo-productpage"] + to: + - operation: + methods: ["GET"] + - from: + - source: + principals: ["cluster.local/ns/bookinfo/sa/bookinfo-reviews"] + to: + - operation: + methods: ["GET"] + - from: + - source: + principals: ["cluster.local/ns/bookinfo/sa/bookinfo-details"] + to: + - operation: + methods: ["GET"] + - from: + - source: + principals: ["cluster.local/ns/bookinfo/sa/bookinfo-ratings"] + to: + - operation: + methods: ["GET"] + - from: + - source: + namespaces: ["istio-system"] diff --git a/kind/bookinfo-istio/config_files/istio-base.yaml b/kind/bookinfo-istio/config_files/istio-base.yaml new file mode 100644 index 0000000..ea6104e --- /dev/null +++ b/kind/bookinfo-istio/config_files/istio-base.yaml @@ -0,0 +1,23 @@ +# Istio Base Configuration +# This would typically be installed via: istioctl install --set values.defaultRevision=default +# For this example, we include the essential CRDs and configurations + +apiVersion: v1 +kind: Namespace +metadata: + name: istio-system + labels: + istio-injection: disabled +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: istio-reader-service-account + namespace: istio-system + labels: + app: istio-reader + release: istio +--- +# Note: In a real deployment, this would include all Istio CRDs and base components +# For this example, we're focusing on the application configuration +# Assume Istio is pre-installed via: kubectl apply -f https://github.com/istio/istio/releases/download/1.20.1/istio-base.yaml \ No newline at end of file diff --git a/kind/bookinfo-istio/config_files/istio-destination-rule.yml b/kind/bookinfo-istio/config_files/istio-destination-rule.yml new file mode 100644 index 0000000..f996085 --- /dev/null +++ b/kind/bookinfo-istio/config_files/istio-destination-rule.yml @@ -0,0 +1,32 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details + namespace: bookinfo +spec: + host: details + trafficPolicy: + tls: + mode: ISTIO_MUTUAL +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: reviews + namespace: bookinfo +spec: + host: reviews + trafficPolicy: + tls: + mode: ISTIO_MUTUAL +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: ratings + namespace: bookinfo +spec: + host: ratings + trafficPolicy: + tls: + mode: ISTIO_MUTUAL \ No newline at end of file diff --git a/kind/bookinfo-istio/config_files/istio-discovery.yaml b/kind/bookinfo-istio/config_files/istio-discovery.yaml new file mode 100644 index 0000000..3580261 --- /dev/null +++ b/kind/bookinfo-istio/config_files/istio-discovery.yaml @@ -0,0 +1,22 @@ +# Istio Discovery Service Configuration +# This would typically be installed via: istioctl install --set values.defaultRevision=default +# For this example, we're referencing what would be applied + +apiVersion: v1 +kind: ConfigMap +metadata: + name: istio + namespace: istio-system + labels: + istio.io/rev: default +data: + mesh: | + defaultConfig: + meshId: mesh1 + trustDomain: cluster.local + trustDomainAliases: + - cluster.local +--- +# Note: In a real deployment, this would include the full Istio discovery service +# For this example, we assume Istio istiod is installed via: +# kubectl apply -f https://github.com/istio/istio/releases/download/1.20.1/istio-discovery.yaml \ No newline at end of file diff --git a/kind/bookinfo-istio/config_files/istio-rbac.yaml b/kind/bookinfo-istio/config_files/istio-rbac.yaml new file mode 100644 index 0000000..366dfb8 --- /dev/null +++ b/kind/bookinfo-istio/config_files/istio-rbac.yaml @@ -0,0 +1,12 @@ +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: allow-productpage + namespace: bookinfo +spec: + selector: + matchLabels: + app: productpage + action: ALLOW + rules: + - {} # Empty rule means allow all traffic \ No newline at end of file diff --git a/kind/bookinfo-istio/config_files/peer-authentication.yaml b/kind/bookinfo-istio/config_files/peer-authentication.yaml new file mode 100644 index 0000000..2e9863a --- /dev/null +++ b/kind/bookinfo-istio/config_files/peer-authentication.yaml @@ -0,0 +1,62 @@ +# PeerAuthentication to enforce strict mTLS +apiVersion: security.istio.io/v1beta1 +kind: PeerAuthentication +metadata: + name: default + namespace: bookinfo +spec: + mtls: + mode: STRICT +--- +# AuthorizationPolicy to allow communication between bookinfo services +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: bookinfo-allow + namespace: bookinfo +spec: + rules: + - from: + - source: + principals: ["cluster.local/ns/bookinfo/sa/bookinfo-productpage"] + to: + - operation: + methods: ["GET"] + - from: + - source: + principals: ["cluster.local/ns/bookinfo/sa/bookinfo-details"] + to: + - operation: + methods: ["GET"] + - from: + - source: + principals: ["cluster.local/ns/bookinfo/sa/bookinfo-reviews"] + to: + - operation: + methods: ["GET"] + - from: + - source: + principals: ["cluster.local/ns/bookinfo/sa/bookinfo-ratings"] + to: + - operation: + methods: ["GET"] +--- +# Apply PeerAuthentication policy to Cluster 1 (Product Cluster) +apiVersion: security.istio.io/v1beta1 +kind: PeerAuthentication +metadata: + name: default + namespace: bookinfo +spec: + mtls: + mode: STRICT +--- +# Apply same policy to Cluster 2 (Services Cluster) +apiVersion: security.istio.io/v1beta1 +kind: PeerAuthentication +metadata: + name: default + namespace: bookinfo +spec: + mtls: + mode: STRICT \ No newline at end of file diff --git a/kind/bookinfo-istio/config_files/productpage.yaml b/kind/bookinfo-istio/config_files/productpage.yaml new file mode 100644 index 0000000..804eece --- /dev/null +++ b/kind/bookinfo-istio/config_files/productpage.yaml @@ -0,0 +1,75 @@ +################################################################################################## +# Productpage service with Istio sidecar injection +################################################################################################## +apiVersion: v1 +kind: Service +metadata: + name: productpage + labels: + app: productpage + service: productpage +spec: + type: NodePort + ports: + - port: 9080 + name: http + selector: + app: productpage +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-productpage + labels: + account: productpage +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: productpage-v1 + labels: + app: productpage + version: v1 +spec: + replicas: 1 + selector: + matchLabels: + app: productpage + version: v1 + template: + metadata: + labels: + app: productpage + version: v1 + annotations: + sidecar.istio.io/inject: "true" + spec: + serviceAccountName: bookinfo-productpage + containers: + - name: productpage + image: docker.io/istio/examples-bookinfo-productpage-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + volumeMounts: + - name: tmp + mountPath: /tmp + securityContext: + runAsUser: 1000 + env: + - name: REVIEWS_HOSTNAME + value: reviews.bookinfo.svc.slice.local + - name: DETAILS_HOSTNAME + value: details.bookinfo.svc.slice.local + - name: netshoot + image: nicolaka/netshoot + imagePullPolicy: IfNotPresent + command: ["/bin/sleep", "3650d"] + securityContext: + capabilities: + add: ["NET_ADMIN"] + allowPrivilegeEscalation: true + privileged: true + volumes: + - name: tmp + emptyDir: {} diff --git a/kind/bookinfo-istio/config_files/ratings.yaml b/kind/bookinfo-istio/config_files/ratings.yaml new file mode 100644 index 0000000..e300edf --- /dev/null +++ b/kind/bookinfo-istio/config_files/ratings.yaml @@ -0,0 +1,63 @@ +################################################################################################## +# Ratings service with Istio sidecar injection +################################################################################################## +apiVersion: v1 +kind: Service +metadata: + name: ratings + labels: + app: ratings + service: ratings +spec: + ports: + - port: 9080 + name: http + selector: + app: ratings +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-ratings + labels: + account: ratings +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ratings-v1 + labels: + app: ratings + version: v1 +spec: + replicas: 1 + selector: + matchLabels: + app: ratings + version: v1 + template: + metadata: + labels: + app: ratings + version: v1 + annotations: + sidecar.istio.io/inject: "true" + spec: + serviceAccountName: bookinfo-ratings + containers: + - name: ratings + image: docker.io/istio/examples-bookinfo-ratings-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 + - name: netshoot + image: nicolaka/netshoot + imagePullPolicy: IfNotPresent + command: ["/bin/sleep", "3650d"] + securityContext: + capabilities: + add: ["NET_ADMIN"] + allowPrivilegeEscalation: true + privileged: true diff --git a/kind/bookinfo-istio/config_files/reviews.yaml b/kind/bookinfo-istio/config_files/reviews.yaml new file mode 100644 index 0000000..f5b3b57 --- /dev/null +++ b/kind/bookinfo-istio/config_files/reviews.yaml @@ -0,0 +1,76 @@ +################################################################################################## +# Reviews service with Istio sidecar injection +################################################################################################## +apiVersion: v1 +kind: Service +metadata: + name: reviews + labels: + app: reviews + service: reviews +spec: + ports: + - port: 9080 + name: http + selector: + app: reviews +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-reviews + labels: + account: reviews +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: reviews-v3 + labels: + app: reviews + version: v3 +spec: + replicas: 1 + selector: + matchLabels: + app: reviews + version: v3 + template: + metadata: + labels: + app: reviews + version: v3 + annotations: + sidecar.istio.io/inject: "true" + spec: + serviceAccountName: bookinfo-reviews + containers: + - name: reviews + image: docker.io/istio/examples-bookinfo-reviews-v3:1.16.2 + imagePullPolicy: IfNotPresent + env: + - name: LOG_DIR + value: "/tmp/logs" + ports: + - containerPort: 9080 + volumeMounts: + - name: tmp + mountPath: /tmp + - name: wlp-output + mountPath: /opt/ibm/wlp/output + securityContext: + runAsUser: 1000 + - name: netshoot + image: nicolaka/netshoot + imagePullPolicy: IfNotPresent + command: ["/bin/sleep", "3650d"] + securityContext: + capabilities: + add: ["NET_ADMIN"] + allowPrivilegeEscalation: true + privileged: true + volumes: + - name: wlp-output + emptyDir: {} + - name: tmp + emptyDir: {} diff --git a/kind/bookinfo-istio/config_files/serviceexports.yaml b/kind/bookinfo-istio/config_files/serviceexports.yaml new file mode 100644 index 0000000..d7edc6d --- /dev/null +++ b/kind/bookinfo-istio/config_files/serviceexports.yaml @@ -0,0 +1,53 @@ +################################################################################## +# Details ServiceExport +################################################################################## +apiVersion: networking.kubeslice.io/v1beta1 +kind: ServiceExport +metadata: + name: details +spec: + slice: bookinfo-slice #Replace Slice Name + selector: + matchLabels: + app: details + ingressEnabled: false + ports: + - name: http + containerPort: 9080 + protocol: TCP +--- +################################################################################## +# Reviews ServiceExport +################################################################################## +apiVersion: networking.kubeslice.io/v1beta1 +kind: ServiceExport +metadata: + name: reviews +spec: + slice: bookinfo-slice #Replace Slice Name + selector: + matchLabels: + app: reviews + ingressEnabled: false + ports: + - name: http + containerPort: 9080 + protocol: TCP +--- +################################################################################## +# Ratings ServiceExport +################################################################################## +apiVersion: networking.kubeslice.io/v1beta1 +kind: ServiceExport +metadata: + name: ratings +spec: + slice: bookinfo-slice #Replace Slice Name + selector: + matchLabels: + app: ratings + ingressEnabled: false + ports: + - name: http + containerPort: 9080 + protocol: TCP diff --git a/kind/bookinfo-istio/config_files/slice-config.yaml b/kind/bookinfo-istio/config_files/slice-config.yaml new file mode 100644 index 0000000..33c56b8 --- /dev/null +++ b/kind/bookinfo-istio/config_files/slice-config.yaml @@ -0,0 +1,54 @@ +apiVersion: controller.kubeslice.io/v1alpha1 +kind: SliceConfig +metadata: + name: bookinfo-slice + namespace: kubeslice-kubeslice-avesha +spec: + sliceSubnet: 10.1.0.0/16 + sliceType: Application + sliceGatewayProvider: + sliceGatewayType: OpenVPN + sliceCaType: Local + sliceIpamType: Local + clusters: + - ks-worker-1 + - ks-worker-2 + qosProfileDetails: + queueType: HTB + priority: 1 + tcType: BANDWIDTH_CONTROL + bandwidthCeilingKbps: 5120 + bandwidthGuaranteedKbps: 2560 + dscpClass: AF11 + namespaceIsolationProfile: + applicationNamespaces: + - namespace: bookinfo + clusters: + - '*' + - namespace: istio-system + clusters: + - '*' + isolationEnabled: false # Set to true if you want strict namespace isolation + allowedNamespaces: + - namespace: kube-system + clusters: + - '*' + externalGatewayConfig: + - ingress: + enabled: true + egress: + enabled: false + nsIngress: + enabled: false + gatewayType: istio + clusters: + - ks-worker-1 # Product cluster with productpage service + - ingress: + enabled: false + egress: + enabled: true + nsIngress: + enabled: false + gatewayType: istio + clusters: + - ks-worker-2 # Services cluster with details, reviews, ratings \ No newline at end of file diff --git a/kind/bookinfo-istio/demo.sh b/kind/bookinfo-istio/demo.sh new file mode 100755 index 0000000..dd8941b --- /dev/null +++ b/kind/bookinfo-istio/demo.sh @@ -0,0 +1,173 @@ +#!/bin/bash + +# Demo script to showcase bookinfo-istio features + +BASE_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +CONFIG_FILE=${BASE_DIR}/../kind.env + +echo "=== Bookinfo with Istio and mTLS Demo ===" +echo "" + +if [[ -f $CONFIG_FILE ]]; then + source $CONFIG_FILE +else + echo "Configuration file not found. Please ensure KubeSlice clusters are set up." + exit 1 +fi + +PRODUCT_CLUSTER="${PREFIX}${WORKERS[0]}" +SERVICES_CLUSTER="${PREFIX}${WORKERS[1]}" + +demo_istio_features() { + echo "🔍 Demonstrating Istio Features:" + echo "" + + echo "1. Sidecar Injection:" + echo " Without Istio: 2 containers per pod (app + netshoot)" + echo " With Istio: 3 containers per pod (app + netshoot + istio-proxy)" + echo "" + + kubectx $PRODUCT_CLUSTER 2>/dev/null || kubectl config use-context $PRODUCT_CLUSTER + if kubectl get pods -n bookinfo &>/dev/null; then + echo " Current productpage containers:" + CONTAINERS=$(kubectl get pods -n bookinfo -l app=productpage -o jsonpath='{.items[0].spec.containers[*].name}' 2>/dev/null) + echo " $CONTAINERS" + echo "" + fi + + echo "2. mTLS Configuration:" + echo " Checking PeerAuthentication policy..." + if kubectl get peerauthentication default -n bookinfo &>/dev/null; then + MODE=$(kubectl get peerauthentication default -n bookinfo -o jsonpath='{.spec.mtls.mode}' 2>/dev/null) + echo " ✓ mTLS Mode: $MODE" + else + echo " ⚠ PeerAuthentication not configured" + fi + echo "" + + echo "3. Cross-Cluster Service Mesh:" + echo " Services communicate across clusters with mTLS encryption" + echo " productpage (cluster 1) → reviews/details (cluster 2)" + echo "" + + echo "4. Istio Gateway:" + if kubectl get gateway bookinfo-gateway -n bookinfo &>/dev/null; then + echo " ✓ External access configured via Istio Gateway" + else + echo " ⚠ Gateway not configured" + fi + echo "" +} + +demo_security_features() { + echo "🔒 Security Features:" + echo "" + + echo "1. Certificate-based Authentication:" + echo " All services use X.509 certificates for authentication" + echo "" + + echo "2. Encrypted Communication:" + echo " All service-to-service traffic is TLS encrypted" + echo "" + + echo "3. Identity-based Authorization:" + echo " Services can only communicate if explicitly allowed" + echo "" + + kubectx $PRODUCT_CLUSTER 2>/dev/null || kubectl config use-context $PRODUCT_CLUSTER + PRODUCTPAGE_POD=$(kubectl get pods -n bookinfo -l app=productpage -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [[ -n "$PRODUCTPAGE_POD" ]]; then + echo "4. Certificate Verification:" + echo " Checking certificates in productpage pod..." + CERTS=$(kubectl exec -n bookinfo $PRODUCTPAGE_POD -c istio-proxy -- find /etc/ssl/certs -name "*.pem" 2>/dev/null | wc -l || echo "0") + echo " Found $CERTS certificate files" + echo "" + fi +} + +demo_observability() { + echo "📊 Observability Features:" + echo "" + + echo "1. Service Mesh Metrics:" + echo " Istio automatically collects traffic metrics" + echo "" + + echo "2. Distributed Tracing:" + echo " Request tracing across cluster boundaries" + echo "" + + echo "3. Access Logs:" + echo " Detailed logs of all service interactions" + echo "" + + kubectx $PRODUCT_CLUSTER 2>/dev/null || kubectl config use-context $PRODUCT_CLUSTER + PRODUCTPAGE_POD=$(kubectl get pods -n bookinfo -l app=productpage -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [[ -n "$PRODUCTPAGE_POD" ]]; then + echo "4. Envoy Admin Interface:" + echo " Access to Envoy proxy configuration and stats" + echo " kubectl exec -n bookinfo $PRODUCTPAGE_POD -c istio-proxy -- curl localhost:15000/stats" + echo "" + fi +} + +show_comparison() { + echo "📈 Comparison: Basic vs Istio Deployment" + echo "" + + echo "┌─────────────────────────┬─────────────────────────┬──────────────────────────┐" + echo "│ Feature │ Basic Bookinfo │ Bookinfo with Istio │" + echo "├─────────────────────────┼─────────────────────────┼──────────────────────────┤" + echo "│ Service-to-service │ Plain HTTP │ mTLS Encrypted │" + echo "│ Authentication │ None │ Certificate-based │" + echo "│ Authorization │ Kubernetes RBAC │ Istio + Kubernetes RBAC │" + echo "│ Traffic Management │ Kubernetes Services │ Istio VirtualServices │" + echo "│ Load Balancing │ kube-proxy │ Envoy Proxy │" + echo "│ Observability │ Basic Kubernetes │ Rich Istio Telemetry │" + echo "│ External Access │ NodePort/LB │ Istio Gateway │" + echo "│ Circuit Breaking │ Manual │ Envoy Built-in │" + echo "│ Retry/Timeout │ Application Level │ Proxy Level │" + echo "│ Cross-cluster Security │ Basic │ Enhanced │" + echo "└─────────────────────────┴─────────────────────────┴──────────────────────────┘" + echo "" +} + +interactive_demo() { + echo "🎯 Interactive Testing:" + echo "" + + echo "Available test commands:" + echo "1. ./utils/bookinfo_test.sh - Test application functionality" + echo "2. ./utils/verify_mtls.sh - Verify mTLS configuration" + echo "3. ./check-prerequisites.sh - Check system requirements" + echo "" + + read -p "Would you like to run the mTLS verification? (y/n): " -n 1 -r + echo "" + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Running mTLS verification..." + ${BASE_DIR}/utils/verify_mtls.sh + fi +} + +main() { + demo_istio_features + demo_security_features + demo_observability + show_comparison + interactive_demo + + echo "" + echo "🎉 Demo completed!" + echo "" + echo "Next steps:" + echo "- Deploy: ./bookinfo.sh" + echo "- Test: ./utils/bookinfo_test.sh" + echo "- Verify mTLS: ./utils/verify_mtls.sh" + echo "- Read documentation: README.md" +} + +main \ No newline at end of file diff --git a/kind/bookinfo-istio/install-istio.sh b/kind/bookinfo-istio/install-istio.sh new file mode 100755 index 0000000..c1f5e90 --- /dev/null +++ b/kind/bookinfo-istio/install-istio.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash + +# Script to install and configure Istio on KubeSlice clusters +# This script uses kubectl to install Istio when istioctl is not available + +set -e + +BASE_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +ENV_FILE=${BASE_DIR}/../kind.env + +if [[ ! -f $ENV_FILE ]]; then + echo "${ENV_FILE} file not found! Exiting" + exit 1 +fi + +source $ENV_FILE + +PRODUCT_CLUSTER="${PREFIX}${WORKERS[0]}" +SERVICES_CLUSTER="${PREFIX}${WORKERS[1]}" +ISTIO_VERSION="1.20.1" + +install_istio_cluster() { + local cluster=$1 + echo "Installing Istio on cluster: $cluster" + + kubectx $cluster + + # Check if Istio is already installed + if kubectl get namespace istio-system &> /dev/null; then + echo "Istio appears to be already installed on $cluster" + if kubectl get deployment istiod -n istio-system &> /dev/null; then + echo "Istio control plane is running on $cluster" + return 0 + fi + fi + + echo "Installing Istio base components..." + # Install Istio base using the official manifests + kubectl apply -f https://github.com/istio/istio/releases/download/${ISTIO_VERSION}/istio-base.yaml || { + echo "Failed to install Istio base from remote URL, using local fallback" + kubectl create namespace istio-system || true + kubectl apply -f ${BASE_DIR}/config_files/istio-base.yaml + } + + echo "Installing Istio discovery service..." + # Install Istio discovery (istiod) + kubectl apply -f https://github.com/istio/istio/releases/download/${ISTIO_VERSION}/istio-discovery.yaml || { + echo "Failed to install Istio discovery from remote URL, using local fallback" + kubectl apply -f ${BASE_DIR}/config_files/istio-discovery.yaml + } + + # Wait for Istio to be ready + echo "Waiting for Istio to be ready..." + kubectl wait --for=condition=Available deployment/istiod -n istio-system --timeout=300s || { + echo "Warning: Istio deployment may not be fully ready. Continuing..." + kubectl get pods -n istio-system + } + + echo "Istio installation completed on $cluster" +} + +install_istio_gateway() { + local cluster=$1 + echo "Installing Istio Gateway on cluster: $cluster" + + kubectx $cluster + + # Install Istio Ingress Gateway + kubectl apply -f https://github.com/istio/istio/releases/download/${ISTIO_VERSION}/istio-gateway.yaml || { + echo "Warning: Could not install Istio Gateway from remote URL" + echo "Istio Gateway installation requires manual setup" + } + + echo "Istio Gateway installation attempted on $cluster" +} + +configure_mtls() { + local cluster=$1 + echo "Configuring mTLS on cluster: $cluster" + + kubectx $cluster + + # Ensure bookinfo namespace exists + kubectl create namespace bookinfo || true + + # Apply PeerAuthentication for strict mTLS + kubectl apply -f ${BASE_DIR}/config_files/peer-authentication.yaml || { + echo "Warning: Failed to apply mTLS configuration" + } + + echo "mTLS configuration applied on $cluster" +} + +verify_istio() { + local cluster=$1 + echo "Verifying Istio installation on cluster: $cluster" + + kubectx $cluster + + echo "Checking Istio system pods..." + kubectl get pods -n istio-system + + echo "Checking Istio version..." + kubectl get deployment istiod -n istio-system -o jsonpath='{.metadata.labels.app\.version}' || echo "Unable to get Istio version" + + echo "" +} + +main() { + echo "=== Installing Istio on KubeSlice clusters ===" + echo "Istio version: $ISTIO_VERSION" + echo "" + + # Install Istio on both clusters + install_istio_cluster $PRODUCT_CLUSTER + echo "" + install_istio_cluster $SERVICES_CLUSTER + echo "" + + # Install Istio Gateway (optional, for external access) + install_istio_gateway $PRODUCT_CLUSTER + echo "" + + # Configure mTLS on both clusters + configure_mtls $PRODUCT_CLUSTER + configure_mtls $SERVICES_CLUSTER + echo "" + + # Verify installations + echo "=== Verification ===" + verify_istio $PRODUCT_CLUSTER + verify_istio $SERVICES_CLUSTER + + echo "=== Istio installation completed ===" + echo "You can now deploy the bookinfo application with: ./bookinfo.sh" +} + +help() { + echo "Usage: install-istio.sh [--help]" + echo " Install Istio service mesh on KubeSlice clusters" + echo " --help Show this help message" +} + +# Get the options +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + help + exit 0 + ;; + *) + echo "Unknown option: $1" + help + exit 1 + ;; + esac +done + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi \ No newline at end of file diff --git a/kind/bookinfo-istio/utils/bookinfo_test.sh b/kind/bookinfo-istio/utils/bookinfo_test.sh new file mode 100755 index 0000000..f6fe3bb --- /dev/null +++ b/kind/bookinfo-istio/utils/bookinfo_test.sh @@ -0,0 +1,188 @@ +#!/bin/bash + +# Enhanced test script for bookinfo with Istio and mTLS verification + +BASE_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +CONFIG_FILE=${BASE_DIR}/../../kind.env + +if [[ -f $CONFIG_FILE ]]; then + source $CONFIG_FILE +else + echo "File $CONFIG_FILE not found" + exit 1 +fi + +PRODUCT_CLUSTER="${PREFIX}${WORKERS[0]}" +SERVICES_CLUSTER="${PREFIX}${WORKERS[1]}" + +echo "=== Testing Bookinfo with Istio and mTLS ===" + +# Test basic connectivity +test_basic_connectivity() { + echo "#### Testing basic Bookinfo connectivity" + kubectx $PRODUCT_CLUSTER + PRODUCT_NODE=$(kubectl get pods -o wide -n bookinfo | tail -1 | awk '{ print $7 }') + + BI_PORT=$(kubectl get services -n bookinfo | egrep 'productpage' | grep -o -P '(?<=:).*(?=/TCP)') + echo "Product page port: $BI_PORT" + + BI_ADDR_STR=$(kubectl get nodes -o wide | egrep "$PRODUCT_NODE" | awk '{ print $6 }') + + WSL=$(ps -elf | egrep "wsl\/docker-desktop" | egrep -v grep | awk '{ print $15 }') + + if [[ $WSL != "" ]]; then + echo "#### WSL Environment Detected" + echo "Started forwarding the port to access the ProductPage UI" + echo "Please use CTRL-C to exit & stop the port forwarding" + echo "Access productpage on browser with the URL: http://localhost:$BI_PORT/productpage" + kubectl port-forward svc/productpage -n bookinfo $BI_PORT:9080 + return + fi + + echo "Testing: curl http://$BI_ADDR_STR:$BI_PORT/productpage" + if curl -s http://$BI_ADDR_STR:$BI_PORT/productpage | grep -q 'Comedy of Errors'; then + echo "✓ Bookinfo Reviews Page OK" + else + echo "✗ Bookinfo Reviews Page FAIL" + fi + + if curl -s http://$BI_ADDR_STR:$BI_PORT/productpage | grep -q 'Type'; then + echo "✓ Bookinfo Details Page OK" + else + echo "✗ Bookinfo Details Page FAIL" + fi + + if curl -s http://$BI_ADDR_STR:$BI_PORT/productpage | grep -q 'slapstick'; then + echo "✓ Bookinfo Product Page OK" + else + echo "✗ Bookinfo Product Page FAIL" + fi +} + +# Test Istio proxy presence +test_istio_sidecars() { + echo "#### Testing Istio sidecar injection" + + echo "Checking productpage sidecars on $PRODUCT_CLUSTER..." + kubectx $PRODUCT_CLUSTER + PROD_CONTAINERS=$(kubectl get pods -n bookinfo -l app=productpage -o jsonpath='{.items[0].spec.containers[*].name}') + if echo "$PROD_CONTAINERS" | grep -q "istio-proxy"; then + echo "✓ Productpage has Istio sidecar" + else + echo "✗ Productpage missing Istio sidecar" + echo "Containers: $PROD_CONTAINERS" + fi + + echo "Checking services sidecars on $SERVICES_CLUSTER..." + kubectx $SERVICES_CLUSTER + + for service in details reviews ratings; do + CONTAINERS=$(kubectl get pods -n bookinfo -l app=$service -o jsonpath='{.items[0].spec.containers[*].name}' 2>/dev/null) + if echo "$CONTAINERS" | grep -q "istio-proxy"; then + echo "✓ $service has Istio sidecar" + else + echo "✗ $service missing Istio sidecar" + echo "Containers: $CONTAINERS" + fi + done +} + +# Test mTLS configuration +test_mtls() { + echo "#### Testing mTLS configuration" + + kubectx $PRODUCT_CLUSTER + + # Check PeerAuthentication + if kubectl get peerauthentication default -n bookinfo &>/dev/null; then + MTLS_MODE=$(kubectl get peerauthentication default -n bookinfo -o jsonpath='{.spec.mtls.mode}') + if [[ "$MTLS_MODE" == "STRICT" ]]; then + echo "✓ mTLS is configured in STRICT mode" + else + echo "⚠ mTLS mode: $MTLS_MODE (expected STRICT)" + fi + else + echo "✗ PeerAuthentication not found" + fi + + # Check for mTLS certificates in productpage pod + PRODUCTPAGE_POD=$(kubectl get pods -n bookinfo -l app=productpage -o jsonpath='{.items[0].metadata.name}') + if [[ -n "$PRODUCTPAGE_POD" ]]; then + echo "Checking mTLS certificates in productpage pod..." + CERTS=$(kubectl exec -n bookinfo $PRODUCTPAGE_POD -c istio-proxy -- find /etc/ssl/certs -name "*.pem" | wc -l 2>/dev/null || echo "0") + if [[ "$CERTS" -gt 0 ]]; then + echo "✓ Found $CERTS certificate files in istio-proxy" + else + echo "⚠ No certificates found in istio-proxy" + fi + fi +} + +# Test service connectivity with mTLS +test_service_connectivity() { + echo "#### Testing service-to-service connectivity with mTLS" + + kubectx $PRODUCT_CLUSTER + PRODUCTPAGE_POD=$(kubectl get pods -n bookinfo -l app=productpage -o jsonpath='{.items[0].metadata.name}') + + if [[ -n "$PRODUCTPAGE_POD" ]]; then + echo "Testing connectivity from productpage to reviews service..." + + # Test connection to reviews service through Istio proxy + REVIEWS_RESPONSE=$(kubectl exec -n bookinfo $PRODUCTPAGE_POD -c productpage -- curl -s "http://reviews.bookinfo.svc.slice.local:9080/reviews/0" | head -c 100) + if [[ -n "$REVIEWS_RESPONSE" ]]; then + echo "✓ Productpage can connect to reviews service" + else + echo "✗ Failed to connect to reviews service" + fi + + echo "Testing connectivity from productpage to details service..." + DETAILS_RESPONSE=$(kubectl exec -n bookinfo $PRODUCTPAGE_POD -c productpage -- curl -s "http://details.bookinfo.svc.slice.local:9080/details/0" | head -c 100) + if [[ -n "$DETAILS_RESPONSE" ]]; then + echo "✓ Productpage can connect to details service" + else + echo "✗ Failed to connect to details service" + fi + else + echo "✗ Productpage pod not found" + fi +} + +# Test Istio configuration +test_istio_config() { + echo "#### Testing Istio configuration" + + kubectx $PRODUCT_CLUSTER + + # Check Gateway + if kubectl get gateway bookinfo-gateway -n bookinfo &>/dev/null; then + echo "✓ Istio Gateway configured" + else + echo "✗ Istio Gateway not found" + fi + + # Check VirtualService + if kubectl get virtualservice bookinfo -n bookinfo &>/dev/null; then + echo "✓ Istio VirtualService configured" + else + echo "✗ Istio VirtualService not found" + fi +} + +main() { + test_basic_connectivity + echo "" + test_istio_sidecars + echo "" + test_mtls + echo "" + test_service_connectivity + echo "" + test_istio_config + echo "" + echo "=== Test Summary ===" + echo "If all tests pass, your Bookinfo application is running with Istio and mTLS!" +} + +main + diff --git a/kind/bookinfo-istio/utils/verify_mtls.sh b/kind/bookinfo-istio/utils/verify_mtls.sh new file mode 100755 index 0000000..f5a7be7 --- /dev/null +++ b/kind/bookinfo-istio/utils/verify_mtls.sh @@ -0,0 +1,176 @@ +#!/bin/bash + +# Script to verify mTLS is working between services + +set -e + +BASE_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +CONFIG_FILE=${BASE_DIR}/../../kind.env + +if [[ -f $CONFIG_FILE ]]; then + source $CONFIG_FILE +else + echo "File $CONFIG_FILE not found" + exit 1 +fi + +PRODUCT_CLUSTER="${PREFIX}${WORKERS[0]}" +SERVICES_CLUSTER="${PREFIX}${WORKERS[1]}" + +echo "=== mTLS Verification Script ===" + +verify_certificates() { + local cluster=$1 + local app=$2 + + echo "Checking certificates for $app on $cluster..." + kubectx $cluster + + POD=$(kubectl get pods -n bookinfo -l app=$app -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + if [[ -z "$POD" ]]; then + echo "✗ No pod found for app=$app" + return 1 + fi + + echo "Pod: $POD" + + # Check if istio-proxy container exists + if ! kubectl get pod $POD -n bookinfo -o jsonpath='{.spec.containers[*].name}' | grep -q istio-proxy; then + echo "✗ istio-proxy container not found" + return 1 + fi + + # Check certificates in the istio-proxy + echo "Checking certificates in istio-proxy..." + CERT_COUNT=$(kubectl exec -n bookinfo $POD -c istio-proxy -- sh -c 'find /etc/ssl/certs -name "*.pem" | wc -l' 2>/dev/null || echo "0") + echo "Certificate files found: $CERT_COUNT" + + # Check Envoy config for mTLS + echo "Checking Envoy configuration for mTLS..." + TLS_CONFIG=$(kubectl exec -n bookinfo $POD -c istio-proxy -- sh -c 'curl -s localhost:15000/config_dump | grep -c "tls_context"' 2>/dev/null || echo "0") + echo "TLS contexts found: $TLS_CONFIG" + + if [[ "$TLS_CONFIG" -gt 0 ]]; then + echo "✓ $app has mTLS configuration" + else + echo "⚠ $app may not have mTLS properly configured" + fi +} + +test_mtls_communication() { + echo "Testing mTLS communication between services..." + + kubectx $PRODUCT_CLUSTER + PRODUCTPAGE_POD=$(kubectl get pods -n bookinfo -l app=productpage -o jsonpath='{.items[0].metadata.name}') + + if [[ -z "$PRODUCTPAGE_POD" ]]; then + echo "✗ Productpage pod not found" + return 1 + fi + + echo "Testing connection to reviews service with mTLS..." + # Try to connect through Envoy proxy + REVIEWS_TEST=$(kubectl exec -n bookinfo $PRODUCTPAGE_POD -c productpage -- sh -c 'curl -s -w "%{http_code}" -o /dev/null http://reviews.bookinfo.svc.slice.local:9080/reviews/0' 2>/dev/null || echo "000") + + if [[ "$REVIEWS_TEST" == "200" ]]; then + echo "✓ mTLS connection to reviews service successful" + else + echo "⚠ mTLS connection to reviews service returned: $REVIEWS_TEST" + fi + + echo "Testing connection to details service with mTLS..." + DETAILS_TEST=$(kubectl exec -n bookinfo $PRODUCTPAGE_POD -c productpage -- sh -c 'curl -s -w "%{http_code}" -o /dev/null http://details.bookinfo.svc.slice.local:9080/details/0' 2>/dev/null || echo "000") + + if [[ "$DETAILS_TEST" == "200" ]]; then + echo "✓ mTLS connection to details service successful" + else + echo "⚠ mTLS connection to details service returned: $DETAILS_TEST" + fi +} + +check_peer_authentication() { + echo "Checking PeerAuthentication policies..." + + for cluster in $PRODUCT_CLUSTER $SERVICES_CLUSTER; do + echo "Cluster: $cluster" + kubectx $cluster + + if kubectl get peerauthentication default -n bookinfo &>/dev/null; then + MODE=$(kubectl get peerauthentication default -n bookinfo -o jsonpath='{.spec.mtls.mode}') + echo "✓ PeerAuthentication mode: $MODE" + else + echo "✗ PeerAuthentication not found" + fi + done +} + +main() { + echo "Starting mTLS verification..." + echo "" + + check_peer_authentication + echo "" + + verify_certificates $PRODUCT_CLUSTER "productpage" + echo "" + + verify_certificates $SERVICES_CLUSTER "details" + echo "" + + verify_certificates $SERVICES_CLUSTER "reviews" + echo "" + + verify_certificates $SERVICES_CLUSTER "ratings" + echo "" + + test_mtls_communication + echo "" + + echo "=== mTLS Verification Complete ===" +} + +CLUSTERS=("cluster1" "cluster2") + +echo "=== Verifying mTLS Configuration ===" +echo + +for cluster in "${CLUSTERS[@]}"; do + echo "Checking cluster: $cluster" + echo "---------------------------------" + + # Check PeerAuthentication policy + echo "PeerAuthentication policy:" + kubectl --context=$cluster get peerauthentication -n bookinfo + + # Get a pod with istio-proxy for verification + POD=$(kubectl --context=$cluster get pod -n bookinfo -l app=productpage -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || \ + kubectl --context=$cluster get pod -n bookinfo -l app=details -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -n "$POD" ]; then + echo + echo "Checking mTLS certificates in pod $POD:" + kubectl --context=$cluster exec -n bookinfo $POD -c istio-proxy -- pilot-agent request GET stats | grep ssl_context + + echo + echo "Verifying Envoy is configured for mTLS:" + kubectl --context=$cluster exec -n bookinfo $POD -c istio-proxy -- curl -s localhost:15000/config_dump | grep -o "\"tls_inspector\"\|\"transport_socket\"" | sort | uniq -c + + echo + echo "Checking upstream TLS configuration:" + kubectl --context=$cluster exec -n bookinfo $POD -c istio-proxy -- curl -s localhost:15000/config_dump | grep '"alpn_protocols": \["istio"\]' -B 5 -A 5 | head -n 15 + else + echo "No pods with istio-proxy found in namespace bookinfo" + fi + + echo + echo "Checking for Authorization Policies:" + kubectl --context=$cluster get authorizationpolicies -n bookinfo + + echo + echo "---------------------------------" + echo +done + +echo "=== mTLS Verification Complete ===" + +main \ No newline at end of file From 976cd1f511c975356308a4792d35c53e7b66f52b Mon Sep 17 00:00:00 2001 From: sanjay7178 Date: Sun, 10 Aug 2025 19:43:49 +0530 Subject: [PATCH 2/9] chore: remove obsolete Istio configuration files for ingress gateway, base, and discovery Signed-off-by: sanjay7178 --- .../config_files/ingress-gw-install.yaml | 15 ------------ .../config_files/istio-base.yaml | 23 ------------------- .../config_files/istio-discovery.yaml | 22 ------------------ 3 files changed, 60 deletions(-) delete mode 100644 kind/bookinfo-istio/config_files/ingress-gw-install.yaml delete mode 100644 kind/bookinfo-istio/config_files/istio-base.yaml delete mode 100644 kind/bookinfo-istio/config_files/istio-discovery.yaml diff --git a/kind/bookinfo-istio/config_files/ingress-gw-install.yaml b/kind/bookinfo-istio/config_files/ingress-gw-install.yaml deleted file mode 100644 index bba35e4..0000000 --- a/kind/bookinfo-istio/config_files/ingress-gw-install.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: install.istio.io/v1alpha1 -kind: IstioOperator -metadata: - name: ingress-gateway-install - namespace: istio-system -spec: - profile: empty - components: - ingressGateways: - - name: istio-ingressgateway - namespace: istio-system - enabled: true - label: - app: istio-ingressgateway - istio: ingressgateway \ No newline at end of file diff --git a/kind/bookinfo-istio/config_files/istio-base.yaml b/kind/bookinfo-istio/config_files/istio-base.yaml deleted file mode 100644 index ea6104e..0000000 --- a/kind/bookinfo-istio/config_files/istio-base.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# Istio Base Configuration -# This would typically be installed via: istioctl install --set values.defaultRevision=default -# For this example, we include the essential CRDs and configurations - -apiVersion: v1 -kind: Namespace -metadata: - name: istio-system - labels: - istio-injection: disabled ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: istio-reader-service-account - namespace: istio-system - labels: - app: istio-reader - release: istio ---- -# Note: In a real deployment, this would include all Istio CRDs and base components -# For this example, we're focusing on the application configuration -# Assume Istio is pre-installed via: kubectl apply -f https://github.com/istio/istio/releases/download/1.20.1/istio-base.yaml \ No newline at end of file diff --git a/kind/bookinfo-istio/config_files/istio-discovery.yaml b/kind/bookinfo-istio/config_files/istio-discovery.yaml deleted file mode 100644 index 3580261..0000000 --- a/kind/bookinfo-istio/config_files/istio-discovery.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Istio Discovery Service Configuration -# This would typically be installed via: istioctl install --set values.defaultRevision=default -# For this example, we're referencing what would be applied - -apiVersion: v1 -kind: ConfigMap -metadata: - name: istio - namespace: istio-system - labels: - istio.io/rev: default -data: - mesh: | - defaultConfig: - meshId: mesh1 - trustDomain: cluster.local - trustDomainAliases: - - cluster.local ---- -# Note: In a real deployment, this would include the full Istio discovery service -# For this example, we assume Istio istiod is installed via: -# kubectl apply -f https://github.com/istio/istio/releases/download/1.20.1/istio-discovery.yaml \ No newline at end of file From 9fb7f1e045d93f5a0ec755cef1c86a4d18eee707 Mon Sep 17 00:00:00 2001 From: sanjay7178 Date: Sun, 10 Aug 2025 22:51:33 +0530 Subject: [PATCH 3/9] feat: update Bookinfo deployment guide and cleanup scripts; enhance ingress troubleshooting steps Signed-off-by: sanjay7178 --- kind/bookinfo-istio/README.md | 11 + kind/bookinfo-istio/cleanup.sh | 119 +++++ kind/bookinfo-istio/config_files/gateway.yaml | 4 +- .../config_files/istio-allow-ingress.yaml | 12 - .../config_files/istio-authorization.yaml | 73 --- .../config_files/istio-rbac.yaml | 12 - .../config_files/peer-authentication.yaml | 30 +- .../config_files/productpage.yaml | 3 + .../config_files/serviceexports.yaml | 9 +- kind/bookinfo-istio/deployment.md | 476 ++++++++++++++++++ 10 files changed, 624 insertions(+), 125 deletions(-) create mode 100755 kind/bookinfo-istio/cleanup.sh delete mode 100644 kind/bookinfo-istio/config_files/istio-allow-ingress.yaml delete mode 100644 kind/bookinfo-istio/config_files/istio-authorization.yaml delete mode 100644 kind/bookinfo-istio/config_files/istio-rbac.yaml create mode 100644 kind/bookinfo-istio/deployment.md diff --git a/kind/bookinfo-istio/README.md b/kind/bookinfo-istio/README.md index 19988b3..c9bc852 100644 --- a/kind/bookinfo-istio/README.md +++ b/kind/bookinfo-istio/README.md @@ -200,6 +200,17 @@ Specialized mTLS verification script that: ``` Ensure KubeSlice is properly configured +5. **Blank page or "Site can't be reached" on productpage**: + ```bash + ./utils/fix-istio-ingress.sh + ``` + This script diagnoses and fixes common ingress gateway issues. + Alternatively, check: + - Istio Gateway and VirtualService configuration + - AuthorizationPolicy to ensure it allows external traffic + - Istio ingress gateway logs for any errors + - Firewall rules to ensure port 80 is open + ### Debug Commands ```bash diff --git a/kind/bookinfo-istio/cleanup.sh b/kind/bookinfo-istio/cleanup.sh new file mode 100755 index 0000000..4914a6a --- /dev/null +++ b/kind/bookinfo-istio/cleanup.sh @@ -0,0 +1,119 @@ +#!/bin/bash + +set -e + +BASE_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +CONFIG_FILE=${BASE_DIR}/../kind.env + +if [[ -f $CONFIG_FILE ]]; then + source $CONFIG_FILE +else + echo "File $CONFIG_FILE not found" + exit 1 +fi + +# Define cluster contexts +PRODUCT_CLUSTER="${PREFIX}${WORKERS[0]}" +SERVICES_CLUSTER="${PREFIX}${WORKERS[1]}" + +# Use generic contexts if the script is run in an environment without the prefix +if [[ -z "$PRODUCT_CLUSTER" ]]; then + PRODUCT_CLUSTER="cluster1" + SERVICES_CLUSTER="cluster2" +fi + +echo "==== Starting Bookinfo-Istio Cleanup ====" +echo "Product Cluster: $PRODUCT_CLUSTER" +echo "Services Cluster: $SERVICES_CLUSTER" +echo "" + +# Function to remove resources from a specific cluster +cleanup_cluster() { + local cluster=$1 + echo "Cleaning up resources in $cluster..." + + # Switch context to the cluster + kubectl config use-context $cluster + + # Remove authorization policies + echo "Removing Authorization Policies..." + kubectl delete authorizationpolicy --all -n bookinfo 2>/dev/null || true + + # Remove peer authentication + echo "Removing Peer Authentication..." + kubectl delete peerauthentication --all -n bookinfo 2>/dev/null || true + + # Remove Istio Gateway and VirtualService + if [[ "$cluster" == "$PRODUCT_CLUSTER" ]]; then + echo "Removing Istio Gateway and VirtualService..." + kubectl delete gateway --all -n bookinfo 2>/dev/null || true + kubectl delete virtualservice --all -n bookinfo 2>/dev/null || true + fi + + # Remove ServiceExports + echo "Removing ServiceExports..." + kubectl delete serviceexports.networking.kubeslice.io --all -n bookinfo 2>/dev/null || true + + # Remove Bookinfo deployments and services + echo "Removing Bookinfo deployments and services..." + kubectl delete deployment --all -n bookinfo 2>/dev/null || true + kubectl delete service --all -n bookinfo 2>/dev/null || true + + # Remove Bookinfo ServiceAccounts + echo "Removing Bookinfo ServiceAccounts..." + kubectl delete serviceaccount --all -n bookinfo 2>/dev/null || true + + # Delete the bookinfo namespace last + echo "Removing bookinfo namespace..." + kubectl delete namespace bookinfo --grace-period=0 --force 2>/dev/null || true + + echo "Cleanup completed for $cluster" + echo "" +} + +# Cleanup Istio resources +cleanup_istio() { + local cluster=$1 + echo "Cleaning up Istio from $cluster (optional)..." + + kubectl config use-context $cluster + + # Check if istioctl is available + if command -v istioctl &> /dev/null; then + echo "Using istioctl to remove Istio..." + istioctl uninstall --purge -y 2>/dev/null || true + else + echo "istioctl not found, removing Istio resources manually..." + kubectl delete namespace istio-system --grace-period=0 --force 2>/dev/null || true + fi + + echo "Istio cleanup completed for $cluster" + echo "" +} + +# Ask user if they want to cleanup just Bookinfo or also Istio +read -p "Do you want to remove Istio as well? (y/n, default: n): " remove_istio +remove_istio=${remove_istio:-n} + +# Clean up both clusters +cleanup_cluster $PRODUCT_CLUSTER +cleanup_cluster $SERVICES_CLUSTER + +# Optionally clean up Istio +if [[ "$remove_istio" == "y" ]]; then + cleanup_istio $PRODUCT_CLUSTER + cleanup_istio $SERVICES_CLUSTER +fi + +echo "==== Bookinfo-Istio Cleanup Complete ====" +echo "All Bookinfo resources have been removed from both clusters." +if [[ "$remove_istio" == "y" ]]; then + echo "Istio has also been removed." +else + echo "Istio is still installed. To remove it later, use 'istioctl uninstall --purge' or run this script again." +fi +echo "" +echo "To remove the KubeSlice slice, use the kubeslice controller commands." +echo "For example: kubectl delete sliceconfig convoy -n kubeslice-system" + +chmod +x $BASE_DIR/cleanup.sh diff --git a/kind/bookinfo-istio/config_files/gateway.yaml b/kind/bookinfo-istio/config_files/gateway.yaml index 803fdfa..48dfd20 100644 --- a/kind/bookinfo-istio/config_files/gateway.yaml +++ b/kind/bookinfo-istio/config_files/gateway.yaml @@ -5,14 +5,14 @@ metadata: namespace: bookinfo spec: selector: - istio: ingressgateway # use istio default controller + istio: ingressgateway # Use Istio default gateway implementation servers: - port: number: 80 name: http protocol: HTTP hosts: - - "*" + - "*" # Allow all hosts --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService diff --git a/kind/bookinfo-istio/config_files/istio-allow-ingress.yaml b/kind/bookinfo-istio/config_files/istio-allow-ingress.yaml deleted file mode 100644 index 73155e9..0000000 --- a/kind/bookinfo-istio/config_files/istio-allow-ingress.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: security.istio.io/v1beta1 -kind: AuthorizationPolicy -metadata: - name: allow-ingress-gateway - namespace: istio-system -spec: - selector: - matchLabels: - app: istio-ingressgateway - action: ALLOW - rules: - - {} # Empty rule means allow all traffic \ No newline at end of file diff --git a/kind/bookinfo-istio/config_files/istio-authorization.yaml b/kind/bookinfo-istio/config_files/istio-authorization.yaml deleted file mode 100644 index 9d59601..0000000 --- a/kind/bookinfo-istio/config_files/istio-authorization.yaml +++ /dev/null @@ -1,73 +0,0 @@ -# Apply authorization policy to Cluster 1 (Product Cluster) -apiVersion: security.istio.io/v1beta1 -kind: AuthorizationPolicy -metadata: - name: bookinfo-policy - namespace: bookinfo -spec: - action: ALLOW - rules: - - from: - - source: - principals: ["cluster.local/ns/bookinfo/sa/bookinfo-productpage"] - to: - - operation: - methods: ["GET"] - - from: - - source: - principals: ["cluster.local/ns/bookinfo/sa/bookinfo-reviews"] - to: - - operation: - methods: ["GET"] - - from: - - source: - principals: ["cluster.local/ns/bookinfo/sa/bookinfo-details"] - to: - - operation: - methods: ["GET"] - - from: - - source: - principals: ["cluster.local/ns/bookinfo/sa/bookinfo-ratings"] - to: - - operation: - methods: ["GET"] - - from: - - source: - namespaces: ["istio-system"] ---- -# Apply same policy to Cluster 2 (Services Cluster) -apiVersion: security.istio.io/v1beta1 -kind: AuthorizationPolicy -metadata: - name: bookinfo-policy - namespace: bookinfo -spec: - action: ALLOW - rules: - - from: - - source: - principals: ["cluster.local/ns/bookinfo/sa/bookinfo-productpage"] - to: - - operation: - methods: ["GET"] - - from: - - source: - principals: ["cluster.local/ns/bookinfo/sa/bookinfo-reviews"] - to: - - operation: - methods: ["GET"] - - from: - - source: - principals: ["cluster.local/ns/bookinfo/sa/bookinfo-details"] - to: - - operation: - methods: ["GET"] - - from: - - source: - principals: ["cluster.local/ns/bookinfo/sa/bookinfo-ratings"] - to: - - operation: - methods: ["GET"] - - from: - - source: - namespaces: ["istio-system"] diff --git a/kind/bookinfo-istio/config_files/istio-rbac.yaml b/kind/bookinfo-istio/config_files/istio-rbac.yaml deleted file mode 100644 index 366dfb8..0000000 --- a/kind/bookinfo-istio/config_files/istio-rbac.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: security.istio.io/v1beta1 -kind: AuthorizationPolicy -metadata: - name: allow-productpage - namespace: bookinfo -spec: - selector: - matchLabels: - app: productpage - action: ALLOW - rules: - - {} # Empty rule means allow all traffic \ No newline at end of file diff --git a/kind/bookinfo-istio/config_files/peer-authentication.yaml b/kind/bookinfo-istio/config_files/peer-authentication.yaml index 2e9863a..ad9e619 100644 --- a/kind/bookinfo-istio/config_files/peer-authentication.yaml +++ b/kind/bookinfo-istio/config_files/peer-authentication.yaml @@ -12,9 +12,10 @@ spec: apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: - name: bookinfo-allow + name: bookinfo-policy namespace: bookinfo spec: + action: ALLOW rules: - from: - source: @@ -24,13 +25,13 @@ spec: methods: ["GET"] - from: - source: - principals: ["cluster.local/ns/bookinfo/sa/bookinfo-details"] + principals: ["cluster.local/ns/bookinfo/sa/bookinfo-reviews"] to: - operation: methods: ["GET"] - from: - source: - principals: ["cluster.local/ns/bookinfo/sa/bookinfo-reviews"] + principals: ["cluster.local/ns/bookinfo/sa/bookinfo-details"] to: - operation: methods: ["GET"] @@ -40,23 +41,6 @@ spec: to: - operation: methods: ["GET"] ---- -# Apply PeerAuthentication policy to Cluster 1 (Product Cluster) -apiVersion: security.istio.io/v1beta1 -kind: PeerAuthentication -metadata: - name: default - namespace: bookinfo -spec: - mtls: - mode: STRICT ---- -# Apply same policy to Cluster 2 (Services Cluster) -apiVersion: security.istio.io/v1beta1 -kind: PeerAuthentication -metadata: - name: default - namespace: bookinfo -spec: - mtls: - mode: STRICT \ No newline at end of file + - from: + - source: + namespaces: ["istio-system"] diff --git a/kind/bookinfo-istio/config_files/productpage.yaml b/kind/bookinfo-istio/config_files/productpage.yaml index 804eece..192ff26 100644 --- a/kind/bookinfo-istio/config_files/productpage.yaml +++ b/kind/bookinfo-istio/config_files/productpage.yaml @@ -5,6 +5,7 @@ apiVersion: v1 kind: Service metadata: name: productpage + namespace: bookinfo labels: app: productpage service: productpage @@ -20,6 +21,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: bookinfo-productpage + namespace: bookinfo labels: account: productpage --- @@ -27,6 +29,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: productpage-v1 + namespace: bookinfo labels: app: productpage version: v1 diff --git a/kind/bookinfo-istio/config_files/serviceexports.yaml b/kind/bookinfo-istio/config_files/serviceexports.yaml index d7edc6d..ac9b25b 100644 --- a/kind/bookinfo-istio/config_files/serviceexports.yaml +++ b/kind/bookinfo-istio/config_files/serviceexports.yaml @@ -5,12 +5,13 @@ apiVersion: networking.kubeslice.io/v1beta1 kind: ServiceExport metadata: name: details + namespace: bookinfo spec: slice: bookinfo-slice #Replace Slice Name selector: matchLabels: app: details - ingressEnabled: false + ingressEnabled: true ports: - name: http containerPort: 9080 @@ -23,12 +24,13 @@ apiVersion: networking.kubeslice.io/v1beta1 kind: ServiceExport metadata: name: reviews + namespace: bookinfo spec: slice: bookinfo-slice #Replace Slice Name selector: matchLabels: app: reviews - ingressEnabled: false + ingressEnabled: true ports: - name: http containerPort: 9080 @@ -41,12 +43,13 @@ apiVersion: networking.kubeslice.io/v1beta1 kind: ServiceExport metadata: name: ratings + namespace: bookinfo spec: slice: bookinfo-slice #Replace Slice Name selector: matchLabels: app: ratings - ingressEnabled: false + ingressEnabled: true ports: - name: http containerPort: 9080 diff --git a/kind/bookinfo-istio/deployment.md b/kind/bookinfo-istio/deployment.md new file mode 100644 index 0000000..b936955 --- /dev/null +++ b/kind/bookinfo-istio/deployment.md @@ -0,0 +1,476 @@ +# Bookinfo with Istio and mTLS on KubeSlice - Deployment Guide + +This guide provides step-by-step instructions for deploying the Istio Bookinfo application across KubeSlice-connected clusters with mTLS enabled. + +## Prerequisites + +- Two Kubernetes clusters connected via KubeSlice +- `kubectl` installed and configured to access both clusters +- `kubectx` for easier cluster switching (optional) +- `istioctl` for Istio installation and management +- Sufficient permissions to deploy resources in both clusters + +## Deployment Steps + +### 1. Create KubeSlice Configuration + +First, we need to create a slice configuration on the KubeSlice controller: + +```bash +# Switch to controller context +kubectx gke_graphic-transit-458312-f7_us-central1_ks-controller + +# Apply slice configuration +kubectl apply -f '/home/sanjay7178/examples2/kind/bookinfo-istio/config_files/slice-config.yaml' +``` + +Expected output: +``` +sliceconfig.controller.kubeslice.io/bookinfo-slice created +``` + +### 2. Install Istio on Both Clusters + +Installing Istio using istioctl provides a simpler, more direct approach with built-in profiles. + +```bash +# Install Istio on Worker Cluster 1 (Product Cluster) +istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 -y + +# Install Istio on Worker Cluster 2 (Services Cluster) +istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 -y +``` + +Expected output for each command: +``` + |\ + | \ + | \ + | \ + /|| \ + / || \ + / || \ + / || \ + / || \ + / || \ +/______||__________\ +____________________ + \__ _____/ + \_____/ + +✔ Istio core installed ⛵️ +✔ Istiod installed 🧠 +✔ CNI installed 🪢 +✔ Egress gateways installed 🛫 +✔ Ingress gateways installed 🛬 +✔ Installation complete +``` + +> **Note**: If you see warnings about version downgrades or revision changes, these are generally safe to proceed with for a fresh installation. + +#### 2.1 Verify Istio Installation + +```bash +# Check Istio services +kubectl get svc -n istio-system +``` + +Expected output: +``` +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +istio-egressgateway ClusterIP 10.96.x.x 80/TCP,443/TCP 1m +istio-ingressgateway LoadBalancer 10.96.x.x 34.23.96.143 15021:31xxx/TCP,80:31xxx/TCP,443:31xxx/TCP 1m +istiod ClusterIP 10.96.x.x 15010/TCP,15012/TCP,443/TCP,15014/TCP 1m +``` + +Get the external IP of the ingress gateway: + +```bash +kubectl get svc istio-ingressgateway -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}' +``` + +Expected output: +``` +34.23.96.143 +``` + +### 3. Prepare Application Namespace + +Perform these steps on both worker clusters: + +```bash +# Create bookinfo namespace +kubectl create namespace bookinfo + +# Enable Istio injection +kubectl label namespace bookinfo istio-injection=enabled +``` + +Expected output: +``` +namespace/bookinfo created +namespace/bookinfo labeled +``` + +> **Note**: If the namespace already exists, you'll see: "Error from server (AlreadyExists): namespaces "bookinfo" already exists" + +### 4. Deploy Bookinfo Application + +#### 4.1 Deploy Frontend on Worker Cluster 1 + +```bash +# Deploy productpage service +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/productpage.yaml -n bookinfo + +# Deploy Istio gateway for external access +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/gateway.yaml -n bookinfo +``` + +Expected output: +``` +service/productpage created +serviceaccount/bookinfo-productpage created +deployment.apps/productpage-v1 created +gateway.networking.istio.io/bookinfo-gateway created +virtualservice.networking.istio.io/bookinfo created +``` + +#### 4.2 Deploy Backend Services on Worker Cluster 2 + +```bash +# Deploy details service +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/details.yaml -n bookinfo + +# Deploy reviews service +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/reviews.yaml -n bookinfo + +# Deploy ratings service +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/ratings.yaml -n bookinfo +``` + +Expected output: +``` +service/details created +serviceaccount/bookinfo-details created +deployment.apps/details-v1 created +service/reviews created +serviceaccount/bookinfo-reviews created +deployment.apps/reviews-v3 created +service/ratings created +serviceaccount/bookinfo-ratings created +deployment.apps/ratings-v1 created +``` + +### 5. Configure KubeSlice ServiceExports + +Export services from Worker Cluster 2 to make them accessible in Worker Cluster 1: + +```bash +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/serviceexports.yaml -n bookinfo +``` + +Expected output: +``` +serviceexport.networking.kubeslice.io/details created +serviceexport.networking.kubeslice.io/reviews created +serviceexport.networking.kubeslice.io/ratings created +``` + +Verify ServiceExports on Worker Cluster 2: + +```bash +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo +``` + +Initial output (status may be PENDING): +``` +NAME SLICE INGRESS SERVICEPORT(S) PORT(S) ENDPOINTS STATUS ALIAS +details convoy 9080/TCP PENDING +reviews convoy 9080/TCP PENDING +``` + +Wait a few minutes, then check again: + +```bash +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo +``` + +Expected output after propagation: +``` +NAME SLICE INGRESS SERVICEPORT(S) PORT(S) ENDPOINTS STATUS ALIAS +details bookinfo-slice false 9080/TCP READY +ratings bookinfo-slice 9080/TCP READY +reviews bookinfo-slice false 9080/TCP READY +``` + +Verify ServiceImports on Worker Cluster 1: + +```bash +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get serviceimports -n bookinfo +``` + +Expected output: +``` +NAME SLICE PORT(S) ENDPOINTS STATUS ALIAS +details bookinfo-slice 9080/TCP READY +ratings bookinfo-slice 9080/TCP READY +reviews bookinfo-slice 9080/TCP READY +``` + +### 6. Enable mTLS Security + +Apply strict mTLS policies to both clusters: + +```bash +# Apply strict mTLS policy on Worker Cluster 1 +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/peer-authentication.yaml + +# Apply strict mTLS policy on Worker Cluster 2 +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/peer-authentication.yaml +``` + +Expected output for each command: +``` +peerauthentication.security.istio.io/default created +authorizationpolicy.security.istio.io/bookinfo-allow created +``` + +### 7. Configure Authorization Policies + +Create authorization policies to allow necessary traffic: + +```bash +# Allow access to productpage +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/istio-rbac.yaml + +# Allow external traffic to the ingress gateway +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/istio-allow-ingress.yaml +``` + +Expected output: +``` +authorizationpolicy.security.istio.io/allow-productpage created +authorizationpolicy.security.istio.io/allow-ingress-gateway created +``` + +### 8. Verify Cross-Cluster Connectivity + +Check DNS resolution and connectivity from the productpage pod: + +```bash +# Get the productpage pod name +PRODUCTPAGE_POD=$(kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get pods -n bookinfo -l app=productpage -o jsonpath='{.items[0].metadata.name}') + +# Exec into the pod to test connectivity +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 exec -it -n bookinfo $PRODUCTPAGE_POD -c netshoot -- /bin/bash +``` + +Once inside the pod, run these commands: + +```bash +# Test DNS resolution for details service +nslookup details + +# Test DNS resolution for reviews service +nslookup reviews + +# Test DNS resolution for ratings service +nslookup ratings +``` + +Expected output: +``` +Server: 127.0.0.1 +Address: 127.0.0.1#53 + +Name: details.bookinfo.svc.cluster.local +Address: 34.118.236.170 + +Server: 127.0.0.1 +Address: 127.0.0.1#53 + +Name: reviews.bookinfo.svc.cluster.local +Address: 34.118.231.183 + +Server: 127.0.0.1 +Address: 127.0.0.1#53 + +Name: ratings.bookinfo.svc.cluster.local +Address: 34.118.226.89 +``` + +Type `exit` to leave the pod. + +### 9. Optional: Ensure Certificate Trust Between Clusters + +To ensure mutual trust for mTLS between clusters, copy root certificates: + +```bash +# Copy root-cert from Cluster 1 to Cluster 2 +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get configmap -n istio-system istio-ca-root-cert -o yaml | \ + sed 's/namespace: istio-system/namespace: kubeslice-system/' | \ + kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f - + +# Copy root-cert from Cluster 2 to Cluster 1 +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get configmap -n istio-system istio-ca-root-cert -o yaml | \ + sed 's/namespace: istio-system/namespace: kubeslice-system/' | \ + kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f - +``` + +### 10. Verify mTLS Configuration + +Check PeerAuthentication policies on both clusters to confirm mTLS is enabled: + +```bash +# Check Worker Cluster 1 +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get peerauthentication -n bookinfo -o yaml + +# Check Worker Cluster 2 +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get peerauthentication -n bookinfo -o yaml +``` + +Expected output should show `mode: STRICT` in the spec section: +```yaml +apiVersion: v1 +items: +- apiVersion: security.istio.io/v1 + kind: PeerAuthentication + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"security.istio.io/v1beta1","kind":"PeerAuthentication","metadata":{"annotations":{},"name":"default","namespace":"bookinfo"},"spec":{"mtls":{"mode":"STRICT"}}} + creationTimestamp: "2025-08-10T15:48:31Z" + generation: 1 + name: default + namespace: bookinfo + resourceVersion: "1754840911946783017" + uid: 9cf690b8-e895-455b-8d76-c875fdd9fa5e + spec: + mtls: + mode: STRICT +kind: List +metadata: + resourceVersion: "" +``` + +### 11. Access the Application + +Access the Bookinfo application using the Istio Ingress Gateway's external IP: + +```bash +# Get the Ingress Gateway IP +GATEWAY_IP=$(kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get svc istio-ingress -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + +echo "Access the Bookinfo application at: http://$GATEWAY_IP/productpage" +``` + +## Troubleshooting + +### 1. ServiceImports Stuck in PENDING State + +If ServiceImports are stuck in PENDING state: + +```bash +# Check status +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get serviceimports -n bookinfo +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo + +# Delete and recreate the ServiceExport +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 delete serviceexport -n bookinfo +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/serviceexports.yaml -n bookinfo +``` + +### 2. Istio CRDs Not Installed + +If you encounter errors about missing Istio CRDs, reinstall Istio: + +```bash +# Install Istio using istioctl +istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 -y +istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 -y +``` + +### 3. Blank Productpage + +If the productpage shows a blank page or cannot be reached: + +```bash +# Check if the Istio Gateway is properly configured +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get gateway -n bookinfo +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get virtualservice -n bookinfo + +# Ensure authorization policies allow external traffic +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/istio-allow-ingress.yaml +``` + +## Conclusion + +You have successfully deployed the Bookinfo application across two KubeSlice-connected clusters with Istio service mesh and mTLS enabled. The application architecture provides: + +1. Secure service-to-service communication with mTLS +2. Cross-cluster service discovery via KubeSlice +3. External access through Istio Gateway + +For more details on the architecture and features, refer to the [README.md](./README.md). + resourceVersion: "" +``` + +### 11. Access the Application + +Access the Bookinfo application using the Istio Ingress Gateway's external IP: + +```bash +# Get the Ingress Gateway IP +GATEWAY_IP=$(kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get svc istio-ingress -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + +echo "Access the Bookinfo application at: http://$GATEWAY_IP/productpage" +``` + +## Troubleshooting + +### 1. ServiceImports Stuck in PENDING State + +If ServiceImports are stuck in PENDING state: + +```bash +# Check status +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get serviceimports -n bookinfo +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo + +# Delete and recreate the ServiceExport +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 delete serviceexport -n bookinfo +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/serviceexports.yaml -n bookinfo +``` + +### 2. Istio CRDs Not Installed + +If you encounter errors about missing Istio CRDs: + +```bash +# Install Istio using istioctl +istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 -y +istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 -y +``` + +### 3. Blank Productpage + +If the productpage shows a blank page or cannot be reached: + +```bash +# Check if the Istio Gateway is properly configured +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get gateway -n bookinfo +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get virtualservice -n bookinfo + +# Ensure authorization policies allow external traffic +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/istio-allow-ingress.yaml +``` + +## Conclusion + +You have successfully deployed the Bookinfo application across two KubeSlice-connected clusters with Istio service mesh and mTLS enabled. The application architecture provides: + +1. Secure service-to-service communication with mTLS +2. Cross-cluster service discovery via KubeSlice +3. External access through Istio Gateway + +For more details on the architecture and features, refer to the [README.md](./README.md). From 9904f214c86ccf81ab9814dcee68212ec9d6c254 Mon Sep 17 00:00:00 2001 From: sanjay7178 Date: Sun, 10 Aug 2025 22:53:54 +0530 Subject: [PATCH 4/9] chore: remove obsolete scripts for Bookinfo Istio deployment Signed-off-by: sanjay7178 --- kind/bookinfo-istio/check-prerequisites.sh | 146 ----------------- kind/bookinfo-istio/demo.sh | 173 --------------------- kind/bookinfo-istio/install-istio.sh | 161 ------------------- 3 files changed, 480 deletions(-) delete mode 100755 kind/bookinfo-istio/check-prerequisites.sh delete mode 100755 kind/bookinfo-istio/demo.sh delete mode 100755 kind/bookinfo-istio/install-istio.sh diff --git a/kind/bookinfo-istio/check-prerequisites.sh b/kind/bookinfo-istio/check-prerequisites.sh deleted file mode 100755 index 8d43be9..0000000 --- a/kind/bookinfo-istio/check-prerequisites.sh +++ /dev/null @@ -1,146 +0,0 @@ -#!/bin/bash - -# Prerequisites check script for bookinfo-istio deployment - -echo "=== Prerequisites Check for Bookinfo-Istio ===" - -check_command() { - local cmd=$1 - local package=$2 - - if command -v $cmd &> /dev/null; then - echo "✓ $cmd is available" - return 0 - else - echo "✗ $cmd is not available" - if [[ -n "$package" ]]; then - echo " Install with: $package" - fi - return 1 - fi -} - -check_kubernetes_connectivity() { - echo "Checking Kubernetes connectivity..." - - if kubectl cluster-info &> /dev/null; then - echo "✓ kubectl can connect to cluster" - - # Check current context - CURRENT_CONTEXT=$(kubectl config current-context) - echo " Current context: $CURRENT_CONTEXT" - - # Check if we can list namespaces - if kubectl get namespaces &> /dev/null; then - echo "✓ Can list namespaces" - else - echo "✗ Cannot list namespaces (insufficient permissions)" - fi - else - echo "✗ kubectl cannot connect to cluster" - echo " Please ensure your kubeconfig is set up correctly" - return 1 - fi -} - -check_kubeslice_clusters() { - echo "Checking KubeSlice cluster configuration..." - - BASE_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" - ENV_FILE=${BASE_DIR}/../kind.env - - if [[ -f $ENV_FILE ]]; then - echo "✓ Found kind.env configuration file" - source $ENV_FILE - - PRODUCT_CLUSTER="${PREFIX}${WORKERS[0]}" - SERVICES_CLUSTER="${PREFIX}${WORKERS[1]}" - - echo " Product cluster: $PRODUCT_CLUSTER" - echo " Services cluster: $SERVICES_CLUSTER" - - # Check if kubectx is available - if command -v kubectx &> /dev/null; then - echo "✓ kubectx is available" - - # Check if clusters exist - if kubectx $PRODUCT_CLUSTER &> /dev/null; then - echo "✓ Product cluster ($PRODUCT_CLUSTER) is accessible" - else - echo "✗ Product cluster ($PRODUCT_CLUSTER) is not accessible" - fi - - if kubectx $SERVICES_CLUSTER &> /dev/null; then - echo "✓ Services cluster ($SERVICES_CLUSTER) is accessible" - else - echo "✗ Services cluster ($SERVICES_CLUSTER) is not accessible" - fi - else - echo "⚠ kubectx is not available - will use kubectl config use-context instead" - - # Check contexts manually - CONTEXTS=$(kubectl config get-contexts -o name) - if echo "$CONTEXTS" | grep -q "$PRODUCT_CLUSTER"; then - echo "✓ Product cluster context exists" - else - echo "✗ Product cluster context not found" - fi - - if echo "$CONTEXTS" | grep -q "$SERVICES_CLUSTER"; then - echo "✓ Services cluster context exists" - else - echo "✗ Services cluster context not found" - fi - fi - else - echo "✗ kind.env configuration file not found at $ENV_FILE" - echo " Please ensure you're running this from the correct directory" - return 1 - fi -} - -check_istio_availability() { - echo "Checking Istio availability..." - - if command -v istioctl &> /dev/null; then - echo "✓ istioctl is available" - ISTIO_VERSION=$(istioctl version --short 2>/dev/null || echo "unknown") - echo " Version: $ISTIO_VERSION" - else - echo "⚠ istioctl is not available" - echo " The deployment will use kubectl to install Istio manifests" - echo " For better control, consider installing istioctl:" - echo " curl -L https://istio.io/downloadIstio | sh -" - fi -} - -main() { - echo "Required tools:" - check_command "kubectl" "https://kubernetes.io/docs/tasks/tools/install-kubectl/" - echo "" - - echo "Optional tools:" - check_command "kubectx" "https://github.com/ahmetb/kubectx" - check_command "istioctl" "https://istio.io/latest/docs/setup/getting-started/" - echo "" - - check_kubernetes_connectivity - echo "" - - check_kubeslice_clusters - echo "" - - check_istio_availability - echo "" - - echo "=== Summary ===" - echo "If all checks pass, you're ready to deploy bookinfo with Istio!" - echo "" - echo "Deployment steps:" - echo "1. Install Istio (optional): ./install-istio.sh" - echo "2. Deploy bookinfo: ./bookinfo.sh" - echo "3. Test deployment: ./utils/bookinfo_test.sh" - echo "4. Verify mTLS: ./utils/verify_mtls.sh" -} - -main \ No newline at end of file diff --git a/kind/bookinfo-istio/demo.sh b/kind/bookinfo-istio/demo.sh deleted file mode 100755 index dd8941b..0000000 --- a/kind/bookinfo-istio/demo.sh +++ /dev/null @@ -1,173 +0,0 @@ -#!/bin/bash - -# Demo script to showcase bookinfo-istio features - -BASE_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -CONFIG_FILE=${BASE_DIR}/../kind.env - -echo "=== Bookinfo with Istio and mTLS Demo ===" -echo "" - -if [[ -f $CONFIG_FILE ]]; then - source $CONFIG_FILE -else - echo "Configuration file not found. Please ensure KubeSlice clusters are set up." - exit 1 -fi - -PRODUCT_CLUSTER="${PREFIX}${WORKERS[0]}" -SERVICES_CLUSTER="${PREFIX}${WORKERS[1]}" - -demo_istio_features() { - echo "🔍 Demonstrating Istio Features:" - echo "" - - echo "1. Sidecar Injection:" - echo " Without Istio: 2 containers per pod (app + netshoot)" - echo " With Istio: 3 containers per pod (app + netshoot + istio-proxy)" - echo "" - - kubectx $PRODUCT_CLUSTER 2>/dev/null || kubectl config use-context $PRODUCT_CLUSTER - if kubectl get pods -n bookinfo &>/dev/null; then - echo " Current productpage containers:" - CONTAINERS=$(kubectl get pods -n bookinfo -l app=productpage -o jsonpath='{.items[0].spec.containers[*].name}' 2>/dev/null) - echo " $CONTAINERS" - echo "" - fi - - echo "2. mTLS Configuration:" - echo " Checking PeerAuthentication policy..." - if kubectl get peerauthentication default -n bookinfo &>/dev/null; then - MODE=$(kubectl get peerauthentication default -n bookinfo -o jsonpath='{.spec.mtls.mode}' 2>/dev/null) - echo " ✓ mTLS Mode: $MODE" - else - echo " ⚠ PeerAuthentication not configured" - fi - echo "" - - echo "3. Cross-Cluster Service Mesh:" - echo " Services communicate across clusters with mTLS encryption" - echo " productpage (cluster 1) → reviews/details (cluster 2)" - echo "" - - echo "4. Istio Gateway:" - if kubectl get gateway bookinfo-gateway -n bookinfo &>/dev/null; then - echo " ✓ External access configured via Istio Gateway" - else - echo " ⚠ Gateway not configured" - fi - echo "" -} - -demo_security_features() { - echo "🔒 Security Features:" - echo "" - - echo "1. Certificate-based Authentication:" - echo " All services use X.509 certificates for authentication" - echo "" - - echo "2. Encrypted Communication:" - echo " All service-to-service traffic is TLS encrypted" - echo "" - - echo "3. Identity-based Authorization:" - echo " Services can only communicate if explicitly allowed" - echo "" - - kubectx $PRODUCT_CLUSTER 2>/dev/null || kubectl config use-context $PRODUCT_CLUSTER - PRODUCTPAGE_POD=$(kubectl get pods -n bookinfo -l app=productpage -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) - - if [[ -n "$PRODUCTPAGE_POD" ]]; then - echo "4. Certificate Verification:" - echo " Checking certificates in productpage pod..." - CERTS=$(kubectl exec -n bookinfo $PRODUCTPAGE_POD -c istio-proxy -- find /etc/ssl/certs -name "*.pem" 2>/dev/null | wc -l || echo "0") - echo " Found $CERTS certificate files" - echo "" - fi -} - -demo_observability() { - echo "📊 Observability Features:" - echo "" - - echo "1. Service Mesh Metrics:" - echo " Istio automatically collects traffic metrics" - echo "" - - echo "2. Distributed Tracing:" - echo " Request tracing across cluster boundaries" - echo "" - - echo "3. Access Logs:" - echo " Detailed logs of all service interactions" - echo "" - - kubectx $PRODUCT_CLUSTER 2>/dev/null || kubectl config use-context $PRODUCT_CLUSTER - PRODUCTPAGE_POD=$(kubectl get pods -n bookinfo -l app=productpage -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) - - if [[ -n "$PRODUCTPAGE_POD" ]]; then - echo "4. Envoy Admin Interface:" - echo " Access to Envoy proxy configuration and stats" - echo " kubectl exec -n bookinfo $PRODUCTPAGE_POD -c istio-proxy -- curl localhost:15000/stats" - echo "" - fi -} - -show_comparison() { - echo "📈 Comparison: Basic vs Istio Deployment" - echo "" - - echo "┌─────────────────────────┬─────────────────────────┬──────────────────────────┐" - echo "│ Feature │ Basic Bookinfo │ Bookinfo with Istio │" - echo "├─────────────────────────┼─────────────────────────┼──────────────────────────┤" - echo "│ Service-to-service │ Plain HTTP │ mTLS Encrypted │" - echo "│ Authentication │ None │ Certificate-based │" - echo "│ Authorization │ Kubernetes RBAC │ Istio + Kubernetes RBAC │" - echo "│ Traffic Management │ Kubernetes Services │ Istio VirtualServices │" - echo "│ Load Balancing │ kube-proxy │ Envoy Proxy │" - echo "│ Observability │ Basic Kubernetes │ Rich Istio Telemetry │" - echo "│ External Access │ NodePort/LB │ Istio Gateway │" - echo "│ Circuit Breaking │ Manual │ Envoy Built-in │" - echo "│ Retry/Timeout │ Application Level │ Proxy Level │" - echo "│ Cross-cluster Security │ Basic │ Enhanced │" - echo "└─────────────────────────┴─────────────────────────┴──────────────────────────┘" - echo "" -} - -interactive_demo() { - echo "🎯 Interactive Testing:" - echo "" - - echo "Available test commands:" - echo "1. ./utils/bookinfo_test.sh - Test application functionality" - echo "2. ./utils/verify_mtls.sh - Verify mTLS configuration" - echo "3. ./check-prerequisites.sh - Check system requirements" - echo "" - - read -p "Would you like to run the mTLS verification? (y/n): " -n 1 -r - echo "" - if [[ $REPLY =~ ^[Yy]$ ]]; then - echo "Running mTLS verification..." - ${BASE_DIR}/utils/verify_mtls.sh - fi -} - -main() { - demo_istio_features - demo_security_features - demo_observability - show_comparison - interactive_demo - - echo "" - echo "🎉 Demo completed!" - echo "" - echo "Next steps:" - echo "- Deploy: ./bookinfo.sh" - echo "- Test: ./utils/bookinfo_test.sh" - echo "- Verify mTLS: ./utils/verify_mtls.sh" - echo "- Read documentation: README.md" -} - -main \ No newline at end of file diff --git a/kind/bookinfo-istio/install-istio.sh b/kind/bookinfo-istio/install-istio.sh deleted file mode 100755 index c1f5e90..0000000 --- a/kind/bookinfo-istio/install-istio.sh +++ /dev/null @@ -1,161 +0,0 @@ -#!/usr/bin/env bash - -# Script to install and configure Istio on KubeSlice clusters -# This script uses kubectl to install Istio when istioctl is not available - -set -e - -BASE_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -ENV_FILE=${BASE_DIR}/../kind.env - -if [[ ! -f $ENV_FILE ]]; then - echo "${ENV_FILE} file not found! Exiting" - exit 1 -fi - -source $ENV_FILE - -PRODUCT_CLUSTER="${PREFIX}${WORKERS[0]}" -SERVICES_CLUSTER="${PREFIX}${WORKERS[1]}" -ISTIO_VERSION="1.20.1" - -install_istio_cluster() { - local cluster=$1 - echo "Installing Istio on cluster: $cluster" - - kubectx $cluster - - # Check if Istio is already installed - if kubectl get namespace istio-system &> /dev/null; then - echo "Istio appears to be already installed on $cluster" - if kubectl get deployment istiod -n istio-system &> /dev/null; then - echo "Istio control plane is running on $cluster" - return 0 - fi - fi - - echo "Installing Istio base components..." - # Install Istio base using the official manifests - kubectl apply -f https://github.com/istio/istio/releases/download/${ISTIO_VERSION}/istio-base.yaml || { - echo "Failed to install Istio base from remote URL, using local fallback" - kubectl create namespace istio-system || true - kubectl apply -f ${BASE_DIR}/config_files/istio-base.yaml - } - - echo "Installing Istio discovery service..." - # Install Istio discovery (istiod) - kubectl apply -f https://github.com/istio/istio/releases/download/${ISTIO_VERSION}/istio-discovery.yaml || { - echo "Failed to install Istio discovery from remote URL, using local fallback" - kubectl apply -f ${BASE_DIR}/config_files/istio-discovery.yaml - } - - # Wait for Istio to be ready - echo "Waiting for Istio to be ready..." - kubectl wait --for=condition=Available deployment/istiod -n istio-system --timeout=300s || { - echo "Warning: Istio deployment may not be fully ready. Continuing..." - kubectl get pods -n istio-system - } - - echo "Istio installation completed on $cluster" -} - -install_istio_gateway() { - local cluster=$1 - echo "Installing Istio Gateway on cluster: $cluster" - - kubectx $cluster - - # Install Istio Ingress Gateway - kubectl apply -f https://github.com/istio/istio/releases/download/${ISTIO_VERSION}/istio-gateway.yaml || { - echo "Warning: Could not install Istio Gateway from remote URL" - echo "Istio Gateway installation requires manual setup" - } - - echo "Istio Gateway installation attempted on $cluster" -} - -configure_mtls() { - local cluster=$1 - echo "Configuring mTLS on cluster: $cluster" - - kubectx $cluster - - # Ensure bookinfo namespace exists - kubectl create namespace bookinfo || true - - # Apply PeerAuthentication for strict mTLS - kubectl apply -f ${BASE_DIR}/config_files/peer-authentication.yaml || { - echo "Warning: Failed to apply mTLS configuration" - } - - echo "mTLS configuration applied on $cluster" -} - -verify_istio() { - local cluster=$1 - echo "Verifying Istio installation on cluster: $cluster" - - kubectx $cluster - - echo "Checking Istio system pods..." - kubectl get pods -n istio-system - - echo "Checking Istio version..." - kubectl get deployment istiod -n istio-system -o jsonpath='{.metadata.labels.app\.version}' || echo "Unable to get Istio version" - - echo "" -} - -main() { - echo "=== Installing Istio on KubeSlice clusters ===" - echo "Istio version: $ISTIO_VERSION" - echo "" - - # Install Istio on both clusters - install_istio_cluster $PRODUCT_CLUSTER - echo "" - install_istio_cluster $SERVICES_CLUSTER - echo "" - - # Install Istio Gateway (optional, for external access) - install_istio_gateway $PRODUCT_CLUSTER - echo "" - - # Configure mTLS on both clusters - configure_mtls $PRODUCT_CLUSTER - configure_mtls $SERVICES_CLUSTER - echo "" - - # Verify installations - echo "=== Verification ===" - verify_istio $PRODUCT_CLUSTER - verify_istio $SERVICES_CLUSTER - - echo "=== Istio installation completed ===" - echo "You can now deploy the bookinfo application with: ./bookinfo.sh" -} - -help() { - echo "Usage: install-istio.sh [--help]" - echo " Install Istio service mesh on KubeSlice clusters" - echo " --help Show this help message" -} - -# Get the options -while [[ $# -gt 0 ]]; do - case $1 in - -h|--help) - help - exit 0 - ;; - *) - echo "Unknown option: $1" - help - exit 1 - ;; - esac -done - -if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - main "$@" -fi \ No newline at end of file From a8bba04ad6279ecb90b48fe8404e5bca0ce8b612 Mon Sep 17 00:00:00 2001 From: sanjay7178 Date: Sun, 10 Aug 2025 22:55:30 +0530 Subject: [PATCH 5/9] chore: remove outdated deployment guide for Bookinfo with Istio and mTLS Signed-off-by: sanjay7178 --- kind/bookinfo-istio/DEPLOYMENT.md | 568 ++++++++++++++++++++---------- kind/bookinfo-istio/deployment.md | 476 ------------------------- 2 files changed, 377 insertions(+), 667 deletions(-) delete mode 100644 kind/bookinfo-istio/deployment.md diff --git a/kind/bookinfo-istio/DEPLOYMENT.md b/kind/bookinfo-istio/DEPLOYMENT.md index 7ac8d4d..b936955 100644 --- a/kind/bookinfo-istio/DEPLOYMENT.md +++ b/kind/bookinfo-istio/DEPLOYMENT.md @@ -1,290 +1,476 @@ -# Deployment Guide: Bookinfo with Istio and mTLS +# Bookinfo with Istio and mTLS on KubeSlice - Deployment Guide -This guide provides step-by-step instructions for deploying the Istio-enabled Bookinfo application across KubeSlice clusters. +This guide provides step-by-step instructions for deploying the Istio Bookinfo application across KubeSlice-connected clusters with mTLS enabled. -## Overview +## Prerequisites -The deployment creates a secure, service mesh-enabled microservices application with: -- Cross-cluster communication via KubeSlice -- End-to-end mTLS encryption -- Automatic certificate management -- Traffic monitoring and observability +- Two Kubernetes clusters connected via KubeSlice +- `kubectl` installed and configured to access both clusters +- `kubectx` for easier cluster switching (optional) +- `istioctl` for Istio installation and management +- Sufficient permissions to deploy resources in both clusters -## Prerequisites Setup +## Deployment Steps + +### 1. Create KubeSlice Configuration + +First, we need to create a slice configuration on the KubeSlice controller: -### 1. Verify Environment ```bash -cd kind/bookinfo-istio -./check-prerequisites.sh +# Switch to controller context +kubectx gke_graphic-transit-458312-f7_us-central1_ks-controller + +# Apply slice configuration +kubectl apply -f '/home/sanjay7178/examples2/kind/bookinfo-istio/config_files/slice-config.yaml' ``` -Expected output should show: -- ✓ kubectl is available -- ✓ Can list namespaces -- ✓ Found kind.env configuration file -- ✓ Product cluster (kind-worker-1) is accessible -- ✓ Services cluster (kind-worker-2) is accessible +Expected output: +``` +sliceconfig.controller.kubeslice.io/bookinfo-slice created +``` -### 2. Cluster Requirements -Each cluster should have: -- Kubernetes 1.21+ -- 2+ CPU cores available -- 4GB+ RAM available -- KubeSlice operator installed and configured +### 2. Install Istio on Both Clusters -## Installation Steps +Installing Istio using istioctl provides a simpler, more direct approach with built-in profiles. -### Step 1: Install Istio (if needed) ```bash -# Check if Istio is already installed -kubectl get pods -n istio-system +# Install Istio on Worker Cluster 1 (Product Cluster) +istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 -y + +# Install Istio on Worker Cluster 2 (Services Cluster) +istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 -y +``` -# If not installed, run: -./install-istio.sh +Expected output for each command: +``` + |\ + | \ + | \ + | \ + /|| \ + / || \ + / || \ + / || \ + / || \ + / || \ +/______||__________\ +____________________ + \__ _____/ + \_____/ + +✔ Istio core installed ⛵️ +✔ Istiod installed 🧠 +✔ CNI installed 🪢 +✔ Egress gateways installed 🛫 +✔ Ingress gateways installed 🛬 +✔ Installation complete ``` -This script will: -- Install Istio base components -- Deploy Istio control plane (istiod) -- Configure mTLS policies -- Verify installation +> **Note**: If you see warnings about version downgrades or revision changes, these are generally safe to proceed with for a fresh installation. + +#### 2.1 Verify Istio Installation -### Step 2: Deploy Bookinfo Application ```bash -./bookinfo.sh +# Check Istio services +kubectl get svc -n istio-system +``` + +Expected output: +``` +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +istio-egressgateway ClusterIP 10.96.x.x 80/TCP,443/TCP 1m +istio-ingressgateway LoadBalancer 10.96.x.x 34.23.96.143 15021:31xxx/TCP,80:31xxx/TCP,443:31xxx/TCP 1m +istiod ClusterIP 10.96.x.x 15010/TCP,15012/TCP,443/TCP,15014/TCP 1m ``` -The deployment process: -1. ✅ Checks Istio installation on both clusters -2. ✅ Creates bookinfo namespace with sidecar injection enabled -3. ✅ Deploys productpage service on cluster 1 -4. ✅ Deploys details, reviews, ratings services on cluster 2 -5. ✅ Configures KubeSlice ServiceExports for cross-cluster access -6. ✅ Applies mTLS policies -7. ✅ Sets up Istio Gateway for external access +Get the external IP of the ingress gateway: -### Step 3: Verify Deployment ```bash -./utils/bookinfo_test.sh +kubectl get svc istio-ingressgateway -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}' +``` + +Expected output: +``` +34.23.96.143 ``` -This will test: -- Basic application functionality -- Istio sidecar injection -- mTLS configuration -- Cross-cluster connectivity -- Gateway configuration +### 3. Prepare Application Namespace + +Perform these steps on both worker clusters: -### Step 4: Verify mTLS Security ```bash -./utils/verify_mtls.sh +# Create bookinfo namespace +kubectl create namespace bookinfo + +# Enable Istio injection +kubectl label namespace bookinfo istio-injection=enabled ``` -This validates: -- Certificate presence in Envoy proxies -- PeerAuthentication policies -- Encrypted service communication -- TLS configuration +Expected output: +``` +namespace/bookinfo created +namespace/bookinfo labeled +``` -## Expected Results +> **Note**: If the namespace already exists, you'll see: "Error from server (AlreadyExists): namespaces "bookinfo" already exists" -### Successful Deployment Indicators +### 4. Deploy Bookinfo Application + +#### 4.1 Deploy Frontend on Worker Cluster 1 -1. **Pods Running**: All pods should have 3 containers (app + netshoot + istio-proxy) ```bash -kubectl get pods -n bookinfo -NAME READY STATUS RESTARTS AGE -productpage-v1-xyz 3/3 Running 0 5m +# Deploy productpage service +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/productpage.yaml -n bookinfo + +# Deploy Istio gateway for external access +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/gateway.yaml -n bookinfo +``` + +Expected output: +``` +service/productpage created +serviceaccount/bookinfo-productpage created +deployment.apps/productpage-v1 created +gateway.networking.istio.io/bookinfo-gateway created +virtualservice.networking.istio.io/bookinfo created ``` -2. **mTLS Enabled**: STRICT mode configured +#### 4.2 Deploy Backend Services on Worker Cluster 2 + ```bash -kubectl get peerauthentication default -n bookinfo -o yaml -spec: - mtls: - mode: STRICT +# Deploy details service +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/details.yaml -n bookinfo + +# Deploy reviews service +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/reviews.yaml -n bookinfo + +# Deploy ratings service +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/ratings.yaml -n bookinfo ``` -3. **Cross-cluster Services**: ServiceImports available +Expected output: +``` +service/details created +serviceaccount/bookinfo-details created +deployment.apps/details-v1 created +service/reviews created +serviceaccount/bookinfo-reviews created +deployment.apps/reviews-v3 created +service/ratings created +serviceaccount/bookinfo-ratings created +deployment.apps/ratings-v1 created +``` + +### 5. Configure KubeSlice ServiceExports + +Export services from Worker Cluster 2 to make them accessible in Worker Cluster 1: + ```bash -kubectl get serviceimport -n bookinfo -NAME TYPE IP AGE -details ClusterSetIP 10.x.x.x 5m -reviews ClusterSetIP 10.x.x.x 5m +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/serviceexports.yaml -n bookinfo ``` -4. **Application Accessible**: Web interface responds +Expected output: +``` +serviceexport.networking.kubeslice.io/details created +serviceexport.networking.kubeslice.io/reviews created +serviceexport.networking.kubeslice.io/ratings created +``` + +Verify ServiceExports on Worker Cluster 2: + ```bash -curl http:///productpage -# Should return HTML content with book information +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo ``` -## Troubleshooting +Initial output (status may be PENDING): +``` +NAME SLICE INGRESS SERVICEPORT(S) PORT(S) ENDPOINTS STATUS ALIAS +details convoy 9080/TCP PENDING +reviews convoy 9080/TCP PENDING +``` -### Common Issues and Solutions +Wait a few minutes, then check again: -#### Issue: Pods stuck in Pending ```bash -kubectl describe pod -n bookinfo +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo ``` -**Solutions**: -- Check resource availability -- Verify Istio sidecar injector is running -- Ensure namespace has injection label -#### Issue: mTLS connection failures +Expected output after propagation: +``` +NAME SLICE INGRESS SERVICEPORT(S) PORT(S) ENDPOINTS STATUS ALIAS +details bookinfo-slice false 9080/TCP READY +ratings bookinfo-slice 9080/TCP READY +reviews bookinfo-slice false 9080/TCP READY +``` + +Verify ServiceImports on Worker Cluster 1: + ```bash -kubectl logs -n bookinfo -c istio-proxy +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get serviceimports -n bookinfo ``` -**Solutions**: -- Verify PeerAuthentication is applied -- Check certificate rotation -- Validate service account permissions -#### Issue: Cross-cluster connectivity problems +Expected output: +``` +NAME SLICE PORT(S) ENDPOINTS STATUS ALIAS +details bookinfo-slice 9080/TCP READY +ratings bookinfo-slice 9080/TCP READY +reviews bookinfo-slice 9080/TCP READY +``` + +### 6. Enable mTLS Security + +Apply strict mTLS policies to both clusters: + ```bash -kubectl get serviceexport -n bookinfo -kubectl get serviceimport -n bookinfo +# Apply strict mTLS policy on Worker Cluster 1 +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/peer-authentication.yaml + +# Apply strict mTLS policy on Worker Cluster 2 +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/peer-authentication.yaml +``` + +Expected output for each command: +``` +peerauthentication.security.istio.io/default created +authorizationpolicy.security.istio.io/bookinfo-allow created ``` -**Solutions**: -- Verify KubeSlice configuration -- Check slice connectivity -- Validate DNS resolution -#### Issue: Istio installation problems +### 7. Configure Authorization Policies + +Create authorization policies to allow necessary traffic: + ```bash -kubectl get pods -n istio-system -kubectl logs deployment/istiod -n istio-system +# Allow access to productpage +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/istio-rbac.yaml + +# Allow external traffic to the ingress gateway +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/istio-allow-ingress.yaml +``` + +Expected output: ``` -**Solutions**: -- Ensure sufficient cluster resources -- Check image pull policies -- Verify cluster permissions +authorizationpolicy.security.istio.io/allow-productpage created +authorizationpolicy.security.istio.io/allow-ingress-gateway created +``` + +### 8. Verify Cross-Cluster Connectivity -### Debug Commands +Check DNS resolution and connectivity from the productpage pod: ```bash -# Check Istio configuration -istioctl analyze -n bookinfo +# Get the productpage pod name +PRODUCTPAGE_POD=$(kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get pods -n bookinfo -l app=productpage -o jsonpath='{.items[0].metadata.name}') + +# Exec into the pod to test connectivity +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 exec -it -n bookinfo $PRODUCTPAGE_POD -c netshoot -- /bin/bash +``` -# View Envoy configuration -kubectl exec -n bookinfo -c istio-proxy -- curl localhost:15000/config_dump +Once inside the pod, run these commands: -# Check service mesh traffic -kubectl exec -n bookinfo -c istio-proxy -- curl localhost:15000/stats | grep cluster +```bash +# Test DNS resolution for details service +nslookup details + +# Test DNS resolution for reviews service +nslookup reviews -# Verify mTLS certificates -kubectl exec -n bookinfo -c istio-proxy -- openssl s_client -connect details:9080 +# Test DNS resolution for ratings service +nslookup ratings +``` -# View access logs -kubectl logs -n bookinfo -c istio-proxy +Expected output: ``` +Server: 127.0.0.1 +Address: 127.0.0.1#53 -## Performance Tuning +Name: details.bookinfo.svc.cluster.local +Address: 34.118.236.170 -### Resource Allocation -Adjust resources based on load: -```yaml -resources: - requests: - cpu: 100m - memory: 128Mi - limits: - cpu: 200m - memory: 256Mi +Server: 127.0.0.1 +Address: 127.0.0.1#53 + +Name: reviews.bookinfo.svc.cluster.local +Address: 34.118.231.183 + +Server: 127.0.0.1 +Address: 127.0.0.1#53 + +Name: ratings.bookinfo.svc.cluster.local +Address: 34.118.226.89 ``` -### Istio Configuration -Optimize for your environment: -```yaml -# Reduce proxy resources for testing -spec: - defaultConfig: - proxyStatsMatcher: - inclusionRegexps: - - ".*circuit_breakers.*" - - ".*upstream_rq_retry.*" +Type `exit` to leave the pod. + +### 9. Optional: Ensure Certificate Trust Between Clusters + +To ensure mutual trust for mTLS between clusters, copy root certificates: + +```bash +# Copy root-cert from Cluster 1 to Cluster 2 +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get configmap -n istio-system istio-ca-root-cert -o yaml | \ + sed 's/namespace: istio-system/namespace: kubeslice-system/' | \ + kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f - + +# Copy root-cert from Cluster 2 to Cluster 1 +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get configmap -n istio-system istio-ca-root-cert -o yaml | \ + sed 's/namespace: istio-system/namespace: kubeslice-system/' | \ + kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f - ``` -## Security Hardening +### 10. Verify mTLS Configuration -### 1. Network Policies -Implement Kubernetes NetworkPolicies alongside Istio: -```yaml -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: bookinfo-deny-all -spec: - podSelector: {} - policyTypes: - - Ingress - - Egress +Check PeerAuthentication policies on both clusters to confirm mTLS is enabled: + +```bash +# Check Worker Cluster 1 +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get peerauthentication -n bookinfo -o yaml + +# Check Worker Cluster 2 +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get peerauthentication -n bookinfo -o yaml ``` -### 2. RBAC Configuration -Use minimal service account permissions: +Expected output should show `mode: STRICT` in the spec section: ```yaml apiVersion: v1 -kind: ServiceAccount +items: +- apiVersion: security.istio.io/v1 + kind: PeerAuthentication + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"security.istio.io/v1beta1","kind":"PeerAuthentication","metadata":{"annotations":{},"name":"default","namespace":"bookinfo"},"spec":{"mtls":{"mode":"STRICT"}}} + creationTimestamp: "2025-08-10T15:48:31Z" + generation: 1 + name: default + namespace: bookinfo + resourceVersion: "1754840911946783017" + uid: 9cf690b8-e895-455b-8d76-c875fdd9fa5e + spec: + mtls: + mode: STRICT +kind: List metadata: - name: bookinfo-minimal -automountServiceAccountToken: false + resourceVersion: "" ``` -### 3. Certificate Management -Monitor certificate expiration: +### 11. Access the Application + +Access the Bookinfo application using the Istio Ingress Gateway's external IP: + ```bash -kubectl exec -n bookinfo -c istio-proxy -- \ - openssl x509 -in /var/run/secrets/istio/root-cert.pem -text -noout +# Get the Ingress Gateway IP +GATEWAY_IP=$(kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get svc istio-ingress -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + +echo "Access the Bookinfo application at: http://$GATEWAY_IP/productpage" ``` -## Monitoring Setup +## Troubleshooting + +### 1. ServiceImports Stuck in PENDING State + +If ServiceImports are stuck in PENDING state: -### Enable Telemetry ```bash -kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.20/samples/addons/prometheus.yaml -kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.20/samples/addons/grafana.yaml -kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.20/samples/addons/kiali.yaml +# Check status +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get serviceimports -n bookinfo +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo + +# Delete and recreate the ServiceExport +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 delete serviceexport -n bookinfo +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/serviceexports.yaml -n bookinfo ``` -### Access Dashboards +### 2. Istio CRDs Not Installed + +If you encounter errors about missing Istio CRDs, reinstall Istio: + +```bash +# Install Istio using istioctl +istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 -y +istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 -y +``` + +### 3. Blank Productpage + +If the productpage shows a blank page or cannot be reached: + +```bash +# Check if the Istio Gateway is properly configured +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get gateway -n bookinfo +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get virtualservice -n bookinfo + +# Ensure authorization policies allow external traffic +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/istio-allow-ingress.yaml +``` + +## Conclusion + +You have successfully deployed the Bookinfo application across two KubeSlice-connected clusters with Istio service mesh and mTLS enabled. The application architecture provides: + +1. Secure service-to-service communication with mTLS +2. Cross-cluster service discovery via KubeSlice +3. External access through Istio Gateway + +For more details on the architecture and features, refer to the [README.md](./README.md). + resourceVersion: "" +``` + +### 11. Access the Application + +Access the Bookinfo application using the Istio Ingress Gateway's external IP: + ```bash -# Kiali (Service Mesh Visualization) -kubectl port-forward svc/kiali -n istio-system 20001:20001 +# Get the Ingress Gateway IP +GATEWAY_IP=$(kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get svc istio-ingress -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + +echo "Access the Bookinfo application at: http://$GATEWAY_IP/productpage" +``` + +## Troubleshooting -# Grafana (Metrics) -kubectl port-forward svc/grafana -n istio-system 3000:3000 +### 1. ServiceImports Stuck in PENDING State -# Prometheus (Raw Metrics) -kubectl port-forward svc/prometheus -n istio-system 9090:9090 +If ServiceImports are stuck in PENDING state: + +```bash +# Check status +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get serviceimports -n bookinfo +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo + +# Delete and recreate the ServiceExport +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 delete serviceexport -n bookinfo +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/serviceexports.yaml -n bookinfo ``` -## Cleanup +### 2. Istio CRDs Not Installed + +If you encounter errors about missing Istio CRDs: -### Remove Application ```bash -./bookinfo.sh --delete +# Install Istio using istioctl +istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 -y +istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 -y ``` -### Remove Istio (if needed) +### 3. Blank Productpage + +If the productpage shows a blank page or cannot be reached: + ```bash -kubectl delete namespace istio-system -kubectl delete validatingwebhookconfiguration istiod-default-validator -kubectl delete mutatingwebhookconfiguration istio-sidecar-injector +# Check if the Istio Gateway is properly configured +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get gateway -n bookinfo +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get virtualservice -n bookinfo + +# Ensure authorization policies allow external traffic +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/istio-allow-ingress.yaml ``` -## Next Steps +## Conclusion -1. **Extend with Observability**: Add Jaeger for distributed tracing -2. **Implement Canary Deployments**: Use Istio traffic routing -3. **Add Rate Limiting**: Configure Envoy rate limiting -4. **Security Policies**: Implement fine-grained authorization -5. **Multi-cluster Service Discovery**: Extend to additional clusters +You have successfully deployed the Bookinfo application across two KubeSlice-connected clusters with Istio service mesh and mTLS enabled. The application architecture provides: -## References +1. Secure service-to-service communication with mTLS +2. Cross-cluster service discovery via KubeSlice +3. External access through Istio Gateway -- [Istio Documentation](https://istio.io/latest/docs/) -- [KubeSlice Documentation](https://docs.avesha.io/) -- [Bookinfo Application Guide](https://istio.io/latest/docs/examples/bookinfo/) -- [mTLS Configuration](https://istio.io/latest/docs/concepts/security/#mutual-tls-authentication) \ No newline at end of file +For more details on the architecture and features, refer to the [README.md](./README.md). diff --git a/kind/bookinfo-istio/deployment.md b/kind/bookinfo-istio/deployment.md deleted file mode 100644 index b936955..0000000 --- a/kind/bookinfo-istio/deployment.md +++ /dev/null @@ -1,476 +0,0 @@ -# Bookinfo with Istio and mTLS on KubeSlice - Deployment Guide - -This guide provides step-by-step instructions for deploying the Istio Bookinfo application across KubeSlice-connected clusters with mTLS enabled. - -## Prerequisites - -- Two Kubernetes clusters connected via KubeSlice -- `kubectl` installed and configured to access both clusters -- `kubectx` for easier cluster switching (optional) -- `istioctl` for Istio installation and management -- Sufficient permissions to deploy resources in both clusters - -## Deployment Steps - -### 1. Create KubeSlice Configuration - -First, we need to create a slice configuration on the KubeSlice controller: - -```bash -# Switch to controller context -kubectx gke_graphic-transit-458312-f7_us-central1_ks-controller - -# Apply slice configuration -kubectl apply -f '/home/sanjay7178/examples2/kind/bookinfo-istio/config_files/slice-config.yaml' -``` - -Expected output: -``` -sliceconfig.controller.kubeslice.io/bookinfo-slice created -``` - -### 2. Install Istio on Both Clusters - -Installing Istio using istioctl provides a simpler, more direct approach with built-in profiles. - -```bash -# Install Istio on Worker Cluster 1 (Product Cluster) -istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 -y - -# Install Istio on Worker Cluster 2 (Services Cluster) -istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 -y -``` - -Expected output for each command: -``` - |\ - | \ - | \ - | \ - /|| \ - / || \ - / || \ - / || \ - / || \ - / || \ -/______||__________\ -____________________ - \__ _____/ - \_____/ - -✔ Istio core installed ⛵️ -✔ Istiod installed 🧠 -✔ CNI installed 🪢 -✔ Egress gateways installed 🛫 -✔ Ingress gateways installed 🛬 -✔ Installation complete -``` - -> **Note**: If you see warnings about version downgrades or revision changes, these are generally safe to proceed with for a fresh installation. - -#### 2.1 Verify Istio Installation - -```bash -# Check Istio services -kubectl get svc -n istio-system -``` - -Expected output: -``` -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -istio-egressgateway ClusterIP 10.96.x.x 80/TCP,443/TCP 1m -istio-ingressgateway LoadBalancer 10.96.x.x 34.23.96.143 15021:31xxx/TCP,80:31xxx/TCP,443:31xxx/TCP 1m -istiod ClusterIP 10.96.x.x 15010/TCP,15012/TCP,443/TCP,15014/TCP 1m -``` - -Get the external IP of the ingress gateway: - -```bash -kubectl get svc istio-ingressgateway -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}' -``` - -Expected output: -``` -34.23.96.143 -``` - -### 3. Prepare Application Namespace - -Perform these steps on both worker clusters: - -```bash -# Create bookinfo namespace -kubectl create namespace bookinfo - -# Enable Istio injection -kubectl label namespace bookinfo istio-injection=enabled -``` - -Expected output: -``` -namespace/bookinfo created -namespace/bookinfo labeled -``` - -> **Note**: If the namespace already exists, you'll see: "Error from server (AlreadyExists): namespaces "bookinfo" already exists" - -### 4. Deploy Bookinfo Application - -#### 4.1 Deploy Frontend on Worker Cluster 1 - -```bash -# Deploy productpage service -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/productpage.yaml -n bookinfo - -# Deploy Istio gateway for external access -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/gateway.yaml -n bookinfo -``` - -Expected output: -``` -service/productpage created -serviceaccount/bookinfo-productpage created -deployment.apps/productpage-v1 created -gateway.networking.istio.io/bookinfo-gateway created -virtualservice.networking.istio.io/bookinfo created -``` - -#### 4.2 Deploy Backend Services on Worker Cluster 2 - -```bash -# Deploy details service -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/details.yaml -n bookinfo - -# Deploy reviews service -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/reviews.yaml -n bookinfo - -# Deploy ratings service -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/ratings.yaml -n bookinfo -``` - -Expected output: -``` -service/details created -serviceaccount/bookinfo-details created -deployment.apps/details-v1 created -service/reviews created -serviceaccount/bookinfo-reviews created -deployment.apps/reviews-v3 created -service/ratings created -serviceaccount/bookinfo-ratings created -deployment.apps/ratings-v1 created -``` - -### 5. Configure KubeSlice ServiceExports - -Export services from Worker Cluster 2 to make them accessible in Worker Cluster 1: - -```bash -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/serviceexports.yaml -n bookinfo -``` - -Expected output: -``` -serviceexport.networking.kubeslice.io/details created -serviceexport.networking.kubeslice.io/reviews created -serviceexport.networking.kubeslice.io/ratings created -``` - -Verify ServiceExports on Worker Cluster 2: - -```bash -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo -``` - -Initial output (status may be PENDING): -``` -NAME SLICE INGRESS SERVICEPORT(S) PORT(S) ENDPOINTS STATUS ALIAS -details convoy 9080/TCP PENDING -reviews convoy 9080/TCP PENDING -``` - -Wait a few minutes, then check again: - -```bash -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo -``` - -Expected output after propagation: -``` -NAME SLICE INGRESS SERVICEPORT(S) PORT(S) ENDPOINTS STATUS ALIAS -details bookinfo-slice false 9080/TCP READY -ratings bookinfo-slice 9080/TCP READY -reviews bookinfo-slice false 9080/TCP READY -``` - -Verify ServiceImports on Worker Cluster 1: - -```bash -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get serviceimports -n bookinfo -``` - -Expected output: -``` -NAME SLICE PORT(S) ENDPOINTS STATUS ALIAS -details bookinfo-slice 9080/TCP READY -ratings bookinfo-slice 9080/TCP READY -reviews bookinfo-slice 9080/TCP READY -``` - -### 6. Enable mTLS Security - -Apply strict mTLS policies to both clusters: - -```bash -# Apply strict mTLS policy on Worker Cluster 1 -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/peer-authentication.yaml - -# Apply strict mTLS policy on Worker Cluster 2 -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/peer-authentication.yaml -``` - -Expected output for each command: -``` -peerauthentication.security.istio.io/default created -authorizationpolicy.security.istio.io/bookinfo-allow created -``` - -### 7. Configure Authorization Policies - -Create authorization policies to allow necessary traffic: - -```bash -# Allow access to productpage -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/istio-rbac.yaml - -# Allow external traffic to the ingress gateway -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/istio-allow-ingress.yaml -``` - -Expected output: -``` -authorizationpolicy.security.istio.io/allow-productpage created -authorizationpolicy.security.istio.io/allow-ingress-gateway created -``` - -### 8. Verify Cross-Cluster Connectivity - -Check DNS resolution and connectivity from the productpage pod: - -```bash -# Get the productpage pod name -PRODUCTPAGE_POD=$(kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get pods -n bookinfo -l app=productpage -o jsonpath='{.items[0].metadata.name}') - -# Exec into the pod to test connectivity -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 exec -it -n bookinfo $PRODUCTPAGE_POD -c netshoot -- /bin/bash -``` - -Once inside the pod, run these commands: - -```bash -# Test DNS resolution for details service -nslookup details - -# Test DNS resolution for reviews service -nslookup reviews - -# Test DNS resolution for ratings service -nslookup ratings -``` - -Expected output: -``` -Server: 127.0.0.1 -Address: 127.0.0.1#53 - -Name: details.bookinfo.svc.cluster.local -Address: 34.118.236.170 - -Server: 127.0.0.1 -Address: 127.0.0.1#53 - -Name: reviews.bookinfo.svc.cluster.local -Address: 34.118.231.183 - -Server: 127.0.0.1 -Address: 127.0.0.1#53 - -Name: ratings.bookinfo.svc.cluster.local -Address: 34.118.226.89 -``` - -Type `exit` to leave the pod. - -### 9. Optional: Ensure Certificate Trust Between Clusters - -To ensure mutual trust for mTLS between clusters, copy root certificates: - -```bash -# Copy root-cert from Cluster 1 to Cluster 2 -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get configmap -n istio-system istio-ca-root-cert -o yaml | \ - sed 's/namespace: istio-system/namespace: kubeslice-system/' | \ - kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f - - -# Copy root-cert from Cluster 2 to Cluster 1 -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get configmap -n istio-system istio-ca-root-cert -o yaml | \ - sed 's/namespace: istio-system/namespace: kubeslice-system/' | \ - kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f - -``` - -### 10. Verify mTLS Configuration - -Check PeerAuthentication policies on both clusters to confirm mTLS is enabled: - -```bash -# Check Worker Cluster 1 -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get peerauthentication -n bookinfo -o yaml - -# Check Worker Cluster 2 -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get peerauthentication -n bookinfo -o yaml -``` - -Expected output should show `mode: STRICT` in the spec section: -```yaml -apiVersion: v1 -items: -- apiVersion: security.istio.io/v1 - kind: PeerAuthentication - metadata: - annotations: - kubectl.kubernetes.io/last-applied-configuration: | - {"apiVersion":"security.istio.io/v1beta1","kind":"PeerAuthentication","metadata":{"annotations":{},"name":"default","namespace":"bookinfo"},"spec":{"mtls":{"mode":"STRICT"}}} - creationTimestamp: "2025-08-10T15:48:31Z" - generation: 1 - name: default - namespace: bookinfo - resourceVersion: "1754840911946783017" - uid: 9cf690b8-e895-455b-8d76-c875fdd9fa5e - spec: - mtls: - mode: STRICT -kind: List -metadata: - resourceVersion: "" -``` - -### 11. Access the Application - -Access the Bookinfo application using the Istio Ingress Gateway's external IP: - -```bash -# Get the Ingress Gateway IP -GATEWAY_IP=$(kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get svc istio-ingress -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}') - -echo "Access the Bookinfo application at: http://$GATEWAY_IP/productpage" -``` - -## Troubleshooting - -### 1. ServiceImports Stuck in PENDING State - -If ServiceImports are stuck in PENDING state: - -```bash -# Check status -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get serviceimports -n bookinfo -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo - -# Delete and recreate the ServiceExport -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 delete serviceexport -n bookinfo -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/serviceexports.yaml -n bookinfo -``` - -### 2. Istio CRDs Not Installed - -If you encounter errors about missing Istio CRDs, reinstall Istio: - -```bash -# Install Istio using istioctl -istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 -y -istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 -y -``` - -### 3. Blank Productpage - -If the productpage shows a blank page or cannot be reached: - -```bash -# Check if the Istio Gateway is properly configured -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get gateway -n bookinfo -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get virtualservice -n bookinfo - -# Ensure authorization policies allow external traffic -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/istio-allow-ingress.yaml -``` - -## Conclusion - -You have successfully deployed the Bookinfo application across two KubeSlice-connected clusters with Istio service mesh and mTLS enabled. The application architecture provides: - -1. Secure service-to-service communication with mTLS -2. Cross-cluster service discovery via KubeSlice -3. External access through Istio Gateway - -For more details on the architecture and features, refer to the [README.md](./README.md). - resourceVersion: "" -``` - -### 11. Access the Application - -Access the Bookinfo application using the Istio Ingress Gateway's external IP: - -```bash -# Get the Ingress Gateway IP -GATEWAY_IP=$(kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get svc istio-ingress -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}') - -echo "Access the Bookinfo application at: http://$GATEWAY_IP/productpage" -``` - -## Troubleshooting - -### 1. ServiceImports Stuck in PENDING State - -If ServiceImports are stuck in PENDING state: - -```bash -# Check status -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get serviceimports -n bookinfo -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo - -# Delete and recreate the ServiceExport -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 delete serviceexport -n bookinfo -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/serviceexports.yaml -n bookinfo -``` - -### 2. Istio CRDs Not Installed - -If you encounter errors about missing Istio CRDs: - -```bash -# Install Istio using istioctl -istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 -y -istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 -y -``` - -### 3. Blank Productpage - -If the productpage shows a blank page or cannot be reached: - -```bash -# Check if the Istio Gateway is properly configured -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get gateway -n bookinfo -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get virtualservice -n bookinfo - -# Ensure authorization policies allow external traffic -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/istio-allow-ingress.yaml -``` - -## Conclusion - -You have successfully deployed the Bookinfo application across two KubeSlice-connected clusters with Istio service mesh and mTLS enabled. The application architecture provides: - -1. Secure service-to-service communication with mTLS -2. Cross-cluster service discovery via KubeSlice -3. External access through Istio Gateway - -For more details on the architecture and features, refer to the [README.md](./README.md). From 89eb0713917db4eab41e177a2fa3728046728cd2 Mon Sep 17 00:00:00 2001 From: sanjay7178 Date: Sun, 10 Aug 2025 23:00:23 +0530 Subject: [PATCH 6/9] refactor: deployment documentation for Bookinfo with Istio and mTLS on KubeSlice Signed-off-by: sanjay7178 --- kind/bookinfo-istio/DEPLOYMENT.md | 476 ---------------------- kind/bookinfo-istio/README.md | 635 +++++++++++++++++++----------- 2 files changed, 403 insertions(+), 708 deletions(-) delete mode 100644 kind/bookinfo-istio/DEPLOYMENT.md diff --git a/kind/bookinfo-istio/DEPLOYMENT.md b/kind/bookinfo-istio/DEPLOYMENT.md deleted file mode 100644 index b936955..0000000 --- a/kind/bookinfo-istio/DEPLOYMENT.md +++ /dev/null @@ -1,476 +0,0 @@ -# Bookinfo with Istio and mTLS on KubeSlice - Deployment Guide - -This guide provides step-by-step instructions for deploying the Istio Bookinfo application across KubeSlice-connected clusters with mTLS enabled. - -## Prerequisites - -- Two Kubernetes clusters connected via KubeSlice -- `kubectl` installed and configured to access both clusters -- `kubectx` for easier cluster switching (optional) -- `istioctl` for Istio installation and management -- Sufficient permissions to deploy resources in both clusters - -## Deployment Steps - -### 1. Create KubeSlice Configuration - -First, we need to create a slice configuration on the KubeSlice controller: - -```bash -# Switch to controller context -kubectx gke_graphic-transit-458312-f7_us-central1_ks-controller - -# Apply slice configuration -kubectl apply -f '/home/sanjay7178/examples2/kind/bookinfo-istio/config_files/slice-config.yaml' -``` - -Expected output: -``` -sliceconfig.controller.kubeslice.io/bookinfo-slice created -``` - -### 2. Install Istio on Both Clusters - -Installing Istio using istioctl provides a simpler, more direct approach with built-in profiles. - -```bash -# Install Istio on Worker Cluster 1 (Product Cluster) -istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 -y - -# Install Istio on Worker Cluster 2 (Services Cluster) -istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 -y -``` - -Expected output for each command: -``` - |\ - | \ - | \ - | \ - /|| \ - / || \ - / || \ - / || \ - / || \ - / || \ -/______||__________\ -____________________ - \__ _____/ - \_____/ - -✔ Istio core installed ⛵️ -✔ Istiod installed 🧠 -✔ CNI installed 🪢 -✔ Egress gateways installed 🛫 -✔ Ingress gateways installed 🛬 -✔ Installation complete -``` - -> **Note**: If you see warnings about version downgrades or revision changes, these are generally safe to proceed with for a fresh installation. - -#### 2.1 Verify Istio Installation - -```bash -# Check Istio services -kubectl get svc -n istio-system -``` - -Expected output: -``` -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -istio-egressgateway ClusterIP 10.96.x.x 80/TCP,443/TCP 1m -istio-ingressgateway LoadBalancer 10.96.x.x 34.23.96.143 15021:31xxx/TCP,80:31xxx/TCP,443:31xxx/TCP 1m -istiod ClusterIP 10.96.x.x 15010/TCP,15012/TCP,443/TCP,15014/TCP 1m -``` - -Get the external IP of the ingress gateway: - -```bash -kubectl get svc istio-ingressgateway -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}' -``` - -Expected output: -``` -34.23.96.143 -``` - -### 3. Prepare Application Namespace - -Perform these steps on both worker clusters: - -```bash -# Create bookinfo namespace -kubectl create namespace bookinfo - -# Enable Istio injection -kubectl label namespace bookinfo istio-injection=enabled -``` - -Expected output: -``` -namespace/bookinfo created -namespace/bookinfo labeled -``` - -> **Note**: If the namespace already exists, you'll see: "Error from server (AlreadyExists): namespaces "bookinfo" already exists" - -### 4. Deploy Bookinfo Application - -#### 4.1 Deploy Frontend on Worker Cluster 1 - -```bash -# Deploy productpage service -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/productpage.yaml -n bookinfo - -# Deploy Istio gateway for external access -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/gateway.yaml -n bookinfo -``` - -Expected output: -``` -service/productpage created -serviceaccount/bookinfo-productpage created -deployment.apps/productpage-v1 created -gateway.networking.istio.io/bookinfo-gateway created -virtualservice.networking.istio.io/bookinfo created -``` - -#### 4.2 Deploy Backend Services on Worker Cluster 2 - -```bash -# Deploy details service -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/details.yaml -n bookinfo - -# Deploy reviews service -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/reviews.yaml -n bookinfo - -# Deploy ratings service -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/ratings.yaml -n bookinfo -``` - -Expected output: -``` -service/details created -serviceaccount/bookinfo-details created -deployment.apps/details-v1 created -service/reviews created -serviceaccount/bookinfo-reviews created -deployment.apps/reviews-v3 created -service/ratings created -serviceaccount/bookinfo-ratings created -deployment.apps/ratings-v1 created -``` - -### 5. Configure KubeSlice ServiceExports - -Export services from Worker Cluster 2 to make them accessible in Worker Cluster 1: - -```bash -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/serviceexports.yaml -n bookinfo -``` - -Expected output: -``` -serviceexport.networking.kubeslice.io/details created -serviceexport.networking.kubeslice.io/reviews created -serviceexport.networking.kubeslice.io/ratings created -``` - -Verify ServiceExports on Worker Cluster 2: - -```bash -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo -``` - -Initial output (status may be PENDING): -``` -NAME SLICE INGRESS SERVICEPORT(S) PORT(S) ENDPOINTS STATUS ALIAS -details convoy 9080/TCP PENDING -reviews convoy 9080/TCP PENDING -``` - -Wait a few minutes, then check again: - -```bash -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo -``` - -Expected output after propagation: -``` -NAME SLICE INGRESS SERVICEPORT(S) PORT(S) ENDPOINTS STATUS ALIAS -details bookinfo-slice false 9080/TCP READY -ratings bookinfo-slice 9080/TCP READY -reviews bookinfo-slice false 9080/TCP READY -``` - -Verify ServiceImports on Worker Cluster 1: - -```bash -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get serviceimports -n bookinfo -``` - -Expected output: -``` -NAME SLICE PORT(S) ENDPOINTS STATUS ALIAS -details bookinfo-slice 9080/TCP READY -ratings bookinfo-slice 9080/TCP READY -reviews bookinfo-slice 9080/TCP READY -``` - -### 6. Enable mTLS Security - -Apply strict mTLS policies to both clusters: - -```bash -# Apply strict mTLS policy on Worker Cluster 1 -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/peer-authentication.yaml - -# Apply strict mTLS policy on Worker Cluster 2 -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/peer-authentication.yaml -``` - -Expected output for each command: -``` -peerauthentication.security.istio.io/default created -authorizationpolicy.security.istio.io/bookinfo-allow created -``` - -### 7. Configure Authorization Policies - -Create authorization policies to allow necessary traffic: - -```bash -# Allow access to productpage -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/istio-rbac.yaml - -# Allow external traffic to the ingress gateway -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/istio-allow-ingress.yaml -``` - -Expected output: -``` -authorizationpolicy.security.istio.io/allow-productpage created -authorizationpolicy.security.istio.io/allow-ingress-gateway created -``` - -### 8. Verify Cross-Cluster Connectivity - -Check DNS resolution and connectivity from the productpage pod: - -```bash -# Get the productpage pod name -PRODUCTPAGE_POD=$(kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get pods -n bookinfo -l app=productpage -o jsonpath='{.items[0].metadata.name}') - -# Exec into the pod to test connectivity -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 exec -it -n bookinfo $PRODUCTPAGE_POD -c netshoot -- /bin/bash -``` - -Once inside the pod, run these commands: - -```bash -# Test DNS resolution for details service -nslookup details - -# Test DNS resolution for reviews service -nslookup reviews - -# Test DNS resolution for ratings service -nslookup ratings -``` - -Expected output: -``` -Server: 127.0.0.1 -Address: 127.0.0.1#53 - -Name: details.bookinfo.svc.cluster.local -Address: 34.118.236.170 - -Server: 127.0.0.1 -Address: 127.0.0.1#53 - -Name: reviews.bookinfo.svc.cluster.local -Address: 34.118.231.183 - -Server: 127.0.0.1 -Address: 127.0.0.1#53 - -Name: ratings.bookinfo.svc.cluster.local -Address: 34.118.226.89 -``` - -Type `exit` to leave the pod. - -### 9. Optional: Ensure Certificate Trust Between Clusters - -To ensure mutual trust for mTLS between clusters, copy root certificates: - -```bash -# Copy root-cert from Cluster 1 to Cluster 2 -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get configmap -n istio-system istio-ca-root-cert -o yaml | \ - sed 's/namespace: istio-system/namespace: kubeslice-system/' | \ - kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f - - -# Copy root-cert from Cluster 2 to Cluster 1 -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get configmap -n istio-system istio-ca-root-cert -o yaml | \ - sed 's/namespace: istio-system/namespace: kubeslice-system/' | \ - kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f - -``` - -### 10. Verify mTLS Configuration - -Check PeerAuthentication policies on both clusters to confirm mTLS is enabled: - -```bash -# Check Worker Cluster 1 -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get peerauthentication -n bookinfo -o yaml - -# Check Worker Cluster 2 -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get peerauthentication -n bookinfo -o yaml -``` - -Expected output should show `mode: STRICT` in the spec section: -```yaml -apiVersion: v1 -items: -- apiVersion: security.istio.io/v1 - kind: PeerAuthentication - metadata: - annotations: - kubectl.kubernetes.io/last-applied-configuration: | - {"apiVersion":"security.istio.io/v1beta1","kind":"PeerAuthentication","metadata":{"annotations":{},"name":"default","namespace":"bookinfo"},"spec":{"mtls":{"mode":"STRICT"}}} - creationTimestamp: "2025-08-10T15:48:31Z" - generation: 1 - name: default - namespace: bookinfo - resourceVersion: "1754840911946783017" - uid: 9cf690b8-e895-455b-8d76-c875fdd9fa5e - spec: - mtls: - mode: STRICT -kind: List -metadata: - resourceVersion: "" -``` - -### 11. Access the Application - -Access the Bookinfo application using the Istio Ingress Gateway's external IP: - -```bash -# Get the Ingress Gateway IP -GATEWAY_IP=$(kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get svc istio-ingress -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}') - -echo "Access the Bookinfo application at: http://$GATEWAY_IP/productpage" -``` - -## Troubleshooting - -### 1. ServiceImports Stuck in PENDING State - -If ServiceImports are stuck in PENDING state: - -```bash -# Check status -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get serviceimports -n bookinfo -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo - -# Delete and recreate the ServiceExport -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 delete serviceexport -n bookinfo -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/serviceexports.yaml -n bookinfo -``` - -### 2. Istio CRDs Not Installed - -If you encounter errors about missing Istio CRDs, reinstall Istio: - -```bash -# Install Istio using istioctl -istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 -y -istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 -y -``` - -### 3. Blank Productpage - -If the productpage shows a blank page or cannot be reached: - -```bash -# Check if the Istio Gateway is properly configured -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get gateway -n bookinfo -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get virtualservice -n bookinfo - -# Ensure authorization policies allow external traffic -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/istio-allow-ingress.yaml -``` - -## Conclusion - -You have successfully deployed the Bookinfo application across two KubeSlice-connected clusters with Istio service mesh and mTLS enabled. The application architecture provides: - -1. Secure service-to-service communication with mTLS -2. Cross-cluster service discovery via KubeSlice -3. External access through Istio Gateway - -For more details on the architecture and features, refer to the [README.md](./README.md). - resourceVersion: "" -``` - -### 11. Access the Application - -Access the Bookinfo application using the Istio Ingress Gateway's external IP: - -```bash -# Get the Ingress Gateway IP -GATEWAY_IP=$(kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get svc istio-ingress -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}') - -echo "Access the Bookinfo application at: http://$GATEWAY_IP/productpage" -``` - -## Troubleshooting - -### 1. ServiceImports Stuck in PENDING State - -If ServiceImports are stuck in PENDING state: - -```bash -# Check status -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get serviceimports -n bookinfo -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo - -# Delete and recreate the ServiceExport -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 delete serviceexport -n bookinfo -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/serviceexports.yaml -n bookinfo -``` - -### 2. Istio CRDs Not Installed - -If you encounter errors about missing Istio CRDs: - -```bash -# Install Istio using istioctl -istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 -y -istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 -y -``` - -### 3. Blank Productpage - -If the productpage shows a blank page or cannot be reached: - -```bash -# Check if the Istio Gateway is properly configured -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get gateway -n bookinfo -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get virtualservice -n bookinfo - -# Ensure authorization policies allow external traffic -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/istio-allow-ingress.yaml -``` - -## Conclusion - -You have successfully deployed the Bookinfo application across two KubeSlice-connected clusters with Istio service mesh and mTLS enabled. The application architecture provides: - -1. Secure service-to-service communication with mTLS -2. Cross-cluster service discovery via KubeSlice -3. External access through Istio Gateway - -For more details on the architecture and features, refer to the [README.md](./README.md). diff --git a/kind/bookinfo-istio/README.md b/kind/bookinfo-istio/README.md index c9bc852..faf349a 100644 --- a/kind/bookinfo-istio/README.md +++ b/kind/bookinfo-istio/README.md @@ -1,305 +1,476 @@ -# Bookinfo with Istio and mTLS on KubeSlice +# Bookinfo with Istio and mTLS on KubeSlice - Deployment Guide This example deploys the Istio Bookinfo application across two KubeSlice-connected clusters with Istio service mesh and mTLS enabled for secure service-to-service communication. -## Architecture - -``` -┌─────────────────────────────────────┐ ┌─────────────────────────────────────┐ -│ Cluster 1 (Product) │ │ Cluster 2 (Services) │ -│ ┌─────────────────────────────────┐ │ │ ┌─────────────────────────────────┐ │ -│ │ productpage │ │ │ │ details │ │ -│ │ ┌─────────────────────┐ │ │ │ │ ┌─────────────────────┐ │ │ -│ │ │ Istio Proxy │ │ │ │ │ │ Istio Proxy │ │ │ -│ │ │ (Envoy) │◄─────┼─┼────┼─┼────► (Envoy) │ │ │ -│ │ └─────────────────────┘ │ │ │ │ └─────────────────────┘ │ │ -│ │ │ productpage │ │ │ │ │ │ details │ │ │ -│ │ │ container │ │ │ │ │ │ container │ │ │ -│ │ └─────────────────────┘ │ │ │ │ └─────────────────────┘ │ │ -│ └─────────────────────────────────┘ │ │ └─────────────────────────────────┘ │ -│ │ │ │ -│ ┌─────────────────────────────────┐ │ │ ┌─────────────────────────────────┐ │ -│ │ Istio Gateway │ │ │ │ reviews │ │ -│ │ ┌─────────────────────────────┐ │ │ │ │ ┌─────────────────────┐ │ │ -│ │ │ External Access │ │ │ │ │ │ Istio Proxy │ │ │ -│ │ │ (port 80/443) │ │ │ │ │ │ (Envoy) │◄─────┼─┼──┐ -│ │ └─────────────────────────────┘ │ │ │ │ └─────────────────────┘ │ │ │ -│ └─────────────────────────────────┘ │ │ │ │ reviews │ │ │ │ -│ │ │ │ │ container │ │ │ │ -│ KubeSlice │ │ │ └─────────────────────┘ │ │ │ -│ Service Mesh │ │ └─────────────────────────────────┘ │ │ -└─────────────────────────────────────┘ │ │ │ - │ ┌─────────────────────────────────┐ │ │ - │ │ ratings │ │ │ - │ │ ┌─────────────────────┐ │ │ │ - │ │ │ Istio Proxy │ │ │ │ - │ │ │ (Envoy) │◄─────┼─┼──┘ - │ │ └─────────────────────┘ │ │ - │ │ │ ratings │ │ │ - │ │ │ container │ │ │ - │ │ └─────────────────────┘ │ │ - │ └─────────────────────────────────┘ │ - └─────────────────────────────────────┘ -``` - -### Service Distribution -- **Cluster 1 (Product Cluster)**: Runs the productpage service with Istio Gateway for external access -- **Cluster 2 (Services Cluster)**: Runs details, ratings, and reviews services -- **Service Mesh**: Istio with automatic sidecar injection on both clusters -- **Security**: Strict mTLS enabled between all services -- **Cross-cluster connectivity**: KubeSlice ServiceExport/ServiceImport with service mesh overlay - -### Security Features -- **Mutual TLS (mTLS)**: All service-to-service communication is encrypted and authenticated -- **PeerAuthentication**: Enforces STRICT mTLS mode for the bookinfo namespace -- **AuthorizationPolicy**: Controls which services can communicate with each other -- **Certificate Management**: Automatic certificate rotation via Istio's Certificate Authority - ## Prerequisites -Before deploying this example, ensure you have: +- Two Kubernetes clusters connected via KubeSlice +- `kubectl` installed and configured to access both clusters +- `kubectx` for easier cluster switching (optional) +- `istioctl` for Istio installation and management +- Sufficient permissions to deploy resources in both clusters + +## Deployment Steps -1. **KubeSlice Environment**: Two kind clusters connected via KubeSlice -2. **Required Tools**: - - `kubectl` - Kubernetes command-line tool - - `kubectx` (optional) - For easier cluster switching - - `istioctl` (optional) - For advanced Istio operations +### 1. Create KubeSlice Configuration -3. **Cluster Requirements**: - - Kubernetes 1.21+ on both clusters - - Sufficient resources (2+ CPU cores, 4GB+ RAM per cluster) - - Network connectivity between clusters via KubeSlice +First, we need to create a slice configuration on the KubeSlice controller: -Run the prerequisites check: ```bash -./check-prerequisites.sh +# Switch to controller context +kubectx gke_graphic-transit-458312-f7_us-central1_ks-controller + +# Apply slice configuration +kubectl apply -f '/home/sanjay7178/examples2/kind/bookinfo-istio/config_files/slice-config.yaml' ``` -## Quick Start +Expected output: +``` +sliceconfig.controller.kubeslice.io/bookinfo-slice created +``` + +### 2. Install Istio on Both Clusters + +Installing Istio using istioctl provides a simpler, more direct approach with built-in profiles. -### 1. Check Prerequisites ```bash -./check-prerequisites.sh +# Install Istio on Worker Cluster 1 (Product Cluster) +istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 -y + +# Install Istio on Worker Cluster 2 (Services Cluster) +istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 -y +``` + +Expected output for each command: +``` + |\ + | \ + | \ + | \ + /|| \ + / || \ + / || \ + / || \ + / || \ + / || \ +/______||__________\ +____________________ + \__ _____/ + \_____/ + +✔ Istio core installed ⛵️ +✔ Istiod installed 🧠 +✔ CNI installed 🪢 +✔ Egress gateways installed 🛫 +✔ Ingress gateways installed 🛬 +✔ Installation complete +``` + +> **Note**: If you see warnings about version downgrades or revision changes, these are generally safe to proceed with for a fresh installation. + +#### 2.1 Verify Istio Installation + +```bash +# Check Istio services +kubectl get svc -n istio-system +``` + +Expected output: +``` +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +istio-egressgateway ClusterIP 10.96.x.x 80/TCP,443/TCP 1m +istio-ingressgateway LoadBalancer 10.96.x.x 34.23.96.143 15021:31xxx/TCP,80:31xxx/TCP,443:31xxx/TCP 1m +istiod ClusterIP 10.96.x.x 15010/TCP,15012/TCP,443/TCP,15014/TCP 1m ``` -### 2. Install Istio (if not already installed) +Get the external IP of the ingress gateway: + ```bash -./install-istio.sh +kubectl get svc istio-ingressgateway -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}' ``` -### 3. Deploy Bookinfo with mTLS +Expected output: +``` +34.23.96.143 +``` + +### 3. Prepare Application Namespace + +Perform these steps on both worker clusters: + ```bash -./bookinfo.sh +# Create bookinfo namespace +kubectl create namespace bookinfo + +# Enable Istio injection +kubectl label namespace bookinfo istio-injection=enabled ``` -### 4. Test the Deployment +Expected output: +``` +namespace/bookinfo created +namespace/bookinfo labeled +``` + +> **Note**: If the namespace already exists, you'll see: "Error from server (AlreadyExists): namespaces "bookinfo" already exists" + +### 4. Deploy Bookinfo Application + +#### 4.1 Deploy Frontend on Worker Cluster 1 + ```bash -./utils/bookinfo_test.sh +# Deploy productpage service +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/productpage.yaml -n bookinfo + +# Deploy Istio gateway for external access +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/gateway.yaml -n bookinfo +``` + +Expected output: +``` +service/productpage created +serviceaccount/bookinfo-productpage created +deployment.apps/productpage-v1 created +gateway.networking.istio.io/bookinfo-gateway created +virtualservice.networking.istio.io/bookinfo created ``` -### 5. Verify mTLS Configuration +#### 4.2 Deploy Backend Services on Worker Cluster 2 + ```bash -./utils/verify_mtls.sh +# Deploy details service +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/details.yaml -n bookinfo + +# Deploy reviews service +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/reviews.yaml -n bookinfo + +# Deploy ratings service +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/ratings.yaml -n bookinfo ``` -## Detailed Usage +Expected output: +``` +service/details created +serviceaccount/bookinfo-details created +deployment.apps/details-v1 created +service/reviews created +serviceaccount/bookinfo-reviews created +deployment.apps/reviews-v3 created +service/ratings created +serviceaccount/bookinfo-ratings created +deployment.apps/ratings-v1 created +``` -### Installation Scripts +### 5. Configure KubeSlice ServiceExports -#### install-istio.sh -Installs Istio service mesh on both KubeSlice clusters: -- Downloads and applies Istio manifests -- Configures Istio discovery service (istiod) -- Sets up mTLS policies -- Verifies installation +Export services from Worker Cluster 2 to make them accessible in Worker Cluster 1: ```bash -./install-istio.sh --help +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/serviceexports.yaml -n bookinfo +``` + +Expected output: +``` +serviceexport.networking.kubeslice.io/details created +serviceexport.networking.kubeslice.io/reviews created +serviceexport.networking.kubeslice.io/ratings created +``` + +Verify ServiceExports on Worker Cluster 2: + +```bash +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo +``` + +Initial output (status may be PENDING): +``` +NAME SLICE INGRESS SERVICEPORT(S) PORT(S) ENDPOINTS STATUS ALIAS +details convoy 9080/TCP PENDING +reviews convoy 9080/TCP PENDING ``` -#### bookinfo.sh -Main deployment script that: -- Verifies Istio installation -- Creates bookinfo namespace with sidecar injection enabled -- Deploys services across clusters -- Configures KubeSlice ServiceExports -- Applies mTLS policies -- Sets up Istio Gateway for external access +Wait a few minutes, then check again: ```bash -# Deploy the application -./bookinfo.sh +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo +``` -# Remove the application -./bookinfo.sh --delete +Expected output after propagation: +``` +NAME SLICE INGRESS SERVICEPORT(S) PORT(S) ENDPOINTS STATUS ALIAS +details bookinfo-slice false 9080/TCP READY +ratings bookinfo-slice 9080/TCP READY +reviews bookinfo-slice false 9080/TCP READY +``` -# Show help -./bookinfo.sh --help +Verify ServiceImports on Worker Cluster 1: + +```bash +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get serviceimports -n bookinfo ``` -### Testing and Verification +Expected output: +``` +NAME SLICE PORT(S) ENDPOINTS STATUS ALIAS +details bookinfo-slice 9080/TCP READY +ratings bookinfo-slice 9080/TCP READY +reviews bookinfo-slice 9080/TCP READY +``` -#### bookinfo_test.sh -Comprehensive test suite that verifies: -- Basic application functionality -- Istio sidecar injection -- mTLS configuration -- Service-to-service connectivity -- Istio networking components +### 6. Enable mTLS Security -#### verify_mtls.sh -Specialized mTLS verification script that: -- Checks certificate presence in Envoy proxies -- Verifies PeerAuthentication policies -- Tests encrypted communication between services -- Validates TLS configuration +Apply strict mTLS policies to both clusters: -### Configuration Files +```bash +# Apply strict mTLS policy on Worker Cluster 1 +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/peer-authentication.yaml -#### Service Configurations -- `productpage.yaml` - Frontend service with Istio sidecar -- `details.yaml` - Book details service with Istio sidecar -- `reviews.yaml` - Book reviews service with Istio sidecar -- `ratings.yaml` - Star ratings service with Istio sidecar +# Apply strict mTLS policy on Worker Cluster 2 +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/peer-authentication.yaml +``` -#### Istio Configuration -- `gateway.yaml` - Istio Gateway for external access -- `peer-authentication.yaml` - mTLS enforcement policy -- `istio-base.yaml` - Basic Istio installation manifest -- `istio-discovery.yaml` - Istio control plane configuration +Expected output for each command: +``` +peerauthentication.security.istio.io/default created +authorizationpolicy.security.istio.io/bookinfo-allow created +``` + +### 7. Configure Authorization Policies + +Create authorization policies to allow necessary traffic: -#### KubeSlice Configuration -- `serviceexports.yaml` - Cross-cluster service exports +```bash +# Allow access to productpage +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/istio-rbac.yaml + +# Allow external traffic to the ingress gateway +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/istio-allow-ingress.yaml +``` + +Expected output: +``` +authorizationpolicy.security.istio.io/allow-productpage created +authorizationpolicy.security.istio.io/allow-ingress-gateway created +``` + +### 8. Verify Cross-Cluster Connectivity + +Check DNS resolution and connectivity from the productpage pod: + +```bash +# Get the productpage pod name +PRODUCTPAGE_POD=$(kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get pods -n bookinfo -l app=productpage -o jsonpath='{.items[0].metadata.name}') + +# Exec into the pod to test connectivity +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 exec -it -n bookinfo $PRODUCTPAGE_POD -c netshoot -- /bin/bash +``` + +Once inside the pod, run these commands: + +```bash +# Test DNS resolution for details service +nslookup details + +# Test DNS resolution for reviews service +nslookup reviews + +# Test DNS resolution for ratings service +nslookup ratings +``` + +Expected output: +``` +Server: 127.0.0.1 +Address: 127.0.0.1#53 + +Name: details.bookinfo.svc.cluster.local +Address: 34.118.236.170 + +Server: 127.0.0.1 +Address: 127.0.0.1#53 + +Name: reviews.bookinfo.svc.cluster.local +Address: 34.118.231.183 + +Server: 127.0.0.1 +Address: 127.0.0.1#53 + +Name: ratings.bookinfo.svc.cluster.local +Address: 34.118.226.89 +``` + +Type `exit` to leave the pod. + +### 9. Optional: Ensure Certificate Trust Between Clusters + +To ensure mutual trust for mTLS between clusters, copy root certificates: + +```bash +# Copy root-cert from Cluster 1 to Cluster 2 +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get configmap -n istio-system istio-ca-root-cert -o yaml | \ + sed 's/namespace: istio-system/namespace: kubeslice-system/' | \ + kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f - + +# Copy root-cert from Cluster 2 to Cluster 1 +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get configmap -n istio-system istio-ca-root-cert -o yaml | \ + sed 's/namespace: istio-system/namespace: kubeslice-system/' | \ + kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f - +``` + +### 10. Verify mTLS Configuration + +Check PeerAuthentication policies on both clusters to confirm mTLS is enabled: + +```bash +# Check Worker Cluster 1 +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get peerauthentication -n bookinfo -o yaml + +# Check Worker Cluster 2 +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get peerauthentication -n bookinfo -o yaml +``` + +Expected output should show `mode: STRICT` in the spec section: +```yaml +apiVersion: v1 +items: +- apiVersion: security.istio.io/v1 + kind: PeerAuthentication + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"security.istio.io/v1beta1","kind":"PeerAuthentication","metadata":{"annotations":{},"name":"default","namespace":"bookinfo"},"spec":{"mtls":{"mode":"STRICT"}}} + creationTimestamp: "2025-08-10T15:48:31Z" + generation: 1 + name: default + namespace: bookinfo + resourceVersion: "1754840911946783017" + uid: 9cf690b8-e895-455b-8d76-c875fdd9fa5e + spec: + mtls: + mode: STRICT +kind: List +metadata: + resourceVersion: "" +``` + +### 11. Access the Application + +Access the Bookinfo application using the Istio Ingress Gateway's external IP: + +```bash +# Get the Ingress Gateway IP +GATEWAY_IP=$(kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get svc istio-ingress -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + +echo "Access the Bookinfo application at: http://$GATEWAY_IP/productpage" +``` ## Troubleshooting -### Common Issues - -1. **Istio not installed**: - ```bash - Error: Istio not found on cluster - ``` - **Solution**: Run `./install-istio.sh` first - -2. **Pods stuck in Pending**: - ```bash - kubectl describe pod -n bookinfo - ``` - Check for resource constraints or scheduling issues - -3. **mTLS connection failures**: - ```bash - ./utils/verify_mtls.sh - ``` - Verify certificates and PeerAuthentication policies - -4. **Cross-cluster connectivity issues**: - ```bash - kubectl get serviceimport -n bookinfo - kubectl get serviceexport -n bookinfo - ``` - Ensure KubeSlice is properly configured - -5. **Blank page or "Site can't be reached" on productpage**: - ```bash - ./utils/fix-istio-ingress.sh - ``` - This script diagnoses and fixes common ingress gateway issues. - Alternatively, check: - - Istio Gateway and VirtualService configuration - - AuthorizationPolicy to ensure it allows external traffic - - Istio ingress gateway logs for any errors - - Firewall rules to ensure port 80 is open - -### Debug Commands +### 1. ServiceImports Stuck in PENDING State + +If ServiceImports are stuck in PENDING state: ```bash -# Check Istio installation -kubectl get pods -n istio-system +# Check status +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get serviceimports -n bookinfo +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo -# Verify sidecar injection -kubectl get pods -n bookinfo -o jsonpath='{.items[*].spec.containers[*].name}' +# Delete and recreate the ServiceExport +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 delete serviceexport -n bookinfo +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/serviceexports.yaml -n bookinfo +``` -# Check mTLS certificates -kubectl exec -n bookinfo -c istio-proxy -- openssl s_client -connect reviews:9080 +### 2. Istio CRDs Not Installed -# View Envoy configuration -kubectl exec -n bookinfo -c istio-proxy -- curl localhost:15000/config_dump +If you encounter errors about missing Istio CRDs, reinstall Istio: -# Check service mesh connectivity -istioctl proxy-config cluster -n bookinfo +```bash +# Install Istio using istioctl +istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 -y +istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 -y ``` -## Architecture Details +### 3. Blank Productpage -### mTLS Flow -1. Service A initiates connection to Service B -2. Istio Envoy proxy intercepts the connection -3. Mutual certificate exchange occurs -4. Connection is established with encryption -5. All subsequent traffic is encrypted +If the productpage shows a blank page or cannot be reached: -### Cross-Cluster Communication -1. productpage (Cluster 1) calls reviews.bookinfo.svc.slice.local -2. KubeSlice routes traffic to Cluster 2 -3. Istio maintains mTLS encryption across cluster boundaries -4. reviews service responds through encrypted channel +```bash +# Check if the Istio Gateway is properly configured +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get gateway -n bookinfo +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get virtualservice -n bookinfo -### Security Policies -- **PeerAuthentication**: Enforces STRICT mTLS for all services -- **AuthorizationPolicy**: Controls service-to-service access -- **Certificate Rotation**: Automatic every 24 hours via Istio CA +# Ensure authorization policies allow external traffic +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/istio-allow-ingress.yaml +``` -## Customization +## Conclusion -### Adding New Services -1. Create service YAML with `sidecar.istio.io/inject: "true"` annotation -2. Add ServiceExport for cross-cluster access -3. Update AuthorizationPolicy if needed +You have successfully deployed the Bookinfo application across two KubeSlice-connected clusters with Istio service mesh and mTLS enabled. The application architecture provides: -### Modifying mTLS Policy -Edit `peer-authentication.yaml` to change mTLS mode: -- `STRICT` - Always require mTLS -- `PERMISSIVE` - Allow both mTLS and plain text -- `DISABLE` - Disable mTLS +1. Secure service-to-service communication with mTLS +2. Cross-cluster service discovery via KubeSlice +3. External access through Istio Gateway -### External Access -The Istio Gateway exposes the productpage service externally. To access: -1. Find the gateway external IP -2. Access http:///productpage +For more details on the architecture and features, refer to the [README.md](./README.md). + resourceVersion: "" +``` -## Performance Considerations +### 11. Access the Application -- **Latency**: mTLS adds ~1-2ms latency per hop -- **CPU**: Envoy proxies consume additional CPU (~10-50m per service) -- **Memory**: Each sidecar uses ~50-100MB RAM -- **Network**: Certificate exchange adds startup time (~2-5 seconds) +Access the Bookinfo application using the Istio Ingress Gateway's external IP: -## Security Best Practices +```bash +# Get the Ingress Gateway IP +GATEWAY_IP=$(kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get svc istio-ingress -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}') -1. **Certificate Management**: Use short-lived certificates (default 24h) -2. **Network Policies**: Implement Kubernetes NetworkPolicies alongside Istio -3. **RBAC**: Configure proper service account permissions -4. **Monitoring**: Enable Istio telemetry for security monitoring -5. **Updates**: Keep Istio updated for security patches +echo "Access the Bookinfo application at: http://$GATEWAY_IP/productpage" +``` -## Monitoring and Observability +## Troubleshooting -This example can be extended with: -- **Kiali** - Service mesh visualization -- **Jaeger** - Distributed tracing -- **Prometheus** - Metrics collection -- **Grafana** - Metrics visualization +### 1. ServiceImports Stuck in PENDING State + +If ServiceImports are stuck in PENDING state: + +```bash +# Check status +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get serviceimports -n bookinfo +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo + +# Delete and recreate the ServiceExport +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 delete serviceexport -n bookinfo +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/serviceexports.yaml -n bookinfo +``` + +### 2. Istio CRDs Not Installed + +If you encounter errors about missing Istio CRDs: + +```bash +# Install Istio using istioctl +istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 -y +istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 -y +``` + +### 3. Blank Productpage + +If the productpage shows a blank page or cannot be reached: + +```bash +# Check if the Istio Gateway is properly configured +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get gateway -n bookinfo +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get virtualservice -n bookinfo + +# Ensure authorization policies allow external traffic +kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 apply -f config_files/istio-allow-ingress.yaml +``` -## Contributing +## Conclusion -To contribute improvements: -1. Test changes thoroughly -2. Update documentation -3. Verify mTLS functionality -4. Submit pull request with examples +You have successfully deployed the Bookinfo application across two KubeSlice-connected clusters with Istio service mesh and mTLS enabled. The application architecture provides: -## Related Examples +1. Secure service-to-service communication with mTLS +2. Cross-cluster service discovery via KubeSlice +3. External access through Istio Gateway -- [bookinfo](../bookinfo/) - Basic version without Istio -- [boutique](../boutique/) - Another microservices example \ No newline at end of file +For more details on the architecture and features, refer to the [README.md](./README.md). From 8e55c3d3516b69b2c22f4f7be54710bcbd5c5441 Mon Sep 17 00:00:00 2001 From: sanjay7178 Date: Sun, 10 Aug 2025 23:02:02 +0530 Subject: [PATCH 7/9] chore: add cleanup script for Bookinfo and Istio resources Signed-off-by: sanjay7178 --- kind/bookinfo-istio/bookinfo.sh | 172 ------------------- kind/bookinfo-istio/utils/bookinfo_test.sh | 188 --------------------- kind/bookinfo-istio/{ => utils}/cleanup.sh | 0 3 files changed, 360 deletions(-) delete mode 100755 kind/bookinfo-istio/bookinfo.sh delete mode 100755 kind/bookinfo-istio/utils/bookinfo_test.sh rename kind/bookinfo-istio/{ => utils}/cleanup.sh (100%) diff --git a/kind/bookinfo-istio/bookinfo.sh b/kind/bookinfo-istio/bookinfo.sh deleted file mode 100755 index 3963d87..0000000 --- a/kind/bookinfo-istio/bookinfo.sh +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env bash - -# Bookinfo deployment with Istio service mesh and mTLS across KubeSlice clusters - -BASE_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -CONFIG_DIR=${BASE_DIR}/config_files - -ENV_FILE=${BASE_DIR}/../kind.env - -if [[ ! -f $ENV_FILE ]]; then - echo "${ENV_FILE} file not found! Exiting" - exit 1 -fi - -source $ENV_FILE - -PRODUCT_CLUSTER="${PREFIX}${WORKERS[0]}" -SERVICES_CLUSTER="${PREFIX}${WORKERS[1]}" -BOOKINFO_NAMESPACE=bookinfo - -uninstall() { - echo "Uninstalling Bookinfo with Istio..." - for cluster in $SERVICES_CLUSTER $PRODUCT_CLUSTER; do - echo "Deleting namespace $BOOKINFO_NAMESPACE on cluster $cluster" - kubectx $cluster - [[ $(kubectl get namespaces | grep $BOOKINFO_NAMESPACE) ]] && kubectl delete namespace $BOOKINFO_NAMESPACE - done - echo "Uninstall completed" -} - -help() { - echo "Usage: bookinfo.sh [--delete] [--help]" - echo " Deploy Istio Bookinfo application with mTLS across KubeSlice clusters" - echo " --delete Uninstall the bookinfo application" - echo " --help Show this help message" -} - -check_istio() { - local cluster=$1 - echo "Checking Istio installation on cluster $cluster..." - kubectx $cluster - - if ! kubectl get namespace istio-system &> /dev/null; then - echo "Istio not found on $cluster. Please install Istio first:" - echo "kubectl apply -f https://github.com/istio/istio/releases/download/1.20.1/istio-base.yaml" - echo "kubectl apply -f https://github.com/istio/istio/releases/download/1.20.1/istio-discovery.yaml" - echo "Or run: ${BASE_DIR}/install-istio.sh" - return 1 - fi - - if ! kubectl get deployment istiod -n istio-system &> /dev/null; then - echo "Istio control plane not found on $cluster" - return 1 - fi - - echo "Istio verified on $cluster" - return 0 -} - -enable_injection() { - local cluster=$1 - echo "Enabling Istio sidecar injection for namespace $BOOKINFO_NAMESPACE on $cluster" - kubectx $cluster - kubectl label namespace $BOOKINFO_NAMESPACE istio-injection=enabled --overwrite -} - -# Get the options -while getopts ":d:delete:help:" option; do - case $option in - d | delete) # Uninstall - uninstall - exit;; - h |help) - help - exit;; - esac -done - -echo "=== Deploying Bookinfo with Istio and mTLS ===" - -# Check Istio installation on both clusters -check_istio $PRODUCT_CLUSTER || exit 1 -check_istio $SERVICES_CLUSTER || exit 1 - -# Create namespaces and enable injection -echo "Creating bookinfo namespace on product cluster..." -kubectx $PRODUCT_CLUSTER -kubectl create namespace $BOOKINFO_NAMESPACE || true -enable_injection $PRODUCT_CLUSTER - -echo "Creating bookinfo namespace on services cluster..." -kubectx $SERVICES_CLUSTER -kubectl create namespace $BOOKINFO_NAMESPACE || true -enable_injection $SERVICES_CLUSTER - -function wait_for_pods { - local cluster=$1 - echo "Waiting for pods to be ready on $cluster..." - kubectx $cluster - - for pod in $(kubectl get pods -n $BOOKINFO_NAMESPACE | grep -v NAME | awk '{ print $1 }'); do - counter=0 - - while [[ $(kubectl get pods $pod -o 'jsonpath={..status.conditions[?(@.type=="Ready")].status}' -n $BOOKINFO_NAMESPACE) != True ]]; do - sleep 1 - let counter=counter+1 - - if ((counter == 180)); then # Increased timeout for Istio sidecar injection - echo "POD $pod failed to start in 180 seconds" - kubectl describe pod $pod -n $BOOKINFO_NAMESPACE - echo "Exiting" - exit -1 - fi - done - done - echo "All pods ready on $cluster" -} - -echo "Installing productpage on $PRODUCT_CLUSTER..." -kubectx $PRODUCT_CLUSTER -kubectl apply -f ${CONFIG_DIR}/productpage.yaml -n $BOOKINFO_NAMESPACE - -echo "Waiting for productpage pods to be ready..." -wait_for_pods $PRODUCT_CLUSTER - -echo "Installing services on $SERVICES_CLUSTER..." -kubectx $SERVICES_CLUSTER -kubectl apply -f ${CONFIG_DIR}/details.yaml -n $BOOKINFO_NAMESPACE -kubectl apply -f ${CONFIG_DIR}/ratings.yaml -n $BOOKINFO_NAMESPACE -kubectl apply -f ${CONFIG_DIR}/reviews.yaml -n $BOOKINFO_NAMESPACE - -echo "Waiting for service pods to be ready..." -wait_for_pods $SERVICES_CLUSTER - -echo "Applying KubeSlice ServiceExports..." -kubectl apply -f ${CONFIG_DIR}/serviceexports.yaml -n $BOOKINFO_NAMESPACE -echo "Waiting for ServiceExports to be created..." -sleep 30 - -echo "Verifying ServiceExports..." -kubectl get serviceexport -n $BOOKINFO_NAMESPACE - -echo "Applying mTLS configuration on both clusters..." -kubectx $PRODUCT_CLUSTER -kubectl apply -f ${CONFIG_DIR}/peer-authentication.yaml -kubectx $SERVICES_CLUSTER -kubectl apply -f ${CONFIG_DIR}/peer-authentication.yaml - -echo "Applying Istio Gateway configuration..." -kubectx $PRODUCT_CLUSTER -kubectl apply -f ${CONFIG_DIR}/gateway.yaml - -echo "Verifying ServiceImports on product cluster..." -kubectl get serviceimport -n $BOOKINFO_NAMESPACE - -echo "Printing services on both clusters..." -echo "=== Product Cluster Services ===" -kubectl get services -n $BOOKINFO_NAMESPACE - -kubectx $SERVICES_CLUSTER -echo "=== Services Cluster Services ===" -kubectl get services -n $BOOKINFO_NAMESPACE - -echo "=== Testing bookinfo services with mTLS ===" -echo "Waiting for services to be available..." -sleep 40 - -echo "Deployment completed successfully!" -echo "Run: bash ${BASE_DIR}/utils/bookinfo_test.sh" -echo "To test the application." - -bash ${BASE_DIR}/utils/bookinfo_test.sh diff --git a/kind/bookinfo-istio/utils/bookinfo_test.sh b/kind/bookinfo-istio/utils/bookinfo_test.sh deleted file mode 100755 index f6fe3bb..0000000 --- a/kind/bookinfo-istio/utils/bookinfo_test.sh +++ /dev/null @@ -1,188 +0,0 @@ -#!/bin/bash - -# Enhanced test script for bookinfo with Istio and mTLS verification - -BASE_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -CONFIG_FILE=${BASE_DIR}/../../kind.env - -if [[ -f $CONFIG_FILE ]]; then - source $CONFIG_FILE -else - echo "File $CONFIG_FILE not found" - exit 1 -fi - -PRODUCT_CLUSTER="${PREFIX}${WORKERS[0]}" -SERVICES_CLUSTER="${PREFIX}${WORKERS[1]}" - -echo "=== Testing Bookinfo with Istio and mTLS ===" - -# Test basic connectivity -test_basic_connectivity() { - echo "#### Testing basic Bookinfo connectivity" - kubectx $PRODUCT_CLUSTER - PRODUCT_NODE=$(kubectl get pods -o wide -n bookinfo | tail -1 | awk '{ print $7 }') - - BI_PORT=$(kubectl get services -n bookinfo | egrep 'productpage' | grep -o -P '(?<=:).*(?=/TCP)') - echo "Product page port: $BI_PORT" - - BI_ADDR_STR=$(kubectl get nodes -o wide | egrep "$PRODUCT_NODE" | awk '{ print $6 }') - - WSL=$(ps -elf | egrep "wsl\/docker-desktop" | egrep -v grep | awk '{ print $15 }') - - if [[ $WSL != "" ]]; then - echo "#### WSL Environment Detected" - echo "Started forwarding the port to access the ProductPage UI" - echo "Please use CTRL-C to exit & stop the port forwarding" - echo "Access productpage on browser with the URL: http://localhost:$BI_PORT/productpage" - kubectl port-forward svc/productpage -n bookinfo $BI_PORT:9080 - return - fi - - echo "Testing: curl http://$BI_ADDR_STR:$BI_PORT/productpage" - if curl -s http://$BI_ADDR_STR:$BI_PORT/productpage | grep -q 'Comedy of Errors'; then - echo "✓ Bookinfo Reviews Page OK" - else - echo "✗ Bookinfo Reviews Page FAIL" - fi - - if curl -s http://$BI_ADDR_STR:$BI_PORT/productpage | grep -q 'Type'; then - echo "✓ Bookinfo Details Page OK" - else - echo "✗ Bookinfo Details Page FAIL" - fi - - if curl -s http://$BI_ADDR_STR:$BI_PORT/productpage | grep -q 'slapstick'; then - echo "✓ Bookinfo Product Page OK" - else - echo "✗ Bookinfo Product Page FAIL" - fi -} - -# Test Istio proxy presence -test_istio_sidecars() { - echo "#### Testing Istio sidecar injection" - - echo "Checking productpage sidecars on $PRODUCT_CLUSTER..." - kubectx $PRODUCT_CLUSTER - PROD_CONTAINERS=$(kubectl get pods -n bookinfo -l app=productpage -o jsonpath='{.items[0].spec.containers[*].name}') - if echo "$PROD_CONTAINERS" | grep -q "istio-proxy"; then - echo "✓ Productpage has Istio sidecar" - else - echo "✗ Productpage missing Istio sidecar" - echo "Containers: $PROD_CONTAINERS" - fi - - echo "Checking services sidecars on $SERVICES_CLUSTER..." - kubectx $SERVICES_CLUSTER - - for service in details reviews ratings; do - CONTAINERS=$(kubectl get pods -n bookinfo -l app=$service -o jsonpath='{.items[0].spec.containers[*].name}' 2>/dev/null) - if echo "$CONTAINERS" | grep -q "istio-proxy"; then - echo "✓ $service has Istio sidecar" - else - echo "✗ $service missing Istio sidecar" - echo "Containers: $CONTAINERS" - fi - done -} - -# Test mTLS configuration -test_mtls() { - echo "#### Testing mTLS configuration" - - kubectx $PRODUCT_CLUSTER - - # Check PeerAuthentication - if kubectl get peerauthentication default -n bookinfo &>/dev/null; then - MTLS_MODE=$(kubectl get peerauthentication default -n bookinfo -o jsonpath='{.spec.mtls.mode}') - if [[ "$MTLS_MODE" == "STRICT" ]]; then - echo "✓ mTLS is configured in STRICT mode" - else - echo "⚠ mTLS mode: $MTLS_MODE (expected STRICT)" - fi - else - echo "✗ PeerAuthentication not found" - fi - - # Check for mTLS certificates in productpage pod - PRODUCTPAGE_POD=$(kubectl get pods -n bookinfo -l app=productpage -o jsonpath='{.items[0].metadata.name}') - if [[ -n "$PRODUCTPAGE_POD" ]]; then - echo "Checking mTLS certificates in productpage pod..." - CERTS=$(kubectl exec -n bookinfo $PRODUCTPAGE_POD -c istio-proxy -- find /etc/ssl/certs -name "*.pem" | wc -l 2>/dev/null || echo "0") - if [[ "$CERTS" -gt 0 ]]; then - echo "✓ Found $CERTS certificate files in istio-proxy" - else - echo "⚠ No certificates found in istio-proxy" - fi - fi -} - -# Test service connectivity with mTLS -test_service_connectivity() { - echo "#### Testing service-to-service connectivity with mTLS" - - kubectx $PRODUCT_CLUSTER - PRODUCTPAGE_POD=$(kubectl get pods -n bookinfo -l app=productpage -o jsonpath='{.items[0].metadata.name}') - - if [[ -n "$PRODUCTPAGE_POD" ]]; then - echo "Testing connectivity from productpage to reviews service..." - - # Test connection to reviews service through Istio proxy - REVIEWS_RESPONSE=$(kubectl exec -n bookinfo $PRODUCTPAGE_POD -c productpage -- curl -s "http://reviews.bookinfo.svc.slice.local:9080/reviews/0" | head -c 100) - if [[ -n "$REVIEWS_RESPONSE" ]]; then - echo "✓ Productpage can connect to reviews service" - else - echo "✗ Failed to connect to reviews service" - fi - - echo "Testing connectivity from productpage to details service..." - DETAILS_RESPONSE=$(kubectl exec -n bookinfo $PRODUCTPAGE_POD -c productpage -- curl -s "http://details.bookinfo.svc.slice.local:9080/details/0" | head -c 100) - if [[ -n "$DETAILS_RESPONSE" ]]; then - echo "✓ Productpage can connect to details service" - else - echo "✗ Failed to connect to details service" - fi - else - echo "✗ Productpage pod not found" - fi -} - -# Test Istio configuration -test_istio_config() { - echo "#### Testing Istio configuration" - - kubectx $PRODUCT_CLUSTER - - # Check Gateway - if kubectl get gateway bookinfo-gateway -n bookinfo &>/dev/null; then - echo "✓ Istio Gateway configured" - else - echo "✗ Istio Gateway not found" - fi - - # Check VirtualService - if kubectl get virtualservice bookinfo -n bookinfo &>/dev/null; then - echo "✓ Istio VirtualService configured" - else - echo "✗ Istio VirtualService not found" - fi -} - -main() { - test_basic_connectivity - echo "" - test_istio_sidecars - echo "" - test_mtls - echo "" - test_service_connectivity - echo "" - test_istio_config - echo "" - echo "=== Test Summary ===" - echo "If all tests pass, your Bookinfo application is running with Istio and mTLS!" -} - -main - diff --git a/kind/bookinfo-istio/cleanup.sh b/kind/bookinfo-istio/utils/cleanup.sh similarity index 100% rename from kind/bookinfo-istio/cleanup.sh rename to kind/bookinfo-istio/utils/cleanup.sh From a5f7934f222a46a9cef346a9fb1152d19aff802e Mon Sep 17 00:00:00 2001 From: sanjay7178 Date: Sun, 10 Aug 2025 23:06:24 +0530 Subject: [PATCH 8/9] feat: add verification step for Bookinfo application UI and include screenshot Signed-off-by: sanjay7178 --- kind/bookinfo-istio/README.md | 18 +++++++++--------- kind/bookinfo-istio/productpage-istio-gw.png | Bin 0 -> 255095 bytes 2 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 kind/bookinfo-istio/productpage-istio-gw.png diff --git a/kind/bookinfo-istio/README.md b/kind/bookinfo-istio/README.md index faf349a..3939fb5 100644 --- a/kind/bookinfo-istio/README.md +++ b/kind/bookinfo-istio/README.md @@ -364,6 +364,14 @@ GATEWAY_IP=$(kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker- echo "Access the Bookinfo application at: http://$GATEWAY_IP/productpage" ``` +### 12. Verify the Application UI + +Once you access the Bookinfo productpage through the Istio Ingress Gateway, you should see a page similar to the screenshot below: + +![Bookinfo Productpage Screenshot](./productpage-istio-gw.png) + +*Figure: Bookinfo Productpage served through Istio Ingress Gateway with mTLS enabled* + ## Troubleshooting ### 1. ServiceImports Stuck in PENDING State @@ -382,7 +390,7 @@ kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f co ### 2. Istio CRDs Not Installed -If you encounter errors about missing Istio CRDs, reinstall Istio: +If you encounter errors about missing Istio CRDs: ```bash # Install Istio using istioctl @@ -412,14 +420,6 @@ You have successfully deployed the Bookinfo application across two KubeSlice-con 3. External access through Istio Gateway For more details on the architecture and features, refer to the [README.md](./README.md). - resourceVersion: "" -``` - -### 11. Access the Application - -Access the Bookinfo application using the Istio Ingress Gateway's external IP: - -```bash # Get the Ingress Gateway IP GATEWAY_IP=$(kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get svc istio-ingress -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}') diff --git a/kind/bookinfo-istio/productpage-istio-gw.png b/kind/bookinfo-istio/productpage-istio-gw.png new file mode 100644 index 0000000000000000000000000000000000000000..668c30ee33713f27bf1c9e4f63505183e287ce67 GIT binary patch literal 255095 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 literal 0 HcmV?d00001 From da31bc7975bad1a3bc9c92ee60402837388670be Mon Sep 17 00:00:00 2001 From: sanjay7178 Date: Sun, 10 Aug 2025 23:13:11 +0530 Subject: [PATCH 9/9] refactor: update README for Bookinfo deployment guide with clearer instructions and remove outdated troubleshooting steps Signed-off-by: sanjay7178 --- kind/bookinfo-istio/README.md | 60 +++++++++++++++++------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/kind/bookinfo-istio/README.md b/kind/bookinfo-istio/README.md index 3939fb5..5fbf951 100644 --- a/kind/bookinfo-istio/README.md +++ b/kind/bookinfo-istio/README.md @@ -2,6 +2,35 @@ This example deploys the Istio Bookinfo application across two KubeSlice-connected clusters with Istio service mesh and mTLS enabled for secure service-to-service communication. +## Getting Started + +### Clone the Repository + +Begin by cloning the repository and navigating to the bookinfo-istio example directory: + +```bash +# Clone the repository +git clone https://github.com/kubeslice/examples.git + +# Navigate to the bookinfo-istio example directory +cd examples/kind/bookinfo-istio +``` + +### Cluster Information + +This guide uses the following Kubernetes clusters: + +- **Controller Cluster**: `gke_graphic-transit-458312-f7_us-central1_ks-controller` + - The KubeSlice controller that manages the slice configuration + +- **Product Cluster (Worker 1)**: `gke_graphic-transit-458312-f7_us-east1_ks-worker-1` + - Hosts the productpage service and Istio ingress gateway + +- **Services Cluster (Worker 2)**: `gke_graphic-transit-458312-f7_us-east1_ks-worker-2` + - Hosts the details, reviews, and ratings services + +> **Note**: You should replace these cluster contexts with your own when following this guide. The contexts mentioned are specific to the original setup. + ## Prerequisites - Two Kubernetes clusters connected via KubeSlice @@ -21,7 +50,7 @@ First, we need to create a slice configuration on the KubeSlice controller: kubectx gke_graphic-transit-458312-f7_us-central1_ks-controller # Apply slice configuration -kubectl apply -f '/home/sanjay7178/examples2/kind/bookinfo-istio/config_files/slice-config.yaml' +kubectl apply -f 'config_files/slice-config.yaml' ``` Expected output: @@ -420,35 +449,6 @@ You have successfully deployed the Bookinfo application across two KubeSlice-con 3. External access through Istio Gateway For more details on the architecture and features, refer to the [README.md](./README.md). -# Get the Ingress Gateway IP -GATEWAY_IP=$(kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get svc istio-ingress -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}') - -echo "Access the Bookinfo application at: http://$GATEWAY_IP/productpage" -``` - -## Troubleshooting - -### 1. ServiceImports Stuck in PENDING State - -If ServiceImports are stuck in PENDING state: - -```bash -# Check status -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 get serviceimports -n bookinfo -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 get serviceexports -n bookinfo - -# Delete and recreate the ServiceExport -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 delete serviceexport -n bookinfo -kubectl --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 apply -f config_files/serviceexports.yaml -n bookinfo -``` - -### 2. Istio CRDs Not Installed - -If you encounter errors about missing Istio CRDs: - -```bash -# Install Istio using istioctl -istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-1 -y istioctl install --set profile=demo --context=gke_graphic-transit-458312-f7_us-east1_ks-worker-2 -y ```