4. 搭建 etcd 压测沙盘环境:Docker Compose 与 Kind 双模式部署

本章将指导你使用两种主流本地容器化方案——Docker Compose 与 Kind (Kubernetes in Docker),分别搭建多节点 etcd 集群,并配置完整的 Prometheus + Grafana 监控栈。前者适合快速原型验证与单机压测,后者更贴近生产级 K8s 跨集群调度场景。两种环境均会输出可复用的配置文件与启动脚本。

4.1 环境概览与选型对比

在开始搭建前,先明确两种方案的适用场景与核心差异:

对比维度 Docker Compose 方案 Kind 方案
部署复杂度 低,单文件编排 中,需先创建 Kind 集群
网络模型 Docker bridge 网络 K8s Pod 网络 (CNI)
etcd 成员发现 静态配置 IP:Port Headless Service + DNS SRV
压测场景适配 单机多进程压测 跨节点/跨集群调度延迟
监控栈集成 手动配置 targets Prometheus Operator CRD
资源占用 约 2GB 内存 约 4GB 内存 (含 K8s 组件)
选型建议: 若你主要关注 etcd 本身的读写性能与 Raft 行为,优先使用 Docker Compose;若需要模拟 K8s 调度器与 etcd 的交互延迟,或进行跨集群调度优化实验,请使用 Kind 方案。

4.2 使用 Docker Compose 搭建 3 节点 etcd 集群

以下配置将启动一个 3 节点 etcd 集群,节点间通过静态 peer URL 通信,并暴露客户端端口供压测工具连接。

4.2.1 docker-compose.yml 核心配置

version: '3.8'

services:
  etcd1:
    image: quay.io/coreos/etcd:v3.5.15
    container_name: etcd-node1
    environment:
      - ETCD_NAME=etcd1
      - ETCD_INITIAL_CLUSTER=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
      - ETCD_INITIAL_CLUSTER_STATE=new
      - ETCD_INITIAL_CLUSTER_=etcd-cluster-
      - ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
      - ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
      - ETCD_ADVERTISE_CLIENT_URLS=http://etcd1:2379
      - ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd1:2380
      - ETCD_QUOTA_BACKEND_BYTES=8589934592  # 8GB 后端配额
      - ETCD_SNAPSHOT_COUNT=10000
    ports:
      - "23791:2379"
      - "23801:2380"
    volumes:
      - etcd1-data:/etcd-data
    networks:
      - etcd-net

  etcd2:
    image: quay.io/coreos/etcd:v3.5.15
    container_name: etcd-node2
    environment:
      - ETCD_NAME=etcd2
      - ETCD_INITIAL_CLUSTER=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
      - ETCD_INITIAL_CLUSTER_STATE=new
      - ETCD_INITIAL_CLUSTER_=etcd-cluster-
      - ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
      - ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
      - ETCD_ADVERTISE_CLIENT_URLS=http://etcd2:2379
      - ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd2:2380
      - ETCD_QUOTA_BACKEND_BYTES=8589934592
      - ETCD_SNAPSHOT_COUNT=10000
    ports:
      - "23792:2379"
      - "23802:2380"
    volumes:
      - etcd2-data:/etcd-data
    networks:
      - etcd-net

  etcd3:
    image: quay.io/coreos/etcd:v3.5.15
    container_name: etcd-node3
    environment:
      - ETCD_NAME=etcd3
      - ETCD_INITIAL_CLUSTER=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
      - ETCD_INITIAL_CLUSTER_STATE=new
      - ETCD_INITIAL_CLUSTER_=etcd-cluster-
      - ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
      - ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
      - ETCD_ADVERTISE_CLIENT_URLS=http://etcd3:2379
      - ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd3:2380
      - ETCD_QUOTA_BACKEND_BYTES=8589934592
      - ETCD_SNAPSHOT_COUNT=10000
    ports:
      - "23793:2379"
      - "23803:2380"
    volumes:
      - etcd3-data:/etcd-data
    networks:
      - etcd-net

volumes:
  etcd1-data:
  etcd2-data:
  etcd3-data:

networks:
  etcd-net:
    driver: bridge

4.2.2 启动与验证

# 启动集群
docker compose -f docker-compose.yml up -d

# 验证成员列表
docker exec etcd-node1 etcdctl member list --write-out=table

# 测试写入与读取
docker exec etcd-node1 etcdctl put /test/key "hello-sandbox"
docker exec etcd-node1 etcdctl get /test/key

# 检查集群健康状态
docker exec etcd-node1 etcdctl endpoint health --cluster
关键参数说明:
  • ETCD_QUOTA_BACKEND_BYTES:设置后端数据库配额为 8GB,避免压测时因空间不足导致写入失败。
  • ETCD_SNAPSHOT_COUNT:每 10000 次提交触发一次快照,平衡恢复速度与 I/O 开销。
  • 端口映射规则:宿主机 23791/23792/23793 分别对应三个节点的客户端端口,便于压测工具直接连接。

4.3 使用 Kind 搭建 K8s 内嵌 etcd 集群

Kind 方案将 etcd 以 StatefulSet 形式运行在本地 K8s 集群中,更真实地模拟生产环境下的网络拓扑与 DNS 服务发现。

4.3.1 创建 Kind 集群并配置节点

# kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    extraPortMappings:
      - containerPort: 30090
        hostPort: 30090
        protocol: TCP
  - role: worker
  - role: worker
  - role: worker
# 创建集群
kind create cluster --config kind-config.yaml --name etcd-sandbox

# 验证节点状态
kubectl get nodes -o wide

4.3.2 部署 etcd StatefulSet 与 Headless Service

# etcd-statefulset.yaml
apiVersion: v1
kind: Service
metadata:
  name: etcd-headless
  namespace: default
spec:
  clusterIP: None
  ports:
    - name: client
      port: 2379
      targetPort: 2379
    - name: peer
      port: 2380
      targetPort: 2380
  selector:
    app: etcd
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: etcd
  namespace: default
spec:
  serviceName: etcd-headless
  replicas: 3
  selector:
    matchLabels:
      app: etcd
  template:
    metadata:
      labels:
        app: etcd
    spec:
      containers:
        - name: etcd
          image: quay.io/coreos/etcd:v3.5.15
          env:
            - name: ETCD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: ETCD_INITIAL_CLUSTER
              value: "etcd-0=http://etcd-0.etcd-headless:2380,etcd-1=http://etcd-1.etcd-headless:2380,etcd-2=http://etcd-2.etcd-headless:2380"
            - name: ETCD_INITIAL_CLUSTER_STATE
              value: "new"
            - name: ETCD_INITIAL_CLUSTER_
              value: "etcd-kind-"
            - name: ETCD_LISTEN_PEER_URLS
              value: "http://0.0.0.0:2380"
            - name: ETCD_LISTEN_CLIENT_URLS
              value: "http://0.0.0.0:2379"
            - name: ETCD_ADVERTISE_CLIENT_URLS
              value: "http://$(POD_NAME).etcd-headless:2379"
            - name: ETCD_INITIAL_ADVERTISE_PEER_URLS
              value: "http://$(POD_NAME).etcd-headless:2380"
            - name: ETCD_QUOTA_BACKEND_BYTES
              value: "8589934592"
          ports:
            - containerPort: 2379
              name: client
            - containerPort: 2380
              name: peer
          volumeMounts:
            - name: data
              mountPath: /etcd-data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: [ "ReadWriteOnce" ]
        resources:
          requests:
            storage: 10Gi
# 部署到 Kind 集群
kubectl apply -f etcd-statefulset.yaml

# 等待 Pod 全部 Running
kubectl get pods -l app=etcd -w

# 通过 DNS 验证集群成员
kubectl run -it --rm debug --image=busybox -- nslookup etcd-headless
注意: Kind 环境下 etcd 的 ETCD_ADVERTISE_CLIENT_URLS 必须使用 $(POD_NAME).etcd-headless:2379 格式,否则客户端无法通过 DNS SRV 记录正确发现所有成员。这是 Kind 网络与生产 K8s 环境的关键差异点。

4.4 配置 Prometheus + Grafana 监控栈

监控栈是压测沙盘的眼睛。我们将为两种环境分别配置 Prometheus 抓取 etcd 指标,并用 Grafana 展示集群状态与性能面板。

4.4.1 监控架构图

etcd 压测沙盘监控架构 etcd-0 :2379/metrics etcd-1 :2379/metrics etcd-2 :2379/metrics Prometheus scrape_interval: 15s Grafana Dashboards 压测工具 benchmark / ghz 数据流方向 压测 → etcd → Prometheus → Grafana

4.4.2 Prometheus 配置(Docker Compose 环境)

# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'etcd-cluster'
    static_configs:
      - targets:
        - 'etcd1:2379'
        - 'etcd2:2379'
        - 'etcd3:2379'
    metrics_path: /metrics
    scheme: http

  - job_name: 'node-exporter'
    static_configs:
      - targets:
        - 'node-exporter:9100'

在 docker-compose.yml 中追加 Prometheus 与 Grafana 服务:

  prometheus:
    image: prom/prometheus:v2.53.0
    container_name: prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"
    networks:
      - etcd-net

  grafana:
    image: grafana/grafana:11.1.0
    container_name: grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    ports:
      - "3000:3000"
    volumes:
      - grafana-data:/var/lib/grafana
    networks:
      - etcd-net

volumes:
  grafana-data:

4.4.3 Prometheus 配置(Kind 环境)

Kind 环境下推荐使用 Prometheus Operator 或直接部署 Prometheus 实例并通过 Service 发现 etcd Pod:

# prometheus-kind.yml
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: monitoring
data:
  prometheus.yml: |
    global:
      scrape_interval: 15s
    scrape_configs:
      - job_name: 'etcd'
        kubernetes_sd_configs:
          - role: pod
        relabel_configs:
          - source_labels: [__meta_kubernetes_pod_label_app]
            regex: etcd
            action: keep
          - source_labels: [__meta_kubernetes_pod_container_port_number]
            regex: "2379"
            action: keep
          - source_labels: [__address__]
            action: replace
            regex: ([^:]+)(?::\d+)?
            replacement: $1:2379
            target_label: __address__
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: prometheus
  namespace: monitoring
spec:
  replicas: 1
  selector:
    matchLabels:
      app: prometheus
  template:
    metadata:
      labels:
        app: prometheus
    spec:
      containers:
        - name: prometheus
          image: prom/prometheus:v2.53.0
          args:
            - "--config.file=/etc/prometheus/prometheus.yml"
            - "--storage.tsdb.path=/prometheus"
          ports:
            - containerPort: 9090
          volumeMounts:
            - name: config
              mountPath: /etc/prometheus
            - name: data
              mountPath: /prometheus
      volumes:
        - name: config
          configMap:
            name: prometheus-config
        - name: data
          emptyDir: {}

4.4.4 Grafana 仪表盘导入

Grafana 启动后,通过浏览器访问 http://localhost:3000(默认账号/密码:admin/admin)。推荐导入以下两个关键仪表盘:

仪表盘名称 Grafana Dashboard ID 核心监控指标
etcd 集群概览 3070 Leader 变化、Raft 提案延迟、DB 大小、磁盘 fsync 延迟
etcd 性能细粒度 16220 99 分位读写延迟、后端提交耗时、WAL 写入速率、gRPC 请求分布
快速导入命令:
# 通过 Grafana API 导入仪表盘 3070
curl -X POST -H "Content-Type: application/json" \
  -d '{"dashboard":{"id":null,"uid":"etcd-cluster","title":"etcd Cluster Overview"},"overwrite":true}' \
  http://admin:admin@localhost:3000/api/dashboards/db

更推荐在 Grafana UI 中通过 Import 功能直接输入 Dashboard ID 进行可视化导入。

4.5 环境验证与压测前置检查

在开始压测前,执行以下清单确认沙盘环境就绪:

  1. etcd 集群健康检查: 所有节点均为 healthy,Leader 唯一且稳定。
  2. Prometheus 目标状态: 访问 http://localhost:9090/targets,确认三个 etcd 节点均为 UP 状态。
  3. Grafana 数据源: 已添加 Prometheus 数据源,且测试通过。
  4. 网络连通性: 压测工具所在容器/主机能够通过 2379 端口访问所有 etcd 节点。
  5. 磁盘 I/O 基线: 使用 fio 或 iostat 记录宿主机磁盘延迟基线,便于后续压测时对比。
常见问题排查:
  • etcd 节点无法加入集群: 检查 ETCD_INITIAL_CLUSTER 中的主机名是否与容器名或 Pod 名完全匹配,Kind 环境下注意 DNS 解析。
  • Prometheus 抓取超时: 增大 scrape_timeout 至 30s,或检查 etcd 节点是否因高负载导致 metrics 端点响应缓慢。
  • Grafana 面板无数据: 确认 Prometheus 数据源中的 URL 是否正确(Docker Compose 使用容器名,Kind 使用 Service ClusterIP)。

4.6 沙盘环境目录结构

为便于后续章节复用,建议按以下结构组织沙盘文件:

etcd-sandbox/
├── docker-compose/
│   ├── docker-compose.yml
│   ├── prometheus.yml
│   └── grafana-dashboards/
│       ├── etcd-cluster-overview.json
│       └── etcd-performance-detailed.json
├── kind/
│   ├── kind-config.yaml
│   ├── etcd-statefulset.yaml
│   ├── prometheus-kind.yml
│   └── grafana-deployment.yaml
├── scripts/
│   ├── start-docker-compose.sh
│   ├── start-kind.sh
│   └── verify-cluster.sh
└── README.md