УРОК 30 / 30 0%

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. 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. 2
    Структура
    
    terraform/
    ├── main.tf           # главный файл
    ├── variables.tf      # переменные
    ├── outputs.tf        # выходные значения
    ├── providers.tf      # провайдеры
    └── terraform.tfstate # состояние (создаётся автоматически, .gitignore!)
    
  3. 3
    providers.tf
    
    terraform {
      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. 4
    variables.tf
    
    variable "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. 5
    main.tf — k8s namespace + deployment
    
    resource "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. 6
    outputs.tf
    
    output "namespace" {
      value = kubernetes_namespace.taskflow.metadata[0].name
    }
    
    output "service_name" {
      value = kubernetes_service.app.metadata[0].name
    }
    
  7. 7
    Workflow
    
    # Инициализация (скачивает провайдеры)
    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. 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. 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
    }
    
  10. 10
    Variables через файл

    prod.tfvars:

    
    environment = "prod"
    app_version = "0.1.0"
    db_password = "REPLACE_VIA_CI"
    
    
    terraform apply -var-file=prod.tfvars
    

    ---

🎯

Зачем это на собесе

После урока ты должен уметь ответить на:

Single source of truth — кластер описан в коде, не в голове DevOps'а.
Reproducibleterraform apply из репо = тот же кластер, что был месяц назад.
Disaster recovery — потеряли кластер? Применили terraform = восстановили.
Code review для инфры — изменения через PR, не руками в AWS Console.

5 вопросов на углубление

Раскрой вопрос и нажми «🤔 Хочу разобрать подробнее» — он попадёт в страницу ответов.

1
Почему terraform.tfstate нельзя коммитить?

Разбор внутри: состояние инфраструктуры, секреты и конфликты.

2
Terraform vs Ansible — где чей сценарий?

Разбор внутри: provisioning инфраструктуры и configuration management.

3
Зачем terraform plan перед apply?

Разбор внутри: diff изменений и защита от случайного удаления.

4
Что такое remote state locking?

Разбор внутри: защита от двух параллельных apply.

5
terraform apply vs terraform import

Разбор внутри: создание новых ресурсов и взятие существующих под управление.

Готов идти дальше?

Выбери вопросы, которые тебе интересны, и изучи их. Потом — к следующему уроку.

🎉 Курс завершён! Открыть профиль

Сначала пройди все секции и выбери хотя бы 1 вопрос

⬅️ Назад к Уроку 29
🎉
Новый тир
Новый тир достигнут!