如何在Ubuntu18.04上使用Ansible设置和保护etcd集群
作为 Write for DOnations 计划的一部分,作者选择了 Wikimedia Foundation 来接受捐赠。
介绍
etcd 是一种分布式键值存储,被许多平台和工具所依赖,包括 Kubernetes、Vulcand 和 Doorman。 在 Kubernetes 中,etcd 用作存储集群状态的全局配置存储。 了解如何管理 etcd 对于管理 Kubernetes 集群至关重要。 尽管有许多托管的 Kubernetes 产品,也称为 Kubernetes-as-a-Service ,可以消除您的这种管理负担,但许多公司仍然选择在本地运行自我管理的 Kubernetes 集群,因为它带来的灵活性。
本文前半部分将指导您在 Ubuntu 18.04 服务器上设置 3 节点 etcd 集群。 下半年将重点关注使用 传输层安全性或 TLS 保护集群。 为了以自动化方式运行每个设置,我们将始终使用 Ansible。 Ansible 是一个 配置管理 工具,类似于 Puppet、Chef 和 SaltStack; 它允许我们在名为 playbooks 的文件中以声明方式定义每个设置步骤。
在本教程结束时,您将在您的服务器上运行一个安全的 3 节点 etcd 集群。 您还将拥有一个 Ansible 剧本,允许您在一组新的服务器上重复且一致地重新创建相同的设置。
先决条件
在开始本指南之前,您需要以下内容:
- Python、
pip
和安装在本地计算机上的 pyOpenSSL 包。 要了解如何安装 Python3、pip 和 Python 包,请参阅 如何在 Ubuntu 18.04 上安装 Python 3 并设置本地编程环境。 - 同一本地网络上的三台 Ubuntu 18.04 服务器,具有至少 2GB 的 RAM 和 root SSH 访问权限。 您还应该将服务器配置为具有主机名 etcd1、etcd2 和 etcd3。 本文中概述的步骤适用于任何通用服务器,不一定适用于 DigitalOcean Droplets。 但是,如果您想在 DigitalOcean 上托管您的服务器,您可以按照 如何从 DigitalOcean 控制面板 指南创建 Droplet 来满足此要求。 请注意,您必须在创建 Droplet 时启用 Private Networking 选项。 要在现有 Droplet 上启用私有网络,请参阅 如何在 Droplets 上启用私有网络。
警告: 由于本文的目的是介绍如何在私有网络上设置 etcd 集群,因此此设置中的三台 Ubuntu 18.04 服务器未使用防火墙进行测试,并且作为 [ X235X]root 用户。 在生产设置中,任何暴露于公共互联网的节点都需要防火墙和 sudo 用户来遵守安全最佳实践。 有关更多信息,请查看使用 Ubuntu 18.04 的 初始服务器设置教程。
- 一个 SSH 密钥对,允许您的本地计算机访问 etcd1、etcd2 和 etcd3 服务器。 如果您不知道 SSH 是什么,或者没有 SSH 密钥对,您可以通过阅读 SSH Essentials: Working with SSH Servers, Clients, and Keys 来了解它。
- Ansible 安装在您的本地计算机上。 例如,如果您运行的是 Ubuntu 18.04,则可以按照 如何在 Ubuntu 18.04 上安装和配置 Ansible 一文的 Step 1 安装 Ansible。 这将使
ansible
和ansible-playbook
命令在您的机器上可用。 您可能还想将这份 如何使用 Ansible:参考指南 放在手边。 本教程中的命令应该适用于 Ansible v2.x; 我们已经在运行 Python v3.8.2 的 Ansible v2.9.7 上对其进行了测试。
第 1 步 — 为控制节点配置 Ansible
Ansible 是一个用于管理服务器的工具。 Ansible 管理的服务器称为 托管节点 ,运行 Ansible 的机器称为 控制节点 。 Ansible 通过使用控制节点上的 SSH 密钥来访问受管节点。 建立 SSH 会话后,Ansible 将运行一组脚本来供应和配置托管节点。 在这一步中,我们将测试我们是否能够使用 Ansible 连接到托管节点并运行 主机名命令 。
系统管理员的典型一天可能涉及管理不同的节点集。 例如,您可以使用 Ansible 来配置一些新服务器,但稍后使用它来重新配置另一组服务器。 为了让管理员更好地组织托管节点集,Ansible 提供了 host inventory(或简称 inventory)的概念。 您可以在 库存文件 中定义您希望使用 Ansible 管理的每个节点,并将它们组织到 组 中。 然后,在运行 ansible
和 ansible-playbook
命令时,您可以指定该命令适用于哪些主机或组。
默认情况下,Ansible 从 /etc/ansible/hosts
读取清单文件; 但是,我们可以使用 --inventory
标志(或简称 -i
)指定不同的库存文件。
首先,在本地机器(控制节点)上创建一个新目录来存放本教程的所有文件:
mkdir -p $HOME/playground/etcd-ansible
然后,进入刚刚创建的目录:
cd $HOME/playground/etcd-ansible
在目录中,使用编辑器创建并打开一个名为 hosts
的空白库存文件:
nano $HOME/playground/etcd-ansible/hosts
在 hosts
文件中,按以下格式列出每个受管节点,将突出显示的公共 IP 地址替换为服务器的实际公共 IP 地址:
~/playground/etcd-ansible/hosts
[etcd] etcd1 ansible_host=etcd1_public_ip ansible_user=root etcd2 ansible_host=etcd2_public_ip ansible_user=root etcd3 ansible_host=etcd3_public_ip ansible_user=root
[etcd]
行定义了一个名为 etcd
的组。 在组定义下,我们列出了所有受管节点。 每行都以一个别名开头(例如,etcd1
),它允许我们使用一个易于记忆的名称而不是长 IP 地址来引用每个主机。 ansible_host
和 ansible_user
是 Ansible 变量。 在这种情况下,它们用于为 Ansible 提供公共 IP 地址和 SSH 用户名,以便在通过 SSH 连接时使用。
为了确保 Ansible 能够与我们的托管节点连接,我们可以使用 Ansible 在 etcd
组中的每个主机上运行 hostname
命令来测试连接性:
ansible etcd -i hosts -m command -a hostname
让我们分解此命令以了解每个部分的含义:
etcd
:指定 主机模式 用于确定清单中的哪些主机正在使用此命令进行管理。 在这里,我们使用组名作为主机模式。-i hosts
:指定要使用的清单文件。-m command
:Ansible 背后的功能由 modules 提供。command
模块接受传入的参数并在每个受管节点上将其作为命令执行。 随着我们的进展,本教程将介绍更多的 Ansible 模块。-a hostname
:传入模块的参数。 参数的数量和类型取决于模块。
运行命令后会看到如下输出,说明 Ansible 配置正确:
Outputetcd2 | CHANGED | rc=0 >> etcd2 etcd3 | CHANGED | rc=0 >> etcd3 etcd1 | CHANGED | rc=0 >> etcd1
Ansible 运行的每个命令都称为 任务。 在命令行中使用 ansible
运行任务称为运行 ad-hoc 命令。 ad-hoc 命令的好处是它们速度快且需要很少的设置。 缺点是它们是手动运行的,因此不能提交给像 Git 这样的版本控制系统。
一个小小的改进是编写一个 shell 脚本并使用 Ansible 的 脚本模块 运行我们的命令。 这将允许我们记录我们在版本控制中采取的配置步骤。 但是,shell 脚本是 势在必行的 ,这意味着我们负责确定要运行的命令(“如何”)以将系统配置为所需的状态。 另一方面,Ansible 提倡 声明式 方法,我们在配置文件中定义服务器的期望状态应该是“什么”,而 Ansible 负责让服务器达到期望的状态。
声明式方法是首选,因为配置文件的意图会立即传达,这意味着它更易于理解和维护。 它还将处理边缘情况的责任交给 Ansible 而不是管理员,为我们节省了大量工作。
现在您已经配置了 Ansible 控制节点以与托管节点通信,在下一步中,我们将向您介绍 Ansible playbooks,它允许您以声明的方式指定任务。
第 2 步 — 使用 Ansible Playbook 获取托管节点的主机名
在这一步中,我们将复制在第 1 步中所做的事情——打印出托管节点的主机名——但我们不会运行临时任务,而是将每个任务声明式地定义为 Ansible playbook 并运行它。 这一步的目的是演示 Ansible playbook 的工作原理; 在后面的步骤中,我们将使用剧本执行更多实质性任务。
在您的项目目录中,使用您的编辑器创建一个名为 playbook.yaml
的新文件:
nano $HOME/playground/etcd-ansible/playbook.yaml
在 playbook.yaml
中,添加以下行:
~/playground/etcd-ansible/playbook.yaml
- hosts: etcd tasks: - name: "Retrieve hostname" command: hostname register: output - name: "Print hostname" debug: var=output.stdout_lines
按 CTRL+X
然后按 Y
关闭并保存 playbook.yaml
文件。
playbook 包含 plays 的列表; 每个播放都包含一个任务列表,这些任务应该在与 hosts
键指定的主机模式匹配的所有主机上运行。 在这个剧本中,我们有一个包含两个任务的剧本。 第一个任务使用 command
模块运行 hostname
命令,并将输出注册到名为 output
的变量中。 在第二个任务中,我们使用 debug 模块打印出 output
变量的 stdout_lines
属性。
我们现在可以使用 ansible-playbook
命令运行这个剧本:
ansible-playbook -i hosts playbook.yaml
您会发现以下输出,这意味着您的 playbook 工作正常:
OutputPLAY [etcd] *********************************************************************************************************************** TASK [Gathering Facts] ************************************************************************************************************ ok: [etcd2] ok: [etcd3] ok: [etcd1] TASK [Retrieve hostname] ********************************************************************************************************** changed: [etcd2] changed: [etcd3] changed: [etcd1] TASK [Print hostname] ************************************************************************************************************* ok: [etcd1] => { "output.stdout_lines": [ "etcd1" ] } ok: [etcd2] => { "output.stdout_lines": [ "etcd2" ] } ok: [etcd3] => { "output.stdout_lines": [ "etcd3" ] } PLAY RECAP ************************************************************************************************************************ etcd1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 etcd2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 etcd3 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
笔记: ansible-playbook
sometimes uses cowsay
as a playful way to print the headings. If you find a lot of ASCII-art cows printed on your terminal, now you know why. To disable this feature, set the ANSIBLE_NOCOWS
environment variable to 1
prior to running ansible-playbook
by running export ANSIBLE_NOCOWS=1
in your shell.
在这一步中,我们已经从运行命令式临时任务转移到运行声明性剧本。 在下一步中,我们将用设置我们的 etcd 集群的任务替换这两个演示任务。
第 3 步 — 在托管节点上安装 etcd
在这一步中,我们将向您展示手动安装 etcd
的命令,并演示如何将这些相同的命令转换为 Ansible playbook 中的任务。
etcd
及其客户端 etcdctl
可作为二进制文件使用,我们将下载、解压缩并移动到属于 PATH
环境变量的目录。 手动配置时,我们将在每个受管节点上执行以下步骤:
mkdir -p /opt/etcd/bin cd /opt/etcd/bin wget -qO- https://storage.googleapis.com/etcd/v3.3.13/etcd-v3.3.13-linux-amd64.tar.gz | tar --extract --gzip --strip-components=1 echo 'export PATH="$PATH:/opt/etcd/bin"' >> ~/.profile echo 'export ETCDCTL_API=3" >> ~/.profile
前四个命令将二进制文件下载并解压缩到 /opt/etcd/bin/
目录。 默认情况下,etcdctl
客户端将使用 API v2 与 etcd
服务器通信。 由于我们正在运行 etcd v3.x,最后一个命令将 ETCDCTL_API
环境变量设置为 3
。
注意: 在这里,我们使用 etcd v3.3.13 为具有使用 AMD64 指令集的处理器的机器构建。 您可以在官方 GitHub Releases 页面上找到其他系统和其他版本的二进制文件。
为了以标准化格式复制相同的步骤,我们可以将任务添加到我们的剧本中。 在编辑器中打开 playbook.yaml
剧本文件:
nano $HOME/playground/etcd-ansible/playbook.yaml
将整个 playbook.yaml
文件替换为以下内容:
~/playground/etcd-ansible/playbook.yaml
- hosts: etcd become: True tasks: - name: "Create directory for etcd binaries" file: path: /opt/etcd/bin state: directory owner: root group: root mode: 0700 - name: "Download the tarball into the /tmp directory" get_url: url: https://storage.googleapis.com/etcd/v3.3.13/etcd-v3.3.13-linux-amd64.tar.gz dest: /tmp/etcd.tar.gz owner: root group: root mode: 0600 force: True - name: "Extract the contents of the tarball" unarchive: src: /tmp/etcd.tar.gz dest: /opt/etcd/bin/ owner: root group: root mode: 0600 extra_opts: - --strip-components=1 decrypt: True remote_src: True - name: "Set permissions for etcd" file: path: /opt/etcd/bin/etcd state: file owner: root group: root mode: 0700 - name: "Set permissions for etcdctl" file: path: /opt/etcd/bin/etcdctl state: file owner: root group: root mode: 0700 - name: "Add /opt/etcd/bin/ to the $PATH environment variable" lineinfile: path: /etc/profile line: export PATH="$PATH:/opt/etcd/bin" state: present create: True insertafter: EOF - name: "Set the ETCDCTL_API environment variable to 3" lineinfile: path: /etc/profile line: export ETCDCTL_API=3 state: present create: True insertafter: EOF
每个任务使用一个模块; 对于这组任务,我们正在使用以下模块:
- file:创建
/opt/etcd/bin
目录,稍后设置etcd
和etcdctl
二进制文件的文件权限。 - get_url:将 gzip 压缩包下载到受管节点。
- unarchive:从 gzip 压缩包中提取和解压缩
etcd
和etcdctl
二进制文件。 - lineinfile:在
.profile
文件中添加一个条目。
要应用这些更改,请按 CTRL+X
然后按 Y
关闭并保存 playbook.yaml
文件。 然后,在终端上,再次运行相同的 ansible-playbook
命令:
ansible-playbook -i hosts playbook.yaml
输出的 PLAY RECAP
部分将仅显示 ok
和 changed
:
Output... PLAY RECAP ************************************************************************************************************************ etcd1 : ok=8 changed=7 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 etcd2 : ok=8 changed=7 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 etcd3 : ok=8 changed=7 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
要确认 etcd 的正确安装,请手动 SSH 到其中一个受管节点并运行 etcd
和 etcdctl
:
ssh root@etcd1_public_ip
etcd1_public_ip
是名为 etcd1 的服务器的公网 IP 地址。 获得 SSH 访问权限后,运行 etcd --version
打印出安装的 etcd 版本:
etcd --version
您会发现类似于以下内容的输出,这意味着 etcd
二进制文件已成功安装:
Outputetcd Version: 3.3.13 Git SHA: 98d3084 Go Version: go1.10.8 Go OS/Arch: linux/amd64
要确认 etcdctl
安装成功,请运行 etcdctl version
:
etcdctl version
您会发现类似于以下内容的输出:
Outputetcdctl version: 3.3.13 API version: 3.3
请注意,输出显示 API version: 3.3
,这也证实了我们的 ETCDCTL_API
环境变量设置正确。
退出 etcd1 服务器返回本地环境。
现在,我们已在所有托管节点上成功安装了 etcd
和 etcdctl
。 在下一步中,我们将添加更多任务来运行 etcd 作为后台服务。
第 4 步 — 为 etcd 创建一个单元文件
使用 Ansible 运行 etcd 的最快方法似乎是使用 command
模块来运行 /opt/etcd/bin/etcd
。 但是,这不起作用,因为它会使 etcd
作为前台进程运行。 使用 command
模块将导致 Ansible 挂起,因为它等待 etcd
命令返回,它永远不会。 因此,在这一步中,我们将更新我们的 playbook 以运行我们的 etcd
二进制文件作为背景 服务 。
Ubuntu 18.04 使用 systemd 作为它的 init system,这意味着我们可以通过编写 unit files 并将它们放在 /etc/systemd/system/
目录中来创建新服务.
首先,在我们的项目目录中,创建一个名为 files/
的新目录:
mkdir files
然后,使用您的编辑器,在该目录中创建一个名为 etcd.service
的新文件:
nano files/etcd.service
接下来,将以下代码块复制到 files/etcd.service
文件中:
~/playground/etcd-ansible/files/etcd.service
[Unit] Description=etcd distributed reliable key-value store [Service] Type=notify ExecStart=/opt/etcd/bin/etcd Restart=always
这个单元文件定义了一个服务,它在 /opt/etcd/bin/etcd
上运行可执行文件,在完成初始化时通知 systemd,如果它退出,总是重新启动。
注意: 如果您想了解更多关于 systemd 和单元文件的信息,或者想根据您的需要定制单元文件,请阅读 了解 Systemd 单元和单元文件 指南。
按 CTRL+X
然后按 Y
关闭并保存 files/etcd.service
文件。
接下来,我们需要在 playbook 中添加一个任务,它将 files/etcd.service
本地文件复制到每个托管节点的 /etc/systemd/system/etcd.service
目录中。 我们可以使用 copy 模块来做到这一点。
打开你的剧本:
nano $HOME/playground/etcd-ansible/playbook.yaml
将以下突出显示的任务附加到我们现有任务的末尾:
~/playground/etcd-ansible/playbook.yaml
- hosts: etcd become: True tasks: ... - name: "Set the ETCDCTL_API environment variable to 3" lineinfile: path: /etc/profile line: export ETCDCTL_API=3 state: present create: True insertafter: EOF - name: "Create a etcd service" copy: src: files/etcd.service remote_src: False dest: /etc/systemd/system/etcd.service owner: root group: root mode: 0644
通过将单元文件复制到 /etc/systemd/system/etcd.service
中,现在定义了一个服务。
保存并退出剧本。
再次运行相同的 ansible-playbook
命令以应用新更改:
ansible-playbook -i hosts playbook.yaml
要确认已应用更改,请首先通过 SSH 连接到其中一个受管节点:
ssh root@etcd1_public_ip
然后,运行 systemctl status etcd
向 systemd 查询 etcd
服务的状态:
systemctl status etcd
您将找到以下输出,表明服务已加载:
Output● etcd.service - etcd distributed reliable key-value store Loaded: loaded (/etc/systemd/system/etcd.service; static; vendor preset: enabled) Active: inactive (dead) ...
注:输出的最后一行(Active: inactive (dead)
)表示服务处于非活动状态,即系统启动时不会自动运行。 这是预期的,而不是错误。
按 q
返回 shell,然后运行 exit
退出被管节点并返回本地 shell:
exit
在这一步中,我们更新了 playbook 以将 etcd
二进制文件作为 systemd 服务运行。 在下一步中,我们将继续设置 etcd,为其提供存储数据的空间。
第 5 步 — 配置数据目录
etcd 是一个键值对数据存储,这意味着我们必须为它提供空间来存储它的数据。 在这一步中,我们将更新我们的 playbook 以定义一个专用的数据目录供 etcd 使用。
打开你的剧本:
nano $HOME/playground/etcd-ansible/playbook.yaml
将以下任务附加到任务列表的末尾:
~/playground/etcd-ansible/playbook.yaml
- hosts: etcd become: True tasks: ... - name: "Create a etcd service" copy: src: files/etcd.service remote_src: False dest: /etc/systemd/system/etcd.service owner: root group: root mode: 0644 - name: "Create a data directory" file: path: /var/lib/etcd/{{ inventory_hostname }}.etcd state: directory owner: root group: root mode: 0755
在这里,我们使用 /var/lib/etcd/hostname.etcd
作为数据目录,其中 hostname
是当前被管节点的主机名。 inventory_hostname
是一个变量,代表当前被管节点的主机名; 它的值由 Ansible 自动填充。 花括号语法(即 模板:Inventory hostname
)用于 变量替换 ,由 Jinja2 模板引擎支持,这是 Ansible 的默认模板引擎。
关闭文本编辑器并保存文件。
接下来,我们需要指示 etcd 使用这个数据目录。 我们通过将 data-dir
参数传递给 etcd 来做到这一点。 要设置 etcd 参数,我们可以使用环境变量、命令行标志和配置文件的组合。 对于本教程,我们将使用一个配置文件,因为将所有配置隔离到一个文件中会更整洁,而不是让配置散布在我们的剧本中。
在您的项目目录中,创建一个名为 templates/
的新目录:
mkdir templates
然后,使用您的编辑器,在目录中创建一个名为 etcd.conf.yaml.j2
的新文件:
nano templates/etcd.conf.yaml.j2
接下来,复制以下行并将其粘贴到文件中:
~/playground/etcd-ansible/templates/etcd.conf.yaml.j2
data-dir: /var/lib/etcd/{{ inventory_hostname }}.etcd
该文件使用与我们的剧本相同的 Jinja2 变量替换语法。 要替换变量并将结果上传到每个托管主机,我们可以使用 template 模块。 它的工作方式与 copy
类似,只是它将在上传之前执行变量替换。
从 etcd.conf.yaml.j2
退出,然后打开你的剧本:
nano $HOME/playground/etcd-ansible/playbook.yaml
将以下任务附加到任务列表以创建目录并将模板化配置文件上传到其中:
~/playground/etcd-ansible/playbook.yaml
- hosts: etcd become: True tasks: ... - name: "Create a data directory" file: ... mode: 0755 - name: "Create directory for etcd configuration" file: path: /etc/etcd state: directory owner: root group: root mode: 0755 - name: "Create configuration file for etcd" template: src: templates/etcd.conf.yaml.j2 dest: /etc/etcd/etcd.conf.yaml owner: root group: root mode: 0600
保存并关闭此文件。
因为我们已经做出了这个改变,所以我们需要更新我们的服务的单元文件,以将我们的配置文件的位置传递给它(即,/etc/etcd/etcd.conf.yaml
)。
在本地机器上打开 etcd 服务文件:
nano files/etcd.service
通过添加以下突出显示的 --config-file
标志来更新 files/etcd.service
文件:
~/playground/etcd-ansible/files/etcd.service
[Unit] Description=etcd distributed reliable key-value store [Service] Type=notify ExecStart=/opt/etcd/bin/etcd --config-file /etc/etcd/etcd.conf.yaml Restart=always
保存并关闭此文件。
在这一步中,我们使用我们的 playbook 为 etcd 提供了一个数据目录来存储其数据。 在下一步中,我们将添加更多任务来重新启动 etcd
服务并让它在启动时运行。
第 6 步 - 启用和启动 etcd 服务
每当我们对服务的单元文件进行更改时,都需要重新启动服务才能使其生效。 我们可以通过运行 systemctl restart etcd
命令来做到这一点。 此外,要使etcd
服务在系统启动时自动启动,我们需要运行systemctl enable etcd
。 在这一步中,我们将使用 playbook 运行这两个命令。
要运行命令,我们可以使用 command 模块:
nano $HOME/playground/etcd-ansible/playbook.yaml
将以下任务附加到任务列表的末尾:
~/playground/etcd-ansible/playbook.yaml
- hosts: etcd become: True tasks: ... - name: "Create configuration file for etcd" template: ... mode: 0600 - name: "Enable the etcd service" command: systemctl enable etcd - name: "Start the etcd service" command: systemctl restart etcd
保存并关闭文件。
再次运行 ansible-playbook -i hosts playbook.yaml
:
ansible-playbook -i hosts playbook.yaml
要检查 etcd
服务现在是否已重新启动并启用,请通过 SSH 连接到其中一个受管节点:
ssh root@etcd1_public_ip
然后,运行 systemctl status etcd
来检查 etcd
服务的状态:
systemctl status etcd
您会发现 enabled
和 active (running)
如下所示; 这意味着我们在剧本中所做的更改已经生效:
Output● etcd.service - etcd distributed reliable key-value store Loaded: loaded (/etc/systemd/system/etcd.service; static; vendor preset: enabled) Active: active (running) Main PID: 19085 (etcd) Tasks: 11 (limit: 2362)
在这一步中,我们使用 command
模块运行 systemctl
命令,这些命令在我们的托管节点上重新启动并启用 etcd
服务。 现在我们已经设置了 etcd 安装,在下一步中,我们将通过执行一些基本的创建、读取、更新和删除 (CRUD) 操作来测试它的功能。
第 7 步 - 测试 etcd
虽然我们有一个工作的 etcd 安装,但它是不安全的,还没有准备好用于生产。 但在我们在后面的步骤中保护我们的 etcd 设置之前,让我们首先了解 etcd 在功能方面可以做什么。 在这一步中,我们将手动向 etcd 发送请求以添加、检索、更新和删除数据。
默认情况下,etcd 公开了一个 API,用于侦听端口 2379
以进行客户端通信。 这意味着我们可以使用 HTTP 客户端向 etcd 发送原始 API 请求。 但是,使用官方 etcd 客户端 etcdctl
会更快,它允许您使用 put
、get
和del
子命令,分别。
确保您仍在 etcd1 受管节点内,并运行以下 etcdctl
命令以确认您的 etcd 安装正常。
首先,使用 put
子命令创建一个新条目。
put
子命令具有以下语法:
etcdctl put key value
在 etcd1 上,运行以下命令:
etcdctl put foo "bar"
我们刚刚运行的命令指示 etcd 将值 "bar"
写入存储中的键 foo
。
然后你会发现 OK
打印在输出中,这表明数据持久化:
OutputOK
然后我们可以使用 get
子命令检索此条目,其语法为 etcdctl get key
:
etcdctl get foo
你会发现这个输出,它在第一行显示了键,在第二行显示了你之前插入的值:
Outputfoo bar
我们可以使用 del
子命令删除该条目,其语法为 etcdctl del key
:
etcdctl del foo
您将找到以下输出,指示删除的条目数:
Output1
现在,让我们再次运行 get
子命令以尝试检索已删除的键值对:
etcdctl get foo
您不会收到输出,这意味着 etcdctl
无法检索键值对。 这确认了条目被删除后,将无法再检索。
现在您已经测试了 etcd 和 etcdctl
的基本操作,让我们退出我们的托管节点并返回您的本地环境:
exit
在这一步中,我们使用 etcdctl
客户端向 etcd 发送请求。 此时,我们正在运行三个独立的 etcd 实例,每个实例相互独立。 然而,etcd 被设计为分布式键值存储,这意味着多个 etcd 实例可以组合成一个 集群; 然后每个实例成为集群的 成员 。 形成集群后,您将能够检索从集群的不同成员插入的键值对。 在下一步中,我们将使用我们的剧本将我们的 3 个单节点集群转换为一个 3 节点集群。
第 8 步 — 使用静态发现形成集群
要创建一个 3 节点集群而不是三个 1 节点集群,我们必须配置这些 etcd 安装以相互通信。 这意味着每个人都必须知道其他人的 IP 地址。 这个过程称为发现。 可以使用 静态配置 或 动态服务发现 来完成发现。 在这一步中,我们将讨论两者之间的区别,并更新我们的剧本以使用静态发现设置 etcd 集群。
通过静态配置发现是需要最少设置的方法; 这是每个成员的端点在执行之前被传递到 etcd
命令的地方。 要使用静态配置,必须在集群初始化之前满足以下条件:
- 成员数量已知
- 每个成员的端点是已知的
- 所有端点的 IP 地址都是静态的
如果不能满足这些条件,那么您可以使用动态发现服务。 使用动态服务发现,所有实例都将注册到发现服务,这允许每个成员检索有关其他成员位置的信息。
因为我们知道我们需要一个 3 节点的 etcd 集群,并且我们所有的服务器都有静态 IP 地址,所以我们将使用静态发现。 要使用静态发现启动我们的集群,我们必须在配置文件中添加几个参数。 使用编辑器打开 templates/etcd.conf.yaml.j2
模板文件:
nano templates/etcd.conf.yaml.j2
然后,添加以下突出显示的行:
~/playground/etcd-ansible/templates/etcd.conf.yaml.j2
data-dir: /var/lib/etcd/{{ inventory_hostname }}.etcd name: {{ inventory_hostname }} initial-advertise-peer-urls: http://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2380 listen-peer-urls: http://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2380,http://127.0.0.1:2380 advertise-client-urls: http://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2379 listen-client-urls: http://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2379,http://127.0.0.1:2379 initial-cluster-state: new initial-cluster: {% for host in groups['etcd'] %}{{ hostvars[host]['ansible_facts']['hostname'] }}=http://{{ hostvars[host]['ansible_facts']['eth1']['ipv4']['address'] }}:2380{% if not loop.last %},{% endif %}{% endfor %}
按 CTRL+X
然后按 Y
关闭并保存 templates/etcd.conf.yaml.j2
文件。
以下是每个参数的简要说明:
name
- 成员的可读名称。 默认情况下,etcd 使用一个唯一的、随机生成的 ID 来标识每个成员; 但是,人类可读的名称允许我们在配置文件和命令行中更轻松地引用它。 在这里,我们将使用主机名作为成员名称(即,etcd1
、etcd2
和etcd3
)。initial-advertise-peer-urls
- 其他成员可以用来与该成员通信的 IP 地址/端口组合列表。 除了 API 端口(2379
)之外,etcd 还暴露了端口2380
用于 etcd 成员之间的对等通信,这允许它们相互发送消息并交换数据。 请注意,这些 URL 必须可由其对等方访问(而不是本地 IP 地址)。listen-peer-urls
- 当前成员将侦听来自其他成员的通信的 IP 地址/端口组合列表。 这必须包括来自--initial-advertise-peer-urls
标志的所有 URL,还包括本地 URL,如127.0.0.1:2380
。 传入对等消息的目标 IP 地址/端口必须与此处列出的 URL 之一匹配。advertise-client-urls
- 客户端用于与该成员通信的 IP 地址/端口组合列表。 客户端必须可以访问这些 URL(而不是本地地址)。 如果客户端通过公共 Internet 访问集群,则这必须是公共 IP 地址。listen-client-urls
- 当前成员将侦听来自客户端的通信的 IP 地址/端口组合列表。 这必须包括来自--advertise-client-urls
标志的所有 URL,还包括本地 URL,如127.0.0.1:2379
。 传入客户端消息的目标 IP 地址/端口必须与此处列出的 URL 之一匹配。initial-cluster
- 集群每个成员的端点列表。 每个端点必须匹配相应成员的initial-advertise-peer-urls
URL 之一。initial-cluster-state
-new
或existing
。
为了确保一致性,etcd 只能在大多数节点都健康时做出决策。 这称为建立 quorum。 换句话说,在三成员集群中,如果两个或更多成员健康,则达到法定人数。
如果 initial-cluster-state
参数设置为 new
,etcd
将知道这是一个正在引导的新集群,并允许成员并行启动,而无需等待 quorum达到。 更具体地说,在第一个成员启动后,它将没有法定人数,因为三分之一(33.33%)小于或等于 50%。 通常,etcd 将停止并拒绝提交任何更多操作,并且永远不会形成集群。 但是,将 initial-cluster-state
设置为 new
时,它将忽略最初的仲裁不足。
如果设置为 existing
,则成员将尝试加入现有集群,并期望已建立仲裁。
注意: 您可以在 etcd 文档的 Configuration 部分找到有关所有支持的配置标志的更多详细信息。
在更新的 templates/etcd.conf.yaml.j2
模板文件中,有几个 hostvars
的实例。 当 Ansible 运行时,它会从各种来源收集变量。 我们之前已经使用过 inventory_hostname
变量,但还有更多可用的变量。 这些变量在 hostvars[inventory_hostname]['ansible_facts']
下可用。 在这里,我们提取每个节点的私有 IP 地址并使用它来构造我们的参数值。
注意: 因为我们在创建服务器时启用了 Private Networking 选项,所以每个服务器将有三个与之关联的 IP 地址:
- A loopback IP 地址 - 仅在同一台机器内有效的地址。 用于机器引用自身,例如
127.0.0.1
- public IP 地址 - 可通过公共互联网路由的地址,例如
178.128.169.51
- private IP 地址 - 只能在专用网络内路由的地址; 对于 DigitalOcean Droplets,每个数据中心内都有一个专用网络,例如
10.131.82.225
这些 IP 地址中的每一个都与不同的网络接口相关联——环回地址与 lo
接口相关联,公共 IP 地址与 eth0
接口相关联,私有 IP 地址与eth1
接口。 我们正在使用 eth1
接口,以便所有流量都停留在专用网络内,而不会到达互联网。
本文不需要了解网络接口,但如果您想了解更多信息,网络术语、接口和协议简介 是一个很好的起点。
{% %}
Jinja2 语法定义了 for
循环结构,它遍历 etcd
组中的每个节点,以将 initial-cluster
字符串构建成 etcd 所需的格式.
要形成新的三成员集群,必须先停止 etcd
服务并清除数据目录,然后再启动集群。 为此,请使用编辑器在本地计算机上打开 playbook.yaml
文件:
nano $HOME/playground/etcd-ansible/playbook.yaml
然后,在 "Create a data directory"
任务之前,添加一个任务来停止 etcd
服务:
~/playground/etcd-ansible/playbook.yaml
- hosts: etcd become: True tasks: ... group: root mode: 0644 - name: "Stop the etcd service" command: systemctl stop etcd - name: "Create a data directory" file: ...
接下来,更新 "Create a data directory"
任务,首先删除数据目录并重新创建:
~/playground/etcd-ansible/playbook.yaml
- hosts: etcd become: True tasks: ... - name: "Stop the etcd service" command: systemctl stop etcd - name: "Create a data directory" file: path: /var/lib/etcd/{{ inventory_hostname }}.etcd state: "{{ item }}" owner: root group: root mode: 0755 with_items: - absent - directory - name: "Create directory for etcd configuration" file: ...
with_items
属性定义了此任务将迭代的字符串列表。 这相当于重复相同的任务两次,但 state
属性的值不同。 在这里,我们使用 absent
和 directory
项对列表进行迭代,以确保先删除数据目录,然后再重新创建数据目录。
按 CTRL+X
然后按 Y
关闭并保存 playbook.yaml
文件。 然后,再次运行 ansible-playbook
。 Ansible 现在将创建一个单一的 3 成员 etcd
集群:
ansible-playbook -i hosts playbook.yaml
您可以通过 SSH-ing 到任何 etcd 成员节点来检查这一点:
ssh root@etcd1_public_ip
然后运行 etcdctl endpoint health --cluster
:
etcdctl endpoint health --cluster
这将列出集群中每个成员的健康状况:
Outputhttp://etcd2_private_ip:2379 is healthy: successfully committed proposal: took = 2.517267ms http://etcd1_private_ip:2379 is healthy: successfully committed proposal: took = 2.153612ms http://etcd3_private_ip:2379 is healthy: successfully committed proposal: took = 2.639277ms
我们现在已经成功创建了一个 3 节点的 etcd 集群。 我们可以通过在一个成员节点上向 etcd 添加一个条目并在另一个成员节点上检索它来确认这一点。 在其中一个成员节点上,运行 etcdctl put
:
etcdctl put foo "bar"
然后,使用新终端通过 SSH 连接到不同的成员节点:
ssh root@etcd2_public_ip
接下来,尝试使用密钥检索相同的条目:
etcdctl get foo
您将能够检索该条目,这证明集群正在运行:
Outputfoo bar
最后,退出每个托管节点并返回本地计算机:
exit
exit
在这一步中,我们配置了一个新的 3 节点集群。 目前,etcd
成员与其对等节点和客户端之间的通信是通过 HTTP 进行的。 这意味着通信是未加密的,任何可以拦截流量的一方都可以读取消息。 如果 etcd
集群和客户端都部署在您完全控制的专用网络或 虚拟专用网络 (VPN) 中,这不是一个大问题。 但是,如果任何流量需要通过共享网络(私有或公共)传输,则应确保此流量已加密。 此外,需要为客户端或对等方建立一种机制来验证服务器的真实性。
在下一步中,我们将研究如何使用 TLS 保护客户端到服务器以及对等通信。
步骤 9 — 获取被管节点的私有 IP 地址
为了对成员节点之间的消息进行加密,etcd 使用 Hypertext Transfer Protocol Secure 或 HTTPS,它是 Transport Layer Security 或 之上的一层]TLS,协议。 TLS 使用一个由私钥、证书和称为 Certificate Authorities (CA) 的受信任实体组成的系统来相互进行身份验证并相互发送加密消息。
在本教程中,每个成员节点都需要生成一个证书来标识自己,并让该证书由 CA 签名。 我们将配置所有成员节点以信任此 CA,因此也信任它签署的任何证书。 这允许成员节点相互验证。
成员节点生成的证书必须允许其他成员节点识别自己。 所有证书都包含与之关联的实体的 通用名称 (CN)。 这通常用作实体的身份。 但是,在验证证书时,客户端实现可能会比较它收集的有关实体的信息是否与证书中给出的信息相匹配。 例如,当客户端下载主题为 CN=foo.bar.com
的 TLS 证书,但客户端实际上是使用 IP 地址(例如 167.71.129.110
)连接到服务器时,则会出现不匹配和客户端可能不信任证书。 通过在证书中指定一个 主题替代名称 (SAN),它通知验证者两个名称属于同一实体。
因为我们的 etcd 成员使用他们的私有 IP 地址相互对等,所以当我们定义我们的证书时,我们需要提供这些私有 IP 地址作为主题替代名称。
要找出受管节点的私有 IP 地址,请通过 SSH 进入该节点:
ssh root@etcd1_public_ip
然后运行以下命令:
ip -f inet addr show eth1
您会发现类似于以下几行的输出:
Output3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 inet 10.131.255.176/16 brd 10.131.255.255 scope global eth1 valid_lft forever preferred_lft forever
在我们的示例输出中,10.131.255.176
是托管节点的私有 IP 地址,也是我们唯一感兴趣的信息。 要过滤除私有 IP 之外的所有其他内容,我们可以将 ip
命令的输出通过管道传输到用于过滤和转换文本的 sed 实用程序 。
ip -f inet addr show eth1 | sed -En -e 's/.*inet ([0-9.]+).*/\1/p'
现在,唯一的输出是私有 IP 地址本身:
Output10.131.255.176
一旦您对前面的命令感到满意,请退出受管节点:
exit
要将上述命令合并到我们的剧本中,首先打开 playbook.yaml
文件:
nano $HOME/playground/etcd-ansible/playbook.yaml
然后,在我们现有的游戏之前添加一个带有单个任务的新游戏:
~/playground/etcd-ansible/playbook.yaml
... - hosts: etcd tasks: - shell: ip -f inet addr show eth1 | sed -En -e 's/.*inet ([0-9.]+).*/\1/p' register: privateIP - hosts: etcd tasks: ...
该任务使用 shell
模块运行 ip
和 sed
命令,获取被管节点的私有 IP 地址。 然后 将 shell 命令的返回值注册到 privateIP
变量中,我们稍后将使用该变量。
在这一步中,我们在 playbook 中添加了一个任务来获取被管节点的私有 IP 地址。 在下一步中,我们将使用此信息为每个成员节点生成证书,并让这些证书由证书颁发机构 (CA) 签名。
第 10 步——生成 etcd 成员的私钥和 CSR
为了让成员节点接收加密流量,发送方必须使用成员节点的公钥对数据进行加密,成员节点必须使用其私钥解密密文并检索原始数据。 公钥被打包成证书并由 CA 签名以确保其真实性。
因此,我们需要为每个 etcd 成员节点生成一个私钥和证书签名请求(CSR)。 为了方便我们,我们将在控制节点上本地生成所有密钥对并签署所有证书,然后将相关文件复制到托管主机。
首先,创建一个名为 artifacts/
的目录,我们将在其中放置在此过程中生成的文件(密钥和证书)。 用编辑器打开 playbook.yaml
文件:
nano $HOME/playground/etcd-ansible/playbook.yaml
在其中,使用 file
模块创建 artifacts/
目录:
~/playground/etcd-ansible/playbook.yaml
... - shell: ip -f inet addr show eth1 | sed -En -e 's/.*inet ([0-9.]+).*/\1/p' register: privateIP - hosts: localhost gather_facts: False become: False tasks: - name: "Create ./artifacts directory to house keys and certificates" file: path: ./artifacts state: directory - hosts: etcd tasks: ...
接下来,在播放的末尾添加另一个任务以生成私钥:
~/playground/etcd-ansible/playbook.yaml
... - hosts: localhost gather_facts: False become: False tasks: ... - name: "Generate private key for each member" openssl_privatekey: path: ./artifacts/{{item}}.key type: RSA size: 4096 state: present force: True with_items: "{{ groups['etcd'] }}" - hosts: etcd tasks: ...
创建私钥和 CSR 可以分别使用 openssl_privatekey 和 openssl_csr 模块完成。
force: True
属性确保每次都重新生成私钥,即使它已经存在。
类似地,使用 openssl_csr
模块将以下新任务附加到同一个 play 以生成每个成员的 CSR:
~/playground/etcd-ansible/playbook.yaml
... - hosts: localhost gather_facts: False become: False tasks: ... - name: "Generate private key for each member" openssl_privatekey: ... with_items: "{{ groups['etcd'] }}" - name: "Generate CSR for each member" openssl_csr: path: ./artifacts/{{item}}.csr privatekey_path: ./artifacts/{{item}}.key common_name: "{{item}}" key_usage: - digitalSignature extended_key_usage: - serverAuth subject_alt_name: - IP:{{ hostvars[item]['privateIP']['stdout']}} - IP:127.0.0.1 force: True with_items: "{{ groups['etcd'] }}"
我们指定此证书可以包含在数字签名机制中,以用于服务器身份验证。 此证书与主机名相关联(例如,etcd1
),但验证者还应将每个节点的私有和本地环回 IP 地址视为替代名称。 请注意,我们使用的是我们在上一场比赛中注册的 privateIP
变量。
按 CTRL+X
然后按 Y
关闭并保存 playbook.yaml
文件。 然后,再次运行我们的剧本:
ansible-playbook -i hosts playbook.yaml
我们现在将在我们的项目目录中找到一个名为 artifacts
的新目录; 使用 ls
列出其内容:
ls artifacts
您将找到每个 etcd 成员的私钥和 CSR:
Outputetcd1.csr etcd1.key etcd2.csr etcd2.key etcd3.csr etcd3.key
在这一步中,我们使用了几个 Ansible 模块为每个成员节点生成私钥和公钥证书。 在下一步中,我们将了解如何签署证书签名请求 (CSR)。
第 11 步 — 生成 CA 证书
在 etcd 集群中,成员节点使用接收者的公钥加密消息。 为确保公钥是真实的,接收方将公钥打包成证书签名请求(CSR),并让受信任的实体(即 CA)对 CSR 进行签名。 由于我们控制了所有的成员节点和他们信任的 CA,我们不需要使用外部 CA,可以充当我们自己的 CA。 在这一步中,我们将充当我们自己的 CA,这意味着我们需要生成一个私钥和一个自签名证书来充当 CA。
首先,使用编辑器打开 playbook.yaml
文件:
nano $HOME/playground/etcd-ansible/playbook.yaml
然后,与上一步类似,在 localhost
播放中附加一个任务,为 CA 生成私钥:
~/playground/etcd-ansible/playbook.yaml
- hosts: localhost ... tasks: ... - name: "Generate CSR for each member" ... with_items: "{{ groups['etcd'] }}" - name: "Generate private key for CA" openssl_privatekey: path: ./artifacts/ca.key type: RSA size: 4096 state: present force: True - hosts: etcd become: True tasks: - name: "Create directory for etcd binaries" ...
接下来,使用 openssl_csr
模块生成新的 CSR。 这与上一步类似,但在此 CSR 中,我们添加了基本约束和密钥使用扩展,以指示此证书可以用作 CA 证书:
~/playground/etcd-ansible/playbook.yaml
- hosts: localhost ... tasks: ... - name: "Generate private key for CA" openssl_privatekey: path: ./artifacts/ca.key type: RSA size: 4096 state: present force: True - name: "Generate CSR for CA" openssl_csr: path: ./artifacts/ca.csr privatekey_path: ./artifacts/ca.key common_name: ca organization_name: "Etcd CA" basic_constraints: - CA:TRUE - pathlen:1 basic_constraints_critical: True key_usage: - keyCertSign - digitalSignature force: True - hosts: etcd become: True tasks: - name: "Create directory for etcd binaries" ...
最后,使用 openssl_certificate 模块对 CSR 进行自签名:
~/playground/etcd-ansible/playbook.yaml
- hosts: localhost ... tasks: ... - name: "Generate CSR for CA" openssl_csr: path: ./artifacts/ca.csr privatekey_path: ./artifacts/ca.key common_name: ca organization_name: "Etcd CA" basic_constraints: - CA:TRUE - pathlen:1 basic_constraints_critical: True key_usage: - keyCertSign - digitalSignature force: True - name: "Generate self-signed CA certificate" openssl_certificate: path: ./artifacts/ca.crt privatekey_path: ./artifacts/ca.key csr_path: ./artifacts/ca.csr provider: selfsigned force: True - hosts: etcd become: True tasks: - name: "Create directory for etcd binaries" ...
按 CTRL+X
然后按 Y
关闭并保存 playbook.yaml
文件。 然后,再次运行我们的 playbook 以应用更改:
ansible-playbook -i hosts playbook.yaml
您也可以运行 ls
来查看 artifacts/
目录的内容:
ls artifacts/
您现在将找到新生成的 CA 证书(ca.crt
):
Outputca.crt ca.csr ca.key etcd1.csr etcd1.key etcd2.csr etcd2.key etcd3.csr etcd3.key
在这一步中,我们为 CA 生成了一个私钥和一个自签名证书。 下一步,我们将使用 CA 证书对每个成员的 CSR 进行签名。
第 12 步——签署 etcd 成员的 CSR
在这一步中,我们将签署每个成员节点的 CSR。 这将类似于我们使用 openssl_certificate
模块对 CA 证书进行自签名的方式,但我们将使用 ownca
提供程序而不是使用 selfsigned
提供程序,它允许我们使用我们自己的 CA 证书进行签名。
打开你的剧本:
nano $HOME/playground/etcd-ansible/playbook.yaml
将以下突出显示的任务附加到 "Generate self-signed CA certificate"
任务:
~/playground/etcd-ansible/playbook.yaml
- hosts: localhost ... tasks: ... - name: "Generate self-signed CA certificate" openssl_certificate: path: ./artifacts/ca.crt privatekey_path: ./artifacts/ca.key csr_path: ./artifacts/ca.csr provider: selfsigned force: True - name: "Generate an `etcd` member certificate signed with our own CA certificate" openssl_certificate: path: ./artifacts/{{item}}.crt csr_path: ./artifacts/{{item}}.csr ownca_path: ./artifacts/ca.crt ownca_privatekey_path: ./artifacts/ca.key provider: ownca force: True with_items: "{{ groups['etcd'] }}" - hosts: etcd become: True tasks: - name: "Create directory for etcd binaries" ...
按 CTRL+X
然后按 Y
关闭并保存 playbook.yaml
文件。 然后,再次运行 playbook 以应用更改:
ansible-playbook -i hosts playbook.yaml
现在,列出 artifacts/
目录的内容:
ls artifacts/
您将找到每个 etcd 成员和 CA 的私钥、CSR 和证书:
Outputca.crt ca.csr ca.key etcd1.crt etcd1.csr etcd1.key etcd2.crt etcd2.csr etcd2.key etcd3.crt etcd3.csr etcd3.key
在这一步中,我们使用 CA 的密钥对每个成员节点的 CSR 进行了签名。 在下一步中,我们要将相关文件复制到每个托管节点中,以便 etcd 可以访问相关密钥和证书以建立 TLS 连接。
第 13 步 — 复制私钥和证书
每个节点都需要有一份 CA 的自签名证书 (ca.crt
)。 每个 etcd
成员节点也需要有自己的私钥和证书。 在这一步中,我们将上传这些文件并将它们放在一个新的 /etc/etcd/ssl/
目录中。
首先,使用编辑器打开 playbook.yaml
文件:
nano $HOME/playground/etcd-ansible/playbook.yaml
要在我们的 Ansible playbook 上进行这些更改,首先更新 Create directory for etcd configuration
任务的 path
属性以创建 /etc/etcd/ssl/
目录:
~/playground/etcd-ansible/playbook.yaml
- hosts: etcd ... tasks: ... with_items: - absent - directory - name: "Create directory for etcd configuration" file: path: "{{ item }}" state: directory owner: root group: root mode: 0755 with_items: - /etc/etcd - /etc/etcd/ssl - name: "Create configuration file for etcd" template: ...
然后,在修改后的任务之后,再添加三个任务来复制文件:
~/playground/etcd-ansible/playbook.yaml
- hosts: etcd ... tasks: ... - name: "Copy over the CA certificate" copy: src: ./artifacts/ca.crt remote_src: False dest: /etc/etcd/ssl/ca.crt owner: root group: root mode: 0644 - name: "Copy over the `etcd` member certificate" copy: src: ./artifacts/{{inventory_hostname}}.crt remote_src: False dest: /etc/etcd/ssl/server.crt owner: root group: root mode: 0644 - name: "Copy over the `etcd` member key" copy: src: ./artifacts/{{inventory_hostname}}.key remote_src: False dest: /etc/etcd/ssl/server.key owner: root group: root mode: 0600 - name: "Create configuration file for etcd" template: ...
按 CTRL+X
然后按 Y
关闭并保存 playbook.yaml
文件。
再次运行 ansible-playbook
以进行以下更改:
ansible-playbook -i hosts playbook.yaml
在此步骤中,我们已成功将私钥和证书上传到托管节点。 复制完文件后,我们现在需要更新我们的 etcd 配置文件以使用它们。
第 14 步 — 在 etcd 上启用 TLS
在本教程的最后一步,我们将更新一些 Ansible 配置以在 etcd 集群中启用 TLS。
首先,使用编辑器打开 templates/etcd.conf.yaml.j2
模板文件:
nano $HOME/playground/etcd-ansible/templates/etcd.conf.yaml.j2
进入后,将所有 URL 更改为使用 https
作为协议而不是 http
。 此外,在模板末尾添加一个部分以指定 CA 证书、服务器证书和服务器密钥的位置:
~/playground/etcd-ansible/templates/etcd.conf.yaml.j2
data-dir: /var/lib/etcd/{{ inventory_hostname }}.etcd name: {{ inventory_hostname }} initial-advertise-peer-urls: https://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2380 listen-peer-urls: https://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2380,https://127.0.0.1:2380 advertise-client-urls: https://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2379 listen-client-urls: https://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2379,https://127.0.0.1:2379 initial-cluster-state: new initial-cluster: {% for host in groups['etcd'] %}{{ hostvars[host]['ansible_facts']['hostname'] }}=https://{{ hostvars[host]['ansible_facts']['eth1']['ipv4']['address'] }}:2380{% if not loop.last %},{% endif %}{% endfor %} client-transport-security: cert-file: /etc/etcd/ssl/server.crt key-file: /etc/etcd/ssl/server.key trusted-ca-file: /etc/etcd/ssl/ca.crt peer-transport-security: cert-file: /etc/etcd/ssl/server.crt key-file: /etc/etcd/ssl/server.key trusted-ca-file: /etc/etcd/ssl/ca.crt
关闭并保存 templates/etcd.conf.yaml.j2
文件。
接下来,运行您的 Ansible 剧本:
ansible-playbook -i hosts playbook.yaml
然后,通过 SSH 连接到其中一个受管节点:
ssh root@etcd1_public_ip
进入后,运行 etcdctl endpoint health
命令检查端点是否使用 HTTPS,以及所有成员是否健康:
etcdctl --cacert /etc/etcd/ssl/ca.crt endpoint health --cluster
因为我们的 CA 证书默认不是安装在 /etc/ssl/certs/
目录中的受信任的根 CA 证书,所以我们需要使用 --cacert
标志将其传递给 etcdctl
。
这将给出以下输出:
Outputhttps://etcd3_private_ip:2379 is healthy: successfully committed proposal: took = 19.237262ms https://etcd1_private_ip:2379 is healthy: successfully committed proposal: took = 4.769088ms https://etcd2_private_ip:2379 is healthy: successfully committed proposal: took = 5.953599ms
为了确认 etcd
集群确实在工作,我们可以再次在一个成员节点上创建一个条目,并从另一个成员节点检索它:
etcdctl --cacert /etc/etcd/ssl/ca.crt put foo "bar"
使用新终端通过 SSH 连接到不同的节点:
ssh root@etcd2_public_ip
现在使用键 foo
检索相同的条目:
etcdctl --cacert /etc/etcd/ssl/ca.crt get foo
这将返回条目,显示以下输出:
Outputfoo bar
您可以在第三个节点上执行相同操作,以确保所有三个成员都可操作。
结论
您现在已经成功配置了一个 3 节点 etcd 集群,使用 TLS 保护它,并确认它正在工作。
etcd 是 CoreOS 最初创建的工具。 要了解 etcd 与 CoreOS 相关的用法,您可以阅读 如何使用 Etcdctl 和 Etcd,CoreOS 的分布式键值存储。 本文还指导您设置动态发现模型,本教程中已讨论但未演示的内容。
正如本教程开头所述,etcd 是 Kubernetes 生态系统的重要组成部分。 要了解更多关于 Kubernetes 和 etcd 在其中的作用,您可以阅读 An Introduction to Kubernetes。 如果您将 etcd 部署为 Kubernetes 集群的一部分,请知道还有其他可用工具,例如 kubespray 和 kubeadm。 有关后者的更多详细信息,您可以阅读 如何在 Ubuntu 18.04 上使用 Kubeadm 创建 Kubernetes 集群。
最后,本教程使用了许多工具,但无法深入每个工具。 在下文中,您将找到对每个工具进行更详细检查的链接:
- 要了解 Ansible playbook 的更高级语法,可以阅读 配置管理 101:编写 Ansible Playbooks。 Ansible 的官方 Intro to Playbooks 也是一个很好的资源。
- 要了解有关 OpenSSL 的更多信息,您可以阅读 OpenSSL Essentials:使用 SSL 证书、私钥和 CSR 。