Loki: збір логів з CloudWatch Logs з використанням Lambda Promtail

Автор |  20/05/2023
 

Збирати логи у Grafana Loki з Kubernetes дуже просто – запускаємо Promtail у DaemonSet, йому вказуємо читати всі дані з /var/logs – і готово (насправді взагалі нічого не вказуємо – з Helm-чарту все працює з коробки).

А от як бути з CloudWatch Logs? На новому проекті маємо купу AWS Lambda, API Gateways і т.д, і всі вони пишуть логи у CloudWatch.

Що стосується Lambda, то можна було б використати Lambda Telemetry API, і писати логи з функції відразу в Loki, див. Building an AWS Lambda Telemetry API extension for direct logging to Grafana Loki, і можливо пізніше ми цей підхід також використаємо, але зараз у нас вже є купа логів від інших сервісів у CloudWatch, і треба таки їх читати.

Ще є варіант встановити CloudWatch як data source у Grafana, і просто користуватись логами з інтерфейсу Grafana та мабуть навіть мати алерти Grafana з цих логів, але рано чи пізно все одно з’явиться Kubernetes або просто ЕС2 інстанси, і треба буде збирати з них логи, тож будемо відразу робити все з Loki, тим більш в неї чудовий LogQL і набагато більше гнучкості у створенні лейбл та алертів.

В такому випадку можемо використати Lambda Promtail від самої Grafana, а працювати воно буде наступним чином:

  • якась Lambda-функція (наприклад) пише лог у CloudWatch Log Group
  • у Log Group будемо мати Subscription filter, який буде слати логи на іншу Lambda-функцію – власне у Lambda Promtail
  • а Lambda Promtail буде пересилати їх до інстансу Loki

Отже, сьогодні створимо тестову Lambda-функцію, яка буде писати логи, і запустимо Lambda Promtail, яка буде слати логи в Grafana Loki, яка вже є.

На що треба звернути увагу, так це на кількість даних, які будуть писатись, бо як завжди з AWS – попасти на гроші досить легко, тому добре мати налаштований AWS Budgets, щоб отримати алерт в разі неочікуваних витрат.

Також треба мати на увазі, що до Loki потрібно буде відкривати доступ на порт 3100, тож Lambda Promtail краще мати в тій самій VPC, де запущена сама Grafana та/або мати якийсь NGINX з HTTP-аутентифікацією.

Тестова Lambda для створення логів

Створюємо функцію, нехай буде на Python:

У коді функції додамо кілька print(), щоб створити запис в лог:

import json
import os

def lambda_handler(event, context):
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }
    print('## ENVIRONMENT VARIABLES')
    print(os.environ['AWS_LAMBDA_LOG_GROUP_NAME'])
    print(os.environ['AWS_LAMBDA_LOG_STREAM_NAME'])
    print('## EVENT')
    print(event)

Тиснемо Test, щоб створити тестовий евент, дані в полі Event JSON нам не важливі, просто вказуємо ім’я евенту, та зберігаємо його:

Тиснемо Test ще раз – функція виконалась, Function Logs пішли:

Переходимо у Monitor > Logs, а звідти у CloudWatch Logs:

І перевіряємо, що Log events є:

Все – тепер можемо переходити до Lambda Promtail.

Запуск Lambda Promtail

Взагалі є готовий Terraform проект і навіть Cloudformation темплейт, тож можна скористатись ними. Єдине, що у Terrafrom треба пофіксити створення resource "aws_iam_role_policy_attachment" "lambda_sqs_execution" у файлі sqs.tf, бо там йде виклик ролі role = aws_iam_role.iam_for_lambda.name, а у main.tf вона називається resource "aws_iam_role" "this".

В усьому іншому Terraform працює – задаємо значення для змінних у variabels.tfwrite_address, log_group_names та lambda_promtail_image, і можна створювати ресурси.

Проте я все ж вважаю за краще на перший раз створити все руками, щоб краще розуміти що і як буде працювати.

Docker образ та Elastic Container Service

Спочатку підготуємо Docker-образ, бо запустити AWS Lambda з публічного ECR Grafana чомусь неможливо, хоча ніде в документації такого обмеження не знайшов.

Переходимо до ECR, створюємо репозиторій:

Завантажуємо публічний образ від Grafana:

[simterm]

$ docker pull public.ecr.aws/grafana/lambda-promtail:main

[/simterm]

Перетегаємо його на свій репозиторій:

[simterm]

$ docker tag public.ecr.aws/grafana/lambda-promtail:main 264***286.dkr.ecr.eu-central-1.amazonaws.com/lambda-promtail-writer:latest

[/simterm]

Логінимось до ECR – вказуємо --profile, якщо не дефолтний, та AWS Region:

[simterm]

$ aws --profile setevoy ecr get-login-password --region eu-central-1 | docker login --username AWS --password-stdin 264***286.dkr.ecr.eu-central-1.amazonaws.com
...
Login Succeeded

[/simterm]

Пушимо туди наш образ:

[simterm]

$ docker push 264***286.dkr.ecr.eu-central-1.amazonaws.com/lambda-promtail-writer:latest

[/simterm]

Переходимо до Lambda.

Створення Lambda Promtail function

Створюємо нову функцію, вибираємо Container image, та вказуємо URI імейджу, який запушили вище:

Переходимо до Configuration > Environment variables, і задаємо мінімально необхідні змінні:

  • EXTRA_LABELS: теги/лейбли, які будуть додані у Loki, тут вказуємо у форматі labelname,labelvalue
  • WRITE_ADDRESS: адреса Loki з https:// та  URI /loki/api/v1/push

Конфігурація CloudWatch Log Group Subscription filters

Повертаємось до CloudWatch Log Group, в якій були наші тестові логи, і у Subscription filters додаємо нову підписку для Lambda-функції (див. Using CloudWatch Logs subscription filters або How can I configure a CloudWatch subscription filter to invoke my Lambda function?):

Вибираємо функцію, в яку будемо стрімити логи, та при потребі у Configure log format and filters можемо вказати фільтр, через який буде вибиратись що саме пересилати у Lambda, щоб не слати зовсім всі строки.

Зараз нам це не потрібно, тож у Log format ставимо Other, а Subscription filter pattern лишаємо пустим.

У Subscription filter name вказуємо ім’я самого фільтру:

Зберігаємо – Start streaming, повертаємось до Lambda write-logs, і кілька раз тиснемо Test, щоб створити ще записів у CloudWatch Log Group, які мають стригерити функцію lambda-promtail-testing і передати їй дані, які вона відправить у Loki.

Перевіряємо у функції lambda-promtail-testing – у Monitoring мають бути виклики:

У випадку Errors – на вкладці Logs є посилання на CloudWatch Log цієї функції, де буде описана помилка.

Якщо ж все Success – то в Loki вже маємо побачити нову лейблу, і по ній можемо вибрати логи з функції write-logs:

Готово.

На сторінці документації Grafana ще пишуть, що “Or, have lambda-promtail write to Promtail and use pipeline stages.“, але я так і не знайшов можливості в Promtail писати дані по gRPC або HTTP, хоча така ідея була ще у 2020 році, але вона досі в Draft – Promtail Push API