Модуль 8: Infrastructure as Code (IaC) — Управление инфраструктурой через код
🏗️ Что такое 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"
}
📖 Полезные ресурсы
- Terraform Documentation — официальная документация
- Terraform Registry — готовые модули и провайдеры
- Ansible Documentation — руководство по Ansible
- YouTube: "Terraform от А до Я" — практический курс по IaC8
- Infrastructure as Code Guide — подробное руководство
✅ Чек-лист модуля
- Понимаю концепции Infrastructure as Code
- Установил и настроил Terraform
- Создал первую инфраструктуру с Terraform
- Освоил основные команды Terraform
- Работал с переменными и outputs
- Создал и использовал Terraform модули
- Настроил удаленное хранение state
- Установил и изучил Ansible
- Создал Ansible playbook для настройки серверов
- Интегрировал Terraform с Ansible
🚀 Что дальше?
После освоения Infrastructure as Code переходите к Модулю 9: Мониторинг и наблюдаемость для изучения систем мониторинга инфраструктуры и приложений.