mcpField
I
infrastructure-as-code

infrastructure-as-code

github.com/seb1nクラウド・DevOps232118 回インストール11 週間前に更新
リポジトリ

infrastructure-as-code について

Terraform、Pulumi、AWS CloudFormation、AWS CDK、Ansibleを使ったクラウドのインフラコード設計・管理を支援するスキルです。モジュール化、状態管理、計画の確認後の適用、ドリフト検出を扱います。

主な機能

  1. 01複数のIaCツール向けコード生成
  2. 02再利用可能なインフラモジュール
  3. 03リモート状態とロックの設定
  4. 04計画確認後の適用フロー
  5. 05ドリフトの検出と修正

活用シーン

  1. 01複数環境へのクラウドリソース構築
  2. 02ネットワーク・コンピュートモジュールの生成
  3. 03デプロイ前の変更計画の確認
  4. 04コード外の変更の検出と調整

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/seb1n/awesome-ai-agent-skills --skill infrastructure-as-code

インストール後、Claude Code で/infrastructure-as-codeを入力して読み込まれたか確認します。

詳細

Infrastructure as Code

このスキルを使うと、エージェントがクラウド環境向けの Infrastructure as Code(IaC)を設計、生成、管理できます。エージェントは Terraform、Pulumi、AWS CloudFormation、AWS CDK の設定を作成し、適切な状態管理、モジュール設計、ドリフト検出を備えた plan/apply ワークフロー全体を実装できます。IaC を使うと、インフラストラクチャをアプリケーションコードとともにバージョン管理できるため、再現性のあるデプロイ、インフラストラクチャの変更に対するピアレビュー、環境全体での自動プロビジョニングが可能になります。

ワークフロー

  1. インフラストラクチャ要件の収集: エージェントは、クラウドプロバイダー(AWS、GCP、Azure)、必要なリソース(コンピューティング、ストレージ、ネットワーク、データベース)、サイズとパフォーマンスの要件、セキュリティ上の制約、対象環境(dev、staging、production)など、必要なインフラストラクチャの詳細を収集します。リソース間の依存関係を特定し、適切なプロビジョニング順序を決定します。

  2. IaC ツールの選択とプロジェクトの初期化: チームの専門知識とプロジェクトの制約に基づいて、エージェントが IaC ツールを推奨します。マルチクラウドやプロバイダーに依存しない構成には Terraform、汎用プログラミング言語を希望するチームには Pulumi、AWS ネイティブの組織には CloudFormation または CDK が推奨されます。モジュール、環境、共有設定用にディレクトリを分けたプロジェクト構成を初期化します。

  3. モジュールを使ったインフラストラクチャコードの生成: エージェントは再利用可能なモジュールを使用して、適切に構成された IaC コードを作成します。ネットワーク(VPC、サブネット、セキュリティグループ)、コンピューティング(EC2、ECS、Lambda)、データ(RDS、S3、DynamoDB)を、明確に定義された入力と出力を持つ独立したモジュールに分割します。変数をパラメーター化することで、サイズを変えながら同じモジュールを各環境で再利用できます。

  4. 状態管理の設定: エージェントは、同時変更を防ぐための状態ロック機能を備えたリモート状態ストレージ(例:Terraform 用の S3 + DynamoDB、Pulumi 用の Pulumi Cloud)を設定します。状態ファイルには機密データが含まれるため、バージョン管理には決してコミットしません。状態バックエンドの保存時暗号化と厳格なアクセス制御も設定します。

  5. Plan と Apply の実行: エージェントは plan ステップ(terraform plan、pulumi preview)を実行して変更案の詳細な差分を生成し、適用前にユーザーに提示してレビューを受けます。予期しないリソースの破棄や再作成がないことを確認します。明示的な承認を得てから、apply ステップを実行してインフラストラクチャをプロビジョニングします。

  6. ドリフトの検出と修正: エージェントは定期的にドリフト検出(terraform plan、pulumi refresh)を実行し、実際のインフラストラクチャの状態を宣言された設定と比較します。コンソールや CLI で行われた IaC 外の変更にはフラグを立て、IaC の定義に合わせて戻すか、明示的に状態へインポートします。これにより、IaC コードを唯一の信頼できる情報源として維持できます。

対応テクノロジー

  • IaC ツール: Terraform(HCL)、Pulumi(TypeScript、Python、Go、C#)、AWS CloudFormation(YAML/JSON)、AWS CDK(TypeScript、Python)、Ansible
  • クラウドプロバイダー: AWS、Google Cloud Platform、Microsoft Azure、DigitalOcean、Cloudflare
  • 状態バックエンド: S3 + DynamoDB、Terraform Cloud、Pulumi Cloud、GCS、Azure Blob Storage
  • CI/CD 統合: Atlantis、Spacelift、Terraform Cloud、GitHub Actions、GitLab CI

使い方

クラウドプロバイダー、プロビジョニングするリソース、サイズ要件、コンプライアンス基準やコスト予算などの制約をエージェントに伝えてください。

プロンプトの例:

Create Terraform configuration for an AWS environment with:
- VPC with public and private subnets across 2 AZs
- An EC2 bastion host in the public subnet
- An RDS PostgreSQL instance in the private subnet
- Security groups allowing SSH to bastion and app-to-database traffic only

例

例 1:AWS VPC + EC2 + RDS の Terraform 設定

main.tf:

terraform {
  required_version = ">= 1.5"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }

  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

provider "aws" {
  region = var.aws_region
}

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.1.0"

  name = "${var.project}-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["${var.aws_region}a", "${var.aws_region}b"]
  public_subnets  = ["10.0.1.0/24", "10.0.2.0/24"]
  private_subnets = ["10.0.10.0/24", "10.0.20.0/24"]

  enable_nat_gateway   = true
  single_nat_gateway   = true
  enable_dns_hostnames = true

  tags = var.common_tags
}

resource "aws_security_group" "bastion" {
  name_prefix = "${var.project}-bastion-"
  vpc_id      = module.vpc.vpc_id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = [var.allowed_ssh_cidr]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = merge(var.common_tags, { Name = "${var.project}-bastion-sg" })
}

resource "aws_instance" "bastion" {
  ami                         = data.aws_ami.amazon_linux.id
  instance_type               = "t3.micro"
  subnet_id                   = module.vpc.public_subnets[0]
  vpc_security_group_ids      = [aws_security_group.bastion.id]
  key_name                    = var.key_pair_name
  associate_public_ip_address = true

  tags = merge(var.common_tags, { Name = "${var.project}-bastion" })
}

resource "aws_security_group" "rds" {
  name_prefix = "${var.project}-rds-"
  vpc_id      = module.vpc.vpc_id

  ingress {
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = [aws_security_group.bastion.id]
  }

  tags = merge(var.common_tags, { Name = "${var.project}-rds-sg" })
}

resource "aws_db_instance" "postgres" {
  identifier             = "${var.project}-db"
  engine                 = "postgres"
  engine_version         = "16.1"
  instance_class         = var.db_instance_class
  allocated_storage      = 20
  max_allocated_storage  = 100
  storage_encrypted      = true
  db_name                = var.db_name
  username               = var.db_username
  password               = var.db_password
  db_subnet_group_name   = module.vpc.database_subnet_group_name
  vpc_security_group_ids = [aws_security_group.rds.id]
  skip_final_snapshot    = false
  final_snapshot_identifier = "${var.project}-db-final"
  backup_retention_period   = 7
  multi_az                  = var.environment == "production"

  tags = var.common_tags
}

data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }
}

output "bastion_public_ip" {
  value = aws_instance.bastion.public_ip
}

output "rds_endpoint" {
  value = aws_db_instance.postgres.endpoint
}

variables.tf:

variable "aws_region"       { default = "us-east-1" }
variable "project"          { default = "myproject" }
variable "environment"      { default = "production" }
variable "allowed_ssh_cidr" { description = "CIDR block allowed to SSH to bastion" }
variable "key_pair_name"    { description = "EC2 key pair name" }
variable "db_instance_class" { default = "db.t3.medium" }
variable "db_name"          { default = "appdb" }
variable "db_username"      { default = "appuser" }
variable "db_password"      { sensitive = true }
variable "common_tags" {
  type    = map(string)
  default = { ManagedBy = "terraform", Project = "myproject" }
}
例 2:サーバーレス API 向けの Pulumi TypeScript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as apigateway from "@pulumi/aws-apigateway";

const config = new pulumi.Config();
const stage = pulumi.getStack();

// DynamoDB table for the API
const table = new aws.dynamodb.Table("items-table", {
  attributes: [{ name: "id", type: "S" }],
  hashKey: "id",
  billingMode: "PAY_PER_REQUEST",
  tags: { Environment: stage, ManagedBy: "pulumi" },
});

// Lambda function for API handlers
const lambdaRole = new aws.iam.Role("api-lambda-role", {
  assumeRolePolicy: JSON.stringify({
    Version: "2012-10-17",
    Statement: [{
      Action: "sts:AssumeRole",
      Effect: "Allow",
      Principal: { Service: "lambda.amazonaws.com" },
    }],
  }),
});

new aws.iam.RolePolicyAttachment("lambda-basic", {
  role: lambdaRole.name,
  policyArn: aws.iam.ManagedPolicies.AWSLambdaBasicExecutionRole,
});

new aws.iam.RolePolicyAttachment("lambda-dynamodb", {
  role: lambdaRole.name,
  policyArn: aws.iam.ManagedPolicies.AmazonDynamoDBFullAccess,
});

const handler = new aws.lambda.Function("api-handler", {
  runtime: aws.lambda.Runtime.NodeJS20dX,
  handler: "index.handler",
  code: new pulumi.asset.AssetArchive({
    ".": new pulumi.asset.FileArchive("./lambda"),
  }),
  role: lambdaRole.arn,
  environment: {
    variables: {
      TABLE_NAME: table.name,
      STAGE: stage,
    },
  },
  memorySize: 256,
  timeout: 30,
  tags: { Environment: stage, ManagedBy: "pulumi" },
});

// API Gateway REST API
const api = new apigateway.RestAPI("items-api", {
  routes: [
    { path: "/items", method: "GET", eventHandler: handler },
    { path: "/items", method: "POST", eventHandler: handler },
    { path: "/items/{id}", method: "GET", eventHandler: handler },
    { path: "/items/{id}", method: "DELETE", eventHandler: handler },
  ],
  stageName: stage,
});

export const apiUrl = api.url;
export const tableName = table.name;

ベストプラクティス

  • 本番環境ではローカルに状態を保存しないでください: 常に、状態ロック機能を備えたリモートバックエンド(S3 + DynamoDB、Terraform Cloud、Pulumi Cloud)を使用してください。ローカルの状態ファイルは、紛失したり破損したり、複数のチームメンバーが同時に apply を実行した際に競合を引き起こしたりする可能性があります。
  • 再利用性を高めるためにモジュールを使用してください: 共通のパターン(VPC、セキュリティグループ、ECS サービス)をバージョン管理されたモジュールに切り出してください。これにより重複が減り、すべての環境とチームでインフラストラクチャ標準を一貫して適用できます。
  • シークレットは機密変数として扱ってください: データベースのパスワード、API キー、トークンには Terraform の sensitive を指定するか、Pulumi のシークレット暗号化を使用してください。IaC ファイルにシークレットを直接記述しないでください。実行時のシークレット注入には、AWS Secrets Manager または HashiCorp Vault を連携させてください。
  • CI で plan を実行し、承認後に apply してください: IaC を CI/CD パイプラインに統合し、すべてのプルリクエストで plan の差分を確認できるようにしてください。Atlantis や Spacelift などのツールで plan のコメントを自動化し、本番環境で apply を実行する前に手動承認を必須にしてください。
  • すべてのリソースに一貫したタグを付けてください: すべてのリソースに標準タグ(Project、Environment、Team、ManagedBy)を適用してください。タグを使うと、コストの割り当て、アクセス制御、孤立したリソースの自動クリーンアップが可能になります。
  • 環境ごとにワークスペースまたはスタックを使用してください: Terraform ワークスペースまたは Pulumi スタックを使い、環境(dev、staging、production)ごとに状態を分けて管理してください。環境固有の変数ファイルを使って同じコードを共有し、環境間の整合性を確保してください。

エッジケース

  • 状態ロックの競合: 前回の apply がクラッシュしたり中断されたりすると、状態ロックが保持されたままになることがあります。他の apply が実行中でないことを確認したうえで、terraform force-unlock(ロック ID を指定)を使用してください。Pulumi では同様の状況で pulumi cancel を使用できます。
  • 手動変更によるドリフト: クラウドコンソールや CLI で変更したリソースは、IaC の状態と一致しません。terraform plan を定期的に実行してドリフトを検出し、手動の変更を元に戻すか、terraform import でインポートしてください。IaC で管理しているリソースを手動で変更することは避けてください。
  • 循環依存関係: Terraform は、循環するリソース参照(例:セキュリティグループ A が B を参照し、B が A を参照するケース)を処理できません。まずルールなしでグループを作成し、別の aws_security_group_rule リソースでルールを追加して、循環を解消してください。
  • プロバイダーのバージョンアップによる破壊的変更: プロバイダーのメジャーアップデートによってリソースのスキーマが変わり、plan が失敗することがあります。required_providers でプロバイダーのバージョンを固定し、テスト済みの plan/apply サイクルを経て慎重にアップグレードしてください。
  • 大規模な状態ファイルとパフォーマンス: インフラストラクチャが拡大すると、状態ファイルが大きくなり、plan/apply の処理が遅くなることがあります。インフラストラクチャを、それぞれ独自の状態ファイルを持つ個別のルートモジュール(ネットワーキング、コンピュート、データ)に分割し、terraform_remote_state データソースで出力を共有してください。

コメント