4. 基础设施即代码入门

在持续交付体系中,Elasticsearch 集群不应通过控制台手工点击创建,而应以代码形式声明并纳入版本控制。基础设施即代码(IaC)的核心价值在于:集群拓扑、节点规格、存储、网络与安全策略都成为可审查、可回滚、可复现的声明式配置。本节以 Terraform 为主线,同时对照 CloudFormation 的等价表达,说明如何定义一套可交付的 Elasticsearch 集群及其依赖资源。

一、先厘清依赖关系,再写资源声明

一个可用的 Elasticsearch 集群并非孤立资源,它依赖一组前置条件:VPC 与子网划分、安全组规则、IAM 角色与策略、KMS 密钥(用于静态加密)、CloudWatch 日志组,以及节点所用的实例配置。IaC 工具通过“资源引用”自动推导依赖顺序,因此我们只需声明“谁引用谁”,而不必手工编排创建次序。下图给出本节示例中资源之间的依赖拓扑与数据流方向。

Elasticsearch 集群 IaC 资源依赖拓扑 VPC 10.20.0.0/16 子网 ×3 跨 3 个可用区 安全组 9200 / 9300 入站 IAM 角色 ES 服务信任策略 KMS 密钥 静态加密 CloudWatch 日志组 慢查询 / 应用日志 Elasticsearch 域 3 主节点 m6g.large.search 3 数据节点 r6g.xlarge.search EBS gp3 100 GiB / 节点 ES 7.10 / 单栈模式 专用主节点启用 输出值 endpoint / kibana_endpoint 引用 引用 引用 依赖 依赖 依赖

二、Terraform 资源声明示例

下面给出一个精简但可运行的 Terraform 片段,覆盖 VPC、子网、安全组与 Elasticsearch 域。示例参数为教学假设值,实际使用时需按区域与容量规划调整。

resource "aws_vpc" "es_vpc" {
  cidr_block           = "10.20.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = { Name = "es-iac-vpc" }
}

resource "aws_subnet" "es_subnet" {
  count             = 3
  vpc_id            = aws_vpc.es_vpc.id
  cidr_block        = cidrsubnet(aws_vpc.es_vpc.cidr_block, 8, count.index)
  availability_zone = element(["ap-southeast-1a","ap-southeast-1b","ap-southeast-1c"], count.index)
  tags = { Name = "es-subnet-${count.index}" }
}

resource "aws_security_group" "es_sg" {
  name   = "es-iac-sg"
  vpc_id = aws_vpc.es_vpc.id
  ingress {
    from_port   = 9200
    to_port     = 9200
    protocol    = "tcp"
    cidr_blocks = ["10.20.0.0/16"]
  }
  ingress {
    from_port   = 9300
    to_port     = 9300
    protocol    = "tcp"
    self        = true
  }
}

resource "aws_elasticsearch_domain" "es" {
  domain_name           = "es-iac-demo"
  elasticsearch_version = "7.10"

  cluster_config {
    instance_type          = "r6g.xlarge.search"
    instance_count         = 3
    dedicated_master_enabled = true
    dedicated_master_type  = "m6g.large.search"
    dedicated_master_count = 3
    zone_awareness_enabled = true
    zone_awareness_config { availability_zone_count = 3 }
  }

  ebs_options {
    ebs_enabled = true
    volume_type = "gp3"
    volume_size = 100
  }

  vpc_options {
    subnet_ids         = aws_subnet.es_subnet[*].id
    security_group_ids = [aws_security_group.es_sg.id]
  }

  encrypt_at_rest { enabled = true }
  node_to_node_encryption { enabled = true }
  domain_endpoint_options {
    enforce_https       = true
    tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
  }

  tags = { Environment = "dev", ManagedBy = "terraform" }
}

output "es_endpoint" {
  value = aws_elasticsearch_domain.es.endpoint
}

关键点在于:vpc_options 引用了子网与安全组资源,Terraform 据此自动建立依赖图,先建网络再建域;zone_awareness_config 的可用区数量必须与子网数量一致,否则 apply 阶段会报错。这类约束正是 IaC 在计划阶段就能暴露的问题。

三、CloudFormation 的等价表达

若团队使用 CloudFormation,可用 AWS::Elasticsearch::Domain 资源类型表达同一集群,并通过 Ref 与 Fn::GetAtt 建立引用。与 Terraform 的差异在于:CloudFormation 使用 JSON/YAML 模板,依赖通过 DependsOn 或隐式引用推导,回滚由栈机制自动完成。两者在语义上等价,选择取决于团队既有工具链与状态管理偏好。

实践建议:无论选用哪种工具,都应把 terraform plan 或 CloudFormation 变更集作为发布流水线的强制门禁。计划输出中若出现“销毁并重建”Elasticsearch 域,必须人工确认,因为域重建会导致数据丢失与服务中断。

四、状态管理与变更安全

Terraform 的远程状态应存放在带版本控制与加密的 S3 后端,并启用 DynamoDB 锁表防止并发 apply。状态文件包含集群端点、ARN 等敏感信息,不应提交到代码仓库。对于 Elasticsearch 域,以下字段属于“不可变或高风险”属性:domain_name、elasticsearch_version 的大版本升级、vpc_options 的子网变更。修改这些字段可能触发重建,应在变更前通过 lifecycle { prevent_destroy = true } 加以保护。

变更类型典型字段影响建议
就地更新instance_count、ebs volume_size滚动扩容,服务不中断可直接 apply
蓝绿替换instance_type节点逐个替换低峰期执行
重建风险domain_name、子网列表域重建,数据丢失prevent_destroy + 人工审批

五、验证与交付闭环

apply 完成后,应通过输出值验证集群可达性。例如使用 curl -XGET https://${es_endpoint}/_cluster/health 检查返回的 status 是否为 green 或 yellow,并确认 number_of_nodes 与声明一致。若状态为 red,优先检查安全组是否放行 9200、子网路由是否可达。将这些验证脚本纳入流水线,即可实现“代码提交 → plan → 审批 → apply → 健康检查”的完整交付闭环,使 Elasticsearch 集群的创建与变更真正具备可重复性与可审计性。