В пості 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:
Готово.