Monitoring EKS cluster with Prometheus and Grafana
Tags: eks, kubernetes, prometheus, grafana
Published: 2022-09-06 09:03:04

In this post, I will go through the minimum steps to configure Prometheus and Grafana in the existing EKS cluster, which is most likely used for monitoring and alerts.

## Configuring Node Groups

### Creating new node group

Assuming that there are no pre-existing node groups, let's create one.

First, let's attach IAM role to Kubernetes service account using `eksctl`.

```sh
eksctl create iamserviceaccount \
  --name aws-node \
  --namespace kube-system \
  --cluster $EKS_CLUSTER_NAME \
  --role-name "AmazonEKSVPCCNIRole" \
  --attach-policy-arn arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy \
  --override-existing-serviceaccounts \
  --approve
```

Now let's head over to `AWS Management Console -> EKS -> Your cluster -> Compute -> Add node group`.

We HAVE to use EC2 for Prometheus and Grafana, since they will both need volumes mounted to them. 

When creating node group, we have to attach an IAM role to EC2 worker nodes. For easy demonstration, I created a new IAM role and attached policies as below.


![New IAM role](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p57qp24v01oy3t6kf3b1.png)

When filling all the information required to create a node group, just make sure to apply values as below.
  - EC2 instance type: m5.large
  - Subnets: Your private subnets within the VPC that EKS cluster exists.

You can leave all the other values as it is.

To confirm that your EC2 worker nodes are properly running, run the command below. The result should say that 2 pods are running.

```sh
$ kubectl delete pods -n kube-system -l k8s-app=aws-node
NAME             READY   STATUS    RESTARTS   AGE
aws-node-6szct   1/1     Running   0          4s
aws-node-nz5cd   1/1     Running   0          8s
```

### aws-ebs-csi-driver

Prometheus and Grafana needs persistent storage attached to them, which is also called PV(Persistent Volume) in terms of Kubernetes. 

For stateful workloads to use Amazon EBS volumes as PV, we have to add aws-ebs-csi-driver into the cluster. 

### Associating IAM role to Service account

Before we add aws-ebs-csi-driver, we first need to create an IAM role, and associate it with Kubernetes service account.

Let's use an example policy file, which you can download using the command below.

```sh
curl -sSL -o ebs-csi-policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-ebs-csi-driver/master/docs/example-iam-policy.json
```

Now let's create a new IAM policy with that file.

```sh
export EBS_CSI_POLICY_NAME=AmazonEBSCSIPolicy
aws iam create-policy \
--region $AWS_REGION \
--policy-name $EBS_CSI_POLICY_NAME \
--policy-document file://ebs-csi-policy.json
export EBS_CSI_POLICY_ARN=$(aws --region ap-northeast-2 iam list-policies --query 'Policies[?PolicyName==`'$EBS_CSI_POLICY_NAME'`].Arn' --output text)
echo $EBS_CSI_POLICY_ARN
# arn:aws:iam::12345678:policy/AmazonEBSCSIPolicy
```

After that, let's attach the new policy to Kubernetes service account.

```sh
eksctl create iamserviceaccount \
  --cluster $EKS_CLUSTER_NAME \
  --name ebs-csi-controller-irsa \
  --namespace kube-system \
  --attach-policy-arn $EBS_CSI_POLICY_ARN \
  --override-existing-serviceaccounts --approve
```

And now, we're ready to install aws-ebs-csi-driver!

### Installing aws-ebs-csi-driver

Assuming that `helm` is installed, let's add new helm repository as below.

```sh
helm repo add aws-ebs-csi-driver https://kubernetes-sigs.github.io/aws-ebs-csi-driver
helm repo update
```

After adding new helm repository, let's install aws-ebs-csi-driver with below command using helm.

```sh
helm upgrade --install aws-ebs-csi-driver \
  --version=1.2.4 \
  --namespace kube-system \
  --set serviceAccount.controller.create=false \
  --set serviceAccount.snapshot.create=false \
  --set enableVolumeScheduling=true \
  --set enableVolumeResizing=true \
  --set enableVolumeSnapshot=true \
  --set serviceAccount.snapshot.name=ebs-csi-controller-irsa \
  --set serviceAccount.controller.name=ebs-csi-controller-irsa \
  aws-ebs-csi-driver/aws-ebs-csi-driver
```

---

## Configuring Prometheus

First, let's create a new Kubernetes namespace where all Prometheus-related resources will reside in.

```sh
kubectl create ns prometheus
```

We will install Prometheus using helm, so let's add the Prometheus repository to helm and install it running the command below.

```sh
# Add helm repositories for Prometheus
helm repo add kube-state-metrics https://kubernetes.github.io/kube-state-metrics
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts

# Install Prometheus
helm upgrade -i prometheus prometheus-community/prometheus \
  --namespace prometheus \
  --set alertmanager.persistentVolume.storageClass="gp2",server.persistentVolume.storageClass="gp2"
```

Few minutes after installing Prometheus, we can see all the resources created as below.


![Prometheus resources](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rb9dzg69egrt9a67t2ss.png)

To test if they are working, let's use kubectl.

```sh
kubectl port-forward -n prometheus deploy/prometheus-server 8081:9090 &
```

Now let's launch web browser, and go to `localhost:8081`.

The below web page should be seen if Prometheus has been successfully installed.


![Prometheus UI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mrpt9nvuvm23ro9ff0zt.png)

---

## Configuring Grafana

As the same as Prometheus, we will create a dedicated Kubernetes namespace for Grafana, and install it using helm.

First let's create a new namespace.

```sh
kubectl create ns grafana
```

We also need a manifest file, which will be used to configure Grafana. Below is an example of the file, called `grafana.yaml`.

```yaml
# grafana.yaml
datasources:
  datasources.yaml:
    apiVersion: 1
    datasources:
      - name: Prometheus
        type: prometheus
        url: http://prometheus-server.prometheus.svc.cluster.local
        access: proxy
        isDefault: true
```

Now let's install Grafana using helm.

```sh
# Add helm repository for Grafana
helm repo add grafana https://grafana.github.io/helm-charts

# Install Grafana
helm install grafana grafana/grafana \
    --namespace grafana \
    --set persistence.storageClass="gp2" \
    --set persistence.enabled=true \
    --set adminPassword='YOUR_PASSWORD' \
    --values grafana.yaml \
    --set service.type=NodePort
```

Before exposing Grafana to the world, let's see how the Kubernetes service running Grafana is defined.

![Grafana service](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m87qa88vsp801ijpsial.png)

We can see that the target port is 3000, which is the port used by pods running Grafana.

So we have to attach a new Security group to EC2 worker nodes, allowing inbound requests for port 3000.

We can do this by creating a new Security group, and attaching it to EC2 worker nodes in the EC2 console of AWS Management Console.

After applying new security group to EC2 worker nodes, let's define a new Kubernetes ingress, which will provision an ALB.

> To make Kubernetes ingress to create an ALB, we have to install [aws-load-balancer-controller](https://docs.aws.amazon.com/eks/latest/userguide/aws-load-balancer-controller.html) first.

Let's see how ingress looks like.

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: grafana-ingress
  namespace: grafana
  annotations:
    alb.ingress.kubernetes.io/load-balancer-name: grafana-alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/subnets: ${PUBLIC_SUBNET_IDs}
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS": 443}]'
    alb.ingress.kubernetes.io/certificate-arn: ${ACM_CERT_ARN}
    alb.ingress.kubernetes.io/security-groups: ${ALB_SECURITY_GROUP_ID}
    alb.ingress.kubernetes.io/healthcheck-port: "3000"
    alb.ingress.kubernetes.io/healthcheck-path: /api/health
spec:
  ingressClassName: alb
  rules:
    - host: ${YOUR_ROUTE53_DOMAIN}
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: grafana
                port:
                  number: 80
```

After applying new ingress and having new ALB ready, we can head over to `${YOUR_ROUTE53_DOMAIN}` and see that Grafana is ready as below.

![Grafana UI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5f2ojiasopy0sxm7gnxl.png)

---

That's it! In this post, we created a new node group which will be used by Prometheus and Grafana, and installed and configured both Prometheus and Grafana.

Hope this post helps you!

> After [importing Grafana dashboard](https://grafana.com/docs/grafana/v9.0/dashboards/export-import/) using `3119`, I got a new dashboard as below.

> ![Grafana UI2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rqu6uyfwdr0p8e5w2j16.png)