June 11, 2025

Модуль 8: Infrastructure as Code (IaC) — Управление инфраструктурой через код

Курс DevOps для новичков 2025


🏗️ Что такое Infrastructure as Code?

IaC — это практика управления инфраструктурой через код вместо ручной настройки. Вместо того чтобы настраивать сервера через веб-интерфейс или командную строку, вы описываете желаемое состояние инфраструктуры в текстовых файлах и применяете их автоматически.

По данным исследований 2025 года, компании, использующие IaC, развертывают инфраструктуру в 10 раз быстрее и имеют на 50% меньше ошибок конфигурации1.


🎯 Преимущества IaC

Версионирование

Инфраструктура хранится в Git с полной историей изменений. Можно откатиться к любой версии и отследить, кто что изменил.

Воспроизводимость

Одинаковые конфигурации в разных средах (dev, staging, production). Исключается проблема "работает на моей машине".

Автоматизация

Быстрое создание и удаление инфраструктуры. Новая среда разворачивается за минуты, а не дни.

Документация

Код служит живой документацией архитектуры. Всегда актуальное описание инфраструктуры.

Совместная работа

Команда может вместе работать над инфраструктурой через pull requests и code review.


🔧 Terraform — лидер IaC

Terraform — инструмент от HashiCorp, который позволяет описывать инфраструктуру на декларативном языке HCL (HashiCorp Configuration Language). Он поддерживает сотни провайдеров: AWS, Azure, GCP, Kubernetes, Docker и многие другие.

Основные концепции Terraform

Provider — плагин для взаимодействия с API сервиса (AWS, Azure, etc.)
Resource — компонент инфраструктуры (сервер, база данных, сеть)
Data Source — информация о существующих ресурсах
Variable — входные параметры конфигурации
Output — выходные значения конфигурации
State — текущее состояние инфраструктуры


⚙️ Установка Terraform

# Загрузка и установка Terraform
wget https://releases.hashicorp.com/terraform/1.7.0/terraform_1.7.0_linux_amd64.zip
unzip terraform_1.7.0_linux_amd64.zip
sudo mv terraform /usr/local/bin/

# Проверка установки
terraform version

# Включение автодополнения
terraform -install-autocomplete

🛠️ Первый проект на Terraform

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

terraform-project/
├── main.tf          # Основная конфигурация
├── variables.tf     # Переменные
├── outputs.tf       # Выходные значения
├── terraform.tfvars # Значения переменных
└── providers.tf     # Провайдеры

providers.tf

terraform {
  required_version = ">= 1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

variables.tf

variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "us-west-2"
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t3.micro"
}

variable "environment" {
  description = "Environment name"
  type        = string
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}

variable "project_name" {
  description = "Name of the project"
  type        = string
  default     = "devops-course"
}

main.tf

# Data source для получения AMI
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-22.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

# Security Group
resource "aws_security_group" "web_sg" {
  name_prefix = "${var.environment}-${var.project_name}-web-"
  description = "Security group for web servers"

  ingress {
    description = "HTTP"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "HTTPS"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "SSH"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name        = "${var.environment}-${var.project_name}-web-sg"
    Environment = var.environment
    Project     = var.project_name
  }
}

# EC2 Instance
resource "aws_instance" "web_server" {
  ami                    = data.aws_ami.ubuntu.id
  instance_type          = var.instance_type
  vpc_security_group_ids = [aws_security_group.web_sg.id]

  user_data = base64encode(templatefile("${path.module}/user_data.sh", {
    environment = var.environment
    project     = var.project_name
  }))

  tags = {
    Name        = "${var.environment}-${var.project_name}-web-server"
    Environment = var.environment
    Project     = var.project_name
  }
}

# Elastic IP
resource "aws_eip" "web_eip" {
  instance = aws_instance.web_server.id
  domain   = "vpc"

  tags = {
    Name        = "${var.environment}-${var.project_name}-eip"
    Environment = var.environment
  }
}

outputs.tf

output "instance_public_ip" {
  description = "Public IP address of the EC2 instance"
  value       = aws_eip.web_eip.public_ip
}

output "instance_public_dns" {
  description = "Public DNS name of the EC2 instance"
  value       = aws_instance.web_server.public_dns
}

output "security_group_id" {
  description = "ID of the security group"
  value       = aws_security_group.web_sg.id
}

output "ssh_command" {
  description = "SSH command to connect to the instance"
  value       = "ssh -i ~/.ssh/your-key.pem ubuntu@${aws_eip.web_eip.public_ip}"
}

terraform.tfvars

aws_region    = "us-west-2"
instance_type = "t3.micro"
environment   = "dev"
project_name  = "devops-learning"

user_data.sh

#!/bin/bash
apt update
apt install -y nginx docker.io

# Настройка nginx
systemctl start nginx
systemctl enable nginx

# Создание простой страницы
cat > /var/www/html/index.html << EOF
<!DOCTYPE html>
<html>
<head>
    <title>${project} - ${environment}</title>
</head>
<body>
    <h1>Welcome to ${project}!</h1>
    <p>Environment: ${environment}</p>
    <p>Server deployed with Terraform</p>
    <p>Timestamp: $(date)</p>
</body>
</html>
EOF

# Настройка Docker
systemctl start docker
systemctl enable docker
usermod -aG docker ubuntu

🚀 Основные команды Terraform

# Инициализация проекта (загрузка провайдеров)
terraform init

# Форматирование кода
terraform fmt

# Валидация конфигурации
terraform validate

# Планирование изменений (dry-run)
terraform plan

# Планирование с сохранением в файл
terraform plan -out=tfplan

# Применение изменений
terraform apply

# Применение сохраненного плана
terraform apply tfplan

# Просмотр текущего состояния
terraform show

# Список ресурсов в state
terraform state list

# Подробная информация о ресурсе
terraform state show aws_instance.web_server

# Уничтожение инфраструктуры
terraform destroy

# Импорт существующего ресурса
terraform import aws_instance.web_server i-1234567890abcdef0

🛠️ Практические задания

Задание 8.1: Создание базовой инфраструктуры

# 1. Создание проекта
mkdir terraform-aws-demo
cd terraform-aws-demo

# 2. Настройка AWS CLI (если еще не настроен)
aws configure

# 3. Создание файлов конфигурации
# (используйте примеры выше)

# 4. Инициализация и применение
terraform init
terraform plan
terraform apply

# 5. Проверка результата
curl http://$(terraform output -raw instance_public_ip)

Задание 8.2: Модули Terraform

# modules/vpc/main.tf
resource "aws_vpc" "main" {
  cidr_block           = var.cidr_block
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = var.vpc_name
  }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "${var.vpc_name}-igw"
  }
}

resource "aws_subnet" "public" {
  count             = length(var.public_subnet_cidrs)
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.public_subnet_cidrs[count.index]
  availability_zone = var.availability_zones[count.index]

  map_public_ip_on_launch = true

  tags = {
    Name = "${var.vpc_name}-public-${count.index + 1}"
    Type = "public"
  }
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }

  tags = {
    Name = "${var.vpc_name}-public-rt"
  }
}

resource "aws_route_table_association" "public" {
  count          = length(aws_subnet.public)
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}
# modules/vpc/variables.tf
variable "vpc_name" {
  description = "Name of the VPC"
  type        = string
}

variable "cidr_block" {
  description = "CIDR block for VPC"
  type        = string
  default     = "10.0.0.0/16"
}

variable "public_subnet_cidrs" {
  description = "CIDR blocks for public subnets"
  type        = list(string)
  default     = ["10.0.1.0/24", "10.0.2.0/24"]
}

variable "availability_zones" {
  description = "Availability zones"
  type        = list(string)
  default     = ["us-west-2a", "us-west-2b"]
}
# modules/vpc/outputs.tf
output "vpc_id" {
  description = "ID of the VPC"
  value       = aws_vpc.main.id
}

output "public_subnet_ids" {
  description = "IDs of the public subnets"
  value       = aws_subnet.public[*].id
}

output "internet_gateway_id" {
  description = "ID of the Internet Gateway"
  value       = aws_internet_gateway.main.id
}

Использование модуля

# main.tf
module "vpc" {
  source = "./modules/vpc"

  vpc_name             = "my-vpc"
  cidr_block           = "10.0.0.0/16"
  public_subnet_cidrs  = ["10.0.1.0/24", "10.0.2.0/24"]
  availability_zones   = ["us-west-2a", "us-west-2b"]
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
  subnet_id     = module.vpc.public_subnet_ids[0]

  tags = {
    Name = "web-server-in-custom-vpc"
  }
}

🔄 Ansible — управление конфигурациями

Ansible — инструмент автоматизации для управления конфигурациями серверов. В отличие от Terraform, который создает инфраструктуру, Ansible настраивает программное обеспечение.

Установка Ansible

# Ubuntu/Debian
sudo apt update
sudo apt install ansible

# Проверка установки
ansible --version

Inventory файл

# inventory/hosts.yml
all:
  children:
    webservers:
      hosts:
        web1.example.com:
          ansible_host: 192.168.1.10
          ansible_user: ubuntu
        web2.example.com:
          ansible_host: 192.168.1.11
          ansible_user: ubuntu
    databases:
      hosts:
        db1.example.com:
          ansible_host: 192.168.1.20
          ansible_user: ubuntu
      vars:
        db_port: 5432

Playbook для настройки веб-сервера

# playbooks/webserver.yml
---
- name: Configure web servers
  hosts: webservers
  become: yes
  vars:
    nginx_port: 80
    app_name: "devops-demo"
    
  tasks:
    - name: Update apt cache
      apt:
        update_cache: yes
        cache_valid_time: 3600

    - name: Install required packages
      apt:
        name:
          - nginx
          - docker.io
          - git
          - curl
        state: present

    - name: Start and enable nginx
      systemd:
        name: nginx
        state: started
        enabled: yes

    - name: Start and enable docker
      systemd:
        name: docker
        state: started
        enabled: yes

    - name: Add ubuntu user to docker group
      user:
        name: ubuntu
        groups: docker
        append: yes

    - name: Create application directory
      file:
        path: /opt/{{ app_name }}
        state: directory
        owner: ubuntu
        group: ubuntu
        mode: '0755'

    - name: Configure nginx
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/sites-available/{{ app_name }}
      notify: restart nginx

    - name: Enable nginx site
      file:
        src: /etc/nginx/sites-available/{{ app_name }}
        dest: /etc/nginx/sites-enabled/{{ app_name }}
        state: link
      notify: restart nginx

    - name: Remove default nginx site
      file:
        path: /etc/nginx/sites-enabled/default
        state: absent
      notify: restart nginx

    - name: Open firewall for HTTP
      ufw:
        rule: allow
        port: "{{ nginx_port }}"

  handlers:
    - name: restart nginx
      systemd:
        name: nginx
        state: restarted

Шаблон Nginx

# templates/nginx.conf.j2
server {
    listen {{ nginx_port }};
    server_name {{ inventory_hostname }};

    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
}

Запуск Ansible

# Проверка подключения
ansible all -i inventory/hosts.yml -m ping

# Запуск playbook
ansible-playbook -i inventory/hosts.yml playbooks/webserver.yml

# Запуск с ограничением на группу хостов
ansible-playbook -i inventory/hosts.yml playbooks/webserver.yml --limit webservers

# Dry-run (проверка без изменений)
ansible-playbook -i inventory/hosts.yml playbooks/webserver.yml --check

📊 Интеграция Terraform и Ansible

Terraform с Ansible provisioner

resource "aws_instance" "web_server" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type
  key_name      = aws_key_pair.deployer.key_name

  tags = {
    Name = "web-server"
  }

  provisioner "remote-exec" {
    inline = [
      "sudo apt update",
      "sudo apt install -y python3"
    ]

    connection {
      type        = "ssh"
      user        = "ubuntu"
      private_key = file("~/.ssh/id_rsa")
      host        = self.public_ip
    }
  }

  provisioner "local-exec" {
    command = "ansible-playbook -i '${self.public_ip},' --private-key ~/.ssh/id_rsa playbooks/webserver.yml"
  }
}

Генерация Ansible inventory из Terraform

# outputs.tf
output "ansible_inventory" {
  value = templatefile("${path.module}/inventory.tpl", {
    webservers = aws_instance.web_servers[*].public_ip
    databases  = aws_instance.db_servers[*].private_ip
  })
}

resource "local_file" "ansible_inventory" {
  content  = templatefile("${path.module}/inventory.tpl", {
    webservers = aws_instance.web_servers[*].public_ip
    databases  = aws_instance.db_servers[*].private_ip
  })
  filename = "${path.module}/generated_inventory.yml"
}

📖 Полезные ресурсы


✅ Чек-лист модуля

  • Понимаю концепции Infrastructure as Code
  • Установил и настроил Terraform
  • Создал первую инфраструктуру с Terraform
  • Освоил основные команды Terraform
  • Работал с переменными и outputs
  • Создал и использовал Terraform модули
  • Настроил удаленное хранение state
  • Установил и изучил Ansible
  • Создал Ansible playbook для настройки серверов
  • Интегрировал Terraform с Ansible

🚀 Что дальше?

После освоения Infrastructure as Code переходите к Модулю 9: Мониторинг и наблюдаемость для изучения систем мониторинга инфраструктуры и приложений.