Как стать автором
Обновить

Запускаем Sentry в Kubernetes в Яндекс облаке и храним Nodestore в S3

Уровень сложностиСредний
Время на прочтение27 мин
Количество просмотров1.4K

Sentry — это инструмент для отслеживания ошибок и производительности приложений в реальном времени.

  • Отслеживает баги и exceptions в бекенд, веб и мобильных приложениях.

  • Показывает стек вызовов, контекст, окружение, пользователя и другую полезную информацию.

  • Помогает разработчикам быстро находить и исправлять баги.

  • Поддерживает множество языков и фреймворков

Для кого этот пост

  • Этот пост для тех кто хочет перейти с Sentry в docker-compose

  • Для тех кто хочет перейти с Nodestore в PostgreSQL в S3

Отличия от предыдущего поста про Sentry

  • Используются Kafka, ClickHouse вне Kubernetes

  • Для Nodestore используется S3

  • Добавлен пример сборки кастомных image sentry, snuba, replay с сертификатом от yandex

  • Подключение Kafka, Redis, ClickHouse, Postgres через SSL (можно отключить).

  • Динамическое формирование values для helm чарта sentry

  • Используется чистый terraform чтобы вам было легче разобраться в коде

Быстрый старт в yandex cloud

Запускаем инфраструктуру:

export YC_FOLDER_ID='ваш folder'
terraform init
terraform apply

Формируем kubeconfig для кластера k8s с указанным ID (идентификатор_кластера) в Yandex Cloud, используя внешний IP (--external)

yc managed-kubernetes cluster get-credentials --id идентификатор_кластера --external --force

Проверяем сгенерированный конфиг values_sentry.yaml из шаблона

Деплоим Sentry в кластер через Helm

kubectl create namespace test
helm repo add sentry https://sentry-kubernetes.github.io/charts
helm repo update
helm upgrade --install sentry -n test sentry/sentry --version 26.15.1 -f values_sentry.yaml

В версии 26.15.1 sentry helm чарта используется 25.2.0 версия sentry

Пароли

Пароли генерируются динамически, но вы можете указать свои пароль в local.tf Их можно получить посмотрев values_sentry.yaml или используя terraform output

Простой пример отправки exception

  • Создаем проект в Sentry, выбираем python, копируем DSN

  • Заходим в директорию example-python

  • Меняем dsn в main.py (Сам DSN лучше хранить в секретах (либо брать из env))

  • Запускаем python код

cd example-python
python3 -m venv venv
source venv/bin/activate
pip install --upgrade sentry-sdk
python3 main.py

Почему важно выносить Kafka, Redis, ClickHouse, Postgres вне Kubernetes

Плюсы такого подхода:

  • Масштабируемость

  • Изоляция ресурсов

  • Более надежное хранилище

Минусы/предостережения:

  • Логирование и трассировка проблем становится чуть сложнее

  • Требует аккуратной настройки переменных и IAM-доступов (особенно к S3)

Подключение Kafka, Redis, ClickHouse, Postgres через SSL

В этом посте в отличие от предыдущего будет подключение Kafka, Redis, Postgres через SSL. Для подключения ClickHouse по SSL ждем вот этого PR. В terraform коде в комментариях указано как настраивать SSL и как отключать SSL

Структура Terraform проекта

  • Список и краткое описание ключевых файлов в репо:

    • example-python — демонстрация, как отправлять ошибки в Sentry из Python

    • clickhouse.tf — managed ClickHouse (Yandex Cloud)

    • ip-dns.tf – настраивает IP-адреса и записи DNS для ресурсов.

    • k8s.tf — managed Kuberbetes (Yandex Cloud) для деплоя Sentry

    • kafka.tf — managed Kafka (Yandex Cloud)

    • locals.tf – определяет локальные переменные, используемые в других файлах Terraform.

    • net.tf – описывает сетевые ресурсы, такие как VPC, подсети и маршруты.

    • postgres.tf — managed Postgres (Yandex Cloud)

    • redis.tf — для кэширования и очередей managed Redis (Yandex Cloud)

    • s3_filestore.tf и s3_nodestore.tf — хранилище blob-данных managed S3 (Yandex Cloud)

    • values_sentry.yaml и values_sentry.yaml.tpl — конфиг для Sentry, параметризуем через Terraform templatefile

    • versions.tf – задаёт версии Terraform и провайдеров, необходимых для работы проекта.

Хранение основных данных (Nodestore) в S3

Отмечу отдельно что основные данные (Nodestore) хранятся в S3, так как хранение в PostgreSQL приводит со временем к проблемам и медленной работе Sentry. Файл s3_nodestore.tf — хранилище blob-данных managed S3 (Yandex Cloud). В файле values_sentry.yaml указание где хранить Nodestore указывается так

sentryConfPy: |
  SENTRY_NODESTORE = "sentry_s3_nodestore.backend.S3NodeStorage"
  SENTRY_NODESTORE_OPTIONS = {
      "bucket_name": "название-бакета",
      "region": "ru-central1",
      "endpoint": "https://storage.yandexcloud.net",
      "aws_access_key_id": "aws_access_key_id",
      "aws_secret_access_key": "aws_secret_access_key",
  }

Динамическое формирование файла values.yaml для helm чарта Sentry

  • Файл values.yaml (values_sentry.yaml) формируется используя шаблон values_sentry.yaml.tpl и templatefile.tf

  • В финальный конфиг через terraform функцию templatefile() превращается в values_sentry.yaml

  • В файлах values_sentry.yaml.tpl и templatefile.tf содержится разные настройки.

Собираем кастомные image

Вы можете использовать docker image по умолчанию или собрать image. В этих кастомных image происходит установка сертификатов и установка sentry-s3-nodestore модуля. Сертификаты устанавливаются в python модуль certifi. Код сборок находится либо в этих репозиториях:

Sentry Kubernetes Hook: как это работает

Параметр asHook в Sentry Helm chart указывает, что основные контейнеры и миграции должны запуститься перед остальными контейнерами. Это нужно для первого запуска Sentry. После его можно отключить.

Планы на следующие посты про Sentry

Исходный terraform код

clickhouse.tf

Скрытый текст
# Создание кластера ClickHouse в Яндекс Облаке
resource "yandex_mdb_clickhouse_cluster" "sentry" {
  folder_id   = coalesce(local.folder_id, data.yandex_client_config.client.folder_id) # ID folder в Yandex Cloud
  name        = "sentry"                       # Название кластера
  environment = "PRODUCTION"                   # Окружение (может быть также PRESTABLE)
  network_id  = yandex_vpc_network.sentry.id   # ID VPC-сети
  version     = 24.8                           # Версия ClickHouse

  clickhouse {
    resources {
      resource_preset_id = "s3-c2-m8"          # Пресет ресурсов для узлов ClickHouse
      disk_type_id       = "network-ssd"       # Тип диска
      disk_size          = 70                  # Размер диска в ГБ
    }
  }

  zookeeper {
    resources {
      resource_preset_id = "s3-c2-m8"          # Пресет ресурсов для узлов ZooKeeper
      disk_type_id       = "network-ssd"       # Тип диска
      disk_size          = 34                  # Размер диска в ГБ
    }
  }

  database {
    name = "sentry"                            # Имя базы данных в ClickHouse
  }

  user {
    name     = local.clickhouse_user           # Имя пользователя для доступа
    password = local.clickhouse_password       # Пароль пользователя
    permission {
      database_name = "sentry"                 # Назначение прав доступа к БД "sentry"
    }
  }

  # Добавление хостов ClickHouse и ZooKeeper с привязкой к подсетям
  host {
    type      = "CLICKHOUSE"                   # Тип узла — ClickHouse
    zone      = yandex_vpc_subnet.sentry-a.zone
    subnet_id = yandex_vpc_subnet.sentry-a.id  # Подсеть в зоне A
  }

  # Три узла ZooKeeper в разных зонах для отказоустойчивости
  host {
    type      = "ZOOKEEPER"
    zone      = yandex_vpc_subnet.sentry-a.zone
    subnet_id = yandex_vpc_subnet.sentry-a.id
  }

  host {
    type      = "ZOOKEEPER"
    zone      = yandex_vpc_subnet.sentry-b.zone
    subnet_id = yandex_vpc_subnet.sentry-b.id
  }

  host {
    type      = "ZOOKEEPER"
    zone      = yandex_vpc_subnet.sentry-d.zone
    subnet_id = yandex_vpc_subnet.sentry-d.id
  }

  timeouts {
    create = "60m"
    update = "60m"
    delete = "60m"
  }

}

# Вывод конфиденциальной информации о ClickHouse-кластере
output "externalClickhouse" {
  value = {
    host     = yandex_mdb_clickhouse_cluster.sentry.host[0].fqdn     # FQDN первого ClickHouse-хоста
    database = one(yandex_mdb_clickhouse_cluster.sentry.database[*].name) # Имя БД
    httpPort = 8123                                                  # HTTP порт ClickHouse
    tcpPort  = 9000                                                  # TCP порт ClickHouse
    username = local.clickhouse_user                                 # Имя пользователя
    password = local.clickhouse_password                             # Пароль пользователя
  }
  sensitive = true                                                   # Отметка, что output содержит чувствительные данные
}

example-python

import sentry_sdk
sentry_sdk.init(
    dsn="http://xxxxx@sentry.apatsev.org.ru/2",
    traces_sample_rate=1.0,
)

try:
    1 / 0
except ZeroDivisionError:
    sentry_sdk.capture_exception()

ip-dns.tf

# Создание внешнего IP-адреса в Yandex Cloud
resource "yandex_vpc_address" "addr" {
  name = "sentry-pip"  # Имя ресурса внешнего IP-адреса

  external_ipv4_address {
    zone_id = yandex_vpc_subnet.sentry-a.zone  # Зона доступности, где будет выделен IP-адрес
  }
}

# Создание публичной DNS-зоны в Yandex Cloud DNS
resource "yandex_dns_zone" "apatsev-org-ru" {
  name  = "apatsev-org-ru-zone"  # Имя ресурса DNS-зоны

  zone  = "apatsev.org.ru."      # Доменное имя зоны (с точкой в конце)
  public = true                  # Указание, что зона является публичной

  # Привязка зоны к VPC-сети, чтобы можно было использовать приватный DNS внутри сети
  private_networks = [yandex_vpc_network.sentry.id]
}

# Создание DNS-записи типа A, указывающей на внешний IP
resource "yandex_dns_recordset" "rs1" {
  zone_id = yandex_dns_zone.apatsev-org-ru.id       # ID зоны, к которой принадлежит запись
  name    = "sentry.apatsev.org.ru."                # Полное имя записи (поддомен)
  type    = "A"                                     # Тип записи — A (IPv4-адрес)
  ttl     = 200                                     # Время жизни записи в секундах
  data    = [yandex_vpc_address.addr.external_ipv4_address[0].address]  # Значение — внешний IP-адрес, полученный ранее
}

k8s.tf

Скрытый текст
# Создание сервисного аккаунта для управления Kubernetes
resource "yandex_iam_service_account" "sa-k8s-editor" {
  folder_id = coalesce(local.folder_id, data.yandex_client_config.client.folder_id) # ID folder в Yandex Cloud
  name      = "sa-k8s-editor"  # Имя сервисного аккаунта
}

# Назначение роли "editor" сервисному аккаунту на уровне папки
resource "yandex_resourcemanager_folder_iam_member" "sa-k8s-editor-permissions" {
  folder_id = coalesce(local.folder_id, data.yandex_client_config.client.folder_id)
  role      = "editor"  # Роль, дающая полные права на ресурсы папки

  member = "serviceAccount:${yandex_iam_service_account.sa-k8s-editor.id}"  # Назначаемый участник
}

# Пауза, чтобы изменения IAM успели примениться до создания кластера
resource "time_sleep" "wait_sa" {
  create_duration = "20s"
  depends_on      = [
    yandex_iam_service_account.sa-k8s-editor,
    yandex_resourcemanager_folder_iam_member.sa-k8s-editor-permissions
  ]
}

# Создание Kubernetes-кластера в Yandex Cloud
resource "yandex_kubernetes_cluster" "sentry" {
  name       = "sentry"  # Имя кластера
  folder_id  = coalesce(local.folder_id, data.yandex_client_config.client.folder_id)
  network_id = yandex_vpc_network.sentry.id  # Сеть, к которой подключается кластер

  master {
    version = "1.30"  # Версия Kubernetes мастера
    zonal {
      zone      = yandex_vpc_subnet.sentry-a.zone  # Зона размещения мастера
      subnet_id = yandex_vpc_subnet.sentry-a.id     # Подсеть для мастера
    }

    public_ip = true  # Включение публичного IP для доступа к мастеру
  }

  # Сервисный аккаунт для управления кластером и нодами
  service_account_id      = yandex_iam_service_account.sa-k8s-editor.id
  node_service_account_id = yandex_iam_service_account.sa-k8s-editor.id

  release_channel = "STABLE"  # Канал обновлений

  # Зависимость от ожидания применения IAM-ролей
  depends_on = [time_sleep.wait_sa]
}

# Группа узлов для Kubernetes-кластера
resource "yandex_kubernetes_node_group" "k8s-node-group" {
  description = "Node group for the Managed Service for Kubernetes cluster"
  name        = "k8s-node-group"
  cluster_id  = yandex_kubernetes_cluster.sentry.id
  version     = "1.30"  # Версия Kubernetes на нодах

  scale_policy {
    fixed_scale {
      size = 3  # Фиксированное количество нод
    }
  }

  allocation_policy {
    # Распределение нод по зонам отказоустойчивости
    location { zone = yandex_vpc_subnet.sentry-a.zone }
    location { zone = yandex_vpc_subnet.sentry-b.zone }
    location { zone = yandex_vpc_subnet.sentry-d.zone }
  }

  instance_template {
    platform_id = "standard-v2"  # Тип виртуальной машины

    network_interface {
      nat = true  # Включение NAT для доступа в интернет
      subnet_ids = [
        yandex_vpc_subnet.sentry-a.id,
        yandex_vpc_subnet.sentry-b.id,
        yandex_vpc_subnet.sentry-d.id
      ]
    }

    resources {
      memory = 20  # ОЗУ
      cores  = 4   # Кол-во ядер CPU
    }

    boot_disk {
      type = "network-ssd"         # Тип диска
      size = 128                   # Размер диска
    }
  }
}

# Настройка провайдера Helm для установки чарта в Kubernetes
provider "helm" {
  kubernetes {
    host                   = yandex_kubernetes_cluster.sentry.master[0].external_v4_endpoint  # Адрес API Kubernetes
    cluster_ca_certificate = yandex_kubernetes_cluster.sentry.master[0].cluster_ca_certificate  # CA-сертификат

    exec {
      api_version = "client.authentication.k8s.io/v1beta1"
      args        = ["k8s", "create-token"]  # Команда получения токена через CLI Yandex.Cloud
      command     = "yc"
    }
  }
}

# Установка ingress-nginx через Helm
resource "helm_release" "ingress_nginx" {
  name             = "ingress-nginx"
  repository       = "https://kubernetes.github.io/ingress-nginx"
  chart            = "ingress-nginx"
  version          = "4.10.6"
  namespace        = "ingress-nginx"
  create_namespace = true
  depends_on       = [yandex_kubernetes_cluster.sentry]

  set {
    name  = "controller.service.loadBalancerIP"
    value = yandex_vpc_address.addr.external_ipv4_address[0].address  # Присвоение внешнего IP ingress-контроллеру
  }
}

# Вывод команды для получения kubeconfig
output "k8s_cluster_credentials_command" {
  value = "yc managed-kubernetes cluster get-credentials --id ${yandex_kubernetes_cluster.sentry.id} --external --force"
}

kafka.tf

Скрытый текст
# Создание Kafka-кластера в Yandex Cloud
# Здесь определяется Kafka кластер с именем "sentry" в Yandex Cloud с необходимыми параметрами конфигурации.
resource "yandex_mdb_kafka_cluster" "sentry" {
  folder_id   = coalesce(local.folder_id, data.yandex_client_config.client.folder_id) # ID folder в Yandex Cloud
  name        = "sentry"                             # Имя кластера
  environment = "PRODUCTION"                         # Среда (может быть PRESTABLE/PRODUCTION)
  network_id  = yandex_vpc_network.sentry.id         # Сеть VPC, в которой будет размещён кластер

  subnet_ids = [                                     # Список подсетей в разных зонах доступности
    yandex_vpc_subnet.sentry-a.id,
    yandex_vpc_subnet.sentry-b.id,
    yandex_vpc_subnet.sentry-d.id
  ]

  config {
    version       = "3.6"                            # Версия kafka
    brokers_count = 1                                # Кол-во брокеров в каждой зоне
    zones = [                                        # Зоны размещения брокеров
      yandex_vpc_subnet.sentry-a.zone,
      yandex_vpc_subnet.sentry-b.zone,
      yandex_vpc_subnet.sentry-d.zone
    ]
    assign_public_ip = false                         # Не присваивать публичный IP
    schema_registry  = false                         # Без поддержки Schema Registry

    kafka {
      resources {
        resource_preset_id = "s3-c2-m8"              # Пресет ресурсов для узлов PostgreSQL
        disk_type_id       = "network-ssd"           # Тип диска
        disk_size          = 200                     # Размер диска в ГБ
      }
      kafka_config {
        # оставьте пустым чтобы terraform не выводил что постоянно что то меняет в kafka_config
        # описание доступных настроек: https://terraform-provider.yandexcloud.net/resources/mdb_kafka_cluster.html#nested-schema-for3
      }
    }
  }
}

# Список топиков Kafka с параметрами
# Переменная локальная, которая содержит все топики Kafka и их параметры.
locals {
  kafka_topics = {
    # Каждый ключ — имя топика. Значение — map опций конфигурации (может быть пустой)
    "events" = {},
    "event-replacements" = {},
    "snuba-commit-log" = {
      cleanup_policy        = "CLEANUP_POLICY_COMPACT_AND_DELETE"
      min_compaction_lag_ms = "3600000"
    },
    "cdc" = {},
    "transactions" = {},
    "snuba-transactions-commit-log" = {
      cleanup_policy        = "CLEANUP_POLICY_COMPACT_AND_DELETE"
      min_compaction_lag_ms = "3600000"
    },
    "snuba-metrics" = {},
    "outcomes" = {},
    "outcomes-dlq" = {},
    "outcomes-billing" = {},
    "outcomes-billing-dlq" = {},
    "ingest-sessions" = {},
    "snuba-metrics-commit-log" = {
      cleanup_policy        = "CLEANUP_POLICY_COMPACT_AND_DELETE"
      min_compaction_lag_ms = "3600000"
    },
    "scheduled-subscriptions-events" = {},
    "scheduled-subscriptions-transactions" = {},
    "scheduled-subscriptions-metrics" = {},
    "scheduled-subscriptions-generic-metrics-sets" = {},
    "scheduled-subscriptions-generic-metrics-distributions" = {},
    "scheduled-subscriptions-generic-metrics-counters" = {},
    "scheduled-subscriptions-generic-metrics-gauges" = {},
    "events-subscription-results" = {},
    "transactions-subscription-results" = {},
    "metrics-subscription-results" = {},
    "generic-metrics-subscription-results" = {},
    "snuba-queries" = {},
    "processed-profiles" = {},
    "profiles-call-tree" = {},
    "snuba-profile-chunks" = {},
    "ingest-replay-events" = {
      max_message_bytes     = "15000000"
    },
    "snuba-generic-metrics" = {},
    "snuba-generic-metrics-sets-commit-log" = {
      cleanup_policy        = "CLEANUP_POLICY_COMPACT_AND_DELETE"
      min_compaction_lag_ms = "3600000"
    },
    "snuba-generic-metrics-distributions-commit-log" = {
      cleanup_policy        = "CLEANUP_POLICY_COMPACT_AND_DELETE"
      min_compaction_lag_ms = "3600000"
    },
    "snuba-generic-metrics-counters-commit-log" = {
      cleanup_policy        = "CLEANUP_POLICY_COMPACT_AND_DELETE"
      min_compaction_lag_ms = "3600000"
    },
    "snuba-generic-metrics-gauges-commit-log" = {
      cleanup_policy        = "CLEANUP_POLICY_COMPACT_AND_DELETE"
      min_compaction_lag_ms = "3600000"
    },
    "generic-events" = {},
    "snuba-generic-events-commit-log" = {
      cleanup_policy        = "CLEANUP_POLICY_COMPACT_AND_DELETE"
      min_compaction_lag_ms = "3600000"
    },
    "group-attributes" = {},
    "snuba-dead-letter-metrics" = {},
    "snuba-dead-letter-generic-metrics" = {},
    "snuba-dead-letter-replays" = {},
    "snuba-dead-letter-generic-events" = {},
    "snuba-dead-letter-querylog" = {},
    "snuba-dead-letter-group-attributes" = {},
    "ingest-attachments" = {},
    "ingest-attachments-dlq" = {},
    "ingest-transactions" = {},
    "ingest-transactions-dlq" = {},
    "ingest-transactions-backlog" = {},
    "ingest-events" = {},
    "ingest-events-dlq" = {},
    "ingest-replay-recordings" = {},
    "ingest-metrics" = {},
    "ingest-metrics-dlq" = {},
    "ingest-performance-metrics" = {},
    "ingest-feedback-events" = {},
    "ingest-feedback-events-dlq" = {},
    "ingest-monitors" = {},
    "monitors-clock-tasks" = {},
    "monitors-clock-tick" = {},
    "monitors-incident-occurrences" = {},
    "profiles" = {},
    "ingest-occurrences" = {},
    "snuba-spans" = {},
    "snuba-eap-spans-commit-log" = {},
    "scheduled-subscriptions-eap-spans" = {},
    "eap-spans-subscription-results" = {},
    "snuba-eap-mutations" = {},
    "snuba-lw-deletions-generic-events" = {},
    "shared-resources-usage" = {},
    "buffered-segments" = {},
    "buffered-segments-dlq" = {},
    "uptime-configs" = {},
    "uptime-results" = {},
    "snuba-uptime-results" = {},
    "task-worker" = {},
    "snuba-ourlogs" = {}
  }
}

# Создание Kafka-топиков на основе описания в locals.kafka_topics
# Итерируем по списку топиков и создаём их в Kafka с конфигурациями.
resource "yandex_mdb_kafka_topic" "topics" {
  for_each = local.kafka_topics                      # Итерируемся по каждому топику

  cluster_id         = yandex_mdb_kafka_cluster.sentry.id
  name               = each.key                      # Имя топика
  partitions         = 1                             # Кол-во партиций
  replication_factor = 1                             # Фактор репликации (можно увеличить для отказоустойчивости)

  topic_config {
    cleanup_policy        = lookup(each.value, "cleanup_policy", null)
    min_compaction_lag_ms = lookup(each.value, "min_compaction_lag_ms", null)
  }

  timeouts {
    create = "60m"
    update = "60m"
    delete = "60m"
  }
}

# Локальная переменная со списком имен всех топиков (используется для прав доступа)
# Список всех имен топиков, используемых для назначения прав доступа.
locals {
  kafka_permissions = keys(local.kafka_topics)
}

# Создание пользователя Kafka и назначение прав доступа к каждому топику
# Создаём пользователя Kafka и настраиваем права доступа для консьюмера и продюсера.
resource "yandex_mdb_kafka_user" "sentry" {
  cluster_id = yandex_mdb_kafka_cluster.sentry.id
  name       = local.kafka_user                     # Имя пользователя
  password   = local.kafka_password                 # Пароль пользователя

  # Назначение роли "консьюмер" для каждого топика
  dynamic "permission" {
    for_each = toset(local.kafka_permissions)
    content {
      topic_name = permission.value
      role       = "ACCESS_ROLE_CONSUMER"
    }
  }

  # Назначение роли "продюсер" для каждого топика
  dynamic "permission" {
    for_each = toset(local.kafka_permissions)
    content {
      topic_name = permission.value
      role       = "ACCESS_ROLE_PRODUCER"
    }
  }
}

# Вывод Kafka-подключения в виде структурированных данных (sensitive — чувствительные данные скрываются)
# Данный вывод предоставляет информацию о подключении к Kafka с учётом безопасности.
output "externalKafka" {
  description = "Kafka connection details in structured format"
  value = {
    cluster = [
      for host in yandex_mdb_kafka_cluster.sentry.host : {
        host = host.name
        port = 9091 # 9091 — если используется SSL, иначе 9092
      } if host.role == "KAFKA"
    ]
    sasl = {
      mechanism = "SCRAM-SHA-512" # Механизм аутентификации (например, PLAIN, SCRAM)
      username  = local.kafka_user
      password  = local.kafka_password
    }
    security = {
      protocol = "SASL_SSL" # Использовать SASL_SSL (или SASL_PLAINTEXT при отсутствии SSL)
    }
  }
  sensitive = true
}

locals.tf

Скрытый текст
# Получаем информацию о конфигурации клиента Yandex
data "yandex_client_config" "client" {}

# Генерация случайного пароля для Kafka
resource "random_password" "kafka" {
  length      = 20            # Длина пароля 20 символов
  special     = false          # Без специальных символов
  min_numeric = 4             # Минимум 4 цифры в пароле
  min_upper   = 4             # Минимум 4 заглавные буквы в пароле
}

# Генерация случайного пароля для ClickHouse
resource "random_password" "clickhouse" {
  length      = 20            # Длина пароля 20 символов
  special     = false          # Без специальных символов
  min_numeric = 4             # Минимум 4 цифры в пароле
  min_upper   = 4             # Минимум 4 заглавные буквы в пароле
}

# Генерация случайного пароля для Redis
resource "random_password" "redis" {
  length      = 20            # Длина пароля 20 символов
  special     = false          # Без специальных символов
  min_numeric = 4             # Минимум 4 цифры в пароле
  min_upper   = 4             # Минимум 4 заглавные буквы в пароле
}

# Генерация случайного пароля для PostgreSQL
resource "random_password" "postgres" {
  length      = 20            # Длина пароля 20 символов
  special     = false          # Без специальных символов
  min_numeric = 4             # Минимум 4 цифры в пароле
  min_upper   = 4             # Минимум 4 заглавные буквы в пароле
}

# Генерация случайного пароля для администратора Sentry
resource "random_password" "sentry_admin_password" {
  length      = 20            # Длина пароля 20 символов
  special     = false          # Без специальных символов
  min_numeric = 4             # Минимум 4 цифры в пароле
  min_upper   = 4             # Минимум 4 заглавные буквы в пароле
}

# Локальные переменные для настройки инфраструктуры
locals {
  folder_id           = data.yandex_client_config.client.folder_id  # ID папки в Yandex Cloud
  sentry_admin_password = random_password.sentry_admin_password.result # Сгенерированный пароль администратора Sentry
  kafka_user          = "sentry"                                    # Имя пользователя для Kafka
  kafka_password      = random_password.kafka.result                # Сгенерированный пароль для Kafka
  clickhouse_user     = "sentry"                                    # Имя пользователя для ClickHouse
  clickhouse_password = random_password.clickhouse.result          # Сгенерированный пароль для ClickHouse
  redis_password      = random_password.redis.result                # Сгенерированный пароль для Redis
  postgres_password   = random_password.postgres.result             # Сгенерированный пароль для PostgreSQL
  filestore_bucket    = "sentry-bucket-apatsev-filestore-test"      # Имя бакета для Filestore
  nodestore_bucket    = "sentry-bucket-apatsev-nodestore-test"      # Имя бакета для Nodestore
}

# Выводим сгенерированные пароли для сервисов
output "generated_passwords" {
  description = "Map of generated passwords for services"  # Описание вывода
  value = {
    kafka_password      = random_password.kafka.result      # Пароль для Kafka
    clickhouse_password = random_password.clickhouse.result # Пароль для ClickHouse
    redis_password      = random_password.redis.result      # Пароль для Redis
    postgres_password   = random_password.postgres.result   # Пароль для PostgreSQL
  }
  sensitive = true  # Скрывает пароли в логах, но они доступны через `terraform output`
}

net.tf

Скрытый текст
# Ресурс для создания сети VPC в Yandex Cloud
resource "yandex_vpc_network" "sentry" {
  name      = "vpc"  # Имя сети VPC
  folder_id = coalesce(local.folder_id, data.yandex_client_config.client.folder_id)  # ID папки, либо из локальной переменной, либо из конфигурации клиента Yandex Cloud
}

# Ресурс для создания подсети в зоне "ru-central1-a"
resource "yandex_vpc_subnet" "sentry-a" {
  folder_id      = coalesce(local.folder_id, data.yandex_client_config.client.folder_id)  # ID папки, либо из локальной переменной, либо из конфигурации клиента Yandex Cloud
  v4_cidr_blocks = ["10.0.1.0/24"]  # CIDR блок для подсети (IP-диапазон)
  zone           = "ru-central1-a"  # Зона, где будет размещена подсеть
  network_id     = yandex_vpc_network.sentry.id  # ID сети, к которой будет привязана подсеть
}

# Ресурс для создания подсети в зоне "ru-central1-b"
resource "yandex_vpc_subnet" "sentry-b" {
  folder_id      = coalesce(local.folder_id, data.yandex_client_config.client.folder_id)  # ID папки, либо из локальной переменной, либо из конфигурации клиента Yandex Cloud
  v4_cidr_blocks = ["10.0.2.0/24"]  # CIDR блок для подсети (IP-диапазон)
  zone           = "ru-central1-b"  # Зона, где будет размещена подсеть
  network_id     = yandex_vpc_network.sentry.id  # ID сети, к которой будет привязана подсеть
}

# Ресурс для создания подсети в зоне "ru-central1-d"
resource "yandex_vpc_subnet" "sentry-d" {
  folder_id      = coalesce(local.folder_id, data.yandex_client_config.client.folder_id)  # ID папки, либо из локальной переменной, либо из конфигурации клиента Yandex Cloud
  v4_cidr_blocks = ["10.0.3.0/24"]  # CIDR блок для подсети (IP-диапазон)
  zone           = "ru-central1-d"  # Зона, где будет размещена подсеть
  network_id     = yandex_vpc_network.sentry.id  # ID сети, к которой будет привязана подсеть
}

postgres.tf

Скрытый текст
# Создание кластера PostgreSQL в Yandex Cloud
resource "yandex_mdb_postgresql_cluster" "postgresql_cluster" {
  # Название кластера
  name                = "sentry"

  # Среда, в которой развертывается кластер
  environment         = "PRODUCTION"

  # Сеть, в которой будет размещен кластер
  network_id          = yandex_vpc_network.sentry.id

  # Конфигурация кластера PostgreSQL
  config {
    # Версия PostgreSQL
    version = "16" # Версия PostgreSQL

    # Включение автофейловера (автоматический перевод на другой узел при сбое)
    autofailover = true

    # Период хранения резервных копий в днях
    backup_retain_period_days = 7

    resources {
      # Размер диска в ГБ
      disk_size          = 129

      # Тип диска
      disk_type_id       = "network-ssd"

      # Пресет ресурсов для узлов PostgreSQL
      resource_preset_id = "s3-c2-m8"
    }
  }

  # Хост в зоне "ru-central1-a"
  host {
    zone      = "ru-central1-a"
    subnet_id = yandex_vpc_subnet.sentry-a.id
  }

  # Хост в зоне "ru-central1-b"
  host {
    zone      = "ru-central1-b"
    subnet_id = yandex_vpc_subnet.sentry-b.id
  }

  # Хост в зоне "ru-central1-d"
  host {
    zone      = "ru-central1-d"
    subnet_id = yandex_vpc_subnet.sentry-d.id
  }
}

# Создание базы данных в PostgreSQL
resource "yandex_mdb_postgresql_database" "postgresql_database" {
  # Идентификатор кластера, к которому относится база данных
  cluster_id = yandex_mdb_postgresql_cluster.postgresql_cluster.id

  # Имя базы данных
  name       = "sentry"

  # Владелец базы данных (пользователь)
  owner      = yandex_mdb_postgresql_user.postgresql_user.name

  # Установка расширений для базы данных
  extension {
    # Расширение для работы с типом данных citext (регистр не учитывается при сравнении строк)
    name = "citext"
  }

  # Зависимость от ресурса пользователя
  depends_on = [yandex_mdb_postgresql_user.postgresql_user]
}

# Создание пользователя PostgreSQL
resource "yandex_mdb_postgresql_user" "postgresql_user" {
  # Идентификатор кластера, к которому принадлежит пользователь
  cluster_id = yandex_mdb_postgresql_cluster.postgresql_cluster.id

  # Имя пользователя
  name       = "sentry"

  # Пароль пользователя
  password   = local.postgres_password

  # Ограничение по количеству соединений
  conn_limit = 300

  # Разрешения для пользователя (пока пустой список)
  grants     = []
}

# Вывод внешних данных для подключения к базе данных PostgreSQL
output "externalPostgresql" {
  value = {
    # Пароль для подключения (значение скрыто)
    password = local.postgres_password

    # Адрес хоста для подключения (с динамическим именем хоста на основе ID кластера)
    host     = "c-${yandex_mdb_postgresql_cluster.postgresql_cluster.id}.rw.mdb.yandexcloud.net"

    # Порт для подключения к базе данных
    port     = 6432

    # Имя пользователя для подключения
    username = yandex_mdb_postgresql_user.postgresql_user.name

    # Имя базы данных для подключения
    database = yandex_mdb_postgresql_database.postgresql_database.name
  }
  # Помечаем значение как чувствительное (не выводить в логах)
  sensitive = true
}

redis.tf

Скрытый текст
# Создание кластера Redis в Yandex Managed Service for Redis
resource "yandex_mdb_redis_cluster" "sentry" {
  name        = "sentry"  # Название кластера
  folder_id   = coalesce(local.folder_id, data.yandex_client_config.client.folder_id) # ID folder в Yandex Cloud
  network_id  = yandex_vpc_network.sentry.id  # ID сети VPC
  environment = "PRODUCTION"  # Среда (может быть PRODUCTION или PRESTABLE)
  tls_enabled = true  # Включение TLS для защищённого подключения

  config {
    password         = local.redis_password  # Пароль для подключения к Redis
    maxmemory_policy = "ALLKEYS_LRU"  # Политика очистки памяти: удаляются наименее используемые ключи
    version          = "7.2"  # Версия Redis
  }

  resources {
    resource_preset_id = "hm3-c2-m8"  # Тип конфигурации по CPU и памяти
    disk_type_id       = "network-ssd"  # Тип диска
    disk_size          = 65  # Размер диска в ГБ
  }

  host {
    zone      = "ru-central1-a"  # Зона доступности
    subnet_id = yandex_vpc_subnet.sentry-a.id  # ID подсети
  }
}

# Вывод внешних параметров подключения к Redis
output "externalRedis" {
  value = {
    # host     = yandex_mdb_redis_cluster.sentry.host[0].fqdn  # FQDN первого хоста Redis
    # Адрес хоста для подключения (с динамическим именем хоста на основе ID кластера)
    host     = "c-${yandex_mdb_redis_cluster.sentry.id}.rw.mdb.yandexcloud.net"
    port     = 6380  # Порт Redis SSL
    password = local.redis_password  # Пароль подключения
  }
  sensitive = true  # Значение помечено как чувствительное
}

s3_filestore.tf

Скрытый текст
# Создание статического ключа доступа для учетной записи сервиса в Yandex IAM
resource "yandex_iam_service_account_static_access_key" "filestore_bucket_key" {
  # ID учетной записи сервиса, для которой создается ключ доступа
  service_account_id = yandex_iam_service_account.sa-s3.id

  # Описание для ключа доступа
  description        = "static access key for object storage"
}

# Создание бакета (хранилища) в Yandex Object Storage
resource "yandex_storage_bucket" "filestore" {
  # Название бакета
  bucket     = local.filestore_bucket

  # Важно: команда sentry cleanup не удаляет файлы, хранящиеся во внешнем хранилище, таком как GCS или S3.
  # https://develop.sentry.dev/self-hosted/experimental/external-storage/
  # Правило жизненного цикла объектов в бакете
  lifecycle_rule {
    # Уникальный идентификатор правила
    id      = "delete-after-30-days"
    # Флаг, указывающий, что правило активно
    enabled = true

    # Параметры истечения срока хранения объектов
    expiration {
      # Объекты будут автоматически удаляться через 30 дней после загрузки
      days = 30
    }
  }

  # Доступ и секретный ключ, полученные от статического ключа доступа
  access_key = yandex_iam_service_account_static_access_key.filestore_bucket_key.access_key
  secret_key = yandex_iam_service_account_static_access_key.filestore_bucket_key.secret_key

  # ID папки, в которой будет размещен бакет
  folder_id = coalesce(local.folder_id, data.yandex_client_config.client.folder_id) # ID folder в Yandex Cloud

  # Указываем зависимость от ресурса IAM-члена, который должен быть создан до бакета
  depends_on = [
    yandex_resourcemanager_folder_iam_member.sa-admin-s3,
  ]
}

# Вывод ключа доступа для бакета (с чувствительным значением)
output "access_key_for_filestore_bucket" {
  # Описание вывода
  description = "access_key filestore_bucket"

  # Значение для вывода (ключ доступа к бакету)
  value       = yandex_storage_bucket.filestore.access_key

  # Указание, что выводимое значение чувствительно
  sensitive   = true
}

# Вывод секретного ключа для бакета (с чувствительным значением)
output "secret_key_for_filestore_bucket" {
  # Описание вывода
  description = "secret_key filestore_bucket"

  # Значение для вывода (секретный ключ для бакета)
  value       = yandex_storage_bucket.filestore.secret_key

  # Указание, что выводимое значение чувствительно
  sensitive   = true
}

s3_nodestore.tf

Скрытый текст
# Создание статического ключа доступа для сервисного аккаунта
resource "yandex_iam_service_account_static_access_key" "nodestore_bucket_key" {
  # Привязка к существующему сервисному аккаунту
  service_account_id = yandex_iam_service_account.sa-s3.id

  # Описание ключа доступа
  description = "static access key for object storage"
}

# Создание бакета для хранения объектов
resource "yandex_storage_bucket" "nodestore" {
  # Имя бакета, которое определено в локальной переменной
  bucket = local.nodestore_bucket

  # Важно: команда sentry cleanup не удаляет файлы, хранящиеся во внешнем хранилище, таком как GCS или S3.
  # https://develop.sentry.dev/self-hosted/experimental/external-storage/
  # Правило жизненного цикла объектов в бакете
  lifecycle_rule {
    # Уникальный идентификатор правила
    id      = "delete-after-30-days"
    # Флаг, указывающий, что правило активно
    enabled = true

    # Параметры истечения срока хранения объектов
    expiration {
      # Объекты будут автоматически удаляться через 30 дней после загрузки
      days = 30
    }
  }

  # Привязка статического ключа доступа (access_key) и секретного ключа (secret_key)
  access_key = yandex_iam_service_account_static_access_key.nodestore_bucket_key.access_key
  secret_key = yandex_iam_service_account_static_access_key.nodestore_bucket_key.secret_key

  # Идентификатор папки, в которой будет создан бакет
  folder_id = coalesce(local.folder_id, data.yandex_client_config.client.folder_id) # ID folder в Yandex Cloud

  # Зависимость от другого ресурса, чтобы этот бакет был создан после предоставления прав сервисному аккаунту
  depends_on = [
    yandex_resourcemanager_folder_iam_member.sa-admin-s3,
  ]
}

# Вывод ключа доступа для бакета, чтобы другие ресурсы могли его использовать
output "access_key_for_nodestore_bucket" {
  # Описание, что это ключ доступа
  description = "access_key nodestore_bucket"

  # Значение — это ключ доступа, привязанный к бакету
  value = yandex_storage_bucket.nodestore.access_key

  # Указание, что это чувствительное значение, и его не следует показывать в логах
  sensitive = true
}

# Вывод секретного ключа для бакета
output "secret_key_for_nodestore_bucket" {
  # Описание, что это секретный ключ
  description = "secret_key nodestore_bucket"

  # Значение — это секретный ключ, привязанный к бакету
  value = yandex_storage_bucket.nodestore.secret_key

  # Указание, что это чувствительное значение, и его не следует показывать в логах
  sensitive = true
}

s3_service_account.tf

# Создание сервисного аккаунта в Yandex IAM
resource "yandex_iam_service_account" "sa-s3" {
  # Имя сервисного аккаунта
  name = "sa-test-apatsev"
}

# Присваивание роли IAM для сервисного аккаунта
resource "yandex_resourcemanager_folder_iam_member" "sa-admin-s3" {
  # Идентификатор папки, в которой будет назначена роль
  folder_id = coalesce(local.folder_id, data.yandex_client_config.client.folder_id) # ID folder в Yandex Cloud

  # Роль, которую мы назначаем сервисному аккаунту
  role      = "storage.admin"

  # Сервисный аккаунт, которому будет назначена роль
  member    = "serviceAccount:${yandex_iam_service_account.sa-s3.id}"
}

templatefile.tf

Скрытый текст
# Ресурс null_resource используется для выполнения локальной команды,
# генерирующей файл конфигурации Sentry на основе шаблона
resource "null_resource" "write_sentry_config" {
  provisioner "local-exec" {
    # Команда записывает сгенерированную строку (YAML) в файл values_sentry.yaml
    command = "echo '${local.sentry_config}' > values_sentry.yaml"
  }

  triggers = {
    # Триггер перезапуска ресурса при изменении содержимого values_sentry.yaml.tpl
    sentry_config = local.sentry_config
  }
}

locals {
  # Локальная переменная с конфигурацией Sentry, генерируемая из шаблона values_sentry.yaml.tpl
  sentry_config = templatefile("values_sentry.yaml.tpl", {
    # Пароль администратора Sentry
    sentry_admin_password  = local.sentry_admin_password

    # Email пользователя-администратора
    user_email     = "admin@sentry.apatsev.org.ru"

    # URL системы Sentry
    # В этом коде не стал делать переменные чтобы не усложнять код
    system_url     = "http://sentry.apatsev.org.ru" # TODO в след посте использовать переменную

    # Включение/отключение Nginx
    nginx_enabled  = false

    # Использование Ingress для доступа к Sentry
    ingress_enabled = true

    # Имя хоста, используемого Ingress
    # В этом коде не стал делать переменные чтобы не усложнять код
    ingress_hostname = "sentry.apatsev.org.ru" # TODO в след посте использовать переменную

    # Имя класса Ingress-контроллера
    ingress_class_name = "nginx"

    # Стиль регулярных путей в Ingress
    ingress_regex_path_style = "nginx"

    # Аннотации Ingress для настройки nginx
    ingress_annotations = {
      proxy_body_size = "200m"          # Максимальный размер тела запроса
      proxy_buffers_number = "16"       # Количество буферов
      proxy_buffer_size = "32k"         # Размер каждого буфера
    }

    # Настройки S3-хранилища для файлового хранилища (filestore)
    filestore = {
      s3 = {
        accessKey = yandex_storage_bucket.filestore.access_key
        secretKey  = yandex_storage_bucket.filestore.secret_key
        bucketName  = yandex_storage_bucket.filestore.bucket
      }
    }

    # Настройки S3-хранилища для хранения событий (nodestore)
    nodestore = {
      s3 = {
        accessKey = yandex_storage_bucket.nodestore.access_key
        secretKey  = yandex_storage_bucket.nodestore.secret_key
        bucketName  = yandex_storage_bucket.nodestore.bucket
      }
    }

    # Отключение встроенного PostgreSQL, использование внешнего
    postgresql_enabled = false

    # Настройки подключения к внешнему PostgreSQL
    external_postgresql = {
      password = local.postgres_password
      host     = "c-${yandex_mdb_postgresql_cluster.postgresql_cluster.id}.rw.mdb.yandexcloud.net"
      port     = 6432
      username = yandex_mdb_postgresql_user.postgresql_user.name
      database = yandex_mdb_postgresql_database.postgresql_database.name
    }

    # Отключение встроенного Redis, использование внешнего
    redis_enabled = false

    # Настройки подключения к внешнему Redis
    external_redis = {
      password = local.redis_password
      host     = "c-${yandex_mdb_redis_cluster.sentry.id}.rw.mdb.yandexcloud.net"
      port     = 6380 # 6380 — если используется SSL, иначе 6379
    }

    # Настройки внешнего Kafka
    external_kafka = {
      cluster = [
        # Получение всех узлов Kafka с ролью "KAFKA"
        for host in yandex_mdb_kafka_cluster.sentry.host : {
          host = host.name
          port = 9091 # 9091 — если используется SSL, иначе 9092
        } if host.role == "KAFKA"
      ]

      # Настройки аутентификации SASL
      sasl = {
        mechanism = "SCRAM-SHA-512"
        username  = local.kafka_user
        password  = local.kafka_password
      }

      # Настройки безопасности Kafka
      security = {
        protocol = "SASL_SSL" # Использовать SASL_SSL (или SASL_PLAINTEXT при отсутствии SSL)
      }
    }

    # Отключение встроенного Kafka
    kafka_enabled = false

    # Отключение встроенного Zookeeper
    zookeeper_enabled = false

    # Отключение встроенного Clickhouse, использование внешнего
    clickhouse_enabled = false

    # Настройки подключения к внешнему Clickhouse
    external_clickhouse = {
      password = local.clickhouse_password
      host     = yandex_mdb_clickhouse_cluster.sentry.host[0].fqdn
      database = one(yandex_mdb_clickhouse_cluster.sentry.database[*].name)
      httpPort = 8123
      tcpPort  = 9000
      username = local.clickhouse_user
    }
  })
}

values_sentry.yaml.tpl

Скрытый текст
# Пользовательская конфигурация для Sentry
user:
  password: "${sentry_admin_password}"  # Пароль администратора Sentry
  email: "${user_email}"                # Email администратора

# Системная информация
system:
  url: "${system_url}"  # URL-адрес системы

# Контейнерные образы компонентов Sentry
images:
  sentry:
    repository: ghcr.io/patsevanton/ghcr-sentry-custom-images  # Кастомный образ Sentry
  snuba:
    repository: ghcr.io/patsevanton/ghcr-snuba-custom-images   # Кастомный образ Snuba
  relay:
    repository: ghcr.io/patsevanton/ghcr-relay-custom-images   # Кастомный образ Relay

# Настройка NGINX
nginx:
  enabled: ${nginx_enabled}  # Включен ли встроенный NGINX

# Настройка ingress-контроллера
ingress:
  enabled: ${ingress_enabled}                     # Включение ingress
  hostname: "${ingress_hostname}"                 # Хостнейм для доступа
  ingressClassName: "${ingress_class_name}"       # Класс ingress-контроллера
  regexPathStyle: "${ingress_regex_path_style}"   # Использование регулярных выражений в путях
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "${ingress_annotations.proxy_body_size}"  # Максимальный размер тела запроса
    nginx.ingress.kubernetes.io/proxy-buffers-number: "${ingress_annotations.proxy_buffers_number}"  # Количество буферов
    nginx.ingress.kubernetes.io/proxy-buffer-size: "${ingress_annotations.proxy_buffer_size}"        # Размер буфера

# Настройки файлового хранилища
filestore:
  backend: "s3"  # Тип backend для хранения файлов — S3
  s3:
    accessKey: "${filestore.s3.accessKey}"          # Access Key от S3
    secretKey: "${filestore.s3.secretKey}"          # Secret Key от S3
    region_name: ru-central1                        # Регион — Яндекс.Облако
    bucketName: "${filestore.s3.bucketName}"        # Название бакета
    endpointUrl: "https://storage.yandexcloud.net"  # Endpoint для доступа к S3
    location: "debug-files"                         # Папка для хранения debug-файлов

# Настройки NODESTORE хранилища
config:
  sentryConfPy: |
    SENTRY_NODESTORE = "sentry_s3_nodestore.backend.S3NodeStorage"
    SENTRY_NODESTORE_OPTIONS = {
        "bucket_name": "${nodestore.s3.bucketName}",
        "region": "ru-central1",
        "endpoint": "https://storage.yandexcloud.net",
        "aws_access_key_id": "${nodestore.s3.accessKey}",
        "aws_secret_access_key": "${nodestore.s3.secretKey}",
    }

# Встроенная PostgreSQL база данных
postgresql:
  enabled: ${postgresql_enabled}  # Использовать ли встроенный PostgreSQL не для NodeStore, а для нужд самой Sentry

# Конфигурация внешней PostgreSQL базы данных
externalPostgresql:
  password: "${external_postgresql.password}"  # Пароль БД
  host: "${external_postgresql.host}"          # Хост БД
  port: ${external_postgresql.port}            # Порт
  username: "${external_postgresql.username}"  # Имя пользователя
  database: "${external_postgresql.database}"  # Название БД
  sslMode: require                             # Добавляем если нужен SSL, если SSL не нужен удаляем эту строку

# Встроенный Redis
redis:
  enabled: ${redis_enabled}  # Включить ли встроенный Redis

# Подключение к внешнему Redis
externalRedis:
  password: "${external_redis.password}"  # Пароль Redis
  host: "${external_redis.host}"          # Хост Redis
  port: ${external_redis.port}            # Порт Redis
  ssl: true                               # Добавляем если нужен SSL, если SSL не нужен удаляем эту строку

# Внешний кластер Kafka
externalKafka:
  cluster:
%{ for kafka_host in external_kafka.cluster ~}
    - host: "${kafka_host.host}"         # Хост Kafka брокера
      port: ${kafka_host.port}           # Порт Kafka брокера
%{ endfor }
  sasl:
    mechanism: "${external_kafka.sasl.mechanism}"  # Механизм аутентификации (например, PLAIN, SCRAM)
    username: "${external_kafka.sasl.username}"    # Имя пользователя Kafka
    password: "${external_kafka.sasl.password}"    # Пароль Kafka
  security:
    protocol: "${external_kafka.security.protocol}"  # Протокол безопасности (например, SASL_SSL, SASL_PLAINTEXT)

# Встроенный кластер Kafka
kafka:
  enabled: ${kafka_enabled}  # Включить встроенный Kafka

# Встроенный ZooKeeper
zookeeper:
  enabled: ${zookeeper_enabled}  # Включить встроенный ZooKeeper

# Встроенный Clickhouse
clickhouse:
  enabled: ${clickhouse_enabled}  # Включить встроенный Clickhouse

# Подключение к внешнему Clickhouse
externalClickhouse:
  password: "${external_clickhouse.password}"      # Пароль
  host: "${external_clickhouse.host}"              # Хост
  database: "${external_clickhouse.database}"      # Название БД
  httpPort: ${external_clickhouse.httpPort}        # HTTP-порт
  tcpPort: ${external_clickhouse.tcpPort}          # TCP-порт
  username: "${external_clickhouse.username}"      # Имя пользователя

Кастомный image

Проект выглядит так

├── ca-certs
 │   └── yandex-ca.crt
 ├── Dockerfile
 ├── enhance-image.sh

Dockerfile

ARG SENTRY_IMAGE
FROM ${SENTRY_IMAGE}

COPY ca-certs/*.crt /usr/local/share/ca-certificates/
COPY enhance-image.sh /usr/src/sentry/

RUN if [ -s /usr/src/sentry/enhance-image.sh ]; then \
    /usr/src/sentry/enhance-image.sh; \
fi

RUN if [ -s /usr/src/sentry/requirements.txt ]; then \
    echo "sentry/requirements.txt is deprecated, use sentry/enhance-image.sh - see https://github.com/getsentry/self-hosted#enhance-sentry-image"; \
    pip install -r /usr/src/sentry/requirements.txt; \
fi

enhance-image.sh

#!/bin/bash

pip install https://github.com/pavels/sentry-s3-nodestore/releases/download/v1.0.3/sentry-s3-nodestore-1.0.3.tar.gz
for c in $(ls -1 /usr/local/share/ca-certificates/)
do
    cat /usr/local/share/ca-certificates/$c >> $(python3 -m certifi) && echo >> $(python3 -m certifi)
done
update-ca-certificates

Github action .github/workflows/build.yml

name: Build and Push Sentry Docker Images

on:
  push:
    branches:
      - main
  workflow_dispatch:

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:
    strategy:
      matrix:
        SENTRY_VERSION: [25.2.0, 25.3.0]
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build Docker image
        run: |
          docker build \
            --build-arg SENTRY_IMAGE=getsentry/sentry:${{ matrix.SENTRY_VERSION }} \
            -t $REGISTRY/${{ env.IMAGE_NAME }}:${{ matrix.SENTRY_VERSION }} .

      - name: Push Docker image
        run: |
          docker push $REGISTRY/${{ env.IMAGE_NAME }}:${{ matrix.SENTRY_VERSION }}
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
+9
Комментарии2

Публикации

Работа

DevOps инженер
31 вакансия

Ближайшие события