Deploy a NGINX Webserver to an Istio Service Mesh

Introduction

Istio is an open platform for providing a uniform way to integrate microservices, manage traffic flow across microservices, enforce policies and aggregate telemetry data. Istio’s control plane provides an abstraction layer over the underlying cluster management platform, such as Kubernetes.

Istio is composed of these components:

  • Envoy - Sidecar proxies per microservice to handle ingress/egress traffic between services in the cluster and from a service to external services. The proxies form a secure microservice mesh providing a rich set of functions like discovery, rich layer-7 routing, circuit breakers, policy enforcement and telemetry recording/reporting functions.

    Note: The service mesh is not an overlay network. It simplifies and enhances how microservices in an application talk to each other over the network provided by the underlying platform.

  • Istiod - The Istio control plane. It provides service discovery, configuration and certificate management. It consists of the following sub-components:

    • Pilot - Responsible for configuring the proxies at runtime.

    • Citadel - Responsible for certificate issuance and rotation.

    • Galley - Responsible for validating, ingesting, aggregating, transforming and distributing config within Istio.

  • Operator - The component provides user-friendly options to operate the Istio service mesh.

Implementation Considerations

This is going to be a Secure Istio Gateway implementation using Let’s Encrypt to provide production-quality certificates for the Istio IngressGateway into the mesh, as well as for the webserver providing the content.

The installation of cert-manager is not covered in this section, but it is clear from the below YAML file that this is a clusterIssuer implementation with a “DNS challenge”. More details on this approach can be seen here.

Let’s look at the TLS configuration for the docs pod using an istioctl authz check:

Davids-MacBook-Pro:xtremecloud-sso-minikube davidjbrewer$ kubectl get pod -o wide
NAME                    READY   STATUS    RESTARTS   AGE   IP            NODE                NOMINATED NODE   READINESS GATES
docs-558df446b5-rwd2s   2/2     Running   17         46h   172.17.0.16   xtremecloud-istio   <none>           <none>
Davids-MacBook-Pro:xtremecloud-sso-minikube davidjbrewer$ istioctl x authz check docs-558df446b5-rwd2s
Checked 8/24 listeners with node IP 172.17.0.16.
LISTENER[FilterChain]     CERTIFICATE          mTLS (MODE)          AuthZ (RULES)
0.0.0.0_80[0]             none                 no (none)            no (none)
0.0.0.0_80[1]             none                 no (none)            no (none)
0.0.0.0_8080[0]           none                 no (none)            no (none)
0.0.0.0_8080[1]           none                 no (none)            no (none)
0.0.0.0_9090[0]           none                 no (none)            no (none)
0.0.0.0_9090[1]           none                 no (none)            no (none)
virtualOutbound           none                 no (none)            no (none)
virtualInbound[0]         noneSDS: default     yes (none)           no (none)
virtualInbound[1]         none                 no (none)            no (none)
virtualInbound[2]         noneSDS: default     yes (none)           no (none)
virtualInbound[3]         none                 no (none)            no (none)
virtualInbound[4]         none                 no (none)            no (none)
virtualInbound[5]         noneSDS: default     yes (PERMISSIVE)     no (none)
virtualInbound[6]         none                 no (PERMISSIVE)      no (none)
0.0.0.0_15010[0]          none                 no (none)            no (none)
0.0.0.0_15010[1]          none                 no (none)            no (none)
0.0.0.0_15014[0]          none                 no (none)            no (none)
0.0.0.0_15014[1]          none                 no (none)            no (none)
0.0.0.0_20001[0]          none                 no (none)            no (none)
0.0.0.0_20001[1]          none                 no (none)            no (none)

Since the webserver is not actually serving sensitive content, the mesh is in permissive mode. So, end-to-end encryption (mTLS) is not absolutely necessary.

# docs-certificate.yaml
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: docs-eupraxialabs-com
  namespace: istio-system
spec:
  secretName: docs-eupraxialabs-com
  dnsNames:
    - docs.eupraxialabs.com
  acme:
    config:
      - dns01:
          provider: cloudflare
        domains:
          - docs.eupraxialabs.com
  issuerRef:
    name: letsencrypt-prod
    # We can reference ClusterIssuers by changing the kind here.
    # The default value is Issuer (i.e. a locally namespaced Issuer)
    kind: ClusterIssuer

We’re going to use an “all-in-one” manifest for Kubernetes and Istio resources. It’s applied like this:

Davids-MacBook-Pro:xtremecloud-sso-minikube davidjbrewer$ kubectl apply -f nginx-docs-all.yaml
# nginx-docs-all.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: docs
  labels:
    istio-injection: enabled
---
apiVersion: v1
kind: Service
metadata:
  name: docs-service
  namespace: docs
  labels:
    app: docs
spec:
  type: ClusterIP
  ports:
    - port: 8080
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app: docs
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: docs
  namespace: docs
spec:
  selector:
    matchLabels:
      app: docs
  replicas: 1
  template:
    metadata:
      labels:
        app: docs
      annotations:
        sidecar.istio.io/rewriteAppHTTPProbers: "true"
    spec:
      containers:
      - name: nginx-docs
        image: quay.io/eupraxialabs/docs-jekyll:1.1
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
          name: http
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: docs-ingress
  namespace: docs
spec:
  rules:
  - host: docs.eupraxialabs.com
    http:
      paths:
      - path: /
        backend:
          serviceName: docs-service
          servicePort: http
---
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: docs-gateway
  namespace: docs
spec:
  selector:
    istio: ingressgateway # use istio default ingress gateway
  servers:
  - port:
      number: 443
      name: https-docs
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: docs-eupraxialabs-com
    hosts:
    - docs.eupraxialabs.com
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: docs-virtualservice
  namespace: docs
spec:
  hosts:
  - docs.eupraxialabs.com
  gateways:
  - docs-gateway
  http:
  - match:
    - uri:
        exact: /
    - uri:
        prefix: /docs
    - uri:
        regex: ^.*\.(ico|png|jpg|css|js|img|jpeg|map)$
    route:
    - destination:
        host: docs-service
        port:
          number: 8080

Let’s take at the certificate used as a credential in the Istio Gateway:

NGINX Content Server within an Istio Service Mesh - click image to enlarge