mcpField
I
infrastructure-as-code

infrastructure-as-code

github.com/seb1n云与 DevOps232118 次安装11 周前更新
代码仓库

关于 infrastructure-as-code

此技能帮助 Agent 使用 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确认已加载。

详细介绍

基础设施即代码

此技能可帮助 Agent 为云环境设计、生成和管理基础设施即代码(IaC)。Agent 可以为 Terraform、Pulumi、AWS CloudFormation 和 AWS CDK 生成配置,并实现完整的计划/应用工作流,同时妥善管理状态、采用模块化设计并检测配置漂移。IaC 可确保基础设施与应用代码一同进行版本管理,从而实现可重复部署、基础设施变更的同行评审,以及跨环境自动化配置。

工作流

  1. 收集基础设施需求: Agent 收集所需基础设施的详细信息,包括云服务提供商(AWS、GCP、Azure)、所需资源(计算、存储、网络、数据库)、容量和性能要求、安全约束以及目标环境(开发、预发布、生产)。Agent 会识别资源之间的依赖关系,以确定正确的配置顺序。

  2. 选择 IaC 工具并初始化项目: Agent 根据团队的专业知识和项目约束推荐 IaC 工具。对于多云和与服务提供商无关的设置,优先选择 Terraform;对于偏好使用通用编程语言的团队,推荐 Pulumi;对于以 AWS 为主的组织,则推荐 CloudFormation 或 CDK。Agent 会初始化项目结构,并为模块、环境和共享配置分别创建目录。

  3. 使用模块生成基础设施代码: Agent 使用可复用模块生成结构清晰的 IaC 代码。网络(VPC、子网、安全组)、计算(EC2、ECS、Lambda)和数据(RDS、S3、DynamoDB)分别拆分为独立模块,并明确定义输入和输出。变量采用参数化配置,以便同一模块在不同环境中使用不同容量配置。

  4. 配置状态管理: Agent 会设置远程状态存储(例如,Terraform 使用 S3 + DynamoDB,Pulumi 使用 Pulumi Cloud),并启用状态锁定以防止并发修改。状态文件包含敏感数据,绝不会提交到版本控制中。Agent 会配置静态状态加密,并对状态后端实施严格的访问控制。

  5. 执行计划和应用: Agent 运行计划步骤(terraform plan、pulumi preview),生成拟议变更的详细差异,然后在应用前将计划提交给用户审核。Agent 会确认没有意外资源将被销毁或重新创建。只有在获得明确批准后,Agent 才会执行应用步骤来配置基础设施。

  6. 检测并修复配置漂移: Agent 会定期运行漂移检测(terraform plan、pulumi refresh),比较实际基础设施状态与声明的配置。通过控制台或 CLI 在代码之外进行的任何更改都会被标记,并会被还原到 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

使用方法

向 Agent 提供云服务提供商、要配置的资源、容量要求,以及合规标准或成本预算等任何约束。

提示词示例:

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 服务)提取到带版本管理的模块中。这样可以减少重复,并确保所有环境和团队始终遵循统一的基础设施标准。
  • 将密钥视为敏感变量: 在 Terraform 中将数据库密码、API 密钥和令牌标记为 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 资源添加规则,以打破循环依赖。
  • Provider 版本的破坏性更改: 主要版本的 Provider 更新可能会更改资源架构并导致 plan 失败。在 required_providers 中固定 Provider 版本,并通过经过测试的 plan/apply 流程谨慎升级。
  • 状态文件过大导致性能下降: 随着基础设施规模扩大,状态文件可能变得很大,从而拖慢 plan/apply 操作。可按状态拆分基础设施,将其组织为独立的根模块(networking、compute、data),每个模块使用自己的状态文件,并通过 terraform_remote_state 数据源共享输出。

评论

支持 Markdown