В пості Loki: збір логів з CloudWatch Logs з використанням Lambda Promtail описано як можна збирати логи з CloudWatch Logs за допомогою Lambda-функції з Promtail, який пересилає логи в Grafana Loki.
Що треба зробити зараз – це описати створення чотирьох таких функцій, по одній на кожен компонент проекту. Функції мають бути розміщені в приватних мережах VPC, щоб мати доступ до Ingress Loki у вигляді Internal Load Balancer.
Зміст
Підготовка
Використаємо “flat-layout” – всі файли Terraform будуть в корні проекту, а значення змінних для Dev та Prod передамо через окремі файли tfvars, див. Terraform Dev/Prod – Helm-like “flat” approach.
У файлі providers.tf описуємо провайдер AWS:
provider "aws" {
region = var.aws_region
default_tags {
tags = {
component = var.component
created-by = "terraform"
environment = var.environment
}
}
}
У файлі backend.tf – бекенд для стейт-файлу в S3:
terraform {
backend "s3" {
bucket = "tf-state-backend-atlas-monitoring"
region = "us-east-1"
encrypt = true
}
}
Значення key та dynamodb_table передамо під час виконання terraform init, бо для Dev і Prod вони будуть різними.
Додаємо файл variables.tf з поки що двома змінними:
variable "aws_region" {
type = string
default = "us-east-1"
}
variable "project_name" {
description = "A project name to be used in resources"
type = string
default = "atlas-lambda"
}
variable "component" {
description = "A team using this project (backend, web, ios, data, devops)"
type = string
}
variable "environment" {
description = "Dev/Prod, will be used in AWS resources Name tag, and resources names"
type = string
}
variable "eks_version" {
description = "Kubernetes version, will be used in AWS resources names and to specify which EKS version to create/update"
type = string
}
Значенням можно передати в defaults, але мені більш подобається задавати значення явно, а не в дефолтах, тому додаємо файл envs/dev/dev.tfvars:
aws_region = "us-east-1" environment = "dev" component = "devops" eks_version = "1.27"
eks_version використовуємо, щоб створювати окремі ресурси під кожну версію EKS-кластеру, бо під час оновлення версій спокійніше буде створити новий кластер і мігрувати workloads, ніж оновлювати живий Production.
Додаємо файл versions.tf з версіями Terraform та провайдерів:
terraform {
required_version = "~> 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.14"
}
}
}
Для аутентификації та авторизації в AWS маємо AWS CLI Profile, в якому виконується AssumeRole:
[profile work] region = us-east-1 output = json [profile tf-admin] role_arn = arn:aws:iam::492***148:role/tf-admin source_profile = work
Для Terraform – задаємо змінну AWS_PROFILE та виконємо terraform init з -backend-config:
$ terraform init -backend-config="key=test/atlas-monitoring-test.tfstate" -backend-config="dynamodb_table=tf-state-lock-atlas-monitoring-test" Initializing the backend... Successfully configured the backend "s3"! Terraform will automatically use this backend unless the backend configuration changes. Initializing provider plugins... - Finding hashicorp/aws versions matching "~> 5.14"... - Installing hashicorp/aws v5.17.0... - Installed hashicorp/aws v5.17.0 (signed by HashiCorp) ... Terraform has been successfully initialized!
Створення Lambda-функції
Використовуємо модуль, знов від @Anton Babenko – terraform-aws-modules/lambda/aws.
Для запуску функції нам потрібно мати Docker-образ з Promtail – він вже є, зберігається у ECR-репозиторії.
Крім того нам потрібно мати параметри для VPC – їх отримаємо із outputs іншого Terraform-проекту, який у нас займається управлінням мережами, аналогічно тому, як робили в Terraform: terraform_remote_state – отримання outputs інших state-файлів.
І третє, що треба буде мати – це Security-група, яка дозволить трафік від та до цих Lambd у приватних Subnets нашої VPC. Для її створення візьмемо ще один модуль Антона – terraform-aws-modules/security-group/aws.
SecurityGroup та remote_state
Готуємо файл main.tf, описуємо terraform_remote_state:
data "terraform_remote_state" "vpc" {
backend = "s3"
config = {
bucket = "tf-state-backend-atlas-vpc"
key = "${var.environment}/atlas-vpc-${var.environment}.tfstate"
region = var.aws_region
dynamodb_table = "tf-state-lock-atlas-vpc-${var.environment}"
}
}
Додаємо локальні змінні:
locals {
# create a name like 'atlas-monitorig-dev-1-27'
env_name = "test-${var.project_name}-${var.environment}-${replace(var.eks_version, ".", "-")}"
# 1.27 => 1-27
env_version = replace(var.eks_version, ".", "-")
# save 'outputs' from the VPC project
vpc_out = data.terraform_remote_state.vpc.outputs
}
В outputs проекту з VPC маємо всі необхідні дані:
...
output "vpc_id" {
value = module.vpc.vpc_id
}
...
output "vpc_private_subnets_cidrs" {
value = module.vpc.private_subnets_cidr_blocks
}
...
І vpc_private_subnets_cidrs видається у формі list(string):
Описуємо SecurityGroup:
module "security_group_lambda" {
source = "terraform-aws-modules/security-group/aws"
version = "~> 4.0"
name = "${local.env_name}-loki-logger-lambda-sg"
description = "Security Group for Lambda Egress"
vpc_id = local.vpc_out.vpc_id
egress_cidr_blocks = local.vpc_out.vpc_private_subnets_cidrs
egress_ipv6_cidr_blocks = []
ingress_cidr_blocks = local.vpc_out.vpc_private_subnets_cidrs
ingress_ipv6_cidr_blocks = []
egress_rules = ["https-443-tcp"]
ingress_rules = ["https-443-tcp"]
}
В egress_cidr_blocks та ingress_cidr_blocks вносимо адреси приватних мереж, а в egress_rules та ingress_rules – значення з auto_groups, де вже маємо готовий набор правил.
Ще раз виконуємо terraform init, щоб додати модуль SecurityGroup, та деплоїмо:
$ terraform apply -var-file=envs/dev/dev.tfvars ... module.security_group_lambda.aws_security_group.this_name_prefix[0]: Creating... module.security_group_lambda.aws_security_group.this_name_prefix[0]: Creation complete after 3s [id=sg-006d09a7a0ff0beb5] module.security_group_lambda.aws_security_group_rule.ingress_rules[0]: Creating... module.security_group_lambda.aws_security_group_rule.egress_rules[0]: Creating... module.security_group_lambda.aws_security_group_rule.ingress_rules[0]: Creation complete after 1s [id=sgrule-1358616028] module.security_group_lambda.aws_security_group_rule.egress_rules[0]: Creation complete after 2s [id=sgrule-892435573] Releasing state lock. This may take a few moments... Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Перевіряємо:
Lambda
Приклад є у файлі examples/with-vpc/main.tf.
До variables.tf додаємо ще дві змінних – список Components, та URL образу з Promtail:
...
variable "promtail_lambdas" {
type = set(string)
}
variable "promtail_image" {
type = string
default = "492***148.dkr.ecr.us-east-1.amazonaws.com/lambda-promtail:latest"
}
І значення promtail_lambdas в dev.tfvars:
... promtail_lambdas = [ "backend", "web", "ios", "eks" ]
До locals додаємо змінну з URL інстансу Grafana Loki:
...
locals {
...
# save 'outputs' from the VPC project
vpc_out = data.terraform_remote_state.vpc.outputs
# build URL 'logger.1-27.dev.example.co'
loki_write_address = "https://logger.${replace(var.eks_version, ".", "-")}.${var.environment}.example.co:443/loki/api/v1/push"
}
...
Додаємо модуль terraform-aws-modules/lambda/aws до нашого main.tf:
...
module "lambda_function_from_container_image" {
source = "terraform-aws-modules/lambda/aws"
version = "~> 6.0"
for_each = var.promtail_lambdas
function_name = "grafana-${local.env_name}-loki-logger-${each.value}"
description = "Promtail instance to colelct logs from CloudWatch Logs"
create_package = false
image_uri = var.promtail_image
package_type = "Image"
architectures = ["x86_64"]
environment_variables = {
EXTRA_LABELS = "component,${each.value}"
KEEP_STREAM = "true"
OMIT_EXTRA_LABELS_PREFIX = "true"
PRINT_LOG_LINE = "true"
WRITE_ADDRESS = local.loki_write_address
}
vpc_subnet_ids = local.vpc_out.vpc_private_subnets_ids
vpc_security_group_ids = [module.security_group_lambda.security_group_id]
attach_network_policy = true
}
Тут в циклі for_each перебираємо всі Components, для яких треба створити фукцію, в package_type вказуємо, що функція буде запускатись з Docker-образу, в environment_variables.EXTRA_LABELS задаємо лейблу component, яка буде додана до логів в Loki.
У vpc_subnet_ids знов використовуємо outputs проекту VPC, в vpc_security_group_ids вказуємо SecurityGroup, яку створили вище.
Параметр attach_network_policy підключить до IAM Role, яка буде підключена до функції, політику AWSLambdaENIManagementAccess, див. terraform-aws-lambda/blob/master/iam.tf.
Виконуємо terraform init, деплоїмо та перевіряємо:
Тестування логів
Додамо Subcription Filter до лог-групи кластеру EKS, щоб перевірити, що функція працює:
Метрики функції – дивимось Invocations та Error count and success rate (%):
І логи в Loki:
Готово.





