第10章:Infrastructure as Code (IaC) と自動化

10.1 IaCの概念とメリット

Infrastructure as Codeという思想の革新性

Infrastructure as Code(IaC)は、インフラストラクチャ管理における最も重要なパラダイムシフトの一つです。手動でサーバーを設定し、GUIでクリックを繰り返していた時代から、プログラマブルで再現可能な方法への移行は、単なる効率化を超えて、インフラストラクチャの品質、信頼性、そして開発速度を根本的に向上させました。

宣言的アプローチと命令的アプローチの本質

IaCツールを理解する上で最も重要な概念は、宣言的(Declarative)アプローチと命令的(Imperative)アプローチの違いです。

宣言的アプローチ:望ましい状態の記述

宣言的アプローチでは、「どのような状態であるべきか」を記述します。

`hcl

Terraform - 宣言的アプローチの例

resource “aws_instance” “web_servers” { count = 3 # 3台のインスタンスが存在すべき instance_type = “t3.medium” ami = data.aws_ami.amazon_linux_2.id

subnet_id = aws_subnet.public[count.index % length(aws_subnet.public)].id vpc_security_group_ids = [aws_security_group.web.id]

tags = { Name = “web-server-${count.index + 1}” Environment = var.environment ManagedBy = “Terraform” }

# 現在2台しかない場合、Terraformは自動的に1台追加 # 現在4台ある場合、Terraformは自動的に1台削除 # 設定が異なる場合、Terraformは差分を適用 } `

命令的アプローチ:手順の記述

命令的アプローチでは、「何をすべきか」の手順を記述します。

`python

Python/Boto3 - 命令的アプローチの例

import boto3

ec2 = boto3.resource(‘ec2’)

現在のインスタンス数を確認

current_instances = list(ec2.instances.filter( Filters=[ {‘Name’: ‘tag:Name’, ‘Values’: [‘web-server-*’]}, {‘Name’: ‘instance-state-name’, ‘Values’: [‘running’]} ] ))

desired_count = 3 current_count = len(current_instances)

不足分を追加

if current_count < desired_count: for i in range(current_count, desired_count): instance = ec2.create_instances( ImageId=’ami-0123456789abcdef0’, InstanceType=’t3.medium’, MinCount=1, MaxCount=1, SubnetId=get_next_subnet(), SecurityGroupIds=[‘sg-1234567890abcdef0’], TagSpecifications=[{ ‘ResourceType’: ‘instance’, ‘Tags’: [ {‘Key’: ‘Name’, ‘Value’: f’web-server-{i+1}’}, {‘Key’: ‘Environment’, ‘Value’: environment}, {‘Key’: ‘ManagedBy’, ‘Value’: ‘Python Script’} ] }] )[0] print(f”Created instance: {instance.id}”)

過剰分を削除

elif current_count > desired_count: for instance in current_instances[desired_count:]: instance.terminate() print(f”Terminated instance: {instance.id}”) `

IaCがもたらす本質的価値

1. 冪等性(Idempotency)の保証

冪等性とは、同じ操作を何度実行しても同じ結果が得られる性質です。宣言的IaCツールは、この冪等性を自動的に保証します。

`yaml

冪等性の実例

初期状態: インスタンス0台 1回目実行: 3台作成 → 結果: 3台 2回目実行: 変更なし → 結果: 3台(変わらず) 3回目実行: 変更なし → 結果: 3台(変わらず)

設定変更時

設定変更: instance_type を t3.large に変更 4回目実行: 3台を更新 → 結果: 3台(t3.large) `

2. バージョン管理による変更追跡

インフラストラクチャをコードとして管理することで、ソフトウェア開発で培われたベストプラクティスを適用できます。

`bash

Git でのインフラ変更管理

git log –oneline terraform/

出力例:

a5f3c21 feat: Add auto-scaling for web servers

82b9e44 fix: Correct security group ingress rules

3d7a891 refactor: Extract RDS configuration to module

f2c6b55 chore: Update instance types for cost optimization

特定の変更の詳細確認

git show a5f3c21

変更内容、理由、影響範囲が明確に記録される

`

3. コラボレーションの促進

`yaml

プルリクエストでのインフラ変更レビュー

レビュープロセス:

  1. 変更案の作成:
    • ブランチで変更を実装
    • terraform plan の結果を確認
  2. プルリクエスト:
    • 変更の意図を説明
    • 影響範囲を明記
    • コスト影響を記載
  3. レビュー:
    • セキュリティチェック
    • ベストプラクティス確認
    • コスト最適化の検討
  4. 承認と適用:
    • 複数人による承認
    • 自動テストの通過
    • 本番環境への適用 `

IaCの成熟度モデル

組織のIaC採用レベルを評価し、段階的な改善を図るための指標です。

`yaml レベル1 - 手動運用: 特徴: - GUI/CLIでの手動設定 - ドキュメント化されていない - 再現性なし リスク: - ヒューマンエラー - 環境間の不整合 - 障害復旧の遅延

レベル2 - スクリプト化: 特徴: - シェルスクリプトでの部分自動化 - 基本的なバージョン管理 - 限定的な再現性 改善点: - 手動作業の削減 - 基本的な標準化

レベル3 - IaCツール導入: 特徴: - Terraform/CloudFormation使用 - コードレビュー実施 - 環境別管理 利点: - 宣言的管理 - 状態管理 - ドリフト検出

レベル4 - 完全自動化: 特徴: - CI/CDパイプライン統合 - 自動テスト実装 - Policy as Code 成果: - 継続的デリバリー - コンプライアンス自動化 - セルフサービス化

レベル5 - GitOps: 特徴: - Git as Single Source of Truth - Pull型デプロイメント - 継続的な同期 最終形: - 完全な監査証跡 - 自動ロールバック - 宣言的運用 `

IaCのアンチパターンと対策

1. 手動変更との混在(Configuration Drift)

最も一般的で危険なアンチパターンは、IaCで管理されているリソースを手動で変更することです。

`hcl

ドリフト検出と防止策

1. 定期的なドリフト検出

resource “null_resource” “drift_check” { provisioner “local-exec” { command = «-EOT terraform plan -detailed-exitcode > /dev/null if [ $? -eq 2 ]; then echo “ALERT: Configuration drift detected!” # Slackやメールでアラート送信 fi EOT }

triggers = { # 1時間ごとに実行 time = timestamp() } }

2. AWS Config Rules による監視

resource “aws_config_config_rule” “terraform_managed” { name = “terraform-managed-resources”

source { owner = “AWS” source_identifier = “REQUIRED_TAGS” }

input_parameters = jsonencode({ tag1Key = “ManagedBy” tag1Value = “Terraform” })

# タグがない(手動作成された)リソースを検出 }

3. IAMポリシーによる手動変更の防止

data “aws_iam_policy_document” “prevent_manual_changes” { statement { effect = “Deny” actions = [ “ec2:”, “rds:”, “s3:” ] resources = [“”]

condition {
  test     = "StringNotEquals"
  variable = "aws:userid"
  values   = [data.aws_caller_identity.terraform.user_id]
}

# Terraform実行ユーザー以外の変更を拒否   } } `

2. 状態ファイルの不適切な管理

Terraformの状態ファイルは、実際のインフラストラクチャとコードをマッピングする重要な情報です。

`hcl

リモートバックエンドの適切な設定

terraform { backend “s3” { # 状態ファイルの保存先 bucket = “terraform-state-bucket” key = “prod/infrastructure/terraform.tfstate” region = “ap-northeast-1”

# 暗号化
encrypt = true
kms_key_id = "arn:aws:kms:ap-northeast-1:123456789012:key/abcd1234"

# 状態ロック
dynamodb_table = "terraform-state-lock"

# バージョニング(履歴保持)
versioning = true

# アクセス制御
acl = "bucket-owner-full-control"   } }

DynamoDBテーブル(状態ロック用)

resource “aws_dynamodb_table” “terraform_locks” { name = “terraform-state-lock” billing_mode = “PAY_PER_REQUEST” hash_key = “LockID”

attribute { name = “LockID” type = “S” }

server_side_encryption { enabled = true }

tags = { Name = “Terraform State Lock Table” Environment = “shared” } } `

3. モノリシックな構成

すべてのインフラストラクチャを単一の巨大な構成で管理すると、様々な問題が発生します。

`hcl

適切なモジュール分割

ディレクトリ構造

terraform/ ├── modules/ │ ├── networking/ │ │ ├── variables.tf │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── README.md │ ├── compute/ │ │ └── … │ ├── database/ │ │ └── … │ └── security/ │ └── … ├── environments/ │ ├── dev/ │ │ ├── main.tf │ │ ├── variables.tf │ │ └── terraform.tfvars │ ├── staging/ │ │ └── … │ └── prod/ │ └── … └── global/ ├── iam/ └── route53/

モジュールの使用例

module “network” { source = “../../modules/networking”

environment = var.environment cidr_block = var.vpc_cidr availability_zones = data.aws_availability_zones.available.names

enable_nat_gateway = var.environment == “prod” ? true : false single_nat_gateway = var.environment != “prod” ? true : false }

module “compute” { source = “../../modules/compute”

environment = var.environment subnet_ids = module.network.private_subnet_ids instance_type = var.instance_types[var.environment] instance_count = var.instance_counts[var.environment]

depends_on = [module.network] } `

10.2 Terraformによるインフラ構築の実践

Terraformの設計哲学と内部動作

Terraformは、HashiCorpが開発した最も人気のあるIaCツールです。その設計哲学を深く理解することで、より効果的な利用が可能になります。

リソースグラフとプランニング

Terraformは内部的に、すべてのリソースとその依存関係を有向非巡回グラフ(DAG)として管理します。

`hcl

依存関係の自動解決

resource “aws_vpc” “main” { cidr_block = “10.0.0.0/16” enable_dns_hostnames = true enable_dns_support = true

tags = { Name = “${var.project}-vpc” } }

resource “aws_internet_gateway” “main” { vpc_id = aws_vpc.main.id # 暗黙的な依存関係

tags = { Name = “${var.project}-igw” } }

resource “aws_subnet” “public” { count = length(var.availability_zones)

vpc_id = aws_vpc.main.id cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index) availability_zone = var.availability_zones[count.index] map_public_ip_on_launch = true

tags = { Name = “${var.project}-public-${var.availability_zones[count.index]}” 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.project}-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

# Terraformは自動的に以下の順序で作成: # 1. VPC # 2. Internet Gateway, Subnets (並列実行可能) # 3. Route Table # 4. Route Table Associations } `

実践的なモジュール設計

再利用可能で保守性の高いモジュールの設計は、大規模インフラストラクチャ管理の鍵です。

完全なVPCモジュールの実装

`hcl

modules/vpc/variables.tf

variable “project_name” { description = “プロジェクト名” type = string validation { condition = can(regex(“^[a-z0-9-]+$”, var.project_name)) error_message = “Project name must contain only lowercase letters, numbers, and hyphens.” } }

variable “environment” { description = “環境名” type = string validation { condition = contains([“dev”, “staging”, “prod”], var.environment) error_message = “Environment must be dev, staging, or prod.” } }

variable “vpc_cidr” { description = “VPCのCIDRブロック” type = string default = “10.0.0.0/16” validation { condition = can(cidrhost(var.vpc_cidr, 0)) error_message = “VPC CIDR must be a valid IPv4 CIDR block.” } }

variable “availability_zones” { description = “使用するAZ” type = list(string) default = [] }

variable “enable_nat_gateway” { description = “NAT Gatewayを有効にするか” type = bool default = true }

variable “single_nat_gateway” { description = “NAT Gatewayを1つだけ作成するか” type = bool default = false }

variable “enable_vpn_gateway” { description = “VPN Gatewayを有効にするか” type = bool default = false }

variable “enable_flow_logs” { description = “VPC Flow Logsを有効にするか” type = bool default = true }

modules/vpc/main.tf

locals { azs = length(var.availability_zones) > 0 ? var.availability_zones : slice(data.aws_availability_zones.available.names, 0, 3)

# サブネット計算 # パブリック: /24 × AZ数 # プライベート: /24 × AZ数 # データベース: /24 × AZ数 public_cidrs = [for i in range(length(local.azs)) : cidrsubnet(var.vpc_cidr, 8, i)] private_cidrs = [for i in range(length(local.azs)) : cidrsubnet(var.vpc_cidr, 8, i + 10)] database_cidrs = [for i in range(length(local.azs)) : cidrsubnet(var.vpc_cidr, 8, i + 20)]

common_tags = { Project = var.project_name Environment = var.environment ManagedBy = “Terraform” } }

data “aws_availability_zones” “available” { state = “available” }

VPC

resource “aws_vpc” “this” { cidr_block = var.vpc_cidr enable_dns_hostnames = true enable_dns_support = true

tags = merge(local.common_tags, { Name = “${var.project_name}-${var.environment}-vpc” }) }

Internet Gateway

resource “aws_internet_gateway” “this” { vpc_id = aws_vpc.this.id

tags = merge(local.common_tags, { Name = “${var.project_name}-${var.environment}-igw” }) }

Public Subnets

resource “aws_subnet” “public” { count = length(local.azs)

vpc_id = aws_vpc.this.id cidr_block = local.public_cidrs[count.index] availability_zone = local.azs[count.index] map_public_ip_on_launch = true

tags = merge(local.common_tags, { Name = “${var.project_name}-${var.environment}-public-${local.azs[count.index]}” Type = “Public” “kubernetes.io/role/elb” = “1” # EKS用タグ }) }

Private Subnets

resource “aws_subnet” “private” { count = length(local.azs)

vpc_id = aws_vpc.this.id cidr_block = local.private_cidrs[count.index] availability_zone = local.azs[count.index]

tags = merge(local.common_tags, { Name = “${var.project_name}-${var.environment}-private-${local.azs[count.index]}” Type = “Private” “kubernetes.io/role/internal-elb” = “1” # EKS用タグ }) }

Database Subnets

resource “aws_subnet” “database” { count = length(local.azs)

vpc_id = aws_vpc.this.id cidr_block = local.database_cidrs[count.index] availability_zone = local.azs[count.index]

tags = merge(local.common_tags, { Name = “${var.project_name}-${var.environment}-database-${local.azs[count.index]}” Type = “Database” }) }

Elastic IPs for NAT Gateways

resource “aws_eip” “nat” { count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(local.azs)) : 0

domain = “vpc”

tags = merge(local.common_tags, { Name = “${var.project_name}-${var.environment}-nat-eip-${count.index + 1}” })

depends_on = [aws_internet_gateway.this] }

NAT Gateways

resource “aws_nat_gateway” “this” { count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(local.azs)) : 0

allocation_id = aws_eip.nat[count.index].id subnet_id = aws_subnet.public[count.index].id

tags = merge(local.common_tags, { Name = “${var.project_name}-${var.environment}-nat-${count.index + 1}” })

depends_on = [aws_internet_gateway.this] }

Route Tables

resource “aws_route_table” “public” { vpc_id = aws_vpc.this.id

tags = merge(local.common_tags, { Name = “${var.project_name}-${var.environment}-public-rt” Type = “Public” }) }

resource “aws_route” “public_internet” { route_table_id = aws_route_table.public.id destination_cidr_block = “0.0.0.0/0” gateway_id = aws_internet_gateway.this.id }

resource “aws_route_table” “private” { count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(local.azs)) : 0

vpc_id = aws_vpc.this.id

tags = merge(local.common_tags, { Name = “${var.project_name}-${var.environment}-private-rt-${count.index + 1}” Type = “Private” }) }

resource “aws_route” “private_nat” { count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(local.azs)) : 0

route_table_id = aws_route_table.private[count.index].id destination_cidr_block = “0.0.0.0/0” nat_gateway_id = var.single_nat_gateway ? aws_nat_gateway.this[0].id : aws_nat_gateway.this[count.index].id }

Route Table Associations

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 }

resource “aws_route_table_association” “private” { count = length(aws_subnet.private)

subnet_id = aws_subnet.private[count.index].id route_table_id = var.enable_nat_gateway ? (var.single_nat_gateway ? aws_route_table.private[0].id : aws_route_table.private[count.index].id) : aws_route_table.public.id }

resource “aws_route_table_association” “database” { count = length(aws_subnet.database)

subnet_id = aws_subnet.database[count.index].id route_table_id = var.enable_nat_gateway ? (var.single_nat_gateway ? aws_route_table.private[0].id : aws_route_table.private[count.index].id) : aws_route_table.public.id }

VPC Flow Logs

resource “aws_flow_log” “this” { count = var.enable_flow_logs ? 1 : 0

iam_role_arn = aws_iam_role.flow_logs[0].arn log_destination = aws_cloudwatch_log_group.flow_logs[0].arn traffic_type = “ALL” vpc_id = aws_vpc.this.id

tags = merge(local.common_tags, { Name = “${var.project_name}-${var.environment}-flow-logs” }) }

resource “aws_cloudwatch_log_group” “flow_logs” { count = var.enable_flow_logs ? 1 : 0

name = “/aws/vpc/${var.project_name}-${var.environment}” retention_in_days = 30

tags = local.common_tags }

resource “aws_iam_role” “flow_logs” { count = var.enable_flow_logs ? 1 : 0

name = “${var.project_name}-${var.environment}-flow-logs-role”

assume_role_policy = jsonencode({ Version = “2012-10-17” Statement = [{ Action = “sts:AssumeRole” Effect = “Allow” Principal = { Service = “vpc-flow-logs.amazonaws.com” } }] })

tags = local.common_tags }

resource “aws_iam_role_policy” “flow_logs” { count = var.enable_flow_logs ? 1 : 0

name = “${var.project_name}-${var.environment}-flow-logs-policy” role = aws_iam_role.flow_logs[0].id

policy = jsonencode({ Version = “2012-10-17” Statement = [{ Action = [ “logs:CreateLogGroup”, “logs:CreateLogStream”, “logs:PutLogEvents”, “logs:DescribeLogGroups”, “logs:DescribeLogStreams” ] Effect = “Allow” Resource = “*” }] }) }

modules/vpc/outputs.tf

output “vpc_id” { description = “VPCのID” value = aws_vpc.this.id }

output “vpc_cidr” { description = “VPCのCIDRブロック” value = aws_vpc.this.cidr_block }

output “public_subnet_ids” { description = “パブリックサブネットのIDリスト” value = aws_subnet.public[*].id }

output “private_subnet_ids” { description = “プライベートサブネットのIDリスト” value = aws_subnet.private[*].id }

output “database_subnet_ids” { description = “データベースサブネットのIDリスト” value = aws_subnet.database[*].id }

output “nat_gateway_ids” { description = “NAT GatewayのIDリスト” value = aws_nat_gateway.this[*].id }

output “availability_zones” { description = “使用されているAZのリスト” value = local.azs } `

環境別構成管理のベストプラクティス

Terraformワークスペース vs ディレクトリ構造

`hcl

ワークスペースを使った環境管理

利点:単一の設定ファイルで複数環境を管理

欠点:環境間の差異が大きい場合に複雑化

locals { # ワークスペース名から環境を判定 environment = terraform.workspace

# 環境別設定 instance_types = { dev = “t3.micro” staging = “t3.small” prod = “t3.large” }

instance_counts = { dev = 1 staging = 2 prod = 4 }

enable_monitoring = { dev = false staging = true prod = true } }

ディレクトリベースの環境管理(推奨)

environments/prod/main.tf

module “vpc” { source = “../../modules/vpc”

project_name = var.project_name environment = “prod” vpc_cidr = “10.0.0.0/16” enable_nat_gateway = true single_nat_gateway = false # 高可用性のため各AZにNAT Gateway enable_flow_logs = true }

module “compute” { source = “../../modules/compute”

project_name = var.project_name environment = “prod” vpc_id = module.vpc.vpc_id subnet_ids = module.vpc.private_subnet_ids instance_type = “t3.large” instance_count = 4

# 本番環境固有の設定 enable_monitoring = true enable_detailed_monitoring = true enable_auto_recovery = true }

environments/dev/main.tf

module “vpc” { source = “../../modules/vpc”

project_name = var.project_name environment = “dev” vpc_cidr = “10.100.0.0/16” # 本番と異なるCIDR enable_nat_gateway = true single_nat_gateway = true # コスト削減のため1つのみ enable_flow_logs = false # 開発環境では不要 } `

高度なTerraform機能の活用

Dynamic Blocksによる柔軟な設定

`hcl

セキュリティグループの動的ルール生成

variable “security_group_rules” { description = “セキュリティグループルール” type = list(object({ type = string from_port = number to_port = number protocol = string cidr_blocks = list(string) description = string })) default = [] }

resource “aws_security_group” “this” { name = “${var.project_name}-${var.environment}-sg” description = “Security group for ${var.project_name}” vpc_id = var.vpc_id

# 動的なインバウンドルール dynamic “ingress” { for_each = [for rule in var.security_group_rules : rule if rule.type == “ingress”]

content {
  from_port   = ingress.value.from_port
  to_port     = ingress.value.to_port
  protocol    = ingress.value.protocol
  cidr_blocks = ingress.value.cidr_blocks
  description = ingress.value.description
}   }

# 動的なアウトバウンドルール dynamic “egress” { for_each = [for rule in var.security_group_rules : rule if rule.type == “egress”]

content {
  from_port   = egress.value.from_port
  to_port     = egress.value.to_port
  protocol    = egress.value.protocol
  cidr_blocks = egress.value.cidr_blocks
  description = egress.value.description
}   }

# デフォルトのアウトバウンドルール egress { from_port = 0 to_port = 0 protocol = “-1” cidr_blocks = [“0.0.0.0/0”] description = “Allow all outbound traffic” }

tags = merge(var.common_tags, { Name = “${var.project_name}-${var.environment}-sg” }) } `

条件式とfor式の組み合わせ

`hcl

複雑な条件に基づくリソース作成

locals { # 本番環境のみマルチAZ、それ以外はシングルAZ db_azs = var.environment == “prod” ? var.availability_zones : [var.availability_zones[0]]

# インスタンスタグの生成 instance_tags = merge( var.common_tags, { for i in range(var.instance_count) : “Instance${i}” => “web-${var.environment}-${i + 1}” } )

# 環境別のバックアップスケジュール backup_schedules = { prod = { frequency = “daily” retention = 30 time = “03:00” } staging = { frequency = “weekly” retention = 7 time = “03:00” } dev = null # 開発環境はバックアップなし } }

RDSクラスターの条件付き作成

resource “aws_rds_cluster” “this” { count = var.create_database ? 1 : 0

cluster_identifier = “${var.project_name}-${var.environment}-cluster” engine = “aurora-mysql” engine_version = var.engine_version

# 環境によって異なる設定 availability_zones = local.db_azs

# バックアップ設定 backup_retention_period = try(local.backup_schedules[var.environment].retention, 1) preferred_backup_window = try(local.backup_schedules[var.environment].time, “03:00-04:00”) enabled_cloudwatch_logs_exports = var.environment == “prod” ? [“error”, “general”, “slowquery”] : []

# 本番環境のみ暗号化 storage_encrypted = var.environment == “prod” kms_key_id = var.environment == “prod” ? aws_kms_key.rds[0].arn : null

# 本番環境のみ削除保護 deletion_protection = var.environment == “prod”

dynamic “scaling_configuration” { for_each = var.serverless ? [1] : []

content {
  auto_pause               = var.environment != "prod"
  min_capacity            = var.environment == "prod" ? 2 : 1
  max_capacity            = var.environment == "prod" ? 16 : 4
  seconds_until_auto_pause = 300
}   }

tags = var.common_tags } `

Terraformの状態管理上級テクニック

状態の分割とリモート参照

`hcl

ネットワーク層の状態を参照

data “terraform_remote_state” “network” { backend = “s3”

config = { bucket = “terraform-state-bucket” key = “env/${var.environment}/network/terraform.tfstate” region = “ap-northeast-1” } }

共有リソースの状態を参照

data “terraform_remote_state” “shared” { backend = “s3”

config = { bucket = “terraform-state-bucket” key = “global/shared/terraform.tfstate” region = “ap-northeast-1” } }

参照した状態からの値の使用

resource “aws_instance” “app” { subnet_id = data.terraform_remote_state.network.outputs.private_subnet_ids[0] vpc_security_group_ids = [data.terraform_remote_state.network.outputs.app_security_group_id] iam_instance_profile = data.terraform_remote_state.shared.outputs.ec2_instance_profile_name

# … } `

状態の移行とリファクタリング

`bash

既存リソースのインポート

terraform import aws_instance.legacy i-1234567890abcdef0

リソースの移動(リファクタリング)

terraform state mv aws_instance.old aws_instance.new terraform state mv aws_instance.web module.compute.aws_instance.web

モジュール間の移動

terraform state mv module.old.aws_vpc.main module.network.aws_vpc.main

状態からの削除(実リソースは削除されない)

terraform state rm aws_instance.temp

状態の一覧表示

terraform state list

特定リソースの詳細表示

terraform state show aws_instance.web `

10.3 Ansibleによる構成管理の基礎

構成管理の本質と必要性

Infrastructure as Codeがインフラストラクチャをプロビジョニングするのに対し、構成管理ツールはそのインフラストラクチャ上でアプリケーションを動作させるための設定を行います。

`yaml

IaCと構成管理の責任分担

Infrastructure as Code (Terraform):

  • ネットワークの作成
  • サーバーのプロビジョニング
  • ロードバランサーの設定
  • データベースの作成
  • セキュリティグループの定義

Configuration Management (Ansible):

  • OSの設定とハードニング
  • ミドルウェアのインストール
  • アプリケーションのデプロイ
  • 設定ファイルの管理
  • ユーザーと権限の管理 `

Ansibleのアーキテクチャと特徴

Ansibleは、エージェントレスで動作し、SSHを通じて管理対象ノードを制御します。この設計により、追加のソフトウェアインストールが不要で、既存環境への導入が容易です。

Playbookの構造と設計

`yaml

site.yml - マスターPlaybook

  • name: Common configuration for all servers hosts: all become: yes roles:
    • common
    • security
  • name: Configure web servers hosts: webservers become: yes roles:
    • nginx
    • app-deploy vars: app_version: “{{ lookup('env', 'APP_VERSION') | default('latest', true) }}
  • name: Configure database servers hosts: databases become: yes roles:
    • postgresql
    • backup

group_vars/all.yml - 全ホスト共通変数


ntp_servers:

  • ntp.nict.jp
  • ntp.jst.mfeed.ad.jp

timezone: Asia/Tokyo

security_ssh_port: 22 security_ssh_password_authentication: “no” security_ssh_permit_root_login: “no”

group_vars/webservers.yml - Webサーバー用変数


nginx_worker_processes: “{{ ansible_processor_vcpus }}” nginx_worker_connections: 2048

app_user: webapp app_group: webapp app_home: /var/www/app app_port: 3000

group_vars/production.yml - 本番環境用変数


nginx_server_tokens: “off” nginx_ssl_protocols: “TLSv1.2 TLSv1.3” nginx_ssl_ciphers: “HIGH:!aNULL:!MD5”

enable_monitoring: true enable_log_shipping: true `

高度なRole設計

`yaml

roles/nginx/tasks/main.yml


  • name: Include OS-specific variables include_vars: “{{ ansible_os_family }}.yml”

  • name: Install Nginx package: name: “{{ nginx_package_name }}” state: present notify: restart nginx

  • name: Create Nginx directories file: path: “{{ item }}” state: directory owner: root group: root mode: ‘0755’ loop:
    • /etc/nginx/sites-available
    • /etc/nginx/sites-enabled
    • /etc/nginx/ssl
    • /var/log/nginx
  • name: Generate DH parameters openssl_dhparam: path: /etc/nginx/ssl/dhparams.pem size: 2048 when: nginx_use_ssl | default(false)

  • name: Configure Nginx template: src: nginx.conf.j2 dest: /etc/nginx/nginx.conf owner: root group: root mode: ‘0644’ validate: ‘nginx -t -c %s’ notify: reload nginx

  • name: Configure virtual hosts template: src: vhost.conf.j2 dest: “/etc/nginx/sites-available/{{ item.name }}” owner: root group: root mode: ‘0644’ loop: “{{ nginx_vhosts }}” when: nginx_vhosts is defined notify: reload nginx

  • name: Enable virtual hosts file: src: “/etc/nginx/sites-available/{{ item.name }}” dest: “/etc/nginx/sites-enabled/{{ item.name }}” state: link loop: “{{ nginx_vhosts }}” when: nginx_vhosts is defined notify: reload nginx

  • name: Remove default site file: path: /etc/nginx/sites-enabled/default state: absent notify: reload nginx

  • name: Ensure Nginx is running systemd: name: nginx state: started enabled: yes daemon_reload: yes

roles/nginx/templates/nginx.conf.j2

user {{ nginx_user }}; worker_processes {{ nginx_worker_processes }}; pid /run/nginx.pid;

events { worker_connections {{ nginx_worker_connections }}; multi_accept on; use epoll; }

http { # 基本設定 sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; server_tokens {{ nginx_server_tokens | default('on') }};

# MIME types
include /etc/nginx/mime.types;
default_type application/octet-stream;

# SSL設定
{% if nginx_use_ssl | default(false) %}
ssl_protocols {{ nginx_ssl_protocols }};
ssl_ciphers {{ nginx_ssl_ciphers }};
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_stapling on;
ssl_stapling_verify on;
{% endif %}

# ログ設定
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;

# Gzip圧縮
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml application/atom+xml image/svg+xml;

# バーチャルホスト設定
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*; }

roles/nginx/handlers/main.yml


  • name: restart nginx systemd: name: nginx state: restarted when: nginx_restart_on_change | default(true)

  • name: reload nginx systemd: name: nginx state: reloaded when: nginx_reload_on_change | default(true)

  • name: validate nginx configuration command: nginx -t changed_when: false `

動的インベントリとクラウド統合

クラウド環境では、インスタンスが動的に作成・削除されるため、静的なインベントリファイルでは管理が困難です。

`python #!/usr/bin/env python3

dynamic_inventory_aws.py

import json import boto3 from collections import defaultdict

class AWSInventory: def init(self): self.inventory = defaultdict(lambda: {‘hosts’: [], ‘vars’: {}}) self.inventory[‘_meta’] = {‘hostvars’: {}}

def get_instances(self):
    """EC2インスタンスを取得してインベントリを構築"""
    ec2 = boto3.client('ec2', region_name='ap-northeast-1')
    
    # 実行中のインスタンスを取得
    response = ec2.describe_instances(
        Filters=[
            {'Name': 'instance-state-name', 'Values': ['running']}
        ]
    )
    
    for reservation in response['Reservations']:
        for instance in reservation['Instances']:
            self._add_instance(instance)
            
def _add_instance(self, instance):
    """インスタンスをインベントリに追加"""
    instance_id = instance['InstanceId']
    
    # プライベートIPアドレスを使用(VPC内通信)
    private_ip = instance.get('PrivateIpAddress')
    if not private_ip:
        return
        
    # タグからグループを決定
    tags = {tag['Key']: tag['Value'] for tag in instance.get('Tags', [])}
    
    # 環境グループ
    environment = tags.get('Environment', 'unknown')
    self.inventory[environment]['hosts'].append(private_ip)
    
    # 役割グループ
    role = tags.get('Role', 'unknown')
    self.inventory[role]['hosts'].append(private_ip)
    
    # 組み合わせグループ
    combined_group = f"{environment}_{role}"
    self.inventory[combined_group]['hosts'].append(private_ip)
    
    # ホスト変数
    self.inventory['_meta']['hostvars'][private_ip] = {
        'instance_id': instance_id,
        'instance_type': instance['InstanceType'],
        'availability_zone': instance['Placement']['AvailabilityZone'],
        'private_dns_name': instance.get('PrivateDnsName', ''),
        'tags': tags,
        'ansible_host': private_ip,
        'ansible_ssh_private_key_file': f"~/.ssh/{environment}.pem"
    }
    
    # 特別な設定
    if role == 'bastion':
        self.inventory['_meta']['hostvars'][private_ip]['ansible_ssh_common_args'] = '-o ProxyCommand="none"'
    else:
        # 踏み台経由の接続
        bastion_ip = self._get_bastion_ip(environment)
        if bastion_ip:
            self.inventory['_meta']['hostvars'][private_ip]['ansible_ssh_common_args'] = \
                f'-o ProxyCommand="ssh -W %h:%p -q ubuntu@{bastion_ip}"'

def _get_bastion_ip(self, environment):
    """踏み台サーバーのIPを取得"""
    bastion_group = f"{environment}_bastion"
    if bastion_group in self.inventory and self.inventory[bastion_group]['hosts']:
        return self.inventory[bastion_group]['hosts'][0]
    return None
    
def get_inventory(self):
    """インベントリをJSON形式で返す"""
    self.get_instances()
    return json.dumps(self.inventory, indent=2)

if name == ‘main’: inventory = AWSInventory() print(inventory.get_inventory()) `

冪等性の確保とベストプラクティス

冪等性は構成管理において最も重要な概念です。同じPlaybookを何度実行しても、システムの状態が同じになることを保証します。

`yaml

冪等性を保証する書き方の例


  • name: 冪等性のあるタスク例 hosts: all become: yes

    tasks: # GOOD: 冪等性あり - ファイルが既に存在すれば変更なし

    • name: Create application directory file: path: /opt/myapp state: directory owner: myapp group: myapp mode: ‘0755’

    # GOOD: 冪等性あり - 行が既に存在すれば追加しない

    • name: Add configuration line lineinfile: path: /etc/sysctl.conf line: ‘vm.swappiness=10’ state: present

    # GOOD: 冪等性あり - パッケージが既にインストール済みなら何もしない

    • name: Install required packages package: name: - git - python3-pip - nginx state: present

    # BAD: 冪等性なし - 実行するたびにファイルに追記される

    • name: Append to log file (非推奨) shell: echo “Deployment at $(date)” » /var/log/deploy.log

    # GOOD: 上記の冪等性のある代替案

    • name: Record deployment copy: content: “Last deployment: {{ ansible_date_time.iso8601 }}\n” dest: /var/log/last_deploy.log owner: root group: root mode: ‘0644’

    # 条件付き実行で冪等性を確保

    • name: Initialize database command: /opt/myapp/bin/init_db.sh args: creates: /opt/myapp/db/.initialized

      .initializedファイルが存在する場合は実行しない

    # チェックモードでの動作確認

    • name: Configure service template: src: myapp.service.j2 dest: /etc/systemd/system/myapp.service register: service_config check_mode: yes

    • name: Reload systemd if needed systemd: daemon_reload: yes when: service_config.changed `

セキュアな変数管理

機密情報を安全に管理するため、Ansible Vaultを活用します。

`yaml

Vault暗号化されたファイルの作成

ansible-vault create vars/secrets.yml

平文の secrets.yml


database_password: “super-secret-password” api_keys: stripe: “sk_live_…” aws_access_key: “AKIA…” aws_secret_key: “…”

暗号化後

$ANSIBLE_VAULT;1.1;AES256 39613836386435386…(暗号化されたデータ)

Playbookでの使用


  • name: Deploy application with secrets hosts: webservers vars_files:
    • vars/common.yml
    • vars/secrets.yml # Vault暗号化されたファイル

    tasks:

    • name: Create database configuration template: src: database.yml.j2 dest: “{{ app_home }}/config/database.yml” owner: “{{ app_user }}” group: “{{ app_group }}” mode: ‘0600’ # 機密情報のため厳格な権限 no_log: true # ログに機密情報を出力しない

templates/database.yml.j2

production: adapter: postgresql encoding: unicode database: {{ database_name }} pool: {{ database_pool | default(5) }} username: {{ database_user }} password: {{ database_password }} # Vaultから取得 host: {{ database_host }} port: {{ database_port | default(5432) }}

実行時

ansible-playbook -i inventory site.yml –ask-vault-pass

または

ansible-playbook -i inventory site.yml –vault-password-file ~/.vault_pass

`

10.4 CI/CDパイプラインとデプロイ自動化

CI/CDの本質と価値

継続的インテグレーション(CI)と継続的デリバリー(CD)は、ソフトウェア開発のスピードと品質を両立させるための方法論です。インフラストラクチャのコード化により、これらの実践をインフラ管理にも適用できるようになりました。

CI/CDパイプラインの設計原則

`yaml パイプライン設計の原則: 高速フィードバック: - 問題の早期発見 - 10分以内の基本的なフィードバック - 段階的な詳細検証

自動化の徹底: - 手動プロセスの排除 - 一貫性の確保 - ヒューマンエラーの防止

段階的なリスク管理: - 環境を段階的に昇格 - 各段階での検証強化 - ロールバック可能性の確保

監査証跡: - すべての変更の記録 - 承認プロセスの可視化 - コンプライアンス対応 `

包括的なCI/CDパイプラインの実装

GitHub Actionsによる完全自動化

`yaml

.github/workflows/infrastructure-pipeline.yml

name: Infrastructure CI/CD Pipeline

on: pull_request: branches: [main] paths: - ‘terraform/’ - ‘ansible/’ push: branches: [main] paths: - ‘terraform/’ - ‘ansible/

env: TF_VERSION: ‘1.5.0’ ANSIBLE_VERSION: ‘2.15.0’ AWS_REGION: ‘ap-northeast-1’

jobs: # 1. 静的解析とlint static-analysis: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4

  - name: Setup Terraform
    uses: hashicorp/setup-terraform@v2
    with:
      terraform_version: ``${{ env.TF_VERSION }}``
      
  - name: Terraform Format Check
    run: |
      cd terraform
      terraform fmt -check -recursive
      
  - name: Terraform Validate
    run: |
      cd terraform
      for dir in $(find . -type f -name "*.tf" -exec dirname {} \; | sort -u); do
        echo "Validating $dir"
        (cd "$dir" && terraform init -backend=false && terraform validate)
      done
      
  - name: TFLint
    uses: terraform-linters/setup-tflint@v3
    with:
      tflint_version: latest
      
  - name: Run TFLint
    run: |
      cd terraform
      tflint --init
      tflint --recursive
      
  - name: Setup Python
    uses: actions/setup-python@v4
    with:
      python-version: '3.11'
      
  - name: Install Ansible and ansible-lint
    run: |
      pip install ansible=``${{ env.ANSIBLE_VERSION }}`` ansible-lint
      
  - name: Ansible Lint
    run: |
      cd ansible
      ansible-lint

# 2. セキュリティスキャン security-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4

  - name: Checkov Security Scan
    id: checkov
    uses: bridgecrewio/checkov-action@master
    with:
      directory: terraform/
      framework: terraform
      output_format: sarif
      output_file_path: reports/checkov.sarif
      
  - name: Upload Checkov results
    uses: github/codeql-action/upload-sarif@v2
    if: always()
    with:
      sarif_file: reports/checkov.sarif
      
  - name: Terrascan
    run: |
      wget https://github.com/tenable/terrascan/releases/latest/download/terrascan_Linux_x86_64.tar.gz
      tar -xf terrascan_Linux_x86_64.tar.gz
      ./terrascan scan -i terraform -d terraform/
      
  - name: Secrets Scan
    uses: trufflesecurity/trufflehog@main
    with:
      path: ./
      base: ``${{ github.event.repository.default_branch }}``
      head: HEAD

# 3. コスト見積もり cost-estimation: runs-on: ubuntu-latest if: github.event_name == ‘pull_request’ permissions: contents: read pull-requests: write steps: - uses: actions/checkout@v4

  - name: Setup Infracost
    uses: infracost/setup-infracost@v2
    with:
      api-key: ``${{ secrets.INFRACOST_API_KEY }}``
      
  - name: Generate Infracost JSON
    run: |
      cd terraform/environments/prod
      infracost breakdown --path . \
        --format json \
        --out-file /tmp/infracost.json
        
  - name: Post Infracost comment
    uses: infracost/infracost-comment@v1
    with:
      path: /tmp/infracost.json
      behavior: update

# 4. Terraformプラン(PR時) terraform-plan: needs: [static-analysis, security-scan] runs-on: ubuntu-latest if: github.event_name == ‘pull_request’ strategy: matrix: environment: [dev, staging, prod] permissions: contents: read pull-requests: write id-token: write

steps:
  - uses: actions/checkout@v4
  
  - name: Configure AWS Credentials
    uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: ``${{ secrets[format('AWS_{0}_ROLE', matrix.environment)] }}``
      aws-region: ``${{ env.AWS_REGION }}``
      
  - name: Setup Terraform
    uses: hashicorp/setup-terraform@v2
    with:
      terraform_version: ``${{ env.TF_VERSION }}``
      
  - name: Terraform Init
    run: |
      cd terraform/environments/``${{ matrix.environment }}``
      terraform init
      
  - name: Terraform Plan
    id: plan
    run: |
      cd terraform/environments/``${{ matrix.environment }}``
      terraform plan -out=tfplan -no-color | tee plan_output.txt
      
  - name: Create Plan Summary
    uses: actions/github-script@v6
    if: github.event_name == 'pull_request'
    with:
      script: |
        const output = `#### Terraform Plan - ``${{ matrix.environment }}`` 📋
        
        <details><summary>Show Plan</summary>
        
        \`\`\`terraform
        ${require('fs').readFileSync('terraform/environments/``${{ matrix.environment }}``/plan_output.txt', 'utf8')}
        \`\`\`
        
        </details>
        
        *Pushed by: @``${{ github.actor }}``, Action: \``${{ github.event_name }}``\`*`;
        
        github.rest.issues.createComment({
          issue_number: context.issue.number,
          owner: context.repo.owner,
          repo: context.repo.repo,
          body: output
        })

# 5. 統合テスト環境の構築 integration-test: needs: terraform-plan runs-on: ubuntu-latest if: github.event_name == ‘pull_request’ steps: - uses: actions/checkout@v4

  - name: Configure AWS Credentials
    uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: ``${{ secrets.AWS_TEST_ROLE }}``
      aws-region: ``${{ env.AWS_REGION }}``
      
  - name: Create Test Environment
    id: test-env
    run: |
      cd terraform/environments/test
      terraform init
      terraform apply -auto-approve \
        -var="pr_number=``${{ github.event.pull_request.number }}``"
      
      # 出力値を環境変数に設定
      echo "test_url=$(terraform output -raw test_environment_url)" >> $GITHUB_OUTPUT
      
  - name: Run Integration Tests
    run: |
      cd tests/integration
      npm install
      npm run test:integration -- \
        --url "``${{ steps.test-env.outputs.test_url }}``"
        
  - name: Run E2E Tests
    uses: cypress-io/github-action@v5
    with:
      config: baseUrl=``${{ steps.test-env.outputs.test_url }}``
      spec: tests/e2e/**/*.cy.js
      
  - name: Cleanup Test Environment
    if: always()
    run: |
      cd terraform/environments/test
      terraform destroy -auto-approve \
        -var="pr_number=``${{ github.event.pull_request.number }}``"

# 6. プロダクションデプロイ deploy-infrastructure: needs: [static-analysis, security-scan, cost-estimation] runs-on: ubuntu-latest if: github.ref == ‘refs/heads/main’ strategy: matrix: environment: [dev, staging, prod] max-parallel: 1 # 環境を順番にデプロイ environment: ${{ matrix.environment }}

steps:
  - uses: actions/checkout@v4
  
  - name: Configure AWS Credentials
    uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: ``${{ secrets[format('AWS_{0}_ROLE', matrix.environment)] }}``
      aws-region: ``${{ env.AWS_REGION }}``
      
  - name: Setup Terraform
    uses: hashicorp/setup-terraform@v2
    with:
      terraform_version: ``${{ env.TF_VERSION }}``
      
  - name: Terraform Apply
    run: |
      cd terraform/environments/``${{ matrix.environment }}``
      terraform init
      terraform apply -auto-approve
      
  - name: Run Smoke Tests
    run: |
      cd tests/smoke
      ./run_smoke_tests.sh ``${{ matrix.environment }}``
      
  - name: Update Documentation
    if: matrix.environment == 'prod'
    run: |
      cd terraform/environments/``${{ matrix.environment }}``
      terraform show -json > ../../../docs/infrastructure-state.json
      terraform graph | dot -Tpng > ../../../docs/infrastructure-diagram.png

# 7. 構成管理の適用 configure-servers: needs: deploy-infrastructure runs-on: ubuntu-latest if: github.ref == ‘refs/heads/main’ strategy: matrix: environment: [dev, staging, prod] max-parallel: 1

steps:
  - uses: actions/checkout@v4
  
  - name: Setup Python
    uses: actions/setup-python@v4
    with:
      python-version: '3.11'
      
  - name: Install Ansible
    run: |
      pip install ansible=``${{ env.ANSIBLE_VERSION }}``
      pip install boto3  # AWS動的インベントリ用
      
  - name: Configure AWS Credentials
    uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: ``${{ secrets[format('AWS_{0}_ROLE', matrix.environment)] }}``
      aws-region: ``${{ env.AWS_REGION }}``
      
  - name: Run Ansible Playbook
    env:
      ANSIBLE_HOST_KEY_CHECKING: False
      ANSIBLE_VAULT_PASSWORD: ``${{ secrets.ANSIBLE_VAULT_PASSWORD }}``
    run: |
      cd ansible
      
      # Vault パスワードファイルの作成
      echo "$ANSIBLE_VAULT_PASSWORD" > .vault_pass
      
      # 動的インベントリを使用してPlaybook実行
      ansible-playbook -i inventory/aws_ec2.yml \
        site.yml \
        --limit "``${{ matrix.environment }}``" \
        --vault-password-file .vault_pass
        
      # クリーンアップ
      rm -f .vault_pass

# 8. 監視とアラートの設定 setup-monitoring: needs: configure-servers runs-on: ubuntu-latest if: github.ref == ‘refs/heads/main’

steps:
  - uses: actions/checkout@v4
  
  - name: Configure Datadog
    env:
      DD_API_KEY: ``${{ secrets.DATADOG_API_KEY }}``
      DD_APP_KEY: ``${{ secrets.DATADOG_APP_KEY }}``
    run: |
      cd monitoring/datadog
      ./setup_monitors.sh
      
  - name: Configure PagerDuty
    env:
      PAGERDUTY_TOKEN: ``${{ secrets.PAGERDUTY_TOKEN }}``
    run: |
      cd monitoring/pagerduty
      ./setup_escalation_policies.sh `

デプロイメント戦略の実装

Blue-Green デプロイメント

`hcl

terraform/modules/blue-green/main.tf

variable “active_environment” { description = “現在アクティブな環境 (blue/green)” type = string default = “blue” }

locals { environments = toset([“blue”, “green”]) inactive_environment = var.active_environment == “blue” ? “green” : “blue” }

両環境のASG

resource “aws_autoscaling_group” “app” { for_each = local.environments

name = “${var.project_name}-${each.key}” vpc_zone_identifier = var.private_subnet_ids target_group_arns = each.key == var.active_environment ? [aws_lb_target_group.app.arn] : [] health_check_type = “ELB” health_check_grace_period = 300

min_size = each.key == var.active_environment ? var.min_size : 0 max_size = var.max_size desired_capacity = each.key == var.active_environment ? var.desired_capacity : 0

launch_template { id = aws_launch_template.app[each.key].id version = “$Latest” }

tag { key = “Name” value = “${var.project_name}-${each.key}” propagate_at_launch = true }

tag { key = “Environment” value = each.key propagate_at_launch = true } }

切り替えスクリプト

resource “local_file” “switch_environment” { filename = “${path.module}/switch_environment.sh” content = «-EOF #!/bin/bash set -e

CURRENT="${var.active_environment}"
TARGET="${local.inactive_environment}"

echo "Switching from $CURRENT to $TARGET environment..."

# 1. 新環境を起動
aws autoscaling set-desired-capacity \
  --auto-scaling-group-name ${var.project_name}-$TARGET \
  --desired-capacity ${var.desired_capacity}

# 2. ヘルスチェックを待つ
aws autoscaling wait \
  --auto-scaling-group-name ${var.project_name}-$TARGET \
  --query "length(AutoScalingGroups[0].Instances[?HealthStatus=='Healthy'])" \
  --desired-value ${var.desired_capacity}

# 3. トラフィックを切り替え
terraform apply -var="active_environment=$TARGET" -auto-approve

# 4. 旧環境を停止
aws autoscaling set-desired-capacity \
  --auto-scaling-group-name ${var.project_name}-$CURRENT \
  --desired-capacity 0

echo "Environment switch completed!"   EOF

file_permission = “0755” } `

カナリアデプロイメント

`yaml

kubernetes/canary-deployment.yaml

apiVersion: v1 kind: Service metadata: name: myapp spec: selector: app: myapp ports: - port: 80 targetPort: 8080 —

安定版(90%のトラフィック)

apiVersion: apps/v1 kind: Deployment metadata: name: myapp-stable spec: replicas: 9 selector: matchLabels: app: myapp version: stable template: metadata: labels: app: myapp version: stable spec: containers: - name: myapp image: myapp:v1.0.0 ports: - containerPort: 8080 —

カナリア版(10%のトラフィック)

apiVersion: apps/v1 kind: Deployment metadata: name: myapp-canary spec: replicas: 1 selector: matchLabels: app: myapp version: canary template: metadata: labels: app: myapp version: canary spec: containers: - name: myapp image: myapp:v2.0.0 ports: - containerPort: 8080


自動化されたカナリア分析

apiVersion: flagger.app/v1beta1 kind: Canary metadata: name: myapp spec: targetRef: apiVersion: apps/v1 kind: Deployment name: myapp service: port: 80 analysis: interval: 1m threshold: 10 maxWeight: 50 stepWeight: 5 metrics: - name: request-success-rate thresholdRange: min: 99 interval: 1m - name: request-duration thresholdRange: max: 500 interval: 1m webhooks: - name: load-test url: http://loadtester/ metadata: cmd: “hey -z 1m -c 10 -q 20 http://myapp/” `

GitOpsによる宣言的デプロイメント

`yaml

argocd/application.yaml

apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: infrastructure namespace: argocd spec: project: default

source: repoURL: https://github.com/myorg/infrastructure targetRevision: HEAD path: kubernetes/overlays/production

destination: server: https://kubernetes.default.svc namespace: production

syncPolicy: automated: prune: true selfHeal: true allowEmpty: false syncOptions: - CreateNamespace=true retry: limit: 5 backoff: duration: 5s factor: 2 maxDuration: 3m

# Terraform連携 initContainers:

  • name: terraform-apply image: hashicorp/terraform:1.5.0 command:
    • sh
    • -c
    • | cd /terraform terraform init terraform apply -auto-approve volumeMounts:
    • name: terraform-config mountPath: /terraform

# Post-sync hooks postSync:

  • name: smoke-tests container: image: postman/newman command: [“newman”, “run”, “smoke-tests.json”]
  • name: notify-slack container: image: curlimages/curl command:
    • sh
    • -c
    • | curl -X POST $SLACK_WEBHOOK
      -H ‘Content-type: application/json’
      -d ‘{“text”:”Deployment completed successfully”}’ `

Infrastructure as Code と自動化は、現代のクラウド運用の基盤です。宣言的な管理、バージョン管理、自動化を組み合わせることで、信頼性が高く、監査可能で、効率的なインフラストラクチャ運用が実現できます。

重要なのは、これらのツールと手法を段階的に導入し、組織の成熟度に合わせて進化させていくことです。完璧を求めるのではなく、継続的な改善を通じて、より良いインフラストラクチャ管理を実現していくことが成功への鍵となります。

第11章へ進む