Overview

Prometheus and Grafana handle metrics. Loki handles logs - the third pillar of observability. Together they answer: "what happened, when, and why."

Pods (frontend + backend)
      ↓ writes logs to stdout
Promtail (DaemonSet - one per node)
      ↓ tails container logs, ships them
Loki (log storage, time-indexed like Prometheus)
      ↓ queried by
Grafana (Explore tab)

Why Loki over ELK Stack?

Loki only indexes metadata (labels), not full log content - dramatically cheaper storage and simpler operation than Elasticsearch. It's designed to pair natively with Grafana and Prometheus, using the same query philosophy (PromQL-like LogQL).

Prerequisites

EBS CSI Driver

Loki needs persistent storage for its single-binary deployment. EKS doesn't include this by default on newer versions - must be installed as an addon.

Defined in modules/eks/main.tf:

resource "aws_iam_role" "ebs_csi_driver" {
  name               = "${var.project}-${var.environment}-ebs-csi-driver-role"
  assume_role_policy = data.aws_iam_policy_document.ebs_csi_assume_role.json
}

resource "aws_iam_role_policy_attachment" "ebs_csi_driver" {
  role       = aws_iam_role.ebs_csi_driver.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy"
}

resource "aws_eks_addon" "ebs_csi_driver" {
  cluster_name             = aws_eks_cluster.main.name
  addon_name               = "aws-ebs-csi-driver"
  service_account_role_arn = aws_iam_role.ebs_csi_driver.arn
  depends_on               = [aws_eks_node_group.main]
}

This uses IRSA (IAM Roles for Service Accounts) - the CSI driver assumes a scoped IAM role via OIDC, not broad node permissions.

Verify it's running:

kubectl get pods -n kube-system | grep ebs

Expected:

ebs-csi-controller-xxx   6/6   Running
ebs-csi-controller-xxx   6/6   Running
ebs-csi-node-xxx         3/3   Running   (one per node)

image.png

Default StorageClass

kubectl patch storageclass gp2 \
  -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

Verify: