EasyТеория7 min

Основы Infrastructure as Code

Что такое IaC, декларативный vs императивный подход, сравнение инструментов, immutable infrastructure, GitOps workflow и drift detection

Что такое 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):

  1. Открыть AWS Console
  2. Перейти в EC2 > Launch Instance
  3. Выбрать AMI, instance type, VPC, subnet...
  4. Настроить Security Group
  5. Создать key pair
  6. Нажать Launch
  7. Повторить для каждого сервера
  8. Записать в 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

  1. Git = Single Source of Truth -- вся инфраструктура в репозитории
  2. PR Review обязателен -- два глаза на каждое изменение
  3. terraform plan в PR -- видно что изменится ДО применения
  4. Merge = Deploy -- после мёржа автоматически применяется
  5. Manual approval для prod -- production требует ручного подтверждения
  6. 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 Серверы не обновляются, а заменяются

Проверь себя

5 из 8

Какой ключевой принцип GitOps для IaC?

Чем декларативный подход отличается от императивного в IaC?

Что такое Drift Detection в контексте IaC?

Что такое Immutable Infrastructure?

Какую основную проблему решает Infrastructure as Code?