HardПрактика4 min

Infrastructure as Code

CloudFormation, Bicep, Terraform и Pulumi: сравнение подходов к описанию инфраструктуры в коде

Зачем IaC

Infrastructure as Code (IaC) -- практика описания облачной инфраструктуры в текстовых файлах вместо ручного создания ресурсов через веб-консоль.

Проблемы ручного управления

Проблема Последствие
Нет документации "Кто создал этот бакет и зачем?"
Невоспроизводимость Невозможно точно воссоздать окружение
Дрифт Реальность отличается от документации
Человеческие ошибки Забыли настроить шифрование
Нет ревью Изменения без проверки коллегами

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

  • Воспроизводимость -- одна команда создаёт всю инфраструктуру
  • Версионирование -- Git хранит историю всех изменений
  • Ревью -- Pull Request для изменений инфраструктуры
  • Автоматизация -- CI/CD для инфраструктуры
  • Документация -- код = документация

AWS CloudFormation / SAM

CloudFormation для CRC

# template.yaml (SAM template)
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Cloud Resume Challenge Infrastructure

Globals:
  Function:
    Runtime: python3.12
    Architectures: [arm64]
    Timeout: 10
    MemorySize: 128

Resources:
  # DynamoDB Table
  VisitorCounterTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: visitor-counter
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH

  # Lambda Function
  VisitorCounterFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: visitor-counter
      CodeUri: backend/
      Handler: lambda_function.lambda_handler
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref VisitorCounterTable
      Events:
        GetCount:
          Type: HttpApi
          Properties:
            Path: /count
            Method: GET
            ApiId: !Ref CrcApi

  # HTTP API
  CrcApi:
    Type: AWS::Serverless::HttpApi
    Properties:
      StageName: prod
      CorsConfiguration:
        AllowOrigins:
          - https://resume.ivanpetrov.com
        AllowMethods:
          - GET
          - OPTIONS

  # S3 Bucket for Frontend
  FrontendBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: ivan-petrov-resume
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true

  # CloudFront Distribution
  CdnDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        DefaultRootObject: index.html
        Enabled: true
        Origins:
          - Id: S3Origin
            DomainName: !GetAtt FrontendBucket.RegionalDomainName
            S3OriginConfig:
              OriginAccessIdentity: ''
            OriginAccessControlId: !Ref OAC
        DefaultCacheBehavior:
          TargetOriginId: S3Origin
          ViewerProtocolPolicy: redirect-to-https
          CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6

  OAC:
    Type: AWS::CloudFront::OriginAccessControl
    Properties:
      OriginAccessControlConfig:
        Name: crc-oac
        OriginAccessControlOriginType: s3
        SigningBehavior: always
        SigningProtocol: sigv4

Outputs:
  ApiUrl:
    Description: API endpoint URL
    Value: !Sub 'https://${CrcApi}.execute-api.${AWS::Region}.amazonaws.com/prod/count'
  CloudFrontUrl:
    Description: CloudFront distribution URL
    Value: !GetAtt CdnDistribution.DomainName

Деплой SAM

# Build
sam build

# Deploy (guided -- запросит параметры)
sam deploy --guided

# Обновление
sam deploy

AWS CDK

CDK позволяет описывать инфраструктуру на языках программирования:

# cdk/app.py
from aws_cdk import (
    Stack, App, Duration,
    aws_dynamodb as dynamodb,
    aws_lambda as _lambda,
    aws_apigatewayv2 as apigw,
    aws_s3 as s3,
    aws_cloudfront as cloudfront,
)
from constructs import Construct

class CrcStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # DynamoDB
        table = dynamodb.Table(self, 'VisitorCounter',
            table_name='visitor-counter',
            partition_key=dynamodb.Attribute(
                name='id', type=dynamodb.AttributeType.STRING
            ),
            billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST
        )

        # Lambda
        fn = _lambda.Function(self, 'CounterFunction',
            runtime=_lambda.Runtime.PYTHON_3_12,
            handler='lambda_function.lambda_handler',
            code=_lambda.Code.from_asset('backend/'),
            architecture=_lambda.Architecture.ARM_64,
            timeout=Duration.seconds(10),
            memory_size=128
        )
        table.grant_read_write_data(fn)

app = App()
CrcStack(app, 'CrcStack')
app.synth()

Azure Bicep

// main.bicep
param location string = resourceGroup().location
param functionAppName string = 'crc-visitor-counter'

// Storage Account for Function App
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: 'crcfuncstorage'
  location: location
  sku: { name: 'Standard_LRS' }
  kind: 'StorageV2'
}

// Cosmos DB
resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' = {
  name: 'crc-cosmos'
  location: location
  properties: {
    databaseAccountOfferType: 'Standard'
    capabilities: [{ name: 'EnableServerless' }]
    locations: [{ locationName: location }]
  }
}

// App Service Plan (Consumption)
resource appServicePlan 'Microsoft.Web/serverfarms@2023-01-01' = {
  name: 'crc-plan'
  location: location
  sku: { name: 'Y1', tier: 'Dynamic' }
  kind: 'functionapp'
}

// Function App
resource functionApp 'Microsoft.Web/sites@2023-01-01' = {
  name: functionAppName
  location: location
  kind: 'functionapp,linux'
  properties: {
    serverFarmId: appServicePlan.id
    siteConfig: {
      pythonVersion: '3.11'
      appSettings: [
        { name: 'COSMOS_CONNECTION', value: cosmosAccount.listConnectionStrings().connectionStrings[0].connectionString }
        { name: 'FUNCTIONS_WORKER_RUNTIME', value: 'python' }
      ]
    }
  }
}
# Деплой Bicep
az deployment group create \
  --resource-group crc-resources \
  --template-file main.bicep

Terraform (мультиклауд)

# main.tf - Terraform для AWS CRC
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

# DynamoDB
resource "aws_dynamodb_table" "visitor_counter" {
  name         = "visitor-counter"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "id"

  attribute {
    name = "id"
    type = "S"
  }
}

# Lambda
resource "aws_lambda_function" "counter" {
  filename         = "function.zip"
  function_name    = "visitor-counter"
  role             = aws_iam_role.lambda_role.arn
  handler          = "lambda_function.lambda_handler"
  runtime          = "python3.12"
  architectures    = ["arm64"]
  timeout          = 10
  memory_size      = 128
  source_code_hash = filebase64sha256("function.zip")
}

# S3 Bucket
resource "aws_s3_bucket" "frontend" {
  bucket = "ivan-petrov-resume"
}
# Terraform workflow
terraform init      # Initialize providers
terraform plan      # Preview changes
terraform apply     # Apply changes
terraform destroy   # Delete everything

Сравнение инструментов IaC

Параметр CloudFormation/SAM CDK Bicep Terraform
Формат YAML/JSON Python/TS/Java DSL HCL
Облако AWS only AWS only Azure only Мультиклауд
State Управляемый AWS Управляемый AWS Azure Файл/Backend
Drift detection Да Через CFn Через Azure terraform plan
Rollback Автоматический Через CFn Ручной terraform destroy
Обучение Среднее Лёгкое (если знаете язык) Лёгкое Среднее
Сообщество Большое Растущее Растущее Очень большое

Рекомендация для CRC

  • Если делаете только AWS: SAM (самый простой для serverless)
  • Если хотите мультиклауд: Terraform
  • Если Azure: Bicep
  • Если любите Python: CDK

Проверь себя

Что делает команда 'terraform plan'?

Чем AWS SAM отличается от обычного CloudFormation?

Какой IaC-инструмент рекомендуется для CRC на чистом AWS с serverless-архитектурой?

Какой IaC-инструмент поддерживает все три облачных провайдера (AWS, Azure, GCP)?

Какая главная проблема ручного управления облачной инфраструктурой?