IaC Week: уроки управления большой инфраструктурой Масштабные проекты в Terraform раскрывают истинную сложность управления инфраструктурой. Недостаточно просто писать код — нужна целая система практик и процессов. Опыт реальных проектов показывает несколько критических моментов.Первый — изоляция компонентов через отдельные state-файлы. База данных живет отдельно от сети, сеть — отдельно от вычислительных ресурсов. Это не только упрощает откат изменений, но и защищает от каскадных сбоев при обновлениях.Второй — строгая система версионирования. Каждый модуль получает свой тег по semver, каждое изменение проходит через пайплайн с тестами. State-файлы хранятся в Object Storage с версионированием и репликацией между регионами.Третий — автоматизация всех рутинных операций. План изменений формируется автоматически, проверяется линтером и security-сканером, а после ревью деплоится в production без ручных действий. Человеческий фактор исключен везде, где это возможно.В итоге все сводится к базовому принципу: инфраструктурный код должен быть таким же надежным и поддерживаемым, как прикладной. Только тогда можно говорить о настоящем Infrastructure as Code.🏴☠️ @happy_devops
Happy Devops — сообщество адекватных инженеров
@happy_devops
Сообщество адекватных инженеров | Все про DevOps и эксплуатацию.Культура, инструменты, подходы и решенияЖиво общаемся (чат): https://t.me/+eNGNnbY_2mVkZTEyПо всем вопросам в бота: @HDFeedBackBotWeb: https://happydevops.ru
Похожие каналы
Все →Последние посты
Best practices для масштабных проектов: принципы здоровой инфраструктуры Успешные инфраструктурные проекты строятся не на конкретных технологиях, а на фундаментальных принципах. Масштабные системы требуют особого внимания к деталям и проверенных практик, которые помогают держать сложность под контролем. Стабильность таких систем обеспечивается сочетанием архитектурных решений, процессов и инструментов.Структура проекта начинается с четкой организации кода. Монорепозиторий с модулями разделяется на слои по уровню абстракции, что упрощает навигацию и поддержку:infrastructure/├── modules/ # Переиспользуемые модули│ ├── network/ # Базовая сетевая инфраструктура│ ├── compute/ # Compute ресурсы│ └── storage/ # Хранилища данных├── environments/ # Окружения│ ├── prod/ # Production│ └── stage/ # Staging└── platform/ # Платформенные сервисы ├── monitoring/ # Мониторинг └── security/ # БезопасностьКаждый модуль проходит через строгий процесс тестирования. Unit-тесты проверяют корректность отдельных компонентов, интеграционные — взаимодействие между ними. Автоматизированные тесты запускаются при каждом изменении:module "test_network" { source = "../../modules/network" providers = { yandex = yandex.testing } environment = "test" subnets = { "a" = ["10.0.1.0/24"] "b" = ["10.0.2.0/24"] "c" = ["10.0.3.0/24"] }}resource "test_assertions" "network" { component = "network" check "subnets_created" { description = "Проверка создания подсетей" condition = length(module.test_network.subnet_ids) == 3 } check "network_connectivity" { description = "Проверка связности подсетей" condition = can(module.test_network.test_connectivity) }}Для крупных проектов критична система ограничений и политик. Terraform Sentinel защищает от нарушения корпоративных стандартов и автоматически блокирует небезопасные изменения:policy "enforce_mandatory_tags" { enforcement_level = "hard-mandatory"
Автоматизация развертывания: Пайплайны Terraform без компромиссов Когда инфраструктурой занимается команда, ручной запуск terraform apply превращается в потенциальную катастрофу. Даже небольшая ошибка может привести к непредсказуемым последствиям, особенно в масштабных проектах. Полноценный CI/CD для инфраструктурного кода становится не просто удобством, а необходимостью. Автоматизация пайплайнов снижает риск человеческого фактора, ускоряет развертывание и обеспечивает прозрачность процессов.Структура базового пайплайнаТипичный пайплайн для Terraform включает четыре ключевых этапа:1. Линтинг — проверка стиля и выявление ошибок в коде.2. Валидация — гарантирует, что конфигурация корректна и соответствует стандартам безопасности.3. Планирование изменений — создаёт план, который можно проверить перед применением.4. Применение — финальный этап, на котором изменения внедряются в инфраструктуру.Пример базового пайплайна на GitLab CI:variables: TF_ROOT: ${CI_PROJECT_DIR}/terraform TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/statestages: - validate - plan - applyfmt: stage: validate script: - cd ${TF_ROOT} - terraform fmt -check -recursive - tflint --config=.tflint.hclvalidate: stage: validate script: - cd ${TF_ROOT} - terraform init - terraform validate - checkov -d . --framework terraformДвухступенчатый процесс для безопасностиДля минимизации рисков используется двухступенчатый процесс: 1. Планирование: план изменений публикуется в Merge Request для ревью.2. Применение: изменения внедряются только после проверки и подтверждения.plan: stage: plan script: - cd ${TF_ROOT} - terraform plan -out=plan.tfplan artifacts: paths: - ${TF_ROOT}/plan.tfplan expire_in: 1 weekapply: stage: apply script: - cd ${TF_ROOT} - terraform apply plan.tfplan dependencies: - plan rules: - if: $CI_COMMIT_BRANCH == "main" when: manualЗащита секретов и управление доступомБезопасность — ключевой аспект. Секреты
Управление состоянием в Terraform: разделяй и властвуй ⚡️Состояние инфраструктуры в больших проектах становится узким местом при масштабировании команд и сервисов. Remote state решает проблемы с блокировками и конкурентным доступом, но требует продуманной структуры. Главный принцип — разделение state-файлов по четким границам ответственности.В основе грамотного управления состоянием лежит принцип разделения state-файлов по логическим границам. Каждый state-файл описывает независимый компонент инфраструктуры. Такой подход уменьшает риск конфликтов при параллельной работе нескольких команд:# network/main.tfterraform { backend "s3" { bucket = "terraform-states" key = "network/terraform.tfstate" endpoint = "storage.yandexcloud.net" }}# databases/main.tfterraform { backend "s3" { bucket = "terraform-states" key = "databases/terraform.tfstate" endpoint = "storage.yandexcloud.net" }}Для обмена данными между состояниями используется data source terraform_remote_state. Сетевые настройки из одного state становятся доступны другим компонентам. Это позволяет избежать жесткой связанности между модулями и упрощает поддержку кода:data "terraform_remote_state" "network" { backend = "s3" config = { bucket = "terraform-states" key = "network/terraform.tfstate" endpoint = "storage.yandexcloud.net" }}resource "yandex_mdb_postgresql_cluster" "postgres" { name = "prod-postgres" environment = "PRODUCTION" network_id = data.terraform_remote_state.network.outputs.network_id config { version = "15" resources { resource_preset_id = "s3-c2-m8" disk_size = 100 } }}При работе с секретами state-файл шифруется на уровне Object Storage через KMS. Ключ доступа выдается только членам инфраструктурной команды. Дополнительный уровень защиты обеспечивает audit log всех операций с state-файлом:terraform { backend "s3" { bucket = "terraform-states" key = "secrets/terraform.tfstate" endpoint
Версионирование инфраструктуры: практики безопасных изменений 📦Современная инфраструктура требует продуманного подхода к версионированию. Неконтролируемые изменения в Terraform приводят к простоям сервисов и потере данных. Правильно выстроенное версионирование позволяет избежать этих проблем.Базовый уровень версионирования — git-тэги для каждого модуля с семантическим версионированием. Мажорная версия растет при несовместимых изменениях, минорная — при добавлении фич, патч — при исправлении ошибок:module "web_cluster" { source = "git::https://github.com/company/tf-modules.git//web-cluster?ref=v2.3.1" cluster_name = "prod-web" instance_count = 5 zone_id = "Z2FDTNDATAQYW2"}История изменений state-файла хранится с помощью версионирования S3. По умолчанию Terraform сохраняет только последнюю версию, поэтому версионирование включается на уровне бакета с правилами очистки старых версий:resource "aws_s3_bucket" "terraform_state" { bucket = "company-terraform-state" versioning { enabled = true } lifecycle_rule { enabled = true noncurrent_version_expiration { days = 90 } abort_incomplete_multipart_upload_days = 7 }}Отдельные ветки для каждого окружения с изолированными pipeline'ами позволяют тестировать изменения безопасно. Новые версии модулей проходят проверку в staging перед деплоем в production. При обнаружении проблем revert коммита автоматически возвращает предыдущую версию через CI/CD.Критичные изменения требуют blue-green deployment. Новая инфраструктура разворачивается параллельно с действующей, трафик переключается постепенно. В случае проблем быстрый откат происходит через DNS:resource "aws_route53_record" "www" { zone_id = aws_route53_zone.primary.zone_id name = "www.example.com" type = "A" alias { name = var.environment == "blue" ? aws_lb.blue.dns_name : aws_lb.green.dns_name zone_id = var.environment == "blue" ? aws_lb.blue.zone_id : aws_lb.green.zone_id evaluate_target_health =
IaC Week: Управляем инфраструктурным кодом как профессионалы Эта неделя посвящена полной перезагрузке подходов к Infrastructure as Code (IaC). Мы разберём, как эффективно версионировать инфраструктуру, управлять состоянием, автоматизировать развертывание и внедрять лучшие практики для масштабных проектов. А начнём с главного вызова — как держать Terraform под контролем, когда проект разрастается до гигантских масштабов.Когда инфраструктурный код выходит из-под контроляКрупные проекты неизбежно сталкиваются с проблемой масштабирования инфраструктурного кода. В одном из наших кейсов код вырос до 50 тысяч строк. Это быстро привело к проблемам: потере структуры, сложностям в управлении и росту риска ошибок. Решение пришло через переосмысление подходов к управлению инфраструктурой.Workspace'ы: отказ от копирования конфигурацийПервым шагом стало использование workspace'ов для изоляции окружений. Вместо устаревшего копирования terraform.tfvars мы перенесли все переменные в код. Такой подход упрощает управление и снижает вероятность ошибок:workspace_config = { production = { instance_type = "t3.large" min_size = 3 max_size = 10 environment = "prod" backup_retention = 30 } staging = { instance_type = "t3.small" min_size = 1 max_size = 3 environment = "stage" backup_retention = 7 }}locals { config = workspace_config[terraform.workspace]}Теперь изменения для каждого окружения четко определены, а переключение между ними стало безопасным и простым.Надёжное хранение состоянияДля хранения state-файлов мы выбрали S3 с включённым версионированием и блокировками через DynamoDB. Это предотвращает одновременное изменение state и защищает данные от случайных повреждений. Более того, мы добавили репликацию бакета в другой регион, чтобы обезопасить инфраструктуру даже в случае полного сбоя одного из регионов AWS:terraform { backend "s3" { bucket = "terraform-state-company" key = "i
Отказоустойчивость базы данных проверяется не в момент настройки репликации, а в момент аварии. И часто в самый неподходящий момент — посреди ночи или во время пиковой нагрузки. Поделюсь реальными историями и разбором полетов.Типичный сценарий: праздничная распродажа, нагрузка на пике, и тут primary-база падает. Автоматический failover звучит заманчиво, но реальность сложнее. В одном проекте автофейловер сработал при кратковременном сетевом сбое, поднял новый primary, а когда связь восстановилась — в системе оказалось два мастера. Итог: split-brain и потерянные транзакции.Теперь в критичных системах используем ручной failover с Patroni. Конфигурация выглядит так:scope: postgres-clusternamespace: /db/name: postgres1restapi: listen: 0.0.0.0:8008 connect_address: 10.0.0.1:8008postgresql: listen: 0.0.0.0:5432 connect_address: 10.0.0.1:5432 data_dir: /data/postgres bin_dir: /usr/lib/postgresql/14/bin parameters: max_connections: 100 shared_buffers: 4GB wal_level: replica hot_standby: "on"Каскадная репликация помогает снизить нагрузку на primary. Вместо десяти реплик, висящих на мастере, строим дерево: пара реплик первого уровня раздает WAL остальным. В PostgreSQL это настраивается через primary_conninfo:-- На реплике первого уровняprimary_conninfo = 'host=10.0.0.1 port=5432'-- На реплике второго уровняprimary_conninfo = 'host=10.0.0.2 port=5432'Географически распределенные системы добавляют веселья. Латенция между дата-центрами может достигать сотен миллисекунд. Синхронная репликация в таких условиях убивает производительность. На практике работает схема с локальным кластером в каждом регионе и асинхронной репликацией между регионами.Мониторинг репликации — отдельное искусство. Следим за метриками через Prometheus:- job_name: 'postgres_exporter' static_configs: - targets: ['10.0.0.1:9187'] metrics_path: /metrics params: query: - 'pg_replication_lag' - 'pg_wal_activity'И обязательно тестируем failover. Регулярно. По графику. С записью
Репликация данных — основа отказоустойчивости в современных системах. База данных без реплик похожа на сервер без бэкапов: всё работает отлично, пока не случится катастрофа. А потом становится поздно.Primary-Secondary архитектура — самый распространённый подход. Primary принимает все изменения и пересылает их на реплики. Secondary работают на чтение и готовы подхватить нагрузку при отказе мастера. Звучит просто, но дьявол в деталях.Синхронная репликация гарантирует, что данные попали на реплику до подтверждения записи. Транзакция не завершится, пока secondary не ответит "данные у меня". Надёжно, но медленно — каждая запись ждёт ответа от реплики. В PostgreSQL это выглядит так:ALTER SYSTEM SET synchronous_commit TO 'on';ALTER SYSTEM SET synchronous_standby_names TO 'replica1';Асинхронная репликация работает в фоне. Primary подтверждает транзакцию сразу, а secondary догоняют когда смогут. Быстро, но есть риск потери данных при падении мастера. MongoDB по умолчанию использует асинхронную репликацию:{w: 1, j: true} // Ждём запись только на primary{w: 'majority', j: true} // Ждём запись на большинство нодMulti-master репликация позволяет писать в любую ноду кластера. Звучит заманчиво, но порождает проблему конфликтов. Две ноды могут одновременно изменить одни и те же данные. Галера-кластер для MySQL решает это через сертификацию транзакций — узлы договариваются о порядке изменений.Отставание реплик — главная метрика здоровья репликации. В PostgreSQL следим за lag в pg_stat_replication, в MongoDB — за replication lag в rs.status(). Если реплика отстает больше определенного порога — пора разбираться в причинах.Мониторинг критичен. Нужно следить не только за отставанием, но и за статусом WAL (Write Ahead Log) архивов, свободным местом на дисках реплик и состоянием сетевого соединения между узлами. Один пропущенный WAL сегмент — и придется переинициализировать реплику с нуля.А еще репликация требует внимательной работы с транзакциями. Длинные транзакции на primary мешают очи
Переход на партиционированные таблицы в боевой системе похож на замену колес на едущей машине. Один неверный шаг — и вся система встанет. Разберем, как провести миграцию безопасно и что делать с гигантскими объемами данных.Вот реальный кейс из практики. Таблица с данными о транзакциях весила 4 ТБ и росла на 50 ГБ в день. Запросы за последний месяц работали быстро благодаря индексам, но исторические отчеты могли считаться часами. Решение — партиционирование по месяцам.Первый шаг — создание структуры:CREATE TABLE transactions_new ( id bigint, created_at timestamp, amount decimal, -- другие поля) PARTITION BY RANGE (created_at);-- Создаем партиции для каждого месяцаCREATE TABLE transactions_2024_01 PARTITION OF transactions_new FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');Дальше начинается самое сложное — перенос данных. Прямой INSERT SELECT на таких объемах заблокирует таблицу на часы. Правильный путь — батчинг с параллельной обработкой:-- Функция для переноса данных за один деньCREATE OR REPLACE FUNCTION migrate_day(day_start date) RETURNS integer AS $$BEGIN RETURN WITH moved AS ( DELETE FROM transactions WHERE created_at >= day_start AND created_at < day_start + interval '1 day' RETURNING * ) INSERT INTO transactions_new SELECT * FROM moved;END;$$ LANGUAGE plpgsql;-- Запускаем параллельно для разных днейSELECT migrate_day(day) FROM generate_series('2023-01-01'::date, '2024-01-01', '1 day') day;Во время миграции критично следить за нагрузкой на диски и CPU. Утилита pg_stat_progress_copy показывает прогресс операций. А автовакуум будет очищать старые версии строк, не давая таблице разрастаться.Отдельная история — обслуживание партиций. Старые данные можно архивировать, перенося целые партиции на медленные диски:-- Перемещаем старую партицию на другое табличное пространствоALTER TABLE transactions_2023_01 SET TABLESPACE cold_storage;-- Отключаем автовакуум для старых партицийALTER TABLE transactions_2023_01 SET
MongoDB, PostgreSQL и Elasticsearch предлагают разные подходы к партиционированию. И дело не только в терминологии — каждая база данных реализует его по-своему, со своими преимуществами и ограничениями.В PostgreSQL после версии 10.0 появилось декларативное партиционирование. Создаём основную таблицу, указываем стратегию разделения, и база сама распределяет данные по дочерним таблицам. Партиции наследуют структуру родительской таблицы, но могут хранить данные на разных табличных пространствах. Под капотом PostgreSQL использует ограничения и триггеры для маршрутизации данных.MongoDB строит партиционирование на концепции шардинга. Каждый шард — отдельный сервер или набор реплик. Данные распределяются по шардам на основе ключа. Интересная фишка — зоны шардинга. Можно связать диапазоны ключей с конкретными шардами и тем самым контролировать, где живут данные. Балансировщик следит за равномерным распределением нагрузки.В Elasticsearch партиционирование встроено изначально. Каждый индекс разбивается на шарды, которые распределяются по узлам кластера. Количество шардов задаётся при создании индекса и не меняется, но можно использовать шаблоны индексов для автоматического создания новых индексов по времени или другим критериям.ClickHouse особенно интересно подходит к партиционированию аналитических данных. Он хранит партиции как отдельные директории на диске, что упрощает работу с ними. Можно замораживать старые партиции, перемещать их на другие диски или даже сервера. А встроенная система материализованных представлений автоматически поддерживает агрегаты по партициям.Главный подводный камень во всех базах — запросы, которые работают с несколькими партициями. PostgreSQL приходится объединять данные из разных таблиц, MongoDB собирает результаты со всех затронутых шардов, а Elasticsearch балансирует нагрузку между узлами. Чем больше партиций затрагивает запрос, тем сложнее его выполнить.И везде есть свои тонкости при работе с индексами. В PostgreSQL индексы создаются отдельно