Load balancing, traffic management, authentication and authorization, service discovery — these are just some of the interactions taking place between microservices. Collectively called a “service mesh”, these interconnections can become an operations headache when handling large‑scale, complex applications.  

Istio seeks to reduce this complexity by providing engineers with an easy way to manage a service mesh. It does this by implementing a sidecar approach, running alongside each service (in Kubernetes, within each pod) and intercepting and managing network communication between the services. Istio can be used to more easily configure and manage load balancing, routing, security and the other types of interactions making up the service mesh.

Istio also generates a lot of telemetry data that can be used to monitor a service mesh, including logs. Envoy, the proxy Istio deploys alongside services, produces access logs. Istio’s different components — Envoy, Mixer, Pilot, Citadel and Galley — also produce logs that can be used to monitor how Istio is performing. 

This is a lot of data and that’s where the ELK Stack can come in handy for collecting and aggregating the logs Istio generates as well as providing analysis tools. This article will explain how to create a data pipeline from Istio to either a self-hosted ELK Stack or Logz.io. I used a vanilla Kubernetes cluster deployed on GKE with 4 n1-standard-1 nodes. 

Step 1: Installing Istio

To start the process of setting up Istio and the subsequent logging components, we’ll first need to grant cluster-admin permissions to the current user:

kubectl create clusterrolebinding cluster-admin-binding 
--clusterrole=cluster-admin --user=$(gcloud config get-value 
core/account)

Next, let’s download the Istio installation file. On Linux, the following command will download and extract the latest release automatically:

curl -L https://git.io/getLatestIstio | ISTIO_VERSION=1.2.2 sh -

Move to the Istio package directory:

cd istio-1.2.2

We’ll now add the istioctl client to our PATH environment variable:

export PATH=$PWD/bin:$PATH

Our next step is to install the Istio Custom Resource Definitions (CRDs). It might take a minute or two for the CRDs to be committed in the Kubernetes API-server:

for i in install/kubernetes/helm/istio-init/files/crd*yaml; do kubectl 
apply -f $i; done

We now have to decide what variant of the demo profile we want to install. For the sake of this tutorial, we will opt for the permissive mutual TLS profile:

kubectl apply -f install/kubernetes/istio-demo.yaml

We can now verify all the Kubernetes services are deployed and that they all have an appropriate CLUSTER-IP.

Start with:

kubectl get svc -n istio-system

NAME                     TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)                              AGE
grafana                  ClusterIP      10.12.1.138    <none>          3000/TCP                              118s
istio-citadel            ClusterIP      10.12.15.34    <none>          8060/TCP,15014/TCP                              115sistio-egressgateway      ClusterIP      10.12.8.187    <none>          80/TCP,443/TCP,15443/TCP                              118sistio-galley             ClusterIP      10.12.6.40     <none>          443/TCP,15014/TCP,9901/TCP                              119sistio-ingressgateway     LoadBalancer   10.12.5.185    34.67.187.168  15020:31309/TCP,80:31380/TCP,443:31390/TCP,31400:31400/TCP,15029:31423/TCP,15030:30698/TCP,15031:31511/TCP,15032:30043/TCP,15443:32571/TCP   118s
istio-pilot              ClusterIP      10.12.10.162   <none>          15010/TCP,15011/TCP,8080/TCP,15014/TCP                              116s
istio-policy             ClusterIP      10.12.12.39    <none>          9091/TCP,15004/TCP,15014/TCP                              117s
istio-sidecar-injector   ClusterIP      10.12.5.126    <none>          443/TCP                              115sistio-telemetry          ClusterIP      10.12.11.68    <none>          9091/TCP,15004/TCP,15014/TCP,42422/TCP                              116s
jaeger-agent             ClusterIP      None           <none>          5775/UDP,6831/UDP,6832/UDP                              108s
jaeger-collector         ClusterIP      10.12.13.219   <none>          14267/TCP,14268/TCP                              108s
jaeger-query             ClusterIP      10.12.9.45     <none>          16686/TCP                              108s
kiali                    ClusterIP      10.12.6.71     <none>          20001/TCP                              117s
prometheus               ClusterIP      10.12.7.232    <none>          9090/TCP                              116s
tracing                  ClusterIP      10.12.10.180   <none>          80/TCP                              107s
zipkin                   ClusterIP      10.12.1.164    <none>          9411/TCP                              107s

And then: 

kubectl get pods -n istio-system

NAME                                      READY   STATUS      RESTARTS   AGE
grafana-7869478fc5-8dbs7                  1/1     Running     0          2m33s
istio-citadel-d6d7fff64-8mrpv             1/1     Running     0          2m30s
istio-cleanup-secrets-1.2.2-j2k8q         0/1     Completed   0          2m46s
istio-egressgateway-d5cc88b7b-nxnfb       1/1     Running     0          2m33s
istio-galley-545fdc5749-mrd4v             1/1     Running     0          2m34s
istio-grafana-post-install-1.2.2-tdvp4    0/1     Completed   0          2m48s
istio-ingressgateway-6d9db74868-wtgkc     1/1     Running     0          2m33s
istio-pilot-69f969cd6f-f9sf4              2/2     Running     0          2m31s
istio-policy-68c868b65c-g5cw8             2/2     Running     2          2m32s
istio-security-post-install-1.2.2-xd5lr   0/1     Completed   0          2m44s
istio-sidecar-injector-68bf9645b-s5pkq    1/1     Running     0          2m30s
istio-telemetry-9c9688fb-fgslx            2/2     Running     2          2m31s
istio-tracing-79db5954f-vwfrm             1/1     Running     0          2m30s
kiali-7b5b867f8-r2lv7                     1/1     Running     0          2m32s
prometheus-5b48f5d49-96jrg                1/1     Running     0          2m31s

Looks like we’re all set. We are now ready for our next step — deploying a sample application to simulate Istio in action.

Step 2: Installing a sample app

Istio conveniently provides users with examples within the installation package. We’ll be using the Bookinfo application which is comprised of four separate microservices for showing off different Istio features — perfect for a logging demo!   

No changes are needed to the application itself. We are just required to make some configurations and run the services in an Istio-enabled environment. 

The default Istio installation uses automatic sidecar injection, so first, we’ll label the namespace that will host the application:

kubectl label namespace default istio-injection=enabled

Next, we’ll deploy the application with:

kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml

All the four services are deployed, and we will confirm this with:

kubectl get services

NAME          TYPE CLUSTER-IP   EXTERNAL-IP PORT(S) AGE

details       ClusterIP 10.0.2.45    <none> 9080/TCP 71s

kubernetes    ClusterIP 10.0.0.1     <none> 443/TCP 12m

productpage   ClusterIP 10.0.7.146   <none> 9080/TCP 69s

ratings       ClusterIP 10.0.3.105   <none> 9080/TCP 71s

reviews       ClusterIP 10.0.2.168   <none> 9080/TCP 70s

And:

kubectl get pods
NAME                              READY   STATUS    RESTARTS   AGE
details-v1-59489d6fb6-xspmq       2/2     Running   0          2m8s
productpage-v1-689ff955c6-94v4k   2/2     Running   0          2m5s
ratings-v1-85f65447f4-gbd47       2/2     Running   0          2m7s
reviews-v1-657b76fc99-gw99m       2/2     Running   0          2m7s
reviews-v2-5cfcfb547f-7jvhq       2/2     Running   0          2m6s
reviews-v3-75b4759787-kcrrp       2/2     Running   0          2m6s

One last step before we can access the application is to make sure it’s accessible from outside our Kubernetes cluster. This is done using an Istio Gateway.

First, we’ll define the ingress gateway for the application:

kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml

Let’s confirm the gateway was created with:

kubectl get gateway

NAME               AGE
bookinfo-gateway   16s

Next, we need to set the INGRESS_HOST and INGRESS_PORT variables for accessing the gateway. To do this, we’re going to verify that our cluster supports an external load balancer:

kubectl get svc istio-ingressgateway -n istio-system

NAME                   TYPE           CLUSTER-IP    EXTERNAL-IP    PORT(S)                                                                                                                                     AGEistio-ingressgateway   LoadBalancer   10.0.15.240   35.239.99.74   15020:31341/TCP,80:31380/TCP,443:31390/TCP,31400:31400/TCP,15029:31578/TCP,15030:32023/TCP,15031:31944/TCP,15032:32131/TCP,15443:3

As we can see, the EXTERNAL_IP value is set, meaning our environment has an external load balancer to use for the ingress gateway. 

To set the ingress IP and ports, we’ll use:

export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].port}')

export SECURE_INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="https")].port}')

Finally, let’s set GATEWAY_URL:

export GATEWAY_URL=$INGRESS_HOST:$INGRESS_PORT

To confirm that the Bookinfo application is accessible from outside the cluster, we can run the following command:

curl -s http://${GATEWAY_URL}/productpage | grep -o "<title>.*</title>"
<title>Simple Bookstore App</title>

You can also point your browser to http://<externalIP>/productpage to view the Bookinfo web page:

comedy of errors

Step 3: Shipping Istio logs

Great! We’ve installed Istio and deployed a sample application that makes use of Istio features for controlling and routing requests to the application’s services. We can now move on to the next step which is monitoring Istio’s operation using the ELK (or EFK) Stack.

Using the EFK Stack

If you want to ship Istion logs into your own EFK Stack (Elasticsearch, fluentd and Kibana), I recommend using the deployment stack documented by the Istio team. Of course, it contains fluentd and not Logstash for aggregating and forwarding the logs. 

Note, the components here are the open-source versions of Elasticsearch and Kibana 6.1. The same logging namespace is used for all the specifications. 

First, create a new deployment YAML:

sudo vim efk-stack.yaml

Then, paste the following deployment specifications:

apiVersion: v1
kind: Namespace
metadata:
  name: logging
---
# Elasticsearch Service
apiVersion: v1
kind: Service
metadata:
  name: elasticsearch
  namespace: logging
  labels:
    app: elasticsearch
spec:
  ports:
  - port: 9200
    protocol: TCP
    targetPort: db
  selector:
    app: elasticsearch
---
# Elasticsearch Deployment
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: elasticsearch
  namespace: logging
  labels:
    app: elasticsearch
spec:
  template:
    metadata:
      labels:
        app: elasticsearch
      annotations:
        sidecar.istio.io/inject: "false"
    spec:
      containers:
      - image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.1.1
        name: elasticsearch
        resources:
          # need more cpu upon initialization, therefore burstable class
          limits:
            cpu: 1000m
          requests:
            cpu: 100m
        env:
          - name: discovery.type
            value: single-node
        ports:
        - containerPort: 9200
          name: db
          protocol: TCP
        - containerPort: 9300
          name: transport
          protocol: TCP
        volumeMounts:
        - name: elasticsearch
          mountPath: /data
      volumes:
      - name: elasticsearch
        emptyDir: {}
---
# Fluentd Service
apiVersion: v1
kind: Service
metadata:
  name: fluentd-es
  namespace: logging
  labels:
    app: fluentd-es
spec:
  ports:
  - name: fluentd-tcp
    port: 24224
    protocol: TCP
    targetPort: 24224
  - name: fluentd-udp
    port: 24224
    protocol: UDP
    targetPort: 24224
  selector:
    app: fluentd-es
---
# Fluentd Deployment
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: fluentd-es
  namespace: logging
  labels:
    app: fluentd-es
spec:
  template:
    metadata:
      labels:
        app: fluentd-es
      annotations:
        sidecar.istio.io/inject: "false"
    spec:
      containers:
      - name: fluentd-es
        image: gcr.io/google-containers/fluentd-elasticsearch:v2.0.1
        env:
        - name: FLUENTD_ARGS
          value: --no-supervisor -q
        resources:
          limits:
            memory: 500Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: config-volume
          mountPath: /etc/fluent/config.d
      terminationGracePeriodSeconds: 30
      volumes:
      - name: config-volume
        configMap:
          name: fluentd-es-config
---
# Fluentd ConfigMap, contains config files.
kind: ConfigMap
apiVersion: v1
data:
  forward.input.conf: |-
    # Takes the messages sent over TCP
    <source>
      type forward
    </source>
  output.conf: |-
    <match **>
       type elasticsearch
       log_level info
       include_tag_key true
       host elasticsearch
       port 9200
       logstash_format true
       # Set the chunk limits.
       buffer_chunk_limit 2M
       buffer_queue_limit 8
       flush_interval 5s
       # Never wait longer than 5 minutes between retries.
       max_retry_wait 30
       # Disable the limit on the number of retries (retry forever).
       disable_retry_limit
       # Use multiple threads for processing.
       num_threads 2
    </match>
metadata:
  name: fluentd-es-config
  namespace: logging
---
# Kibana Service
apiVersion: v1
kind: Service
metadata:
  name: kibana
  namespace: logging
  labels:
    app: kibana
spec:
  ports:
  - port: 5601
    protocol: TCP
    targetPort: ui
  selector:
    app: kibana
---
# Kibana Deployment
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: kibana
  namespace: logging
  labels:
    app: kibana
spec:
  template:
    metadata:
      labels:
        app: kibana
      annotations:
        sidecar.istio.io/inject: "false"
    spec:
      containers:
      - name: kibana
        image: docker.elastic.co/kibana/kibana-oss:6.1.1
        resources:
          # need more cpu upon initialization, therefore burstable class
          limits:
            cpu: 1000m
          requests:
            cpu: 100m
        env:
          - name: ELASTICSEARCH_URL
            value: http://elasticsearch:9200
        ports:
        - containerPort: 5601
          name: ui
          protocol: TCP
---

Then, create the resources with:

kubectl apply -f logging-stack.yaml

namespace "logging" created
service "elasticsearch" created
deployment "elasticsearch" created
service "fluentd-es" created
deployment "fluentd-es" created
configmap "fluentd-es-config" created
service "kibana" created
deployment "kibana" created

To access the data in Kibana, you’ll need to set up port forwarding. Run the command below and leave it running:

kubectl -n logging port-forward $(kubectl -n logging get pod -l 
app=kibana -o jsonpath='{.items[0].metadata.name}') 5601:5601 &

Using Logz.io

Istio logging with Logz.io is done using a dedicated daemonset for shipping Kubernetes logs to Logz.io. Every node in your Kubernetes cluster will deploy a fluentd pod that is configured to ship container logs in the pods on that node to Logz.io. Including our Istio pods.  

First, clone the Logz.io Kubernetes repo:

git clone https://github.com/logzio/logzio-k8s/
cd /logz.io/logzio-k8s/ 

Open the daemonset configuration file:

sudo vim logzio-daemonset-rbac.yaml

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluentd
  namespace: kube-system

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: fluentd
  namespace: kube-system
rules:
- apiGroups:
  - ""
  resources:
  - pods
  - namespaces
  verbs:
  - get
  - list
  - watch

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: fluentd
roleRef:
  kind: ClusterRole
  name: fluentd
  apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
  name: fluentd
  namespace: kube-system
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: fluentd-logzio
  namespace: kube-system
  labels:
    k8s-app: fluentd-logzio
    version: v1
    kubernetes.io/cluster-service: "true"
spec:
  template:
    metadata:
      labels:
        k8s-app: fluentd-logzio
        version: v1
        kubernetes.io/cluster-service: "true"
    spec:
      serviceAccount: fluentd
      serviceAccountName: fluentd
      tolerations:
      - key: node-role.kubernetes.io/master
        effect: NoSchedule
      containers:
      - name: fluentd
        image: logzio/logzio-k8s:latest
        env:
        - name:  LOGZIO_TOKEN
          value: "yourToken"
        - name:  LOGZIO_URL
          value: "listenerURL"
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers

Enter the values for the following two environment variables in the file:

  • LOGZIO_TOKEN – your Logz.io account token. Can be retrieved from within the Logz.io UI, on the Settings page.
  • LOGZIO_URL – the Logz.io listener URL. If the account is in the EU region insert https://listener-eu.logz.io:8071. Otherwise, use https://listener.logz.io:8071. You can tell your account’s region by checking your login URL – app.logz.io means you are in the US. app-eu.logz.io means you are in the EU.

Save the file.

Create the resource with:

kubectl create -f logzio-daemonset-rbac.yaml

serviceaccount "fluentd" created
clusterrole.rbac.authorization.k8s.io "fluentd" created
clusterrolebinding.rbac.authorization.k8s.io "fluentd" created
daemonset.extensions "fluentd-logzio" created

In Logz.io, you will see container logs displayed on the Discover page in Kibana after a minute or two:

containers

Step 4: Analyzing Istio logs in Kibana

Congrats! You’ve built a logging pipeline for monitoring your Kubernetes cluster and your Istio service mesh! What now?

Kibana is a great tool for diving into logs and offers users a wide variety of search methods when troubleshooting. Recent improvement to the search experience in Kibana, including new filtering and auto-completion, make querying your logs an easy and intuitive experience. 

Starting with the basics, you can enter a free text search for a specific URL called by a request. Say you want to look for Istio Envoy logs:

"envoy"
envoy

Or, you can use a field-level search to look for Istio Mixer telemetry logs:

kubernetes.container_name : "mixer" and 
kubernetes.labels.istio-mixer-type : "telemetry" 
mixer

As you start analyzing your Istio logs, you’ll grow more comfortable with performing different types of searches and as I mentioned above, Kibana makes this an extremely simple experience. 

What about visualizing Istio logs? 

Well, Kibana is renowned for its visualization capabilities with almost 20 different visualization types which you can choose from. Below are some examples.

No. of Istio logs

Let’s start with the basics – a simple metric visualization showing the number of incoming Istio logs coming in from the different Istio components (i.e. Envoy, Mixer, Citadel, etc.). Since fluentd is shipping logs across the Kubernetes cluster, I’m using a search to narrow down on Istio logs only:

kubernetes.namespace_name : "istio-system"
number

No. of Istio logs over time

What about a trend over time of the incoming Istio logs? As with any infrastructure layer, this could be a good indicator of abnormal behavior. 

To visualize this, we can use the same search in a line chart visualization. We will also add a split series into the mix, to breakdown the logs per Istio component using the kubernetes.labels.istio field:

line graph

Istio logs breakdown

A good old pie chart can provide us with a general idea of what Istio components is creating the more noise:

pie

Once you have your Iatio visualizations lined up, you can add them all up into one beautiful Kibana dashboard:

Summing it up

Microservice architectures solve some problems but introduce others. Yes, developing, deploying and scaling applications have become simpler. But the infrastructure layer handling the communication between these applications, aka the service mesh, can become very complicated. Istio aims to reduce this complexity and the ELK Stack can be used to compliment Istio’s monitoring features by providing a centralized data backend together with rich analysis functionality.

Whether or not you need to implement a service mesh is an entirely different question. For some organizations, service discovery and network management features of existing API gateways and Kubernetes might be enough. The technology itself is still relatively immature, so there is some risk involved. Still, 2019 is developing to be the year of the service mesh and Istio itself is seeing growing adoption. As the technology matures, and costs and risks gradually go down, the tipping point for adopting service mesh is fast approaching.

Get started for free

Completely free for 14 days, no strings attached.