CONTENTS

You've successfully subscribed to The DevOps Bootcamp 🚀
Great! Next, complete checkout for full access to The DevOps Bootcamp 🚀
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.
Success! Your billing info is updated.
Billing info update failed.

Lab 3/4 🔬Istio Ambiant Mesh

Yiadh TLIJANI
Yiadh TLIJANI


L7 authorization policies

L4 policies are useful but may not be sufficient for your needs.

For example, you’ll be able to send any request to the web-api service from the sleep pod while you may only want to allow requests with the GET method.

In order to have any L7 policy enforced for the web-api service, you’ll need to deploy a waypoint proxy for the service account used by the web-api pod.

waypoint proxy

The Kubernetes gateway API is used to deploy a waypoint proxy for a given service account or the namespace.

The example below demonstrates how to ask Istio to deploy a waypoint proxy for the web-api service account.

kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: Gateway
metadata:
  name: web-api
  namespace: default
  annotations:
    istio.io/service-account: web-api
spec:
  gatewayClassName: istio-mesh
EOF

Let's check that the waypoint proxy has been deployed correctly:

kubectl get pods -l ambient-type=waypoint

Here is the expected output:

NAME                                      READY   STATUS    RESTARTS   AGE
web-api-waypoint-proxy-7bbfdc784c-5vg9n   1/1     Running   0          69s

Now, let's allow the sleep pod to only send GET requests to the web-api service:

kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: "web-api-rbac"
  namespace: default
spec:
  selector:
    matchLabels:
      app: web-api
  action: ALLOW
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/default/sa/sleep","cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"]
    to:
    - operation:
        methods: ["GET"]
EOF


You should still be able to access the web-api application from the sleep pod.

kubectl exec deploy/sleep -- curl http://web-api:8080/

But if you try to send a DELETE request, it will be denied:

kubectl exec deploy/sleep -- curl http://web-api:8080/ -X DELETE

You should get the following output:

RBAC: access denied

Note that a deny at L7 is also providing a better user experience (with an explicit error message and a 403 response code).

L7 observability


With waypoint proxy deployed for the web-api service, you automatically get L7 metrics for this service.

For example, you can view the 403 response code from the web-api service’s waypoint proxy’s /stats/prometheus endpoint:

kubectl exec deploy/web-api-waypoint-proxy -- curl http://localhost:15020/stats/prometheus | grep istio_requests_total

You should get something similar to this:

istio_requests_total{response_code="200",reporter="destination",source_workload="sleep",source_workload_namespace="default",source_principal="spiffe://cluster.local/ns/default/sa/sleep",source_app="unknown",source_version="unknown",source_cluster="Kubernetes",destination_workload="web-api",destination_workload_namespace="default",destination_principal="spiffe://cluster.local/ns/default/sa/web-api",destination_app="unknown",destination_version="unknown",destination_service="web-api.default.svc.cluster.local",destination_service_name="web-api",destination_service_namespace="default",destination_cluster="Kubernetes",request_protocol="http",response_flags="-",grpc_response_status="",connection_security_policy="mutual_tls",source_canonical_service="sleep",destination_canonical_service="web-api",source_canonical_revision="latest",destination_canonical_revision="v1"} 1
istio_requests_total{response_code="403",reporter="destination",source_workload="sleep",source_workload_namespace="default",source_principal="spiffe://cluster.local/ns/default/sa/sleep",source_app="unknown",source_version="unknown",source_cluster="Kubernetes",destination_workload="web-api",destination_workload_namespace="default",destination_principal="spiffe://cluster.local/ns/default/sa/web-api",destination_app="unknown",destination_version="unknown",destination_service="web-api.default.svc.cluster.local",destination_service_name="web-api",destination_service_namespace="default",destination_cluster="Kubernetes",request_protocol="http",response_flags="-",grpc_response_status="",connection_security_policy="mutual_tls",source_canonical_service="sleep",destination_canonical_service="web-api",source_canonical_revision="latest",destination_canonical_revision="v1"} 1
istio_requests_total{response_code="200",reporter="source",source_workload="web-api-waypoint-proxy",source_workload_namespace="default",source_principal="unknown",source_app="unknown",source_version="unknown",source_cluster="Kubernetes",destination_workload="recommendation",destination_workload_namespace="default",destination_principal="unknown",destination_app="unknown",destination_version="unknown",destination_service="recommendation.default.svc.cluster.local",destination_service_name="recommendation",destination_service_namespace="default",destination_cluster="Kubernetes",request_protocol="http",response_flags="-",grpc_response_status="",connection_security_policy="unknown",source_canonical_service="web-api-waypoint-proxy",destination_canonical_service="recommendation",source_canonical_revision="latest",destination_canonical_revision="v1"} 1
istio_requests_total{response_code="403",reporter="destination",source_workload="sleep",source_workload_namespace="default",source_principal="spiffe://cluster.local/ns/default/sa/sleep",source_app="unknown",source_version="unknown",source_cluster="Kubernetes",destination_workload="web-api",destination_workload_namespace="default",destination_principal="spiffe://cluster.local/ns/default/sa/web-api",destination_app="unknown",destination_version="unknown",destination_service="web-api.default.svc.cluster.local",destination_service_name="web-api",destination_service_namespace="default",destination_cluster="Kubernetes",request_protocol="http",response_flags="-",grpc_response_status="",connection_security_policy="mutual_tls",source_canonical_service="sleep",destination_canonical_service="web-api",source_canonical_revision="latest",destination_canonical_revision="v1"} 2


Fault injection

It is difficult to get service timeouts and circuit-breaker configured properly in a distributed microservice application.

Istio makes it easier to get these settings correct by enabling you to inject faults into your service requests without the need to modify your code.

Inject some fault (such as 5 second delay) to the web-api service to test resiliency:

kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: web-api-gw-vs
spec:
  hosts:
  - "istioexplained.io"
  gateways:
  - web-api-gateway
  http:
  - fault:
      delay:
        fixedDelay: 5s
        percentage:
          value: 100
    match:
    - headers:
        user:
          exact: Amy
    route:
    - destination:
        host: web-api.default.svc.cluster.local
        port:
          number: 8080
  - route:
    - destination:
        host: web-api.default.svc.cluster.local
        port:
          number: 8080
EOF

If you try to access the web-api service through the ingress gateway, with the header user: Amy, you'll notice a 5 seconds delay:

curl -H "Host: istioexplained.io" -H "user: Amy" "http://${GATEWAY_IP}/"

Traffic shifting

A canary test is often performed to ensure the new version of a service not only functions properly but also doesn’t cause a degradation in performance or reliability.

For example, if there is a new version (v2) of the purchase-history service, you may want to gradually roll out the v2 version of the service by sending only 10% of the traffic to it.

Deploy the v2 version of purchase-history service:

kubectl apply -f data/steps/traffic-shifting/purchase-history-v2.yaml

In order to apply traffic shift on the purchase-history service, you’ll need to deploy a waypoint proxy for the service, similar to deploying a waypoint proxy via a Kubernetes gateway resource for the web-api service earlier.

kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: Gateway
metadata:
  name: purchase-history
  annotations:
    istio.io/service-account: purchase-history
spec:
  gatewayClassName: istio-mesh
EOF

Apply a VirtualService resource that configures the traffic shifting:

kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: purchase-history-vs
spec:
  hosts:
  - purchase-history.default.svc.cluster.local
  http:
  - route:
    - destination:
        host: purchase-history.default.svc.cluster.local
        subset: v1
        port:
          number: 8080
      weight: 90
    - destination:
        host: purchase-history.default.svc.cluster.local
        subset: v2
        port:
          number: 8080
      weight: 10
EOF

The subset referred in the above VirtualService must be defined in a DestinationRule resource so that Istio knows which subsets map to what labels for the purchase-history host.

kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: purchase-history-dr
spec:
  host: purchase-history.default.svc.cluster.local
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
EOF

You can confirm that around 10% of the traffic is sent to the v2 version of the purchase-history service by running the following command:

kubectl exec deploy/sleep -- sh -c 'for i in $(seq 1 100); do curl -s http://web-api:8080/; done | grep -c purchase-history-v2'

This command has sent a total of 100 requests, so you should get around 10 requests sent to v2.

root@virtualmachine:~# kubectl exec deploy/sleep -- sh -c 'for i in $(seq 1 100); do curl -s http://web-api:8080/; done | grep -c purchase-history-v2'
9
root@virtualmachine:~# kubectl exec deploy/sleep -- sh -c 'for i in $(seq 1 100); do curl -s http://web-api:8080/; done | grep -c purchase-history-v2'
6
root@virtualmachine:~# kubectl exec deploy/sleep -- sh -c 'for i in $(seq 1 100); do curl -s http://web-api:8080/; done | grep -c purchase-history-v2'
9
root@virtualmachine:~# kubectl exec deploy/sleep -- sh -c 'for i in $(seq 1 100); do curl -s http://web-api:8080/; done | grep -c purchase-history-v2'
15
root@virtualmachine:~# kubectl exec deploy/sleep -- sh -c 'for i in $(seq 1 100); do curl -s http://web-api:8080/; done | grep -c purchase-history-v2'
6

Yiadh TLIJANI

Founder of The DevOps Bootcamp | Senior DevSecOps Engineer | Certified Kubernetes Developer, Administrator and Security Specialist