Grafana Loki: збираємо логи AWS LoadBalancer з S3 за допомогою Promtail Lambda

Автор |  15/11/2023
 

Зараз ми вміємо збирати логи API Gateway та CloudWatch Logs, див. Loki: збір логів з CloudWatch Logs з використанням Lambda Promtail.

Але в процесі міграції в Kubernetes у нас з’явились Application Load Balancers, які вміють писати логи тільки в S3, і нам треба навчитись збирати логи і звідти.

Формат логу AWS ALB див. у Access logs for your Application Load Balancer, налаштування логування в S3 – в Enable access logs for your Application Load Balancer.

Технічна реалізація

Начебто і нічого складного, але по-перше є деякі нюанси, особливо з IAM та VPC, по-друге – я ніде не знаходив такої документації, тож довелося писати її самому.

В принципі, тут все майже однаково зі збором логів з CloudWatch Logs:

  • використовуємо Promtail Lambda
  • на S3 бакеті налаштовуємо тригер на відправку івента в Lambda, коли з’явлється або оновлюється об’єкт в корзині
  • Promtail сходить в корзину, і забере звідти лог

Схематично можна відобразити так:

Тепер давайте все зробимо руками, а потім вже будемо думати як його прикрутити до автоматизації з Terraform.

Створення S3 для логів

Для S3 бакету нам потрібна політика, яка буде дозволяти:

  • писати в корзину логи з Application Load Balancer
  • забирати з корзини логи в Lambda

Для цього нам будуть потрібні:

  • ELB Account ID для Load Balancer – див. Step 2: Attach a policy to your S3 bucket
    • в нашому випадку AWS Region == us-east-1, тож elb-account-id буде 127311923021
  • IAM Role ARN – ролі, яка підключена до нашої Lambda-фунції з Promtail

Створюємо файл s3-alb-logs-policy.json з двома Allow:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::127311923021:root"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::eks-alb-logs-test/AWSLogs/492***148/*"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::492***148:role/grafana-atlas-monitoring-dev-1-28-loki-logger-backend"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::eks-alb-logs-test/*"
        }
    ]
}

Створюємо корзину:

$ aws --profile work s3api create-bucket --bucket eks-alb-logs-test --region us-east-1

І підключаємо політику:

$ aws --profile work s3api put-bucket-policy --bucket eks-alb-logs-test --policy file://s3-alb-logs-policy.json

Далі треба налаштувати відправку повідомлень до нашої Lambda.

S3 Event notifications

Переходимо в Properties > Event notifications:

Задаємо ім’я та префікс – каталог AWSLogs і в ньому каталог з ім’ям аккаунту:

Нижче вибираємо типи подій – всі ObjectCreated:

І задаємо Destination – ARN функції, до якої будемо слати повідомлення:

Promtail Lambda permissions

Перевіримо дозволи в Lambda. Це знадобиться, коли будемо робити автоматизацію, та й для дебагу.

Переходимо в Configuration > Permissions:

І внизу, в Resource-based policy statements, маємо побачити нові дозволи – додалось автоматом, коли ми створили Event notification в S3:

Створення Ingress/ALB з логуванням в S3

Створюємо Kubernetes Ingress, і в annotations alb.ingress.kubernetes.io/load-balancer-attributes включаємо йому логуваня в тестову корзину:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-demo-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-demo
  template:
    metadata:
      labels:
        app: nginx-demo
    spec:
      containers:
        - name: nginx-demo-container
          image: nginx:latest
          ports:
            - containerPort: 80

---
apiVersion: v1
kind: Service
metadata:
  name: nginx-demo-service
spec:
  selector:
    app: nginx-demo
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: ClusterIP

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]'
    alb.ingress.kubernetes.io/load-balancer-attributes: access_logs.s3.enabled=true,access_logs.s3.bucket=eks-alb-logs-test
spec:
  ingressClassName: alb
  rules:
    - host: example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nginx-demo-service
                port:
                  number: 80

Створюємо ресурси:

$ kk apply -f ingress-svc-deploy.yaml 
deployment.apps/nginx-demo-deployment created
service/nginx-demo-service created
ingress.networking.k8s.io/example-ingress created

Перевіряємо:

$ kk get ingress
NAME              CLASS   HOSTS         ADDRESS                                                                  PORTS   AGE
example-ingress   alb     example.com   k8s-default-examplei-b48cab7a95-1519459738.us-east-1.elb.amazonaws.com   80      22s

Перевіряємо файли в корзині:

$ aws s3 ls s3://eks-alb-logs-test/AWSLogs/492***148/
2023-11-15 14:51:43        106 ELBAccessLogTestFile

Робимо кілька запитів до АЛБ:

$ curl k8s-default-examplei-b48cab7a95-1519459738.us-east-1.elb.amazonaws.com

І за 2-3 хвилини – маємо новий файл в корзині:

$ aws s3 ls s3://eks-alb-logs-test/AWSLogs/492***148/elasticloadbalancing/us-east-1/2023/11/15/
...
2023-11-15 16:15:11        344 492***148_elasticloadbalancing_us-east-1_app.k8s-default-examplei-b48cab7a95.bc6b35b432aa3492_20231115T1415Z_54.211.225.139_2ir4tbe3.log.gz

І ще за хвилину маємо івент в Lambda:

Та її логи – можна пошукати по імені файла, bc6b35b432aa3492_20231115T1415Z_54.211.225.139_2ir4tbe3:

І логи в Loki – тут можна пошукати по X-Ray ID, який додається до всіх запитів до ALB в хедері X-Amzn-Trace-Id:

Помилка Lambda: Task timed out, та VPC Endpoints

Поки налаштовува це, зіткнувся з помилкою “Task timed out“.

В логах це виглядало так:

msg=”fetching s3 file: AWSLogs/492***148/elasticloadbalancing/us-east-1/2023/11/15/492***148_elasticloadbalancing_us-east-1_app.k8s-default-examplei-b48cab7a95.21d12877724a6c9f_20231115T1205Z_52.44.168.196_jin2v33x.log.gz”

Task timed out after 3.00 seconds

Спочатку подумав, що Promtail за 3 секунди не встигає отримати лог з S3, і збільшив таймаут функції:

Але виявилось, що причина в іншому: Lambda Promtail запущена в VPC, і до S3 ходить через VPC Ednpoint (див. Terraform: створення EKS, частина 1 – VPC, Subnets та Endpoints), але в SecurityGtoup, яка підключена до цієї функції, Outbound доступ був дозволений тільки до адрес в приватних сабнетах цієї VPC, бо тоді налаштовувалась тільки передача логів в Grafana Loki:

А нам потрібно надати доступ до VPC енпоінту S3, і якби це був тип Interface Ednpoint – то в SecurityGroup лямбди можна було б задати SecurityGroup ID цього Interface Endpoint-у:

Але у Gateway такої опції нема.

Проте ми можемо використати Prefix List ID – знаходимо його:

І додаємо в Outbound Rules:

Готово.