Автоматизация Terraform, CloudFormation в CI/CD, state management.
Infrastructure as Code позволяет управлять инфраструктурой через версионируемый код. Изучите интеграцию Terraform, CloudFormation и Pulumi в CI/CD пайплайны.
terraform/
├── main.tf
├── variables.tf
├── outputs.tf
├── versions.tf
├── environments/
│ ├── staging.tfvars
│ └── production.tfvars
└── modules/
├── vpc/
├── eks/
└── rds/
# versions.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.23"
}
}
backend "s3" {
bucket = "my-terraform-state"
key = "production/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
# main.tf
provider "aws" {
region = var.aws_region
}
resource "aws_s3_bucket" "app" {
bucket = var.bucket_name
tags = {
Environment = var.environment
ManagedBy = "Terraform"
}
}
resource "aws_s3_bucket_versioning" "app" {
bucket = aws_s3_bucket.app.id
versioning_configuration {
status = "Enabled"
}
}
# variables.tf
variable "aws_region" {
description = "AWS region"
type = string
default = "us-east-1"
}
variable "environment" {
description = "Environment name"
type = string
}
variable "bucket_name" {
description = "S3 bucket name"
type = string
}
# outputs.tf
output "bucket_arn" {
description = "ARN of the S3 bucket"
value = aws_s3_bucket.app.arn
}
output "bucket_name" {
description = "Name of the S3 bucket"
value = aws_s3_bucket.app.bucket
}name: Terraform CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
TF_VERSION: "1.6.0"
AWS_REGION: "us-east-1"
jobs:
terraform-plan:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./terraform
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: Terraform Init
run: terraform init
- name: Terraform Format Check
run: terraform fmt -check -recursive
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
run: terraform plan -out=tfplan -var-file=environments/${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}.tfvars
- name: Upload Plan
uses: actions/upload-artifact@v4
with:
name: tfplan
path: terraform/tfplan
terraform-apply:
runs-on: ubuntu-latest
needs: terraform-plan
if: github.ref == 'refs/heads/main'
environment: production
defaults:
run:
working-directory: ./terraform
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: Terraform Init
run: terraform init
- name: Download Plan
uses: actions/download-artifact@v4
with:
name: tfplan
path: terraform/
- name: Terraform Apply
run: terraform apply -auto-approve tfplan
- name: Terraform Output
run: terraform output -jsonname: Terraform with Approval
on:
pull_request:
branches: [main]
types: [opened, synchronize, labeled]
jobs:
plan:
runs-on: ubuntu-latest
if: contains(github.event.pull_request.labels.*.name, 'terraform')
outputs:
plan_exists: ${{ steps.plan.outputs.plan_exists }}
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: us-east-1
- name: Terraform Init
run: terraform init
- name: Terraform Plan
id: plan
run: |
terraform plan -out=tfplan -no-color > plan_output.txt
echo "plan_exists=true" >> $GITHUB_OUTPUT
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const plan = fs.readFileSync('plan_output.txt', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Terraform Plan\n\n\`\`\`\n${plan}\n\`\`\``
})
apply:
runs-on: ubuntu-latest
needs: plan
if: contains(github.event.pull_request.labels.*.name, 'terraform-apply')
environment: production
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: us-east-1
- name: Terraform Init
run: terraform init
- name: Terraform Apply
run: terraform apply -auto-approve
- name: Remove apply label
uses: actions/github-script@v7
with:
script: |
github.rest.issues.removeLabel({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
name: 'terraform-apply'
})# cloudformation/template.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: Application Infrastructure
Parameters:
Environment:
Type: String
Default: staging
AllowedValues:
- staging
- production
InstanceType:
Type: String
Default: t3.micro
AllowedValues:
- t3.micro
- t3.small
- t3.medium
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-vpc"
- Key: Environment
Value: !Ref Environment
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
MapPublicIpOnLaunch: true
AvailabilityZone: !Select [0, !GetAZs ""]
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-public-subnet"
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Application security group
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
EC2Instance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
SubnetId: !Ref PublicSubnet
SecurityGroupIds:
- !Ref SecurityGroup
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMI]
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-instance"
- Key: Environment
Value: !Ref Environment
Mappings:
RegionMap:
us-east-1:
AMI: ami-0c55b159cbfafe1f0
us-west-2:
AMI: ami-0c55b159cbfafe1f1
eu-west-1:
AMI: ami-0c55b159cbfafe1f2
Outputs:
VPCId:
Description: VPC ID
Value: !Ref VPC
Export:
Name: !Sub "${AWS::StackName}-VPCId"
InstanceId:
Description: EC2 Instance ID
Value: !Ref EC2Instance
Export:
Name: !Sub "${AWS::StackName}-InstanceId"name: CloudFormation CI/CD
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: us-east-1
- name: Validate Template
run: |
aws cloudformation validate-template \
--template-body file://cloudformation/template.yaml
- name: Deploy Stack
run: |
aws cloudformation deploy \
--template-file cloudformation/template.yaml \
--stack-name my-app-${{ github.ref == 'refs/heads/main' && 'prod' || 'staging' }} \
--parameter-overrides \
Environment=${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }} \
InstanceType=t3.small \
--capabilities CAPABILITY_NAMED_IAM \
--no-fail-on-empty-changeset \
--tags \
Project=my-app \
DeployedBy=github-actions \
CommitSha=${{ github.sha }}
- name: Get Stack Outputs
run: |
aws cloudformation describe-stacks \
--stack-name my-app-prod \
--query "Stacks[0].Outputs" \
--output table// index.ts
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const environment = config.get("environment") || "staging";
// S3 Bucket
const bucket = new aws.s3.Bucket("app-bucket", {
tags: {
Environment: environment,
ManagedBy: "Pulumi"
}
});
// Enable versioning
const bucketVersioning = new aws.s3.BucketVersioningV2("app-bucket-versioning", {
bucket: bucket.id,
versioningConfiguration: {
status: "Enabled"
}
});
// CloudFront Distribution
const oai = new aws.cloudfront.OriginAccessIdentity("app-oai", {
comment: "OAI for app bucket"
});
const distribution = new aws.cloudfront.Distribution("app-cdn", {
enabled: true,
origins: [{
originId: bucket.arn,
domainName: bucket.bucketRegionalDomainName,
s3OriginConfig: {
originAccessIdentity: oai.cloudfrontAccessIdentityPath
}
}],
defaultCacheBehavior: {
targetOriginId: bucket.arn,
viewerProtocolPolicy: "redirect-to-https",
allowedMethods: ["GET", "HEAD", "OPTIONS"],
cachedMethods: ["GET", "HEAD"],
forwardedValues: {
queryString: false,
cookies: { forward: "none" }
}
},
restrictions: {
geoRestriction: {
restrictionType: "none"
}
},
viewerCertificate: {
cloudfrontDefaultCertificate: true
},
tags: {
Environment: environment
}
});
// Outputs
export const bucketName = bucket.bucket;
export const bucketArn = bucket.arn;
export const cdnDomain = distribution.domainName;
export const cdnId = distribution.id;name: my-app-infra
runtime: nodejs
description: Application infrastructure
config:
my-app-infra:environment:
default: staging
my-app-infra:region:
default: us-east-1name: Pulumi CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
AWS_REGION: us-east-1
jobs:
pulumi-preview:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: Pulumi Preview
uses: pulumi/actions@v5
with:
command: preview
stack-name: prod
work-dir: ./pulumi
comment-on-pr: true
pulumi-up:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: Pulumi Up
uses: pulumi/actions@v5
with:
command: up
stack-name: prod
work-dir: ./pulumi
- name: Get Stack Outputs
uses: pulumi/actions@v5
with:
command: stack-output
stack-name: prod
work-dir: ./pulumi# backend "s3" с DynamoDB locking
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "production/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
# DynamoDB таблица для locking
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
tags = {
Name = "Terraform Lock Table"
}
}# Импортировать существующий S3 bucket
terraform import aws_s3_bucket.app my-existing-bucket
# Импортировать VPC
terraform import aws_vpc.main vpc-12345678
# Импортировать с указанием модуля
terraform import module.vpc.aws_vpc.main vpc-12345678# Terraform workspaces
terraform workspace new staging
terraform workspace new production
# Переключение
terraform workspace select staging
# Применить с workspace
terraform apply -var-file=environments/$(terraform workspace show).tfvars# GitHub Actions с workspaces
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Terraform Workspace & Apply
run: |
terraform workspace select ${{ vars.TF_WORKSPACE }}
terraform apply -auto-approve \
-var-file=environments/${{ vars.TF_WORKSPACE }}.tfvarsВопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.