Terraform — Infrastructure as Code
Цель урока
Сделать полноценный CRUD для задач с правильными HTTP-статусами (201/204/404), обработкой ошибок, и подготовить Task к подключению JPA.
Теория · для собеса
1 Что такое IaC ▸
Infrastructure as Code — описание инфраструктуры (серверы, БД, сети) в виде кода, который можно версионировать, ревьюить, воспроизводить.
2 Terraform ▸
Terraform — самый популярный IaC-инструмент от HashiCorp. Декларативный:
resource "aws_instance" "web" {
ami = "ami-0c55b..."
instance_type = "t3.micro"
}
Что хотим получить — не как. Terraform сам создаст.
3 Provider / Resource / State ▸
- Provider — плагин для конкретного cloud (AWS, GCP, Azure, Kubernetes)
- Resource — то, что создаём (instance, db, s3 bucket)
- State — файл
terraform.tfstateс текущим состоянием инфраструктуры - Plan — что изменится (
terraform plan) - Apply — применить (
terraform apply) - Destroy — удалить всё (
terraform destroy)
---
Практика: улучшаем CRUD
-
1Установка
# macOS brew tap hashicorp/tap brew install hashicorp/tap/terraform # Linux (Ubuntu) wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list sudo apt update && sudo apt install terraform -
2Структура
terraform/ ├── main.tf # главный файл ├── variables.tf # переменные ├── outputs.tf # выходные значения ├── providers.tf # провайдеры └── terraform.tfstate # состояние (создаётся автоматически, .gitignore!) -
3
providers.tfterraform { required_version = ">= 1.9" required_providers { kubernetes = { source = "hashicorp/kubernetes" version = "~> 2.30" } aws = { source = "hashicorp/aws" version = "~> 5.50" } } } provider "kubernetes" { config_path = "~/.kube/config" } provider "aws" { region = "eu-central-1" } -
4
variables.tfvariable "environment" { description = "Environment name (dev, staging, prod)" type = string default = "prod" } variable "app_version" { description = "Application version (Docker image tag)" type = string default = "0.1.0" } variable "db_password" { description = "Database password" type = string sensitive = true # не показывать в выводе } -
5
main.tf— k8s namespace + deploymentresource "kubernetes_namespace" "taskflow" { metadata { name = "taskflow" } } resource "kubernetes_config_map" "app_config" { metadata { name = "taskflow-config" namespace = kubernetes_namespace.taskflow.metadata[0].name } data = { SPRING_PROFILES_ACTIVE = var.environment SPRING_DATASOURCE_URL = "jdbc:postgresql://postgres:5432/taskflow" } } resource "kubernetes_secret" "app_secret" { metadata { name = "taskflow-secret" namespace = kubernetes_namespace.taskflow.metadata[0].name } data = { SPRING_DATASOURCE_PASSWORD = var.db_password } } resource "kubernetes_deployment" "app" { metadata { name = "taskflow" namespace = kubernetes_namespace.taskflow.metadata[0].name labels = { app = "taskflow" } } spec { replicas = 3 selector { match_labels = { app = "taskflow" } } template { metadata { labels = { app = "taskflow" } } spec { container { name = "taskflow" image = "ghcr.io/YOUR_USER/taskflow:${var.app_version}" port { container_port = 8080 } env_from { config_map_ref { name = kubernetes_config_map.app_config.metadata[0].name } } env { name = "SPRING_DATASOURCE_PASSWORD" value_from { secret_key_ref { name = kubernetes_secret.app_secret.metadata[0].name key = "SPRING_DATASOURCE_PASSWORD" } } } resources { limits = { cpu = "500m" memory = "512Mi" } requests = { cpu = "250m" memory = "256Mi" } } } } } } } resource "kubernetes_service" "app" { metadata { name = "taskflow" namespace = kubernetes_namespace.taskflow.metadata[0].name } spec { selector = { app = "taskflow" } port { port = 80 target_port = 8080 } type = "ClusterIP" } } -
6
outputs.tfoutput "namespace" { value = kubernetes_namespace.taskflow.metadata[0].name } output "service_name" { value = kubernetes_service.app.metadata[0].name } -
7Workflow
# Инициализация (скачивает провайдеры) cd terraform/ terraform init # План (что изменится) terraform plan -var="db_password=secret123" # +0 to add, +0 to change, +0 to destroy — без изменений # или: # + kubernetes_namespace.taskflow # + kubernetes_deployment.app # ... # Применить terraform apply -var="db_password=secret123" # Terraform will perform the following actions: # ... # Do you want to perform these actions? yes # Apply complete! # Посмотреть state terraform show # Outputs terraform output # Удалить всё terraform destroy -
8Удалённый state (S3 + DynamoDB для локинга)
terraform { backend "s3" { bucket = "mycompany-terraform-state" key = "taskflow/terraform.tfstate" region = "eu-central-1" dynamodb_table = "terraform-locks" encrypt = true } }⚠️ Хранить state в Git — НЕЛЬЗЯ! В нём пароли, секреты, и онлайн-state кластера. Используй S3/GCS с шифрованием.
-
9Модули (переиспользуемые блоки)
# modules/postgres/main.tf variable "namespace" { type = string } variable "password" { type = string } resource "kubernetes_deployment" "postgres" { # ... } output "service_name" { value = kubernetes_service.postgres.metadata[0].name }# main.tf module "postgres" { source = "./modules/postgres" namespace = kubernetes_namespace.taskflow.metadata[0].name password = var.db_password } -
10Variables через файл
prod.tfvars:environment = "prod" app_version = "0.1.0" db_password = "REPLACE_VIA_CI"terraform apply -var-file=prod.tfvars---
Зачем это на собесе
После урока ты должен уметь ответить на:
terraform apply из репо = тот же кластер, что был месяц назад.5 вопросов на углубление
Раскрой вопрос и нажми «🤔 Хочу разобрать подробнее» — он попадёт в страницу ответов.
Разбор внутри: состояние инфраструктуры, секреты и конфликты.
Разбор внутри: provisioning инфраструктуры и configuration management.
Разбор внутри: diff изменений и защита от случайного удаления.
Разбор внутри: защита от двух параллельных apply.
Разбор внутри: создание новых ресурсов и взятие существующих под управление.
Готов идти дальше?
Выбери вопросы, которые тебе интересны, и изучи их. Потом — к следующему уроку.
🎉 Курс завершён! Открыть профильСначала пройди все секции и выбери хотя бы 1 вопрос
⬅️ Назад к Уроку 29