Skip to content

02安装部署:手把手教你玩转etcd搭建 ​

这一讲我们将一同学习 etcd 部署的几种方式,以及如何进行 TLS 加密实践,以保证 etcd 的通信安全。

etcd 单机安装部署 ​

etcd 的安装有多种方式,这里我以 CentOS 7 为例,可以通过yum install etcd进行安装。然而通过系统工具安装的 etcd 版本比较滞后,如果需要安装最新版本的 etcd ,我们可以通过二进制包、源码编译以及 Docker 容器安装。

二进制安装 ​

目前最新的 etcd API 版本为 v3.4,我们基于 3.4.4 版本进行实践,API 版本与最新版保持一致,在 CentOS 7 上面使用如下脚本进行安装:

java
ETCD_VER=v3.4.4
GITHUB_URL=https://github.com/etcd-io/etcd/releases/download
DOWNLOAD_URL=${GITHUB_URL}
rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
rm -rf /tmp/etcd-download-test && mkdir -p /tmp/etcd-download-test
curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/etcd-download-test --strip-components=1
rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
/tmp/etcd-download-test/etcd --version
/tmp/etcd-download-test/etcdctl version

下载可能比较慢,执行后,查看 etcd 版本的结果如下:

java
etcd Version: 3.4.4
Git SHA: e784ba73c
Go Version: go1.12.12
Go OS/Arch: linux/amd64

根据上面的执行结果可以看到,我们在 Linux 上安装成功,macOS 的二进制安装也类似,这里不重复演示了。关于 Windows 系统的安装比较简单,下载好安装包后,直接执行。其中 etcd.exe 是服务端,etcdctl.exe 是客户端,如下图所示:

源码安装 ​

使用源码安装,首先你需要确保本地的 Go 语言环境。如未安装,请参考 https://golang.org/doc/install 安装 Go 语言环境。我们需要 Go 版本为 1.13+,来构建最新版本的 etcd。如果你想尝试最新版本,也可以从 master 分支构建 etcd。

首先查看一下我们本地的 Go 版本:

java
$ go version
Go version go1.13.6 linux/amd64

经检测,本地的 Go 版本满足要求。将制定版本的 etcd 项目 clone 到本地之后,在项目文件夹构建 etcd。构建好之后,执行测试命令,确保 etcd 编译安装成功:

java
$ ./etcdctl version
etcdctl version: 3.4.4
API version: 3.4

经过上述步骤,我们已经通过源码编译成功安装 etcd。

除此之外,还可以使用 Docker 容器安装,这种方式更为简单,但这种方式有一个弊端:启动时会将 etcd 的端口暴露出来。

etcd 集群安装部署 ​

刚刚我们介绍了 etcd 的单机安装,但是在实际生产环境中,为了整个集群的高可用,etcd 通常都会以集群方式部署,以避免单点故障。下面我们看看如何进行 etcd 集群部署。

引导 etcd 集群的启动有以下三种方式:

  • 静态启动

  • etcd 动态发现

  • DNS 发现

其中静态启动 etcd 集群的方式要求每个成员都知道集群中的其他成员。然而很多场景中,群集成员的 IP 可能未知。因此需要借助发现服务引导 etcd 群集启动。下面我们将会分别介绍这几种方式。

静态方式启动 etcd 集群 ​

如果我们想在一台机器上实践 etcd 集群的搭建,可以通过 goreman 工具。

goreman 是一个 Go 语言编写的多进程管理工具,是对 Ruby 下广泛使用的 Foreman 的重写。下面我们使用 goreman 来演示在一台物理机上面以静态方式启动 etcd 集群。

前面我们已经确认过 Go 语言的安装环境,现在可以直接执行:

java
go get github.com/mattn/goreman

编译后的文件放在$GOPATH/bin中,$GOPATH/bin目录已经添加到了系统$PATH中,所以我们可以方便地执行命令goreman命令。

下面就是编写 Procfile 脚本,我们启动三个 etcd,具体对应如下:

Procfile 脚本中 infra1 启动命令如下:

java
etcd1: etcd --name infra1 --listen-client-urls http://127.0.0.1:12379 --advertise-client-urls http://127.0.0.1:12379 --listen-peer-urls http://127.0.0.1:12380 --initial-advertise-peer-urls http://127.0.0.1:12380 --initial-cluster-token etcd-cluster-1 --initial-cluster 'infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380' --initial-cluster-state new --enable-pprof --logger=zap --log-outputs=stderr

infra2 和 infra3 的启动命令类似。下面我们看一下其中各配置项的说明。

  • --name:etcd 集群中的节点名,这里可以随意,方便区分且不重复即可。

  • --listen-client-urls:监听用于客户端通信的 url,同样可以监听多个。

  • --advertise-client-urls:建议使用的客户端通信 url,该值用于 etcd 代理或 etcd 成员与 etcd 节点通信。

  • --listen-peer-urls:监听用于节点之间通信的 url,可监听多个,集群内部将通过这些 url 进行数据交互(如选举、数据同步等)。

  • --initial-advertise-peer-urls:建议用于节点之间通信的 url,节点间将以该值进行通信。

  • --initial-cluster-token: etcd-cluster-1,节点的 token 值,设置该值后集群将生成唯一 ID,并为每个节点也生成唯一 ID。当使用相同配置文件再启动一个集群时,只要该 token 值不一样,etcd 集群就不会相互影响。

  • --initial-cluster:集群中所有的 initial-advertise-peer-urls 的合集。

  • --initial-cluster-state:new,新建集群的标志。

注意上面的脚本,etcd 命令执行时需要根据本地实际的安装地址进行配置 。下面我们使用goreman命令启动 etcd 集群:

java
goreman -f /opt/procfile start

启动完成后查看集群内的成员:

java
$ etcdctl --endpoints=http://localhost:22379  member list
8211f1d0f64f3269, started, infra1, http://127.0.0.1:12380, http://127.0.0.1:12379, false
91bc3c398fb3c146, started, infra2, http://127.0.0.1:22380, http://127.0.0.1:22379, false
fd422379fda50e48, started, infra3, http://127.0.0.1:32380, http://127.0.0.1:32379, false

现在我们的 etcd 集群已经搭建成功。需要注意的是:在集群启动时,我们通过静态的方式指定集群的成员。但在实际环境中,集群成员的 IP 可能不会提前知道,此时需要采用动态发现的机制。

动态发现启动 etcd 集群 ​

Discovery Service,即发现服务,帮助新的 etcd 成员使用共享 URL 在集群引导阶段发现所有其他成员。

Discovery Service 使用已有的 etcd 集群来协调新集群的启动,主要操作如下:

  • 首先,所有新成员都与发现服务交互,并帮助生成预期的成员列表;

  • 之后,每个新成员使用此列表引导其服务器,该列表执行与--initial-cluster标志相同的功能,即设置所有集群的成员信息。

我们的实验中启动三个 etcd 实例,具体对应如下:

下面就开始以动态发现的方式来启动 etcd 集群,具体步骤如下。

获取 discovery 的 token

首先需要生成标识新集群的唯一令牌。该令牌将用于键空间中的唯一前缀,比较简单的方法是使用 uuidgen 生成 UUID:

java
UUID=$(uuidgen)

指定集群的大小

获取令牌时,必须指定群集大小。发现服务使用该数值来确定组成集群的所有成员:

java
curl -X PUT http://10.0.10.10:2379/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83/_config/size -d value=3

我们需要把该 url 作为--discovery参数来启动 etcd,节点会自动使用该路径对应的目录进行 etcd 的服务注册与发现。

公共发现服务

当我们本地没有可用的 etcd 集群,etcd 官网提供了一个可以公网访问的 etcd 存储地址。你可以通过如下命令得到 etcd 服务的目录,并把它作为--discovery参数使用。

公共发现服务discovery.etcd.io以相同的方式工作,但是有一层修饰,在此之上仍然使用 etcd 群集作为数据存储。

java
$ curl https://discovery.etcd.io/new?size=3
https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de

以动态发现方式启动集群

etcd 发现模式下,启动 etcd 的命令如下:

java
# etcd1 启动
$ /opt/etcd/bin/etcd  --name etcd1 --initial-advertise-peer-urls http://192.168.202.128:2380 \
  --listen-peer-urls http://192.168.202.128:2380 \
  --data-dir /opt/etcd/data \
  --listen-client-urls http://192.168.202.128:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://192.168.202.128:2379 \
  --discovery https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de

etcd2 和 etcd3 启动类似,替换 listen-peer-urls 和 advertise-client-urls 即可。需要注意的是:在我们完成了集群的初始化后, --discovery就失去了作用。当需要增加节点时,需要使用 etcdctl 进行操作。

为了安全,每次启动新 etcd 集群时,都会使用新的 discovery token 进行注册。当出现初始化启动的节点超过了指定的数量时,多余的节点会自动转化为 Proxy 模式的 etcd(在后面课时会详细介绍)。

结果验证

集群启动后,进行验证,我们看一下集群节点的健康状态:

java
$ /opt/etcd/bin/etcdctl  --endpoints="http://192.168.202.128:2379,http://192.168.202.129:2379,http://192.168.202.130:2379"  endpoint  health
# 结果如下
    http://192.168.202.128:2379 is healthy: successfully committed proposal: took = 3.157068ms
    http://192.168.202.130:2379 is healthy: successfully committed proposal: took = 3.300984ms
    http://192.168.202.129:2379 is healthy: successfully committed proposal: took = 3.263923ms

可以看到,集群中的三个节点都是健康的正常状态,可以说明以动态发现方式启动集群成功。

除此之外,etcd 还支持使用 DNS SRV 记录启动 etcd 集群。使用 DNSmasq 创建 DNS 服务,实际上是利用 DNS 的 SRV 记录不断轮训查询实现,DNS SRV 是 DNS 数据库中支持的一种资源记录的类型,它记录了计算机与所提供服务信息的对应关系。

至此,我们就介绍完了 etcd 安装部署的两种方式,对于可靠的系统来说,还需要考虑 etcd 集群通信的安全性,为我们的数据安全增加防护。

etcd 通信安全 ​

etcd 支持通过 TLS 协议进行的加密通信,TLS 通道可用于对等体(指 etcd 集群中的服务实例)之间的加密内部群集通信以及加密的客户端流量。这一节我们看一下客户端 TLS 设置群集的实现。

想要实现数据 HTTPS 加密协议访问、保障数据的安全,就需要 SSL 证书,TLS 是 SSL 与 HTTPS 安全传输层协议名称。

进行 TLS 加密实践 ​

为了进行实践,我们将安装一些实用的命令行工具,这包括 CFSSL 、cfssljson。

CFSSL 是 CloudFlare 的 PKI/TLS 加密利器。它既是命令行工具,也可以用于签名、验证和捆绑 TLS 证书的 HTTP API 服务器,需要 Go 1.12+ 版本才能构建。

环境配置

安装 CFSSL

java
$ ls ~/Downloads/cfssl
cfssl-certinfo_1.4.1_linux_amd64 cfssl_1.4.1_linux_amd64          cfssljson_1.4.1_linux_amd64
chmod +x cfssl_1.4.1_linux_amd64 cfssljson_1.4.1_linux_amd64 cfssl-certinfo_1.4.1_linux_amd64
mv cfssl_1.4.1_linux_amd64 /usr/local/bin/cfssl
mv cfssljson_1.4.1_linux_amd64 /usr/local/bin/cfssljson
mv cfssl-certinfo_1.4.1_linux_amd64 /usr/bin/cfssl-certinfo

安装完成后,查看版本信息如下所示:

java
$ cfssl version
Version: 1.4.1
Runtime: go1.12.12

配置 CA 并创建 TLS 证书

我们将使用 CloudFlare's PKI 工具 CFSSL 来配置 PKI 安全策略,然后用它创建 Certificate Authority(CA,即证书机构),并为 etcd 创建 TLS 证书。

首先创建 SSL 配置目录:

java
mkdir /opt/etcd/{bin,cfg,ssl} -p
cd /opt/etcd/ssl/

接下来完善 etcd CA 配置,写入 ca-config.json 如下的配置:

java
cat << EOF | tee ca-config.json
{
  "signing": {
    "default": {
      "expiry": "87600h"
    },
    "profiles": {
      "etcd": {
         "expiry": "87600h",
         "usages": [
            "signing",
            "key encipherment",
            "server auth",
            "client auth"
        ]
      }
    }
  }
}
EOF

生成获取 etcd ca 证书,需要证书签名的请求文件,因此在ca-csr.json 写入如下的配置:

java
cat << EOF | tee ca-csr.json
{
    "CN": "etcd CA",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "Shanghai",
            "ST": "Shanghai"
        }
    ]
}
EOF

根据上面的配置,生成 CA 凭证和私钥:

java
$ cfssl gencert -initca ca-csr.json | cfssljson -bare ca

生成 etcd server 证书,写入 server-csr.json 如下的配置:

java
cat << EOF | tee server-csr.json
{
    "CN": "etcd",
    "hosts": [
    "192.168.202.128",
    "192.168.202.129",
    "192.168.202.130"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "Beijing",
            "ST": "Beijing"
        }
    ]
}
EOF

最后就可以生成 server 证书了:

java
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=etcd server-csr.json | cfssljson -bare server

启动 etcd 集群,配置如下:

java
#etcd1 启动
$ /opt/etcd/bin/etcd --name etcd1 --initial-advertise-peer-urls https://192.168.202.128:2380 \
     --listen-peer-urls https://192.168.202.128:2380 \
     --listen-client-urls https://192.168.202.128:2379,https://127.0.0.1:2379 \
     --advertise-client-urls https://192.168.202.128:2379 \
     --initial-cluster-token etcd-cluster-1 \
     --initial-cluster etcd1=https://192.168.202.128:2380, etcd2=https://192.168.202.129:2380, etcd3=https://192.168.202.130:2380 \
     --initial-cluster-state new \
     --client-cert-auth --trusted-ca-file=/opt/etcd/ssl/ca.pem \
     --cert-file=/opt/etcd/ssl/server.pem --key-file=/opt/etcd/ssl/server-key.pem \
     --peer-client-cert-auth --peer-trusted-ca-file=/opt/etcd/ssl/ca.pem \
     --peer-cert-file=/opt/etcd/ssl/server.pem --peer-key-file=/opt/etcd/ssl/server-key.pem

etcd2 和 etcd3 启动类似,注意替换 listen-peer-urls 和 advertise-client-urls。通过三台服务器的控制台可以知道,集群已经成功建立。

下面我们进行验证:

java
$ /opt/etcd/bin/etcdctl --cacert=/opt/etcd/ssl/ca.pem --cert=/opt/etcd/ssl/server.pem --key=/opt/etcd/ssl/server-key.pem --endpoints="https://192.168.202.128:2379,https://192.168.202.129:2379,https://192.168.202.130:2379"  endpoint health
# 输出如下:
https://192.168.202.129:2379 is healthy: successfully committed proposal: took = 9.492956ms
https://192.168.202.130:2379 is healthy: successfully committed proposal: took = 12.805109ms
https://192.168.202.128:2379 is healthy: successfully committed proposal: took = 13.036091ms

查看三个节点的健康状况,endpoint health,输出的结果符合我们的预期。

经过 TLS 加密的 etcd 集群,在进行操作时,需要加上认证相关的信息。

自动证书

如果集群需要加密的通信但不需要经过身份验证的连接,可以将 etcd 配置为自动生成其密钥。在初始化时,每个成员都基于其通告的 IP 地址和主机创建自己的密钥集。

在每台机器上,etcd 将使用以下标志启动:

java
$ etcd --name etcd1 --initial-advertise-peer-urls https://192.168.202.128:2380 \
  --listen-peer-urls https://192.168.202.128:2380 \
  --listen-client-urls https://192.168.202.128:2379,https://127.0.0.1:2379 \
  --advertise-client-urls https://10.0.1.10:2379 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-cluster infra0=https://192.168.202.128:2380,infra1=https://192.168.202.129:2380,infra2=https://192.168.202.130:2380 \
  --initial-cluster-state new \
  --auto-tls \
  --peer-auto-tls

由于自动签发证书并不能认证身份,直接 curl 会返回错误,需要使用 curl 的-k命令屏蔽对证书链的校验。

小结 ​

这一讲我们主要介绍了 etcd 的安装部署方式,包括单机和集群的部署。为了高可用,在生产环境中我们一般选择使用集群的方式部署 etcd。在集群部署的时候,有静态和动态两种方式发现集群中的成员。静态的方式是直接在启动时指定各个成员的地址,但在实际环境中,集群成员的 IP 可能不会提前知道,这时候就需要采用动态发现的机制。

Discovery Service 用于生成集群的发现令牌,需要注意的是,该令牌仅用于集群引导阶段,不能用于运行时重新配置或集群监视。一个发现令牌只能代表一个 etcd 集群,只要此令牌上的发现协议启动,即使它中途失败,也不能用于引导另一个 etcd 集群。我在这一讲最后也介绍了 etcd 集群通过 TLS 协议进行的加密通信,来保证 etcd 通信的安全。

下一讲我们将学习如何使用 etcdctl 客户端与 etcd 服务端进行交互,以及操作 etcd 服务端。有任何疑问欢迎在留言区和我互动,我们下一讲再见!


[

](https://shenceyun.lagou.com/t/Mka)

拉勾背书内推 + 硬核实战技术干货,帮助每位 Java 工程师达到阿里 P7 技术能力。点此链接,快来领取!