Contents

etcd教程(十六)---如何搭建生产可用高可用集群

本文主要记录了如何部署生产可用的高可用 etcd 集群。

按照本文操作,你可以获得一个由 systemd 管理的,开启 tls 认证的生产可用的 3 节点 etcd 集群。

生产环境推荐为 etcd 生成证书,以 https 方式对外暴露服务以提升安全性。

之前实验时使用 docker 部署的,具体见:etcd教程(一)—通过docker安装etcd集群,但是在生产环境肯定不能这样简单粗暴,一般是使用 systemd 来管理,而且对硬件也有一定要求。

都提供了相应脚本,只需要替换部分变量即可实现快速部署。

0. 环境准备

准备 3 个节点来部署 etcd 集群。

官方文档:Hardware recommendations

一般来说推荐使用 4C8G 50G SSD + 1000Mbps 网卡,这个配置就够用了。

CPU

etcd 不怎么需要 cpu,因此一般的集群 2 或者 4核 cpu 的节点即可。

对于 每秒服务数千个客户端或数万个请求的大型 etcd 集群,则建议 8 或者 16核。

内存

Etcd 同样不太需要内存,etcd 内存消耗可以分为两部分:

  • Blotdb 数据缓存(treeindex),这部分一般不会超过 2 G
  • watcher,客户端调用 watch 方法后,需要在内存中存储大量数据,这部分会比较消耗内存

还有就是 etcd 启动时会把 blotdb 中的所有数据加载到内存中,因此启动时对内存的消耗会比较大。

一般 8G 即可,对于具有数千个观察者和数百万个密钥的集群,则建议分配 16GB 到 64GB 内存。

磁盘

磁盘速度是 etcd 部署性能和稳定性的最关键因素。

不过 etcd 作为元数据存储,对磁盘容量没有太大要求,社区一般推荐 etcd 数据库大小到 8G,那么磁盘分配 50G 就绰绰有余了。

主要是对性能(延迟)有要求,建议使用 FIO 工具进行测试,测试命令如下:

需要 fio 3.5 以上版本,否则报告中没有 fdatasync 的百分位数据

mkdir test-data
fio --rw=write --ioengine=sync --fdatasync=1 --directory=test-data --size=22m --bs=2300 --name=mytest

fdatasync p99 超过 10,000 us (10 milliseconds) 的话就说明磁盘性能对 etcd 来说不够用,小于这个值则说明磁盘足够快。

在测试期间,整个 I/O 负载来自 fio。在真实的场景中,除了与 wal_fsync_duration_seconds 相关的请求之外,很可能还会有其他写入请求。额外的负载将增加 wal_fsync_duration_seconds 的值。因此,如果第 99 百分位数几乎达到 10 毫秒,则说明您的磁盘将不够快。

比如下面是一个 fio 测试报告,可以看到 p99 为 23200(23ms),远大于阈值,说明这个磁盘对 etcd 来说就比较慢。

 fsync/fdatasync/sync_file_range:
    sync (usec): min=857, max=358913, avg=2528.48, stdev=5491.03
    sync percentiles (usec):
     |  1.00th=[  1106],  5.00th=[  1237], 10.00th=[  1319], 20.00th=[  1418],
     | 30.00th=[  1500], 40.00th=[  1565], 50.00th=[  1647], 60.00th=[  1745],
     | 70.00th=[  1860], 80.00th=[  2057], 90.00th=[  2999], 95.00th=[  4555],
     | 99.00th=[ 23200], 99.50th=[ 24511], 99.90th=[ 32375], 99.95th=[ 40633],
     | 99.99th=[181404]

对于 SSD 这个值一般在 4ms 以内。

Etcd 对磁盘带宽需求不大,因为 etcd 作为一个元数据的存储服务,db 大小最大一般也就 8G,不会有太大的读写量,但是重启或故障恢复的时候磁盘带宽越大,恢复速度会越快。一般来说 100 MB/s 就够用了。

注意:SSD 很重要,其他配置低一点都可以,SSD 真不行

网络

网络的低延迟可以保证 etcd 节点之间能高速通信。

高网络带宽则可以减少节点恢复时间(例如新加入节点会从其他节点同步所有数据)

一般网络带宽在 1GbE 即可,至于延迟的话内网部署可以忽略。

如果为了高可用做跨数据中心部署,则会因为网络延迟降低性能,这个即时性能和可用性之间的取舍了。

1. 下载 etcd

先将 etcd 相关二进制文件下载到各个节点上

可以先到 release 页面 查看最新版本,然后替换一下变量的值即可。

version=v3.5.9
wget https://github.com/etcd-io/etcd/releases/download/"$version"/etcd-"$version"-linux-amd64.tar.gz
tar -zxvf etcd-"$version"-linux-amd64.tar.gz
mv etcd-"$version"-linux-amd64/etcd* /usr/local/bin/  
etcd --version

命令执行完成,正常应该会打印出版本号

[root@etcd-1 ~]# etcd --version
etcd Version: 3.5.9
Git SHA: bdbbde998
Go Version: go1.19.9
Go OS/Arch: linux/amd64

2. 生成证书

这里使用官方推荐的 cfssl 工具来生成证书。

安装 cfssl

下载 cfssl 工具用于生成证书

其他版本见 release 页面

version=1.6.4

wget https://github.com/cloudflare/cfssl/releases/download/v"$version"/cfssl_"$version"_linux_amd64 -O cfssl

wget https://github.com/cloudflare/cfssl/releases/download/v"$version"/cfssljson_"$version"_linux_amd64 -O cfssljson

wget https://github.com/cloudflare/cfssl/releases/download/v"$version"/cfssl-certinfo_"$version"_linux_amd64 -O cfssl-certinfo

chmod +x cfssl cfssljson cfssl-certinfo

mv cfssl*  /usr/local/bin

cfssl version
cfssljson -version

制作证书

CA

mkdir -p tls-setup/config
cd tls-setup
cat > config/ca-csr.json << EOF
{
  "CN": "Autogenerated CA",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "O": "Honest Achmed's Used Certificates",
      "OU": "Hastily-Generated Values Divison",
      "L": "San Francisco",
      "ST": "California",
      "C": "US"
    }
  ]
}
EOF
cat > config/ca-config.json << EOF
{
  "signing": {
    "default": {
        "usages": [
          "signing",
          "key encipherment",
          "server auth",
          "client auth"
        ],
        "expiry": "876000h"
    }
  }
}
EOF
mkdir -p certs
cfssl gencert -initca config/ca-csr.json | cfssljson -bare certs/ca

自签证书

使用刚才生成的 CA 签名 etcd-server、etcd-peer 证书。

首先需要准备一个 csr 文件,用于生成证书。

注意📢

  • hosts 列表始终需要指定 127.0.0.1localhost
  • hosts 部分需要指定所有节点 IP,如果有 vip 也需要签名进来。

修改后大概长这样:

除了两个默认 host 外,还添加了 3 个节点的 ip 以及一个 域名。

cat > config/req-csr.json << EOF
{
  "CN": "etcd",
  "hosts": [
    "localhost",
    "127.0.0.1",
    "192.168.10.13",
    "192.168.10.52",
    "192.168.10.75",
    "192.168.10.27",
    "etcd.demo.com"
  ],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "O": "autogenerated",
      "OU": "etcd cluster",
      "L": "the internet"
    }
  ]
}
EOF

生成 etcd-server 证书

cfssl gencert \
  -ca certs/ca.pem \
  -ca-key certs/ca-key.pem \
  -config config/ca-config.json \
  config/req-csr.json | cfssljson -bare certs/etcd

生成 etcd-peer 证书

和 etcd-server 证书是一样的,只是用不同的名字,区分一下。

cfssl gencert \
  -ca certs/ca.pem \
  -ca-key certs/ca-key.pem \
  -config config/ca-config.json \
  config/req-csr.json | cfssljson -bare certs/peer

生成的证书保证在 certs 目录,具体如下:

[root@etcd-1 tls-setup]# ls certs
ca.csr  ca-key.pem  ca.pem  etcd.csr  etcd-key.pem  etcd.pem  peer.csr  peer-key.pem  peer.pem

包括以下几类证书:

  • CA
  • Etcd-server
  • Etcd-peer

没有单独生成 client 证书,可以直接把 peer 证书作为 client 证书使用。

重签证书

如果要增加新节点,需要重新为所有节点签发证书,和部署时制作证书的流程是一样的。

也就是 req-csr.json 中的 hosts 增加新节点 IP 重新生成。

由于签署的证书只用于数据传输,不用于数据加密,所以即使更换 CA,重新签署证书,也是没有问题的。但是,如果我们首次启动etcd集群的时候使用的是非加密方式,后面改成SSL方式启动etcd集群的时候就会报错。

将证书拷贝到 3 个部署节点。

scp -r certs root@$HOST_1:/etc/etcd/
scp -r certs root@$HOST_2:/etc/etcd/
scp -r certs root@$HOST_3:/etc/etcd/

至此,证书准备好了,接下来开始部署。

3. 部署 etcd

和不带 tls 的命令有两个区别:

  • 1)url 中的 http 改为 https
  • 2)增加 tls 相关参数

创建 env 文件

所有节点执行, apply 一些通用变量

IP 修改为真实值

TOKEN=my-etcd
CLUSTER_STATE=new
HOST_1=10.0.0.124
HOST_2=10.0.0.229
HOST_3=10.0.0.31
NAME_1=etcd1
NAME_2=etcd2
NAME_3=etcd3
CLUSTER="$NAME_1"=https://"$HOST_1":2380,"$NAME_2"=https://"$HOST_2":2380,"$NAME_3"=https://"$HOST_3":2380

DATA_DIR=/data/etcd
mkdir -p "$DATA_DIR"  

到每个节点执行对应命令,不同节点有区别的变量,需要分别到 apply 到对应节点

# 节点 1:生成节点1 的env 文件
THIS_NAME="$NAME_1"
THIS_IP="$HOST_1"

# 节点 2
THIS_NAME="$NAME_2"
THIS_IP="$HOST_2"

# 节点 3
THIS_NAME="$NAME_3"
THIS_IP="$HOST_3"

到所有节点执行以生成 env 文件,这个命令由于使用环境变量进行替换,因此各个节点都一样:

# 证书相关变量
CERTS=/etc/etcd/certs
CA="$CERTS"/ca.pem
ETCD_CERT="$CERTS"/etcd.pem
ETCD_KEY="$CERTS"/etcd-key.pem
ETCD_PEER_CERT="$CERTS"/peer.pem
ETCD_PEER_KEY="$CERTS"/peer-key.pem

mkdir -p /etc/etcd
cat > /etc/etcd/conf << EOF   
TOKEN=${TOKEN}
CLUSTER_STATE=${CLUSTER_STATE}
DATA_DIR=${DATA_DIR}
CLUSTER=${CLUSTER}
THIS_NAME=${THIS_NAME}
THIS_IP=${THIS_IP}
# tls
CA=${CA}
ETCD_CERT=${ETCD_CERT}
ETCD_KEY=${ETCD_KEY}
ETCD_PEER_CERT=${ETCD_PEER_CERT}
ETCD_PEER_KEY=${ETCD_PEER_KEY}
EOF
cat /etc/etcd/conf

systemd.service

到所有节点执行,然后把启动命令封装成 systemd.service,同样的以环境变量方式引用,因此节点都一样:

cat > /usr/lib/systemd/system/etcd.service << 'EOF'
[Unit]   
Description=Etcd Server
After=network.target
After=network-online.target
Wants=network-online.target

[Service]  
Type=simple
EnvironmentFile=/etc/etcd/conf
ExecStart=/usr/local/bin/etcd \
  --name ${THIS_NAME} \
  --data-dir ${DATA_DIR} \
  --listen-client-urls https://${THIS_IP}:2379 \
  --advertise-client-urls https://${THIS_IP}:2379 \
  --listen-peer-urls https://${THIS_IP}:2380 \
  --initial-advertise-peer-urls https://${THIS_IP}:2380 \
  --initial-cluster ${CLUSTER} \
  --initial-cluster-token ${TOKEN} \
  --initial-cluster-state ${CLUSTER_STATE} \
  --cert-file=${ETCD_CERT} \
  --key-file=${ETCD_KEY} \
  --peer-cert-file=${ETCD_PEER_CERT} \
  --peer-key-file=${ETCD_PEER_KEY} \
  --trusted-ca-file=${CA} \
  --peer-trusted-ca-file=${CA}
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF
cat  /usr/lib/systemd/system/etcd.service

启动

到所有节点执行,启动 etcd

systemctl daemon-reload
systemctl enable etcd
systemctl start etcd
systemctl status etcd

4. 验证

任意一个节点执行即可,组装 endpoints

ENDPOINT_1=https://"$HOST_1":2379
ENDPOINT_2=https://"$HOST_2":2379
ENDPOINT_3=https://"$HOST_3":2379

ENDPOINTS=$ENDPOINT_1,$ENDPOINT_2,$ENDPOINT_3
ETCDCTL_API=3

开启 tls 之后,etcdctl 也必须指定证书才能访问。

查看集群 member 列表

etcdctl --endpoints=$ENDPOINTS --cacert=$CA --cert=$ETCD_CERT --key=$ETCD_KEY member list -w table

查看 endpoint 的状态(用于查看那个是 leader)

etcdctl --endpoints=$ENDPOINTS --cacert=$CA --cert=$ETCD_CERT --key=$ETCD_KEY endpoint status -w table
etcdctl --endpoints=$ENDPOINTS --cacert=$CA --cert=$ETCD_CERT --key=$ETCD_KEY endpoint status -w json

crud 测试

# 写入数据并查看
etcdctl --endpoints=$ENDPOINTS --cacert=$CA --cert=$ETCD_CERT --key=$ETCD_KEY put key1 value1
etcdctl --endpoints=$ENDPOINTS --cacert=$CA --cert=$ETCD_CERT --key=$ETCD_KEY get key1 

# 修改后再次查看
etcdctl --endpoints=$ENDPOINTS --cacert=$CA --cert=$ETCD_CERT --key=$ETCD_KEY put key1 value2
etcdctl --endpoints=$ENDPOINTS --cacert=$CA --cert=$ETCD_CERT --key=$ETCD_KEY get key1

# 删除数据
etcdctl --endpoints=$ENDPOINTS --cacert=$CA --cert=$ETCD_CERT --key=$ETCD_KEY del key1
etcdctl --endpoints=$ENDPOINTS --cacert=$CA --cert=$ETCD_CERT --key=$ETCD_KEY get key1

批量操作

# 循环写入
for i in {1..1000};do
etcdctl --endpoints=$ENDPOINTS --cacert=$CA --cert=$ETCD_CERT --key=$ETCD_KEY put key"$i" value"$i"
done
# 批量删除
etcdctl --endpoints=$ENDPOINTS --cacert=$CA --cert=$ETCD_CERT --key=$ETCD_KEY  del "key" --prefix

5. 清理

停止掉各个节点上的 etcd 服务

systemctl stop etcd
systemctl disable etcd
systemctl daemon-reload
rm -rf /usr/lib/systemd/system/etcd.service

然后移除对应数据目录

rm -rf "$DATA_DIR"

最后移除配置文件目录

rm -rf /etc/etcd

6. 小结

本文主要分享了如何使用 systemd 部署 etcd 集群。对于生产环境 etcd 集群的性能、可用性、安全性等方面都有较高的要求。

  • 1)准备环境,使用官方推荐的硬件,特别是磁盘一定要用 SSD
  • 2)配置 TLS,提升安全性
  • 3)多节点集群提升可用性,3节点环境下故障一个节点也不影响集群使用