Что такое Infrastructure as Code
Infrastructure as Code (IaC) -- это практика управления инфраструктурой через машинно-читаемые конфигурационные файлы, а не через ручные процессы в веб-консолях или CLI-командах.
Проблема ручного управления
Представьте, что ваше PHP/Go приложение работает на нескольких серверах. Каждый сервер настраивался вручную:
Ручное управление:
┌─────────────────────────────────────────────────────┐
│ DevOps-инженер │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ Web1 │ │ Web2 │ │ Web3 │ │
│ │PHP 8.3│ │PHP 8.4│ │PHP 8.4│ │
│ │Nginx │ │Apache │ │Nginx │ │
│ │v1.24 │ │v2.4 │ │v1.25 │ │
│ └──────┘ └──────┘ └──────┘ │
│ │
│ Snowflake servers -- каждый уникален! │
└─────────────────────────────────────────────────────┘
Типичные проблемы ручного управления:
| Проблема | Описание |
|---|---|
| Configuration drift | Серверы со временем расходятся в конфигурации |
| Snowflake servers | Каждый сервер уникален, невозможно воспроизвести |
| Нет аудита изменений | Кто, когда и что изменил -- неизвестно |
| Медленное масштабирование | Каждый новый сервер -- ручная работа |
| Нет тестирования | Изменения сразу в production |
| Нет отката | Откатить изменения крайне сложно |
Как IaC решает эти проблемы
Infrastructure as Code:
┌─────────────────────────────────────────────────────┐
│ Git Repository │
│ ┌──────────────────────┐ │
│ │ infrastructure/ │ │
│ │ ├── main.tf │ │
│ │ ├── variables.tf │ │
│ │ ├── networking.tf │ │
│ │ └── compute.tf │ │
│ └──────────┬───────────┘ │
│ │ │
│ terraform apply │
│ │ │
│ ┌─────────────────┼─────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ Web1 │ │ Web2 │ │ Web3 │ │
│ │PHP 8.4│ │PHP 8.4│ │PHP 8.4│ │
│ │Nginx │ │Nginx │ │Nginx │ │
│ │v1.25 │ │v1.25 │ │v1.25 │ │
│ └──────┘ └──────┘ └──────┘ │
│ │
│ Идентичные серверы из одного кода! │
└─────────────────────────────────────────────────────┘
Преимущества IaC:
- Версионирование -- вся инфраструктура в Git, полный аудит изменений
- Повторяемость -- одна команда создаёт идентичное окружение
- Тестируемость -- можно проверить план до применения
- Самодокументирование -- код IS документация
- Code review -- инфраструктурные изменения проходят PR review
- Масштабирование -- добавить 10 серверов = изменить число в переменной
Пример: вручную vs код
Вручную (AWS Console):
- Открыть AWS Console
- Перейти в EC2 > Launch Instance
- Выбрать AMI, instance type, VPC, subnet...
- Настроить Security Group
- Создать key pair
- Нажать Launch
- Повторить для каждого сервера
- Записать в Wiki (может быть)
Кодом (Terraform):
# Infrastructure for PHP/Go application
resource "aws_instance" "web" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.medium"
subnet_id = aws_subnet.private.id
tags = {
Name = "web-${count.index + 1}"
Environment = "production"
ManagedBy = "terraform"
}
}
resource "aws_security_group" "web" {
name = "web-sg"
description = "Security group for web servers"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
Декларативный vs Императивный подход
Два фундаментально разных способа описать инфраструктуру:
┌────────────────────────────────┬────────────────────────────────┐
│ ДЕКЛАРАТИВНЫЙ │ ИМПЕРАТИВНЫЙ │
│ "Что я хочу получить" │ "Как это сделать" │
├────────────────────────────────┼────────────────────────────────┤
│ │ │
│ resource "aws_instance" "w" { │ #!/bin/bash │
│ ami = "ami-abc123" │ if ! aws ec2 describe... │
│ instance_type = "t3.medium" │ then │
│ } │ aws ec2 run-instances \ │
│ │ --image-id ami-abc123 \ │
│ Terraform определяет: │ --instance-type t3.medium │
│ - Создать если нет │ fi │
│ - Обновить если changed │ │
│ - Удалить если убрано │ Скрипт сам определяет: │
│ │ - Проверить текущее состояние │
│ │ - Принять решение │
│ │ - Выполнить действия │
└────────────────────────────────┴────────────────────────────────┘
Декларативный подход
Вы описываете желаемое состояние (desired state), а инструмент сам определяет, какие шаги нужны для достижения этого состояния.
Terraform (HCL):
# I want 3 servers with these parameters
resource "aws_instance" "app" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.medium"
tags = {
Name = "app-server-${count.index}"
}
}
Pulumi (Go):
package main
import (
"fmt"
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
for i := 0; i < 3; i++ {
_, err := ec2.NewInstance(ctx, fmt.Sprintf("app-server-%d", i), &ec2.InstanceArgs{
Ami: pulumi.String("ami-0c55b159cbfafe1f0"),
InstanceType: pulumi.String("t3.medium"),
Tags: pulumi.StringMap{
"Name": pulumi.Sprintf("app-server-%d", i),
},
})
if err != nil {
return err
}
}
return nil
})
}
Императивный подход
Вы описываете конкретные шаги (how), которые нужно выполнить.
Ansible (YAML + задачи):
# Ansible playbook - sequence of tasks
- name: Configure web servers
hosts: webservers
become: yes
tasks:
- name: Install Nginx
apt:
name: nginx
state: present
- name: Copy config
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx
- name: Install PHP 8.4
apt:
name: php8.4-fpm
state: present
- name: Start services
service:
name: "{{ item }}"
state: started
enabled: yes
loop:
- nginx
- php8.4-fpm
Примечание: Ansible часто называют "декларативным" из-за YAML-синтаксиса, но по сути это императивный инструмент -- он выполняет задачи последовательно и описывает как настроить, а не что должно быть.
AWS CDK (TypeScript) -- императивная обёртка над декларативным CloudFormation
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
export class AppStack extends cdk.Stack {
constructor(scope: cdk.App, id: string) {
super(scope, id);
const vpc = new ec2.Vpc(this, 'AppVpc', {
maxAzs: 2,
});
// Imperative loop, but generates declarative CloudFormation
for (let i = 0; i < 3; i++) {
new ec2.Instance(this, `AppServer${i}`, {
vpc,
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3,
ec2.InstanceSize.MEDIUM
),
machineImage: ec2.MachineImage.latestAmazonLinux2(),
});
}
}
}
Сравнительная таблица инструментов IaC
| Характеристика | Terraform | Pulumi | AWS CDK | CloudFormation | Ansible |
|---|---|---|---|---|---|
| Подход | Декларативный | Декларативный | Декларативный (с императивным API) | Декларативный | Императивный |
| Язык | HCL | Go, Python, TS, C# | TS, Python, Go, Java | JSON/YAML | YAML |
| Multi-cloud | Да | Да | Нет (AWS only) | Нет (AWS only) | Да (через SSH) |
| State | Файл/Remote | Файл/Pulumi Cloud | CloudFormation Stack | Stack | Нет state |
| Learning curve | Средняя | Низкая (знакомый язык) | Средняя | Высокая | Низкая |
| Provisioning | Инфраструктура | Инфраструктура | Инфраструктура | Инфраструктура | Конфигурация |
| Зрелость | Высокая | Средняя | Средняя | Высокая | Высокая |
| Сообщество | Огромное | Растущее | Большое (AWS) | Большое (AWS) | Огромное |
| Drift detection | terraform plan | pulumi preview | cdk diff | Drift detection | Нет (idempotent runs) |
| Цена | Free / Enterprise | Free / Team / Enterprise | Free | Free | Free / Enterprise |
Когда что использовать
┌─────────────────────────────────────────────────────────┐
│ Выбор инструмента IaC │
├─────────────────────────────────────────────────────────┤
│ │
│ Multi-cloud? │
│ ├── Да ──► Terraform или Pulumi │
│ │ ├── Команда знает HCL? ──► Terraform │
│ │ └── Команда знает Go/TS? ──► Pulumi │
│ │ │
│ └── Нет (только AWS) │
│ ├── Нужна глубокая AWS-интеграция? ──► CDK / CF │
│ └── Нет ──► Terraform (переносимость) │
│ │
│ Конфигурация серверов (SSH)? │
│ └── Да ──► Ansible (+ Terraform для инфраструктуры) │
│ │
│ Рекомендация для новых проектов: │
│ Terraform + Ansible = инфраструктура + конфигурация │
└─────────────────────────────────────────────────────────┘
Immutable vs Mutable Infrastructure
Mutable Infrastructure (изменяемая)
Серверы обновляются на месте: пакеты обновляются, конфигурации меняются, код деплоится поверх.
Mutable Infrastructure:
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Server │ │ Server │ │ Server │ │ Server │
│ v1.0 │ ──► │ v1.0 │ ──► │ v1.0 │ ──► │ v1.0 │
│ │ │ +patch │ │ +patch │ │ +patch │
│ │ │ │ │ +hotfix │ │ +hotfix │
│ │ │ │ │ │ │ +config │
│ Day 1 │ │ Week 2 │ │ Month 3 │ │ Year 1 │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
"Что тут
стоит?!"
Проблемы:
- Configuration drift между серверами
- Невоспроизводимость -- нельзя создать идентичную копию
- "Works on my server" -- работает на одном, падает на другом
- Страх обновлений -- "не трогай, оно работает"
Immutable Infrastructure (неизменяемая)
Серверы никогда не обновляются. Вместо обновления создаётся новый сервер с новым образом, а старый уничтожается.
Immutable Infrastructure:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Image │ │ Image │ │ Image │
│ v1.0 │ │ v1.1 │ │ v1.2 │
│ PHP 8.4 │ │ PHP 8.4 │ │ PHP 8.4 │
│ App v1 │ │ App v2 │ │ App v3 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Server │ destroy │ Server │ destroy │ Server │
│ from │ ──────► │ from │ ──────► │ from │
│ v1.0 │ │ v1.1 │ │ v1.2 │
└─────────┘ └─────────┘ └─────────┘
Инструменты для создания образов:
- Packer -- создание AMI, Docker images
- Docker -- контейнерные образы
- Nix -- воспроизводимые сборки
Сравнение подходов
| Критерий | Mutable | Immutable |
|---|---|---|
| Обновление | На месте (apt upgrade, deploy) | Замена целиком (новый образ) |
| Воспроизводимость | Низкая | Полная |
| Откат | Сложный | Переключить на старый образ |
| Configuration drift | Да, со временем | Невозможен |
| Время деплоя | Быстрее (только изменения) | Дольше (новый сервер) |
| Отладка | SSH на сервер | Только логи (нет SSH) |
| Инструменты | Ansible, Chef, Puppet | Terraform + Packer + Docker |
GitOps workflow для IaC
GitOps -- это когда Git является единственным источником правды для инфраструктуры. Все изменения проходят через Pull Request.
GitOps Workflow для IaC:
Developer Git Repository CI/CD Cloud
┌──────┐ ┌──────────────┐ ┌──────────┐ ┌───────┐
│ │──push──► │ feature/ │ │ │ │ │
│ │ │ add-redis │ │ │ │ │
│ │ └──────┬───────┘ │ │ │ │
│ │ │ │ │ │ │
│ │ Create PR │ │ │ │
│ │ │ │ │ │ │
│ │ ┌──────▼───────┐ │ │ │ │
│ │ │ PR Review │────►│terraform │ │ │
│ │ │ │ │ plan │ │ │
│ │ │ + plan output│◄────│ │ │ │
│ │ └──────┬───────┘ │ │ │ │
│ │ │ │ │ │ │
│ │ Merge to main │ │ │ │
│ │ │ │ │ │ │
│ │ ┌──────▼───────┐ │ │ │ │
│ │ │ main branch │────►│terraform │──►│ Apply │
│ │ │ │ │ apply │ │ │
│ │ └──────────────┘ └──────────┘ └───────┘
└──────┘
Ключевые принципы GitOps для IaC
- Git = Single Source of Truth -- вся инфраструктура в репозитории
- PR Review обязателен -- два глаза на каждое изменение
- terraform plan в PR -- видно что изменится ДО применения
- Merge = Deploy -- после мёржа автоматически применяется
- Manual approval для prod -- production требует ручного подтверждения
- No ClickOps -- никаких ручных изменений через консоль
Пример: GitHub Actions для Terraform
# .github/workflows/terraform.yml
name: Terraform
on:
pull_request:
paths:
- 'infrastructure/**'
push:
branches:
- main
paths:
- 'infrastructure/**'
jobs:
plan:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init
working-directory: infrastructure
- name: Terraform Plan
id: plan
run: terraform plan -no-color -out=tfplan
working-directory: infrastructure
- name: Post plan to PR
uses: actions/github-script@v7
with:
script: |
const output = `#### Terraform Plan
\`\`\`
${{ steps.plan.outputs.stdout }}
\`\`\`
`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
});
apply:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
environment: production # Requires manual approval
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init
working-directory: infrastructure
- name: Terraform Apply
run: terraform apply -auto-approve
working-directory: infrastructure
Drift Detection
Drift (дрейф) -- это расхождение между тем, что описано в коде, и тем, что реально существует в облаке. Кто-то зашёл в консоль и изменил Security Group вручную -- возник drift.
Как обнаружить drift
terraform plan -- основной инструмент:
$ terraform plan
# Output shows drift
~ aws_security_group.web
~ ingress {
- cidr_blocks = ["0.0.0.0/0"]
+ cidr_blocks = ["10.0.0.0/8"] # Someone changed this manually!
}
Plan: 0 to add, 1 to change, 0 to destroy.
Инструменты безопасности IaC
Checkov -- статический анализ Terraform-кода:
$ checkov -d infrastructure/
Passed checks: 12, Failed checks: 3
Check: CKV_AWS_24: "Ensure no security groups allow ingress from 0.0.0.0/0 to port 22"
FAILED for resource: aws_security_group.web
File: /main.tf:15-30
Check: CKV_AWS_145: "Ensure that S3 bucket uses customer-managed CMK"
FAILED for resource: aws_s3_bucket.data
File: /storage.tf:1-10
tfsec -- поиск уязвимостей:
$ tfsec infrastructure/
Result: HIGH - Security group rule allows ingress from 0.0.0.0/0
Resource: aws_security_group.web
Location: main.tf:20
Result: MEDIUM - Bucket does not have versioning enabled
Resource: aws_s3_bucket.data
Location: storage.tf:5
Рекомендуемый набор проверок в CI/CD:
┌──────────────────────────────────────────────────────┐
│ IaC Security Pipeline │
├──────────────────────────────────────────────────────┤
│ │
│ 1. terraform fmt -check Format validation │
│ 2. terraform validate Syntax validation │
│ 3. tfsec Security scanning │
│ 4. checkov Compliance checks │
│ 5. infracost Cost estimation │
│ 6. terraform plan Change preview │
│ │
│ Всё в CI → блокирует merge при проблемах │
└──────────────────────────────────────────────────────┘
Пример: PHP/Go приложение -- инфра в коде
Рассмотрим типичное приложение на PHP (Symfony) и Go, работающее в Docker. Как выглядит его инфраструктура в виде кода:
Структура проекта
my-app/
├── api/ # PHP Symfony backend
├── gateway/ # Go API gateway
├── app/ # Frontend (Nuxt)
├── infrastructure/
│ ├── terraform/
│ │ ├── main.tf # Provider, backend config
│ │ ├── variables.tf # Input variables
│ │ ├── outputs.tf # Output values
│ │ ├── networking.tf # VPC, subnets, security groups
│ │ ├── compute.tf # ECS, Fargate tasks
│ │ ├── database.tf # RDS PostgreSQL
│ │ ├── cache.tf # ElastiCache Redis
│ │ └── monitoring.tf # CloudWatch, alarms
│ ├── ansible/
│ │ ├── playbook.yml # Server configuration
│ │ └── roles/
│ └── docker/
│ ├── php/Dockerfile
│ ├── go/Dockerfile
│ └── nginx/Dockerfile
├── docker-compose.yml # Local development
└── Makefile
Локальная разработка (Docker Compose)
# docker-compose.yml - local development
services:
php:
build: ./infrastructure/docker/php
volumes:
- ./api:/app
environment:
DATABASE_URL: postgresql://app:secret@postgres:5432/app_db
gateway:
build: ./infrastructure/docker/go
volumes:
- ./gateway:/app
ports:
- "8080:8080"
postgres:
image: postgres:18
environment:
POSTGRES_DB: app_db
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
pgdata:
Production инфраструктура (Terraform)
# infrastructure/terraform/main.tf
terraform {
required_version = ">= 1.9"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
# Remote state in S3
backend "s3" {
bucket = "my-app-terraform-state"
key = "production/terraform.tfstate"
region = "eu-central-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Project = "my-app"
Environment = var.environment
ManagedBy = "terraform"
}
}
}
Этот код -- начало полной инфраструктуры, которую мы подробно разберём в следующих статьях.
Ключевые термины
| Термин | Определение |
|---|---|
| IaC | Управление инфраструктурой через код |
| Desired State | Желаемое состояние, описанное в конфигурации |
| Drift | Расхождение реального состояния и кода |
| Idempotent | Повторное применение даёт тот же результат |
| Provider | Плагин для работы с API облака |
| State | Файл, хранящий текущее состояние ресурсов |
| Plan | Предпросмотр изменений до применения |
| Apply | Применение изменений к инфраструктуре |
| GitOps | Git как единственный источник правды |
| Immutable | Серверы не обновляются, а заменяются |