Working with Istio

In this lab, you will learn how to install and work with Istio, a popular service mesh, using Helm, a package manager for Kubernetes.

Installing Istio #

Installing Istio is a multi-install process. We will first install the base Istio system.

To begin, create a dedicated directory for this lab and switch into it:

cd ~

mkdir istio && cd istio

Use the following ArgoCD Application manifest to deploy the Istio base. Copy the following manifest and apply it to your cluster:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: istio-base
  namespace: argocd
spec:
  destination:
    namespace: istio-system
    server: https://kubernetes.default.svc
  source:
    repoURL: https://istio-release.storage.googleapis.com/charts
    targetRevision: 1.18.0
    chart: base
  project: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
  ignoreDifferences:
    - group: admissionregistration.k8s.io
      kind: ValidatingWebhookConfiguration
      jqPathExpressions:
      - .webhooks[].failurePolicy

Once deployed, go to your ArgoCD UI and wait for the application to become Healthy.

Next, we will actually deploy Istio with the following ArgoCD Application manifest. Again, copy the below manifest and apply it to your cluster:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: istiod
  namespace: argocd
spec:
  destination:
    namespace: istio-system
    server: https://kubernetes.default.svc
  source:
    repoURL: https://istio-release.storage.googleapis.com/charts
    targetRevision: 1.18.0
    chart: istiod
    helm:
      values: |-
        meshConfig:
          outboundTrafficPolicy:
            mode: ALLOW_ANY        
  project: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

Once deployed, go to your ArgoCD UI and wait for the application to become Healthy.

Finally, let’s install the Istio’s Gateway, specifically, the Ingress Gateway. This Gateway behaves similar to the NGINX Ingress Gateway you deployed earlier in the labs.

In the Understanding Kubernetes Networking and Ingress lab, you provisioned the NGINX Ingress Gateway with a static IP address that was already reserved named wildcard. We will do the same here but use the other static IP address provisioned named istio-wildcard. You can get that IP by running the following gcloud command:

gcloud compute addresses list

Copy the below ArgoCD Application manifest to deploy the Istio Gateway:

Note: Ensure you update the manifest with your Istio Wildcard IP.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: istio-gateway
  namespace: argocd
spec:
  destination:
    namespace: istio-gateway
    server: https://kubernetes.default.svc
  source:
    repoURL: https://istio-release.storage.googleapis.com/charts
    targetRevision: 1.18.0
    chart: gateway
    helm:
      values: |-
        service:
          loadBalancerIP: <YOUR_ISTIO_WILDCARD_IP>        
  project: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

Once deployed, go back to your ArgoCD UI and wait for the application to become Healthy.

Also validate that your Istio Ingress Gateway Service took the appropriate IP:

kubectl get service --namespace istio-gateway

The “EXTERNAL-IP” should match your Istio Wildcard IP.

Deploying an Application to the Istio Service Mesh #

Let’s now look at deploying an application that will be part of the Istio Service Mesh and accessible via our Istio Ingress Gateway.

Create a new namespace called istio-app and label it with lab=istio-app.

Copy the following Deployment manifest and apply it to your cluster. Be sure to update the image spec to point to your Artifact Registry.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: random-facts-app
  namespace: istio-app
  labels:
    lab: istio-app
spec:
  replicas: 1
  selector:
    matchLabels:
      lab: istio-app
  template:
    metadata:
      labels:
        lab: istio-app
    spec:
      containers:
      - name: random-facts-app
        image: us-central1-docker.pkg.dev/<YOUR_PROJECT_ID>/<YOUR_REGISTRY_NAME>/random-facts-app:1.0
        ports:
        - containerPort: 5000

Once deployed, validate that the Pod is running. Notice that the application still has a single container running in the Pod. We want to onboard our application to the Istio Service Mesh. To do that, update your Namespace and add the following label istio-injection=enabled. Once updated, delete the Pods in the namespace with the following command:

kubectl delete pods --all --namespace istio-app

Check the Pods again and you will now see an output similar to below:

NAME                                READY   STATUS    RESTARTS   AGE
random-facts-app-7f4fcdb99c-z67vk   2/2     Running   0          1m

We can now see a second container has been added. You can check that the second container is an Istio container by running kubectl describe pod --namespace istio-app.

Let’s create a Service for our application. This Service is similar to other Services you have deployed in previous labs. Apply the following manifest to your cluster:

apiVersion: v1
kind: Service
metadata:
  name: random-facts-app-service
  namespace: istio-app
  labels:
    lab: istio-app
spec:
  selector:
    lab: istio-app
  ports:
  - name: http
    port: 5000
    protocol: TCP
    targetPort: 5000
  type: ClusterIP

Exposing our Application through the Istio Ingress Gateway #

In previous labs, you exposed your application with an Ingress object. With Istio, it is broken up into two parts, the Gateway and the VirtualService.

Let’s deploy the Gateway configuration first. Copy the following Gateway manifest and apply it to your cluster:

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: main-gateway
  namespace: istio-gateway
spec:
  selector:
    istio: gateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*.istio.<YOUR_STUDENT_ID>.<RANDOM_ID>.workshops.acceleratorlabs.ca"

Once applied, you can create the VirtualService. The VirtualService is the link between your Kubernetes Service and the Gateway.

Copy the following VirtualService manifest and apply it to your cluster:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: random-facts-app-vs
  namespace: istio-app
spec:
  hosts:
  - "random-facts-app.istio.<YOUR_STUDENT_ID>.<RANDOM_ID>.workshops.acceleratorlabs.ca"
  gateways:
  - istio-gateway/main-gateway
  http:
  - route:
    - destination:
        port:
          number: 5000
        host: random-facts-app-service

You can now visit your application at http://random-facts-app.istio.<YOUR_STUDENT_ID>.<RANDOM_ID>.workshops.acceleratorlabs.ca

Protecting Your Service Mesh #

Although we have on-boarded our application to the Istio Service Mesh, we still are allowing traffic from applications that are not part of the mesh to communicate with applications that are.

We can now lock down our entire mesh with an Istio PeerAuthentication policy. Copy the following manifest and apply it to your cluster:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT

Let’s now deploy a Pod outside of the mesh to test connectivity to our application inside the mesh. Run the following command to start that Pod:

kubectl run --rm -it toolbox --image=jacobmammoliti/toolbox -- sh

Once a prompt comes up, run the following cURL command against your application in the istio-app namespace with the following command:

curl random-facts-app-service.istio-app:5000

You will get back the following:

curl: (56) Recv failure: Connection reset by peer

We have effectively blocked all traffic from outside of our mesh to our applications.

To allow connectivity, let’s deploy our application in a new namespace where we will enable Istio Injection.

Create a new namespace called istio-allowed and add the following labels: lab=istio-app and istio-injection=enabled.

Once created, run the following command to create the test Pod in the new istio-allowed namespace:

kubectl run --rm -it toolbox --image=jacobmammoliti/toolbox --namespace istio-allowed -- sh

Once a prompt comes up, run the following cURL command against your application in the istio-app namespace with the following command:

# show document info only
curl -I random-facts-app-service.istio-app:5000

You will now see an output similar to below:

HTTP/1.1 200 OK
server: istio-envoy
...

If you remember this exercise from the Applying Kubernetes NetworkPolicy lab, you’ll notice a difference in the server. Previously, we got back Werkzeug/2.3.4 Python/3.12.0b1 and now because our application is in the mesh, we are now hitting the Istio Envoy proxy first.

We have successfully ensured that all of our workloads in our mesh communicate using mutual TLS.

Restricting Outbound Connections #

Previously, we restricted inbound connections to our applications. What about outbound connections? If you look at your istiod Helm values from earlier, you will notice this following snippet:

        meshConfig:
          outboundTrafficPolicy:
            mode: ALLOW_ANY

This tells Istio, specifically the envoy proxies, to allow all traffic out of the Pod. It some cases, we may want to restrict the outbound connections of our applications. To do this, update your manifest to change the mode to REGISTRY_ONLY and re-apply your ArgoCD Application CRD to your cluster. This will update Istio’s settings to only allow connections to services inside the mesh or specific external services.

After updating the Istio config, run the following command to create the test Pod in the istio-allowed namespace:

kubectl run --rm -it toolbox --image=jacobmammoliti/toolbox --namespace istio-allowed -- sh

Once a prompt comes up, run the following cURL command against google.ca:

curl https://www.google.ca -I

You will now see an output similar to below:

curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to google.ca:443

We are getting an error because google.ca is not part of our mesh or registered within our mesh. We can register an external service via an Istio ServiceEntry.

Copy the following ServiceEntry manifest and apply it to your cluster:

apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
  name: external-svc-https
spec:
  hosts:
  - www.google.ca
  location: MESH_EXTERNAL
  ports:
  - number: 443
    name: https
    protocol: TLS
  resolution: DNS

Run the following command again to create the test Pod in the istio-allowed namespace:

kubectl run --rm -it toolbox --image=jacobmammoliti/toolbox --namespace istio-allowed -- sh

Once a prompt comes up, run the following cURL command against google.ca:

curl https://www.google.ca -I

You will now see an output similar to below:

HTTP/2 200 
content-type: text/html; charset=ISO-8859-1
...

We see now we are getting a 200 response!

Troubleshooting #

If you try and access your application again at http://random-facts-app.istio.<YOUR_STUDENT_ID>.<RANDOM_ID>.workshops.acceleratorlabs.ca, you will notice that you are getting a 500 Internal Server Error. Try and figure out why this is happening and fix it.

Hint: Looking back at your source code from the Creating Your First Dockerfile lab may help you.