Technical

Deploy Gloo Mesh with Argo CD

GitOps is becoming increasingly popular approach to manage Kubernetes components. It works by using Git as a single source of truth for declarative infrastructure and applications, allowing your application definitions, configurations, and environments to be declarative and version controlled. This helps to make these workflows automated, auditable, and easy to understand.

Purpose of This Tutorial

The main goal of this tutorial is to showcase how Gloo Mesh components can seamlessly integrate into a GitOps workflow, with Argo CD being our tool of choice. We’ll guide you through the installation of Argo CD, Gloo Platform, Istio, and we’ll explore the Gloo Mesh Dashboard.

Beyond the installation walkthroughs, we’ll engage in practical exercises specifically designed to showcase the robustness of the system. These exercises serve as a technical demonstration of how the pull/sync mechanism offered by Argo CD enhances the reliability and resilience of your applications and service mesh.

Where This Has Been Tested

  • Kubernetes versions 1.25-1.28
  • Google Kubernetes Engine using 2x n2-standard-4 instance types
  • AWS EKS using 2x m5.xlarge instance types
  • Microsoft AKS using 2x Standard_DS3_v2 instance types
  • Locally using K3D and Colima+K3S on Macbook Pro M1

High Level Architecture

Request a Trial

If you are interested in walking through the following steps hands-on, request a trial license of Gloo Mesh!

Prerequisites

This tutorial assumes a single Kubernetes cluster for demonstration. Instructions have been validated on k3d, as well as in EKS and GKE. Please note that the setup and installation of Kubernetes are beyond the scope of this guide. Ensure that your cluster contexts are named gloo by running:

kubectl config get-contexts
CURRENT   NAME   CLUSTER    AUTHINFO         NAMESPACE
*         gloo   k3d-gloo   admin@k3d-gloo   

Set Environment Variables

To get started, set the MY_CLUSTER_CONTEXT and MY_CLUSTER_NAME vars for our cluster.

export MY_CLUSTER_CONTEXT=gloo
export MY_CLUSTER_NAME=gloo

Renaming Cluster Context

If your local clusters have a different context name, you will want to have it match the expected context name(s). In this example, we are setting the context name as gloo.

kubectl config rename-context <k3d-your_cluster_name> "${MY_CLUSTER_CONTEXT}"

Gloo Mesh Enterprise requires a Trial License Key:

GLOO_MESH_LICENSE_KEY=<input_license_key_here>

To deploy Istio, set the the Istio hub, version, and revision label.

export HUB=us-docker.pkg.dev/gloo-mesh/istio-workshops
export ISTIO_VERSION=1.20.2-solo
export ISTIO_REVISION=1-20

Reminder if you want a specific version of Istio or to use the officially supported images provided by Solo.io, get the Hub value from the Solo support page for Istio Solo images. The value is present within the Solo.io Istio Versioning Repo key section.

Otherwise, we can use the 1.20.2-solo/ Istio image provided in our Solo Workshops.

Provide the Gloo Mesh version:

export GLOO_MESH_VERSION=2.5.0

Installing Argo CD

Let’s start by deploying Argo CD to our gloo cluster context.

Deploy Argo CD 2.9.5 using the non-HA YAML manifests.

kubectl create namespace argocd --context "${MY_CLUSTER_CONTEXT}"

until kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v2.9.5/manifests/install.yaml --context "${MY_CLUSTER_CONTEXT}" > /dev/null 2>&1; do sleep 2; done

Check deployment status:

kubectl --context "${MY_CLUSTER_CONTEXT}" -n argocd rollout status deploy/argocd-applicationset-controller
kubectl --context "${MY_CLUSTER_CONTEXT}" -n argocd rollout status deploy/argocd-dex-server
kubectl --context "${MY_CLUSTER_CONTEXT}" -n argocd rollout status deploy/argocd-notifications-controller
kubectl --context "${MY_CLUSTER_CONTEXT}" -n argocd rollout status deploy/argocd-redis
kubectl --context "${MY_CLUSTER_CONTEXT}" -n argocd rollout status deploy/argocd-repo-server
kubectl --context "${MY_CLUSTER_CONTEXT}" -n argocd rollout status deploy/argocd-server

Check to see Argo CD status.

kubectl get pods -n argocd --context "${MY_CLUSTER_CONTEXT}"

Output should look similar to below.

NAME                                                READY   STATUS    RESTARTS   AGE
argocd-application-controller-0                     1/1     Running   0          31s
argocd-applicationset-controller-765944f45d-569kn   1/1     Running   0          33s
argocd-dex-server-7977459848-swnm6                  1/1     Running   0          33s
argocd-notifications-controller-6587c9d9-6t4hg      1/1     Running   0          32s
argocd-redis-b5d6bf5f5-52mk5                        1/1     Running   0          32s
argocd-repo-server-7bfc968f69-hrqt6                 1/1     Running   0          32s
argocd-server-77f84bfbb8-lgdlr                      2/2     Running   0          32s

We can also change the password to: admin / solo.io:

# bcrypt(password)=$2a$10$79yaoOg9dL5MO8pn8hGqtO4xQDejSEVNWAGQR268JHLdrCw6UCYmy
# password: solo.io
kubectl --context "${MY_CLUSTER_CONTEXT}" -n argocd patch secret argocd-secret \
  -p '{"stringData": {
    "admin.password": "$2a$10$79yaoOg9dL5MO8pn8hGqtO4xQDejSEVNWAGQR268JHLdrCw6UCYmy",
    "admin.passwordMtime": "'$(date +%FT%T%Z)'"
  }}'

Note: if you want to change the password to something else, follow these instructions in the Argo CD Docs.

Access Argo CD with port-forwarding

At this point, we should be able to access our Argo CD server using port-forward at http://localhost:9999.

kubectl port-forward svc/argocd-server -n argocd 9999:443 --context "${MY_CLUSTER_CONTEXT}"

Installing Gloo Mesh

Gloo Mesh can be installed and configured easily using Helm + Argo CD. To install Gloo Mesh Enterprise:

First, we will deploy the Gloo Platform CRD helm chart using an Argo Application.

kubectl apply --context "${MY_CLUSTER_CONTEXT}" -f- <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: gloo-platform-crds
  namespace: argocd
spec:
  destination:
    namespace: gloo-mesh
    server: https://kubernetes.default.svc
  project: default
  source:
    chart: gloo-platform-crds
    repoURL: https://storage.googleapis.com/gloo-platform/helm-charts
    targetRevision: "${GLOO_MESH_VERSION}"  
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
    - CreateNamespace=true 
    retry:
      limit: 2
      backoff:
        duration: 5s
        maxDuration: 3m0s
        factor: 2
EOF

Then deploy the Gloo Platform helm chart.

kubectl apply --context "${MY_CLUSTER_CONTEXT}" -f- <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: gloo-platform-helm
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  destination:
    server: https://kubernetes.default.svc
    namespace: gloo-mesh
  project: default
  source:
    chart: gloo-platform
    helm:
      skipCrds: true
      values: |
        licensing:
          licenseKey: ${GLOO_MESH_LICENSE_KEY}
        common:
          cluster: "${MY_CLUSTER_NAME}"
        glooMgmtServer:
          enabled: true
          serviceType: ClusterIP
          registerCluster: true
          createGlobalWorkspace: true
        prometheus:
          enabled: true
        redis:
          deployment:
            enabled: true
        telemetryCollector:
          enabled: true
          config:
            exporters:
              otlp:
                endpoint: gloo-telemetry-gateway.gloo-mesh:4317
        glooUi:
          enabled: true
          serviceType: ClusterIP
        glooAgent:
          enabled: true
          relay:
            serverAddress: gloo-mesh-mgmt-server:9900
    repoURL: https://storage.googleapis.com/gloo-platform/helm-charts
    targetRevision: "${GLOO_MESH_VERSION}"
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
EOF

More information on the gloo-platform helm values is present here.

You may also retrieve the gloo-platform helm package, and download it locally.

helm repo add gloo-platform https://storage.googleapis.com/gloo-platform/helm-charts
helm pull gloo-platform/gloo-platform --version=${GLOO_MESH_VERSION} --untar
open gloo-platform/values.yaml

You can check to see that the Gloo Mesh Management Plane is deployed.

kubectl get pods -n gloo-mesh  --context "${MY_CLUSTER_CONTEXT}"

Output should look similar to below:

NAME                                      READY   STATUS    RESTARTS   AGE
gloo-mesh-agent-7b79dd5c9-hfw9p           1/1     Running   0          62s
gloo-mesh-mgmt-server-7f6968d9b9-67l7j    1/1     Running   0          62s
gloo-mesh-redis-84f57d46bc-dwqx9          1/1     Running   0          62s
gloo-mesh-ui-56879cb8b-52vpx              3/3     Running   0          61s
gloo-telemetry-collector-agent-7fgb5      1/1     Running   0          62s
gloo-telemetry-collector-agent-txwbm      1/1     Running   0          62s
gloo-telemetry-gateway-5445d7d6b5-5k9sd   1/1     Running   0          62s
prometheus-server-565cb79f89-jc5ns        2/2     Running   0          62s

Install meshctl

curl -sL https://run.solo.io/meshctl/install | GLOO_MESH_VERSION="v${GLOO_MESH_VERSION}" sh - ;
export PATH=$HOME/.gloo-mesh/bin:$PATH

You can check status using the meshctl CLI.

meshctl check --kubecontext "${MY_CLUSTER_CONTEXT}"

Output should look similar to below:

🟒 License status

 INFO  gloo-mesh enterprise license expiration is 04 Jun 24 16:41 EDT
 INFO  No GraphQL license module found for any product

🟒 CRD version check


🟒 Gloo Platform deployment status

Namespace | Name                           | Ready | Status
gloo-mesh | gloo-mesh-agent                | 1/1   | Healthy
gloo-mesh | gloo-mesh-mgmt-server          | 1/1   | Healthy
gloo-mesh | gloo-mesh-redis                | 1/1   | Healthy
gloo-mesh | gloo-mesh-ui                   | 1/1   | Healthy
gloo-mesh | gloo-telemetry-gateway         | 1/1   | Healthy
gloo-mesh | prometheus-server              | 1/1   | Healthy
gloo-mesh | gloo-telemetry-collector-agent | 2/2   | Healthy

🟒 Mgmt server connectivity to workload agents

Cluster                         | Registered | Connected Pod
demo-gloo-platform-cluster-arka | true       | gloo-mesh/gloo-mesh-mgmt-server-7f6968d9b9-67l7j

Connected Pod                                    | Clusters
gloo-mesh/gloo-mesh-mgmt-server-7f6968d9b9-67l7j | 1

At this point, we should be able to access our Gloo Platform UI using port-forward at http://localhost:8090.

kubectl port-forward svc/gloo-mesh-ui -n gloo-mesh 8090:8090 --context "${MY_CLUSTER_CONTEXT}"

Install Istio with IstioLifecycleManager

Now that we have Gloo Mesh installed, we can use the IstioLifecycleManager CRD to deploy and manage Istio across all of our workload clusters. This allows us to treat our Istio deployment like any other CRD in the Kubernetes cluster, rather than a separate deployment mechanism such as using helm install to deploy a chart. While there is nothing wrong the Helm-based approach (and in the next section of this blog we will also review using Helm + Argo CD to install Istio) the IstioLifecycleManager API was designed to provide value at scale, such as being able to target multiple clusters at a time to handle day 2 operations such as updating Istio to a new image for a security patch, or upgrading to a new release.

The following instructions will configure the lifecycle manager CRDs directly, but afterwards we will explore how to automate this with an Argo CD Application.

Let’s deploy the Istio control plane using the IstioLifecycleManager:

kubectl apply --context "${MY_CLUSTER_CONTEXT}" -f - <<EOF
apiVersion: admin.gloo.solo.io/v2
kind: IstioLifecycleManager
metadata:
  name: istiod-control-plane
  namespace: gloo-mesh
spec:
  installations:
      # The revision for this installation, such as 1-20
    - revision: ${ISTIO_REVISION}
      # List all workload clusters to install Istio into
      clusters:
      - name: ${MY_CLUSTER_NAME}
        # If set to true, the spec for this revision is applied in the cluster
        defaultRevision: true
      # When set to true, the lifecycle manager allows you to perform in-place upgrades by skipping checks that are required for canary upgrades
      skipUpgradeValidation: true
      istioOperatorSpec:
        # Only the control plane components are installed
        # (https://istio.io/latest/docs/setup/additional-setup/config-profiles/)
        profile: minimal
        # Solo.io Istio distribution repository; required for Gloo Istio.
        # You get the repo key from your Solo Account Representative.
        hub: ${HUB}
        # Any Solo.io Gloo Istio tag
        tag: ${ISTIO_VERSION}
        namespace: istio-system
        # Mesh configuration
        meshConfig:
          # Enable access logging only if using.
          accessLogFile: /dev/stdout
          # Encoding for the proxy access log (TEXT or JSON). Default value is TEXT.
          accessLogEncoding: JSON
          # Enable span tracing only if using.
          enableTracing: true
          defaultConfig:
            # Wait for the istio-proxy to start before starting application pods
            holdApplicationUntilProxyStarts: true
            proxyMetadata:
              # Enable Istio agent to handle DNS requests for known hosts
              # Unknown hosts are automatically resolved using upstream DNS servers
              # in resolv.conf (for proxy-dns)
              ISTIO_META_DNS_CAPTURE: "true"
              # Enable automatic address allocation (for proxy-dns)
              ISTIO_META_DNS_AUTO_ALLOCATE: "true"
          # Set the default behavior of the sidecar for handling outbound traffic
          # from the application
          outboundTrafficPolicy:
            mode: ALLOW_ANY
          # The administrative root namespace for Istio configuration
          rootNamespace: istio-system
        # Traffic management
        values:
          global:
            meshID: gloo-mesh
            network: ${MY_CLUSTER_NAME}
            multiCluster:
              clusterName: ${MY_CLUSTER_NAME}
        # Traffic management
        components:
          pilot:
            k8s:
              env:
              # Disable selecting workload entries for local service routing.
              # Required for Gloo VirtualDestinaton functionality.
              - name: PILOT_ENABLE_K8S_SELECT_WORKLOAD_ENTRIES
                value: "false"
              # Reload cacerts when cert-manager changes it
              - name: AUTO_RELOAD_PLUGIN_CERTS
                value: "true"
EOF

You can check to see that istiod has been deployed.

kubectl get pods -n istio-system --context "${MY_CLUSTER_CONTEXT}"

Output should look similar to below:

NAME                           READY   STATUS    RESTARTS   AGE
istiod-1-20-78b54758c5-q852m   1/1     Running   0          2m16s

Next we will configure the istio-gateways namespace and Kubernetes service for the gateway. Separating the Kubernetes Service is recommended because it allows us to manage the lifecycle of the load balancer in front of the Istio ingressgateway separate from the lifecycle of the deployment. For example, having full control over the revision selector of the Service and when to make the traffic switchover when doing a canary upgrade.

kubectl --context "${MY_CLUSTER_CONTEXT}" create ns istio-gateways

kubectl apply --context "${MY_CLUSTER_CONTEXT}" -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  labels:
    app: istio-ingressgateway
    istio: ingressgateway
  annotations:
    # uncomment if using the default AWS Cloud in-tree controller 
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
    # uncomment if using the default AWS LB controller
    #service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
    #service.beta.kubernetes.io/aws-load-balancer-type: "external"
    #service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
  name: istio-ingressgateway
  namespace: istio-gateways
spec:
  ports:
  - name: http2
    port: 80
    protocol: TCP
    targetPort: 8080
  - name: https
    port: 443
    protocol: TCP
    targetPort: 8443
  selector:
    app: istio-ingressgateway
    istio: ingressgateway
    revision: ${ISTIO_REVISION}
  type: LoadBalancer
EOF

Now configure the GatewayLifecycleManager.

kubectl apply --context "${MY_CLUSTER_CONTEXT}" -f - <<EOF
apiVersion: admin.gloo.solo.io/v2
kind: GatewayLifecycleManager
metadata:
  name: istio-ingressgateway
  namespace: gloo-mesh
spec:
  installations:
      # The revision for this installation, such as 1-20
    - gatewayRevision: ${ISTIO_REVISION}
      # List all workload clusters to install Istio into
      clusters:
      - name: ${MY_CLUSTER_NAME}
        # If set to true, the spec for this revision is applied in the cluster
        activeGateway: true
      istioOperatorSpec:
        # No control plane components are installed
        profile: empty
        # Solo.io Istio distribution repository; required for Gloo Istio.
        # You get the repo key from your Solo Account Representative.
        hub: ${HUB}
        # The Solo.io Gloo Istio tag
        tag: ${ISTIO_VERSION}
        values:
          gateways:
            istio-ingressgateway:
              customService: true
        components:
          ingressGateways:
            - name: istio-ingressgateway
              namespace: istio-gateways
              enabled: true
              label:
                istio: ingressgateway
EOF

You can check to see that the Istio ingressgateway has been deployed.

kubectl get pods -n istio-gateways --context "${MY_CLUSTER_CONTEXT}"

Output should look similar to below:

NAME                                        READY   STATUS    RESTARTS   AGE
istio-ingressgateway-1-20-5bc944987-882ls   1/1     Running   0          55s

Configuring IstioLifecycleManager or GatewayLifecycleManager with Argo CD

Since we can treat the IstioLifecycleManager and GatewayLifecycleManager the same as any other Kubernetes CRD, we can deploy Istio on our cluster by using an Argo Application that is configured to deploy any valid YAML configuration in the /lifecyclemanager directory in this repo. This will allow us to configure all of the steps we just did with one Argo CD Application resource.

Note the use of the annotation argocd.argoproj.io/sync-wave in each manifest, which can help with the deployment ordering of each component. The order of operations would go least to greatest. More on Argo CD sync-waves.

The corresponding Argo Application would look like this, but note that the example here is set to use a cluster named gloo, Istio version 1.20.2-solo and Istio revision set to 1-20 and would require a PR to this repo to be re-configured.

kubectl apply --context "${MY_CLUSTER_CONTEXT}" -f - <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: istio-lifecyclemanager-deployments
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/solo-io/solo-cop
    path: blogs/gloo-mesh-argocd/istiolifecyclemanager
    targetRevision: HEAD
    directory:
      recurse: true
  destination:
    server: https://kubernetes.default.svc
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m0s
EOF

In the future, we can commit more Istio configuration to this directory to continue building out our cluster, or even to deploy Istio onto other newly onboarded workload clusters.

Installing Istio with Helm

NOTE: If you have already completed the installation using IstioLifecycleManager, you can skip this step completely.

Let’s set our environment variables for this exercise.

export MY_CLUSTER_NAME=gloo
export HUB=us-docker.pkg.dev/gloo-mesh/istio-workshops
export TAG=1.20.2-solo
export ISTIO_VERSION=1.20.2
export ISTIO_REVISION=1-20

Here we will use Argo CD to demonstrate how to deploy and manage Istio using helm.

First, deploy the istio-base helm chart.

kubectl apply --context "${MY_CLUSTER_CONTEXT}" -f- <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: istio-base
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
  annotations:
    argocd.argoproj.io/sync-wave: "-3"
spec:
  destination:
    server: https://kubernetes.default.svc
    namespace: istio-system
  project: default
  source:
    chart: base
    repoURL: https://istio-release.storage.googleapis.com/charts
    targetRevision: "${ISTIO_VERSION}"
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
    - CreateNamespace=true
EOF

Now, let’s deploy the Istio control plane:

Reminder if you want a specific version of Istio or to use the officially supported images provided by Solo.io, get the Hub value from the Solo support page for Istio Solo images. The value is present within the Solo.io Istio Versioning Repo key section.

Otherwise, we can use the 1.20.2 Istio image provided in our Solo Workshops.

kubectl apply --context "${MY_CLUSTER_CONTEXT}" -f- <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: istiod
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  destination:
    server: https://kubernetes.default.svc
    namespace: istio-system
  project: default
  source:
    chart: istiod
    repoURL: https://istio-release.storage.googleapis.com/charts
    targetRevision: "${ISTIO_VERSION}"
    helm:
      values: |
        revision: "${ISTIO_REVISION}"
        global:
          meshID: gloo-mesh
          multiCluster:
            clusterName: gloo
          network: network1
          hub: ${HUB}
          tag: "${TAG}"
        meshConfig:
          trustDomain: "${MY_CLUSTER_NAME}.local"
          accessLogFile: /dev/stdout
          accessLogEncoding: JSON
          enableAutoMtls: true
          defaultConfig:
            # Wait for the istio-proxy to start before starting application pods
            holdApplicationUntilProxyStarts: true
            envoyAccessLogService:
              address: gloo-mesh-agent.gloo-mesh:9977
            proxyMetadata:
              ISTIO_META_DNS_CAPTURE: "true"
              ISTIO_META_DNS_AUTO_ALLOCATE: "true"
          outboundTrafficPolicy:
            mode: ALLOW_ANY
          rootNamespace: istio-system
        pilot:
          env:
            PILOT_ENABLE_K8S_SELECT_WORKLOAD_ENTRIES: "false"
            PILOT_SKIP_VALIDATE_TRUST_DOMAIN: "true"
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    #automated: {}
EOF

Now we can deploy the Istio Ingressgateway.

kubectl apply --context "${MY_CLUSTER_CONTEXT}" -f- <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: istio-ingressgateway
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
  annotations:
    argocd.argoproj.io/sync-wave: "-1"
spec:
  destination:
    server: https://kubernetes.default.svc
    namespace: istio-gateways
  project: default
  source:
    chart: gateway
    repoURL: https://istio-release.storage.googleapis.com/charts
    targetRevision: "${ISTIO_VERSION}"
    helm:
      values: |
        # Name allows overriding the release name. Generally this should not be set
        name: "istio-ingressgateway-${ISTIO_REVISION}"
        # revision declares which revision this gateway is a part of
        revision: "${ISTIO_REVISION}"
        
        replicaCount: 1
        
        service:
          # Type of service. Set to "None" to disable the service entirely
          type: LoadBalancer
          ports:
          - name: http2
            port: 80
            protocol: TCP
            targetPort: 80
          - name: https
            port: 443
            protocol: TCP
            targetPort: 443
          annotations:
            # AWS NLB Annotation
            service.beta.kubernetes.io/aws-load-balancer-type: "nlb-ip"
          loadBalancerIP: ""
          loadBalancerSourceRanges: []
          externalTrafficPolicy: ""
        
        # Pod environment variables
        env: {}

        annotations:
          proxy.istio.io/config: '{ "holdApplicationUntilProxyStarts": true }'
        
        # Labels to apply to all resources
        labels:
          istio.io/rev: ${ISTIO_REVISION}
          istio: ingressgateway
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
    - CreateNamespace=true
EOF

You can check to see that istiod and the istio ingressgateways have been deployed.

kubectl get pods -n istio-system --context "${MY_CLUSTER_CONTEXT}" && \
kubectl get pods -n istio-gateways --context "${MY_CLUSTER_CONTEXT}"

Output should look similar to below:

NAME                             READY   STATUS    RESTARTS   AGE
istiod-1-20-6-86499c5945-bbsfl   1/1     Running   0          38m
NAME                                           READY   STATUS    RESTARTS   AGE
istio-ingressgateway-1-20-6-6575484979-5fbn7   1/1     Running   0          36m

Visualize in Gloo Mesh UI With Port-Forwarding

Using port-forwarding, access Gloo Mesh Dashboard at http://localhost:8090:

kubectl port-forward -n gloo-mesh svc/gloo-mesh-ui 8090 --context "${MY_CLUSTER_CONTEXT}"

Visualize in Gloo Mesh UI with meshctl

Additionally you can use meshctl to do the port-forward for you.

meshctl dashboard

At this point, you should have ArgoCD, Gloo Mesh, and Istio installed on the cluster!

Let's Deploy Our First App!

Let’s deploy an Application using Argo CD! Similar to before, we will confgure an Argo Application that is configured to deploy any valid YAML configuration in the easybutton/workloads directory onto the cluster.

kubectl apply --context "${MY_CLUSTER_CONTEXT}" -f - <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: workloads
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/solo-io/solo-cop
    path: blogs/gloo-mesh-argocd/easybutton/workloads
    targetRevision: HEAD
    directory:
      recurse: true
  destination:
    server: https://kubernetes.default.svc
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m0s
EOF

In the easybutton/workloads directory is the Bookinfo application, the corresponding RouteTable, and a VirtualGateway. In the future, we can commit more mesh configuration to this directory to continue deploying new applications to this cluster.

Confirm that the virtual gateway was configured:

kubectl get virtualgateway -n istio-gateways --context "${MY_CLUSTER_CONTEXT}"

Output should look similar to below.

NAME             AGE
north-south-gw   100s

Confirm that a route table was created.

kubectl get routetables -A --context "${MY_CLUSTER_CONTEXT}"

Output should look similar to below.

NAMESPACE            NAME                  AGE
bookinfo-frontends   bookinfo-routetable   51s

You can check to see that the bookinfo application has been deployed.

kubectl get pods -n bookinfo-frontends --context "${MY_CLUSTER_CONTEXT}" && \
kubectl get pods -n bookinfo-backends --context "${MY_CLUSTER_CONTEXT}"

Output should look similar to below:

NAME                             READY   STATUS    RESTARTS   AGE
productpage-v1-b8f647485-cnzqj   2/2     Running   0          3m8s

NAME                          READY   STATUS    RESTARTS   AGE
ratings-v1-85b5789856-ppzzg   2/2     Running   0          3m8s
reviews-v2-56bc64445f-c54fn   2/2     Running   0          3m8s
reviews-v1-749bbb44c5-bp8z4   2/2     Running   0          3m8s
details-v1-984bdbc4d-7zjjp    2/2     Running   0          3m8s

The configured virtual gateway and route table exposes the Bookinfo application on port 80 of the Istio Ingress Gateway we deployed earlier. You should be able to access it with the following command:

echo "access the Bookinfo application at http://$(kubectl --context "${MY_CLUSTER_CONTEXT}" get svc -n istio-gateways --selector=istio=ingressgateway -o jsonpath='{.items[*].status.loadBalancer.ingress[0].*}')/productpage"

Observe All the Things!

By seamlessly integrating with popular tools like Grafana and Prometheus, implementing Istio allows organizations to gain valuable insights into the health, performance, and overall behavior of their distributed applications within the service mesh.

Similar to before, we will confgure an Argo Application that is configured to deploy any valid YAML configuration in the easybutton/observability directory onto the cluster. This application will manage the deployment of Prometheus and Grafana, configure default dashboards, and expose the Grafana UI to us through the gateway with another route table.

kubectl apply --context "${MY_CLUSTER_CONTEXT}" -f - <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: observability
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/solo-io/solo-cop
    path: blogs/gloo-mesh-argocd/easybutton/observability
    targetRevision: HEAD
    directory:
      recurse: true
  destination:
    server: https://kubernetes.default.svc
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m0s
EOF

You can check to see that the Grafana and Prometheus applications have been deployed.

kubectl get pods -n grafana --context "${MY_CLUSTER_CONTEXT}"
kubectl get pods -n istio-system --context "${MY_CLUSTER_CONTEXT}"

Output should look similar to below:

NAME                       READY   STATUS    RESTARTS   AGE
grafana-576f6587c5-v4bjr   2/2     Running   0          13m

NAME                           READY   STATUS    RESTARTS   AGE
istiod-1-20-6b99fdd545-c8m5v   1/1     Running   0          47m
prometheus-6cf7c7cb94-bpn5v    2/2     Running   0          23m

You can see that a few default dashboards configured using configmaps have been created.

kubectl get configmap -n grafana --context "${MY_CLUSTER_CONTEXT}"

Output should look similar to below:

NAME                       DATA   AGE
solo-istio-dashboard       1      34m
kubernetes-dashboard       1      34m
istio-grafana-dashboards   6      34m

Confirm that an additional route table was created as well.

kubectl get routetables -A --context "${MY_CLUSTER_CONTEXT}"

Output should look similar to below.

NAMESPACE            NAME                  AGE
bookinfo-frontends   bookinfo-routetable   16m
gloo-mesh            grafana-routetable    119s

The configured virtual gateway and route table exposes the Grafana application on port 80 at the /grafana path of the Istio Ingress Gateway we deployed earlier. You should be able to access it with the following command:

echo "access the Grafana UI at http://$(kubectl --context "${MY_CLUSTER_CONTEXT}" get svc -n istio-gateways --selector=istio=ingressgateway -o jsonpath='{.items[*].status.loadBalancer.ingress[0].*}')/grafana"

If you navigate to the available dashboards, we should see a kubernetes dashboard, the upstream community Istio dashboards, as well as the Solo Istio Performance Dashboard!

Resiliency Testing

From a resiliency perspective, deploying Gloo Mesh with Argo CD or any other GitOps tool provides a few clear benefits to a manual or traditional push based approach.

  • Declarative Configuration: Argo CD enables the definition of the desired state of your system through declarative configuration stored in Git, ensuring a clear and version-controlled source of truth.
  • Automated Synchronization: The pull-based approach of Argo CD ensures automatic synchronization between the desired state in Git and the actual state in the Kubernetes cluster, minimizing manual intervention and reducing the risk of configuration drift.
  • Self-Healing Mechanisms: Argo CD can automatically detect discrepancies between the desired and actual states and initiate corrective actions, contributing to a self-healing mechanism that enhances the overall resilience of the deployed components.

We have already demonstrated using declarative configuration and Git as our source of truth when deploying and installing Gloo Mesh, but let’s take a look at auto sync and self-healing.

Scenario 1 – Infrastructure Resiliency

For Kubernetes Pods, in the event of a pod failure the scheduler will automatically start a new pod. But what happens if the Deployment is deleted?

In Kubernetes, removing a Deployment such as the gloo-mesh-mgmt-server is assumed to be part of a controlled process. Kubernetes does not automatically create replacement Pods in this case. Deleting a Deployment is often done when you want to intentionally scale down or update the application.

Let’s take a look at the Gloo Mesh Deployment.

kubectl get deployments -n gloo-mesh --context "${MY_CLUSTER_CONTEXT}"

Output should look similar to below.

NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
gloo-telemetry-gateway   1/1     1            1           66m
gloo-mesh-mgmt-server    1/1     1            1           66m
gloo-mesh-redis          1/1     1            1           66m
gloo-mesh-ui             1/1     1            1           66m
gloo-mesh-agent          1/1     1            1           66m
prometheus-server        1/1     1            1           66m

With Argo CD, in the Application resource we can set selfHeal: true and prune: true options so that if a Deployment in an application controlled by Argo CD is deleted, it will trigger Argo CD’s reconciliation process. This includes self-healing mechanisms to ensure the desired state of the Kubernetes cluster is declared state in Git and automatically takes corrective actions to make the system to manual changes.

Since we deployed Gloo Mesh using Argo CD, let’s create some real chaos and delete all of the deployments in the gloo-mesh namespace and see what happens.

kubectl delete deployments --all -n gloo-mesh --context "${MY_CLUSTER_CONTEXT}"
kubectl delete secrets --all -n gloo-mesh --context "${MY_CLUSTER_CONTEXT}"

In a typical installation, all of the deployments would be deleted permanently. Let’s take a look at what happens when we are using the self-healing capabilities of Argo CD.

kubectl get pods -n gloo-mesh --context "${MY_CLUSTER_CONTEXT}"
kubectl get deployments -n gloo-mesh --context "${MY_CLUSTER_CONTEXT}"
kubectl get secrets -n gloo-mesh --context "${MY_CLUSTER_CONTEXT}"

You can see almost immediately that all of the deployments are re-synced to the cluster.

NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
gloo-mesh-mgmt-server    0/1     1            0           7s
gloo-mesh-redis          0/1     1            0           7s
gloo-telemetry-gateway   1/1     1            1           7s
gloo-mesh-agent          0/1     1            0           2s
gloo-mesh-ui             0/1     1            0           2s
prometheus-server        0/1     1            0           2s

Another important detail to note is that Gloo Mesh is not in the data path! This means that although the entire deployment was deleted and restarted, the traffic routing to our applications remains unaffected.

Scenario 2 – Configuration Resiliency

Similarly to keeping the health of our Applications and Infra alive, we can benefit from self-healing capabilities by syncing our application networking configuration as well. Let’s test another scenario in which a piece of our service mesh configuration is mistakenly removed from the cluster and see what happens.

Let’s take a look at our virtual gateways and route tables.

kubectl get routetables -A --context "${MY_CLUSTER_CONTEXT}" && \
kubectl get virtualgateway -A --context "${MY_CLUSTER_CONTEXT}"

Output should look similar to below.

NAMESPACE            NAME                  AGE
bookinfo-frontends   bookinfo-routetable   2m41s

NAMESPACE        NAME             AGE
istio-gateways   north-south-gw   2m41s

Now let’s take down both our virtual gateway and route table. In our current environment this would just be the Bookinfo application, but a similar scenario in a large environment could have significant consequences affecting multiple teams.

kubectl delete routetables -n bookinfo-frontends bookinfo-routetable --context "${MY_CLUSTER_CONTEXT}"
kubectl delete virtualgateway -n istio-gateways north-south-gw --context "${MY_CLUSTER_CONTEXT}"

Again we see Argo CD to the rescue! Instead of suffering a total outage, Argo CD detected a drift in the desired state and reconciled the virtual gateway and route table.

Scenario 3 – Application Resiliency

To show another example of how Argo CD resiliency capabilities can help, let’s explore a scenario where a user can intentionally/unintentionally modify the deployment of a running application such as the image of a running Pod. In this scenario, without Argo CD the image update would be treated as an intentional action β€” however with Argo CD in place we should observe a syncing event to reconfigure to the source of truth in Git.

First, in a separate terminal we can do a watch on the image. Note, you may need to set your MY_CLUSTER_CONTEXT variable again.

kubectl --context "${MY_CLUSTER_CONTEXT}" get deployment/productpage-v1 -n bookinfo-frontends -oyaml -w | grep image:

Now we can update the image of the productpage-v1 deployment to bogus-container:hack and see what happens.

kubectl --context "${MY_CLUSTER_CONTEXT}" set image deployment/productpage-v1 -n bookinfo-frontends productpage=bogus-container:hack

What we should see is that even though the image was updated, it was automatically reconciled back to the desired state in Git. The output of the watch command should reflect this.

image: us-central1-docker.pkg.dev/solo-test-236622/jmunozro/examples-bookinfo-productpage-v1:1.16.2
image: bogus-container:hack
image: us-central1-docker.pkg.dev/solo-test-236622/jmunoexamples-bookinfo-productpage-v1:1.16.2

Again we can see the value of drift detection and sync back to the desired state using Argo CD.

Cleanup

To uninstall, you can delete the Argo Applications.

# observability
kubectl --context "${MY_CLUSTER_CONTEXT}" delete applications -n argocd observability

# workloads
kubectl --context "${MY_CLUSTER_CONTEXT}" delete applications -n argocd workloads

# helm install
kubectl --context "${MY_CLUSTER_CONTEXT}" delete applications -n argocd istio-ingressgateway
kubectl --context "${MY_CLUSTER_CONTEXT}" delete applications -n argocd istiod
kubectl --context "${MY_CLUSTER_CONTEXT}" delete applications -n argocd istio-base

# ilm install
kubectl --context "${MY_CLUSTER_CONTEXT}" delete service -n istio-gateways istio-ingressgateway
kubectl --context "${MY_CLUSTER_CONTEXT}" delete gatewaylifecyclemanager -n gloo-mesh istio-ingressgateway
kubectl --context "${MY_CLUSTER_CONTEXT}" delete istiolifecyclemanager -n gloo-mesh istiod-control-plane

# gloo platform
kubectl --context "${MY_CLUSTER_CONTEXT}" delete applications -n argocd gloo-platform-helm
kubectl --context "${MY_CLUSTER_CONTEXT}" delete applications -n argocd gloo-platform-crds

# secrets
kubectl --context "${MY_CLUSTER_CONTEXT}" delete secrets -n gloo-mesh --all

Let's Have Some Fun

To streamline deployment, utilize the Argo app-of-apps pattern again to manage and orchestrate the entire setup in one Application!

However, since the state is committed to Git, we have a few requirements:

  • Argo CD installed on the cluster (preferably fresh install)
  • Cluster context named gloo

Renaming Cluster Context

If your local clusters have a different context name, you will want to have it match the expected context name(s). In this example, we are setting the context name as gloo.

kubectl config rename-context <k3d-your_cluster_name> "${MY_CLUSTER_CONTEXT}"

Provide Gloo Mesh Enterprise License Key Variable

In case you haven’t configured it already, Gloo Mesh Enterprise requires a Trial License Key.

To avoid storing the license key in Git, we can enhance security by utilizing a licenseSecretKeyRef in the Helm values, necessitating the creation of the license key as a secret.

GLOO_MESH_LICENSE_KEY=<input_license_key_here>

The license is stored as a base64 encoded secret, set your the following variable based on your OS:

For Linux:

# Linux
    BASE64_LICENSE_KEY=$(echo -n "${GLOO_MESH_LICENSE_KEY}" | base64 -w 0)

For Mac:

# Mac OSX
    BASE64_LICENSE_KEY=$(echo -n "${GLOO_MESH_LICENSE_KEY}" | base64 | tr -d '[:space:]')

Create the license secret:

# Create namespace for Gloo Mesh
kubectl create ns gloo-mesh --context "${MY_CLUSTER_CONTEXT}"

# Apply license keys as a Kubernetes secret
kubectl apply --context "${MY_CLUSTER_CONTEXT}" -f - <<EOF
apiVersion: v1
data:
  gloo-gateway-license-key: ${BASE64_LICENSE_KEY}
  gloo-mesh-license-key: ${BASE64_LICENSE_KEY}
  gloo-network-license-key: ${BASE64_LICENSE_KEY}
  gloo-trial-license-key: ${BASE64_LICENSE_KEY}
kind: Secret
metadata:
  name: gloo-license
  namespace: gloo-mesh
type: Opaque
EOF

Now we can deploy an Argo app-of-app which will configure the entire setup that we walked through in the previous section.

kubectl apply --context "${MY_CLUSTER_CONTEXT}" -f - <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: easybutton
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/solo-io/solo-cop
    path: blogs/gloo-mesh-argocd/easybutton/app-of-app
    targetRevision: HEAD
    directory:
      recurse: true
  destination:
    server: https://kubernetes.default.svc
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m0s
EOF

You can check to see that Gloo Mesh, Istiod, and the Istio ingressgateways, and our mesh config have been deployed.

kubectl get pods -n gloo-mesh --context "${MY_CLUSTER_CONTEXT}" && \
kubectl get pods -n istio-system --context "${MY_CLUSTER_CONTEXT}" && \
kubectl get pods -n istio-gateways --context "${MY_CLUSTER_CONTEXT}" && \
kubectl get virtualgateway -n istio-gateways --context "${MY_CLUSTER_CONTEXT}" && \
kubectl get routetables -A --context "${MY_CLUSTER_CONTEXT}"

Visualize in Gloo Mesh UI with Port-Forwarding

Using port-forwarding, access Gloo Mesh Dashboard at http://localhost:8090:

kubectl port-forward -n gloo-mesh svc/gloo-mesh-ui 8090 --context "${MY_CLUSTER_CONTEXT}"

Visualize in Gloo Mesh UI With meshctl

Additionally you can use meshctl to do the port-forward for you.

meshctl dashboard

Access the Bookinfo Application

The configured route table exposes the Bookinfo application on port 80 of the Istio Ingress Gateway we deployed earlier. You should be able to access it with the following command:

echo "access the Bookinfo application at http://$(kubectl --context "${MY_CLUSTER_CONTEXT}" get svc -n istio-gateways --selector=istio=ingressgateway -o jsonpath='{.items[*].status.loadBalancer.ingress[0].*}')/productpage"

Access the Grafana UI

The configured virtual gateway and route table exposes the Grafana application on port 80 at the /grafana path of the Istio Ingress Gateway we deployed earlier. You should be able to access it with the following command:

echo "access the Grafana UI at http://$(kubectl --context "${MY_CLUSTER_CONTEXT}" get svc -n istio-gateways --selector=istio=ingressgateway -o jsonpath='{.items[*].status.loadBalancer.ingress[0].*}')/grafana"

Cleanup

To uninstall, you can delete the easybutton Argo Application.

kubectl --context "${MY_CLUSTER_CONTEXT}" delete applications -n argocd easybutton

Learn More About Gloo Mesh

Gloo Mesh delivers a more powerful service mesh solution, powered by Istio and Cilium, for your cloud-native workloads. Boost security, resiliency, and observability at your organization. Learn more about Gloo Mesh today.