AWS: VPC Flow Logs – логи до S3 та Grafana dashboard з Loki

Автор |  16/11/2024
 

В продовження теми AWS: VPC Flow Logs, NAT Gateways, та Kubernetes Pods – детальний обзор.

Там ми розбирали роботу з VPC Flow Logs в цілому, і дізнались, як ми можемо отримувати інформацію про трафік з/до Kubernetes Pods.

Але при використанні Flow Logs з CloudWatch Logs є одна проблема – це вартість.

Наприклад, коли ми включаємо Flow Logs і вони пишуться до CloudWatch Logs, то навіть у невеликому проекті з невеликим трафіком кости на CloudWatch Logs виглядають так – 23-го жовтня включив, 8 листопада відключив:

Тому замість використання CloudWatch Logs ми можемо зробити інакше: Flow Logs писати до AWS S3 бакета, а звідти забирати з Promtail і писати в Grafana Loki, див. Grafana Loki: збираємо логи AWS LoadBalancer з S3 за допомогою Promtail Lambda. А вже маючи логи в Loki – мати і алерти з VMAlert/Alertmanager, і дашборди в Grafana.

Головна проблема, яку ми зараз хочемо вирішити за допомогою VPC Flow Logs – це визначити, хто шле багато трафіку через NAT Gateway, бо це теж з’їдає наші гроші.

Друга задача – це надалі мати загальну картину і якісь алерти по трафіку.

Отже, що будемо робити:

  • створимо AWS S3 для логів
  • створимо Lambda-функцію з інстансом Promtail, який буде писати логи з бакета до Grafana Loki
  • подивимось що ми маємо в логах цікавого, і що корисного там може бути для нас по трафіку
  • створимо Grafana Dashboard

Наша інфраструктура

Спочатку трохи подивимось на наш проект.

Маємо:

Terraform та VPC Flow Logs до S3

Створення самих корзин та Lambda-функції описано в Terraform: створення модулю для збору логів AWS ALB в Grafana Loki, тому тут детально зупинятись не буду.

Взагалі модуль створювався для збору логів AWS Load Balancers, тому в іменах буде зустрічатись “alb” – потім треба буде його переписати аби імена бакетів та функцій передавати параметром.

Єдиний момент, який треба мати на увазі: VPC Flow Logs пише багато даних, тому варто додати більший таймаут для Lambda, бо частина записів втрачалась через помилку Lambda “Task timed out after 3.05 seconds“.

...
module "promtail_lambda" {
  source  = "terraform-aws-modules/lambda/aws"
  version = "~> 7.8.0"
  # key: dev
  # value:  ops-1-28-backend-api-dev-alb-logs
  for_each = aws_s3_bucket.alb_s3_logs

  # <aws_env>-<eks_version>-<component>-<application>-<app_env>-alb-logs-logger
  # bucket name: ops-1-28-backend-api-dev-alb-logs
  # lambda name: ops-1-28-backend-api-dev-alb-logs-loki-logger
  function_name = "${each.value.id}-loki-logger"
  description   = "Promtail instance to collect logs from ALB Logs in S3"

  create_package = false
  # https://github.com/terraform-aws-modules/terraform-aws-lambda/issues/36
  publish = true

  # an error when sending logs from Flow Logs S3:
  # 'Task timed out after 3.05 seconds'
  timeout = 60
...

Отже, ми маємо AWS S3 бакет, маємо Lambda, яка з цього бакету бути отримувати повідомлення про появу нових об’єктів, а потім Promtail з цієї Lambda-функції відправляє логи до інстансу Loki через Internal LoadBalancer:

При передачі логів до Loki Promtail додає кілька нових лейбл – component=vpc-flow-logs, logtype=alb, environment=ops. Далі ми зможемо їх використовувати в метриках та Grafana dashboards.

logtype=alb то знов-таки модуль писався під логи ALB, і це треба буде змінити

Тепер нам треба налаштувати Flow Logs для нашої VPC.

В модулі terraform-aws-modules/vpc/aws для цього є кілька параметрів:

...
  enable_flow_log = var.vpc_params.enable_flow_log

  # Default: "cloud-watch-logs"
  flow_log_destination_type = "s3"

  # disalbe to use S3
  create_flow_log_cloudwatch_log_group = false
  create_flow_log_cloudwatch_iam_role  = false

  # ARN of the a CloudWatch log group or an S3 bucket
  # disable if use 'create_flow_log_cloudwatch_log_group' and the default 'flow_log_destination_type' value (cloud-watch-logs)
  flow_log_destination_arn = "arn:aws:s3:::ops-1-30-vpc-flow-logs-devops-ops-alb-logs"

  # set 60 to use more detailed recoring
  flow_log_max_aggregation_interval         = 600
  # when use CloudWatch Logs, set this prefix
  flow_log_cloudwatch_log_group_name_prefix = "/aws/${local.env_name}-flow-logs/"

  # set custom log format for more detailed information
  flow_log_log_format = "$${region} $${vpc-id} $${az-id} $${subnet-id} $${instance-id} $${interface-id} $${flow-direction} $${srcaddr} $${dstaddr} $${srcport} $${dstport} $${pkt-srcaddr} $${pkt-dstaddr} $${pkt-src-aws-service} $${pkt-dst-aws-service} $${traffic-path} $${packets} $${bytes} $${action}"

...

Нас тут зараз цікавлять такі параметри:

  • flow_log_destination_type: замість дефолтного cloud-watch-logs задаємо s3
  • create_flow_log_cloudwatch_log_group та create_flow_log_cloudwatch_iam_role: відключаємо створення ресурсів для CloudWatch Logs
  • flow_log_destination_arn: задаємо ARN корзини, в яку будуть писатись логи
  • flow_log_log_format: створюємо власний формат, аби мати більше інформації, в тому числі з IP подів в Kubernetes, див. VPC Flow Log – Custom format

Виконуємо terraform apply, перевіряємо нашу VPC:

І через 10 хвилин перевіряємо логи в Grafana Loki:

Чудово – логи пішли.

Далі до запиту в Loki додаємо парсер pattern, аби сформувати поля в записах:

{logtype="alb", component="vpc-flow-logs"} | pattern `<region> <vpc_id> <az_id> <subnet_id> <instance_id> <interface_id> <flow_direction> <src_add> <dst_addr> <src_port> <dst_port> <pkt_src_addr> <pkt_dst_addr> <pkt_src_aws_service> <pkt_dst_aws_service> <traffic_path> <packets> <bytes> <action>`

Окей. Тепер, маючи логи і поля, можемо подумати про метрики з Loki Recording Rules, Grafana dashboard та алерти в Alertmanager.

Аналіз запису VPC Flow Logs з NAT Gateway

Спочатку давайте глянемо весь трафік через NAT Gateway – додаємо фільтр по interface_id="eni-0352f8c82da6aa229":

{logtype="alb", component="vpc-flow-logs"} | pattern `<region> <vpc_id> <az_id> <subnet_id> <instance_id> <interface_id> <flow_direction> <src_add> <dst_addr> <src_port> <dst_port> <pkt_src_addr> <pkt_dst_addr> <pkt_src_aws_service> <pkt_dst_aws_service> <traffic_path> <packets> <bytes> <action>`
| interface_id="eni-0352f8c82da6aa229"

Що ми тут бачимо?

Наприклад, в першому записі:

Тут:

  • ingress: пакет вийшов до інтерфейсу NAT Gateway
  • 52.46.154.111: це src_addr – якийсь remote server
  • 10.0.5.175: це dst_addr – приватний IP нашого NAT Gateway
  • 443: це src_port – звідки прийшов пакет
  • 18779: це dst_port – куди прийшов пакет
  • 52.46.154.111 та 10.0.5.175:  це pkt_src_addr та pkt_dst_addr відповідно, значення такі ж, як і в5432 src_addr та dst_addr – тобто трафік явно “чисто NAT”, як розбирали в Від Remote Server до NAT Gateway
  • AMAZON: сервіс, від якого пакет отримано (але про це трохи далі)
  • pkt_dst_aws_service та traffic_path: пусті
  • 105: кількість пакетів
  • 113390: кількість байт
  • ACCEPT: пакет пройшов через Security Group/WAF

А в наступному запису бачимо dst_port 5432 – тут трафік явно до PostgreSQL RDS.

NAT Gateway та traffic_path

З цікавих моментів, які можна побачити в логах.

По-перше – це traffic_path. Іноді в логах, які пов’язані в NAT Gateway можна побачити “8”, тобто “Through an internet gateway” – див. Available fields.

Чому Internet Gateway? Бо трафік приходить з приватної мережі на NAT Gateway, але далі в інтернет він йде вже через Internet Gateway – див. One to Many: Evolving VPC Design.

pkt_src_aws_service, pkt_dst_aws_service та визначення сервісу

Щодо адрес не з нашої мережі, тобто якихось зовнішніх сервісів. В полях pkt_src_aws_service та pkt_dst_aws_service часто можна побачити запис типу “EC2” або “AMAZON” – але нам це нічого не каже.

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

Але тут є хак: якщо в src_port/dst_port ми бачимо порт 443 – то можна просто відкрити IP в браузері, де ми отримаємо помилку SSL, і в помилці буде ім’я сервісу, на який це сертифікат видано.

Наприклад, вище ми бачили, що pkt_src_aws_service == AMAZON. Якщо відкрити https://52.46.154.111 – то побачимо що саме на цьому IP:

Аналогічно будуть записи типу monitoring.us-east-1.amazonaws.com для AWS CloudWatch або athena.us-east-1.amazonaws.com для AWS Athena.

Створення Grafana dashboard

Тепер давайте пробувати створити Grafana dashboard.

Планування

Отже, головна мета – це мати уяву про трафік, який  проходить через AWS NAT Gateway.

Що ми знаємо та маємо?

  • знаємо CIDR приватних сабнетів для Kubernetes Pods
  • знаємо Elastic Network Interface ID, Public IP та Private IP для NAT Gateway – він у нас один, тому тут все просто
  • в логах маємо IP подів Kubernetes та якихось зовнішніх ресурсів
  • в логах маємо напрямок трафіку через інтерфейс NAT Gateway – IN/OUT (ingress/egress, або RX/TX – Recieved та Transmitted)

Що ми б хотіли бачити на дашборді?

  • загальний об’єм трафіку, який пройшов через NAT Gateway і за який ми заплатити
  • загальний об’єм трафіку NAT Gateway за напрямком – ingress/egress
  • сервіси в Kubernetes, які генерують найбільший трафік
  • AWS сервіси та зовнішні ресурси, які генерують трафік – для цього маємо поля pkt-src-aws-service та pkt-dst-aws-service
  • дія з пакетами ACCEPT та REJECT – може бути корисним, якщо є AWS Web Application Firewall, або вам цікаві спрацювання VPC Network Access List
  • Availability Zones для визначення cross-AZ трафіку – але це наразі не в нашому випадку, бо у нас все в одній зоні
  • traffic-path – може бути корисним для визначення якого типу трафік йде – всередині VPC, через VPC Endpoint тощо (хоча особисто я не став це використовувати в дашборді)

NAT Gateway total traffic processed

Отримати суму всього трафіку за період часу ми можемо таким запитом:

sum (
    sum_over_time(
        ({logtype="alb", component="vpc-flow-logs", __aws_s3_vpc_flow="fl-01e52787e4495dfee"} | pattern `<region> <vpc_id> <az_id> <subnet_id> <instance_id> <interface_id> <flow_direction> <src_add> <dst_addr> <src_port> <dst_port> <pkt_src_addr> <pkt_dst_addr> <pkt_src_aws_service> <pkt_dst_aws_service> <traffic_path> <packets> <bytes> <action>`
        | interface_id="eni-0352f8c82da6aa229"
        | unwrap bytes
        | __error__=""
        )[$__range]
    )
)

Використовуємо [$__range], аби взяти проміжок часу, який задано в Grafana dashboard. В sum_over_time рахуємо всі bytes за цей час, і “загортаємо” все в sum(), аби мати просто цифру.

Для панелі “Total traffic processed” можна взяти тип візуалізації Stat, використати Unit з Bytes(IEC), і виглядати це буде так:

Маємо тут 5.8 GB за 15 хвилин.

Зараз маю для перевірки Flow Logs в CloudWatch, де можемо зробити такий запит для порівняння:

parse @message "* * * * * * * * * * * * * * * * * * *" 
| as region, vpc_id, az_id, subnet_id, instance_id, interface_id, 
| flow_direction, srcaddr, dstaddr, srcport, dstport, 
| pkt_srcaddr, pkt_dstaddr, pkt_src_aws_service, pkt_dst_aws_service, 
| traffic_path, packets, bytes, action 
| filter (interface_id like "eni-0352f8c82da6aa229") | stats sum(bytes) as bytesTransferred
| sort bytesTransferred desc
| limit 10

Тут у нас за ті ж 15 хвилин 4835737572 байт, тобто такі ж 4.8 гігабайта.

Окей – загальний трафік маємо.

Давайте додамо відображення Egress та Ingress.

NAT Gateway Egress та Ingress traffic processed – Stat

Тут все аналогічно, тільки додаємо фільтр | flow_direction="egress" або “ingress” відповідно:

sum (
    sum_over_time(
        ({logtype="alb", component="vpc-flow-logs", __aws_s3_vpc_flow="fl-01e52787e4495dfee"} | pattern `<region> <vpc_id> <az_id> <subnet_id> <instance_id> <interface_id> <flow_direction> <src_add> <dst_addr> <src_port> <dst_port> <pkt_src_addr> <pkt_dst_addr> <pkt_src_aws_service> <pkt_dst_aws_service> <traffic_path> <packets> <bytes> <action>`
        | interface_id="eni-0352f8c82da6aa229"
        | flow_direction="egress"
        | unwrap bytes
        | __error__=""
        )[$__range]
    )
)

Так як панель у нас з типом Stat, де просто відображається цифра – то задля зменшення навантаження на Grafana та Loki в Options є сенс поставити Type == Instant.

Змінні дашборди – $kubernetes_pod_ip та $remote_svc_ip

По-перше – нам цікавий трафік саме з/до Kubernetes Pods, бо майже всі наші сервіси живуть там.

По-друге – хочеться мати можливість вибрати дані тільки по обраним pkt_src_addr та pkt_dst_addr – це може бути або Kubernetes Pod, або якийсь зовнішній сервіс – в залежності від ingress/egress трафіку.

Так як ми оперуємо з “сирими” записами в логах, а не метриками з лейблами – то ми не можемо просто взяти значення з полів, тому я додав дві змінні з типом Textbox, в які можна внести IP вручну:

А далі ми можемо ці змінні додати в усі наші запити з регуляркою pkt_src_addr=~"${kubernetes_pod_ip}", аби запит спрацьовував, якщо в змінній не задано жодного значення:

sum (
    sum_over_time(
        ({logtype="alb", component="vpc-flow-logs", __aws_s3_vpc_flow="fl-01e52787e4495dfee"} | pattern `<region> <vpc_id> <az_id> <subnet_id> <instance_id> <interface_id> <flow_direction> <src_add> <dst_addr> <src_port> <dst_port> <pkt_src_addr> <pkt_dst_addr> <pkt_src_aws_service> <pkt_dst_aws_service> <traffic_path> <packets> <bytes> <action>`
        | interface_id="eni-0352f8c82da6aa229"
        | flow_direction="egress"
        | pkt_src_addr=~"${kubernetes_pod_ip}"
        | pkt_dst_addr=~"${remote_svc_ip}"        
        | unwrap bytes
        | __error__=""
        )[$__range]
    )
)

Loki Recording Rules, поля, High Cardinality issue, та performance

Чому ми оперуємо з “сирими” записами в логах, а не метриками з лейблами?

Можна було б створити Recording Rule для Loki по типу такого:

...
        - record: aws:nat:egress_bytes:sum:15m
          expr: |
            sum (
                sum_over_time(
                    ({logtype="alb", component="vpc-flow-logs", __aws_s3_vpc_flow="fl-01e52787e4495dfee"} | pattern `<region> <vpc_id> <az_id> <subnet_id> <instance_id> <interface_id> <flow_direction> <src_add> <dst_addr> <src_port> <dst_port> <pkt_src_addr> <pkt_dst_addr> <pkt_src_aws_service> <pkt_dst_aws_service> <traffic_path> <packets> <bytes> <action>`
                    | interface_id="eni-0352f8c82da6aa229"
                    | flow_direction="egress"
                    | pkt_src_addr=ip("10.0.32.0/20")
                    | unwrap bytes
                    | __error__=""
                    )[15m]
                )
            )
...

Але якщо з даними типу “total processed bytes” це нормальний варіант, то далі, коли ми будемо створювати панелі з інформацією по IP, у нас буде проблема в тому, як ці IP зберігати в метриках.

Якщо ми будемо значення з pkt_src_addr та pkt_dst_addr заносити в лейбли метрики – то це призведе до того, що Loki буде створювати окремий набір блоків даних (chunks) на кожний унікальний набір лейбл.

А так як IP в VPC у нас багато, а зовнішніх IP може бути ще більше – то можемо отримати мільйони блоків даних, що вплине і на вартість зберігання даних в AWS S3, і на перформанс самої Loki та VictoriaMetrics або Prometheus, бо їм доведеться всі ці дані завантажувати про виконанні запитів в Grafana. Див. Loki Recording Rules, Prometheus/VictoriaMetrics та High Cardinality.

Крім того, лейбли в метриках взагалі мають використовуватись для “опису” цієї метрики і можливості вибирати дані, а не для зберігання якихось даних для подальшого використання. Тобто лейбла – це тег, який “описує” метрику, а не поле для передачі параметрів.

Тому тут варіант або змиритись з high cardinality issue і не дотримуватись best practices – або використовувати “сирі логи” в дашбордах.

При роботі з сирими логами в Loki та Grafana ми, звісно, обмежуємо себе, бо на запитах за відносно великий проміжок часу – наприклад, кілька годин – Loki починає жрати ресурси, як дурна, див. колонку CPU – майже 4 ядра зайняті повністю:

Можливо, я все ж спробую створити Recording Rules з IP в лейблах, подивитись як це вплине на систему. Поки у нас невеликий стартап і мало трафіку – це ще може бути варіантом. Але на великих обсягах такого краще не робити.

Іншим рішенням може бути спробувати використати VictoriaLogs (див. VictoriaLogs: знайомство, запуск в Kubernetes, LogsQL та Grafana), яка набагато краще може з цим працювати, і, скоріш за все, на наступному тижні я буду перероблювати цю схему саме з VictoriaLogs, тим більш сама VictoriaLogs вже отримала свій перший реліз, а скоро і її Grafana datasource вже буде офіційно додано до Grafana.

Крім того, до VictoriaLogs вже завели і підтримку Recording Rules, і алерти – див. vmalert.

NAT Gateway та traffic processed – графіки

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

Тут запит може бути аналогічним, тільки замість sum_over_time() використаємо rate(), в Options використовуємо Type == Range, а в Standart Options > Unit задаємо “bytes/sec”:

sum (
    rate(
        ({logtype="alb", component="vpc-flow-logs", __aws_s3_vpc_flow="fl-01e52787e4495dfee"} | pattern `<region> <vpc_id> <az_id> <subnet_id> <instance_id> <interface_id> <flow_direction> <src_add> <dst_addr> <src_port> <dst_port> <pkt_src_addr> <pkt_dst_addr> <pkt_src_aws_service> <pkt_dst_aws_service> <traffic_path> <packets> <bytes> <action>`
        | interface_id="eni-0352f8c82da6aa229"
        | unwrap bytes
        | __error__=""
        )[15m]
    )
)

Для rate() беремо період 15 хвилин, бо логи у нас пишуться раз на 10 хвилин – дефолтне значення для flow_log_max_aggregation_interval в модулі terraform-aws-modules/vpc/aws.

Сервіси в Kubernetes, які генерують найбільший трафік

Наступним хочеться бачити IP з Kubernetes Pods, які генерують трафік.

Тут можемо створити візуалізацію з типом Pie chart і таким запитом:

topk (5,
  sum by (pkt_src_addr) (
    sum_over_time(
      (
        {component="vpc-flow-logs", __aws_s3_vpc_flow="fl-01e52787e4495dfee"}
        | pattern `<region> <vpc_id> <az_id> <subnet_id> <instance_id> <interface_id> <flow_direction> <src_addr> <dst_addr> <src_port> <dst_port> <pkt_src_addr> <pkt_dst_addr> <pkt_src_aws_service> <pkt_dst_aws_service> <traffic_path> <packets> <bytes> <action>`
        | interface_id="eni-0352f8c82da6aa229"
        | action="ACCEPT"
        | pkt_src_addr=ip("10.0.32.0/20")
        | pkt_src_addr=~"${kubernetes_pod_ip}"
        | pkt_dst_addr=~"${remote_svc_ip}"
        | unwrap bytes
        | __error__=""
      )[$__range]
    )
  )
)

Використовуємо ip() з CIDR нашої приватної мережі, аби вибрати записи тільки з IP наших Pods (див. Matching IP addresses), і topk(5), аби відобразити тільки ті Pods, які генерують найбільше трафіку.

В результаті маємо таку картину:

В топі у нас IP 10.0.44.66 – глянемо, що за сервіс:

$ kk get pod -A -o wide | grep -w 10.0.44.66
dev-backend-api-ns    backend-celery-workers-deployment-b68f88f74-rzq4j    ... 10.0.44.66 ...

Є такий Kubernetes Pod, окей. Тепер маємо уяву хто шле багато трафіку.

Grafana Data links

Аби швидко отримати інформацію що за IP, та до якого Kubernetes Pod він належить – можемо додати Grafana Data Links.

Наприклад, в мене є окрема дашборда, де по Pod IP можна отримати всю інформацію про нього.

Тоді можемо створити Data link з полем ${__field.labels.pkt_src_addr}:

І дашборда по IP “10.0.44.66”:

Всі доступні поля для Data links можна отримати з Ctrl+Space.

Або замість (чи на додачу) Pie chart можемо створити звичайний графік, аби мати “історичну картину”, як ми це робили для NAT Gateway Total traffic:

topk(5,
  sum by (pkt_src_addr) (
    rate(
      (
        {component="vpc-flow-logs", __aws_s3_vpc_flow="fl-01e52787e4495dfee"}
        | pattern `<region> <vpc_id> <az_id> <subnet_id> <instance_id> <interface_id> <flow_direction> <src_addr> <dst_addr> <src_port> <dst_port> <pkt_src_addr> <pkt_dst_addr> <pkt_src_aws_service> <pkt_dst_aws_service> <traffic_path> <packets> <bytes> <action>`
        | interface_id="eni-0352f8c82da6aa229"
        | action="ACCEPT"
        | pkt_src_addr=ip("10.0.32.0/20")
        | pkt_src_addr=~"${kubernetes_pod_ip}"
        | pkt_dst_addr=~"${remote_svc_ip}"
        | unwrap bytes
        | __error__=""
      )[15m]
    )
  )
)

Remote services, які генерують найбільший трафік

По Kubernetes Pods інформацію отримали – давайте глянемо, звідки до нас приходить найбільше трафіку.

Тут все аналогічно, тільки фільтр робимо по pkt_dst_addr=ip("10.0.32.0/20") – тобто вибираємо всі записи, де пакет йде ззовні на NAT Gateway і потім до наших Pods:

topk(10,
  sum by (pkt_src_addr) (
    sum_over_time(
      (
        {component="vpc-flow-logs", __aws_s3_vpc_flow="fl-01e52787e4495dfee"}
        | pattern `<region> <vpc_id> <az_id> <subnet_id> <instance_id> <interface_id> <flow_direction> <src_addr> <dst_addr> <src_port> <dst_port> <pkt_src_addr> <pkt_dst_addr> <pkt_src_aws_service> <pkt_dst_aws_service> <traffic_path> <packets> <bytes> <action>`
        | interface_id="eni-0352f8c82da6aa229"
        | action="ACCEPT"
        | pkt_dst_addr=ip("10.0.32.0/20")
        | pkt_src_addr=~"${remote_svc_ip}"
        | pkt_dst_addr=~"${kubernetes_pod_ip}"
        | unwrap bytes
        | __error__=""
      )[$__range]
    )
  )
)

А в Data Links можемо використати сервіс https://ipinfo.io і поле pkt_src_addr:

Сервіси в Kubernetes, які генерують найбільший трафік – таблиця з портами

Окремо можна додати табличку, де буде трохи більше інформації по кожному запису з логів.

Чому окремо – бо тут ми робимо запит з великою вибіркою по декільком полям, і через це Loki доведеться тягнути додаткові дані. Тому на запитах за великий проміжок часу нехай краще не прогрузиться одна табличка – але будуть графіки.

В табличку можемо додати відображення портів – буде корисно при визначенні сервісу.

Створюємо візуалізацію з типом Table і таким запитом:

topk(10,
  sum by (pkt_src_addr, src_port, pkt_dst_addr, dst_port, pkt_dst_aws_service) (
    sum_over_time(
      (
        {component="vpc-flow-logs", __aws_s3_vpc_flow="fl-01e52787e4495dfee"}
        | pattern `<region> <vpc_id> <az_id> <subnet_id> <instance_id> <interface_id> <flow_direction> <src_addr> <dst_addr> <src_port> <dst_port> <pkt_src_addr> <pkt_dst_addr> <pkt_src_aws_service> <pkt_dst_aws_service> <traffic_path> <packets> <bytes> <action>`
        | interface_id="eni-0352f8c82da6aa229"
        | action="ACCEPT"
        | pkt_src_addr=ip("10.0.32.0/20")
        | pkt_src_addr=~"${kubernetes_pod_ip}"
        | pkt_dst_addr=~"${remote_svc_ip}"
        | unwrap bytes
        | __error__=""
      )[$__range]
    )
  )
)

В Options задаємо Type == Instant:

Додаємо Transformations:

  • Filter fields by name: тут прибираємо Time
  • Organize fields by name: міняємо заголовки колонок

Значення для Standard options > Unit та Data Links задаємо через Fields override, бо для кожної колонки у нас будуть власні параметри:

Grafana dashboard: фінальний результат

І все разом в мене поки що вийшло ось так:

Якщо не зважати не проблеми з перформансом Loki при використанні raw logs – то наче непогано. Вже дуже допомогло визначити зайвий трафік, наприклад – багато трафіку йшло від Athena, тому додамо VPC Endpoint для неї, аби не ганяти цей трафік через NAT Gateway.

Далі, мабуть, таки спробую варіант з Recording Rules для Loki, і точно буду пробувати писати логи до VictoriaLogs, і робити графіки та алерти через неї.