如何在Ubuntu18.04上使用Ansible设置和保护etcd集群

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 Wikimedia Foundation 来接受捐赠。

介绍

etcd 是一种分布式键值存储,被许多平台和工具所依赖,包括 KubernetesVulcandDoorman。 在 Kubernetes 中,etcd 用作存储集群状态的全局配置存储。 了解如何管理 etcd 对于管理 Kubernetes 集群至关重要。 尽管有许多托管的 Kubernetes 产品,也称为 Kubernetes-as-a-Service ,可以消除您的这种管理负担,但许多公司仍然选择在本地运行自我管理的 Kubernetes 集群,因为它带来的灵活性。

本文前半部分将指导您在 Ubuntu 18.04 服务器上设置 3 节点 etcd 集群。 下半年将重点关注使用 传输层安全性或 TLS 保护集群。 为了以自动化方式运行每个设置,我们将始终使用 Ansible。 Ansible 是一个 配置管理 工具,类似于 PuppetChefSaltStack; 它允许我们在名为 playbooks 的文件中以声明方式定义每个设置步骤。

在本教程结束时,您将在您的服务器上运行一个安全的 3 节点 etcd 集群。 您还将拥有一个 Ansible 剧本,允许您在一组新的服务器上重复且一致地重新创建相同的设置。

先决条件

在开始本指南之前,您需要以下内容:

  • Pythonpip 和安装在本地计算机上的 pyOpenSSL 包。 要了解如何安装 Python3、pip 和 Python 包,请参阅 如何在 Ubuntu 18.04 上安装 Python 3 并设置本地编程环境
  • 同一本地网络上的三台 Ubuntu 18.04 服务器,具有至少 2GB 的 RAM 和 root SSH 访问权限。 您还应该将服务器配置为具有主机名 etcd1etcd2etcd3。 本文中概述的步骤适用于任何通用服务器,不一定适用于 DigitalOcean Droplets。 但是,如果您想在 DigitalOcean 上托管您的服务器,您可以按照 如何从 DigitalOcean 控制面板 指南创建 Droplet 来满足此要求。 请注意,您必须在创建 Droplet 时启用 Private Networking 选项。 要在现有 Droplet 上启用私有网络,请参阅 如何在 Droplets 上启用私有网络

警告: 由于本文的目的是介绍如何在私有网络上设置 etcd 集群,因此此设置中的三台 Ubuntu 18.04 服务器未使用防火墙进行测试,并且作为 [ X235X]root 用户。 在生产设置中,任何暴露于公共互联网的节点都需要防火墙和 sudo 用户来遵守安全最佳实践。 有关更多信息,请查看使用 Ubuntu 18.04 初始服务器设置教程。


  • 一个 SSH 密钥对,允许您的本地计算机访问 etcd1etcd2etcd3 服务器。 如果您不知道 SSH 是什么,或者没有 SSH 密钥对,您可以通过阅读 SSH Essentials: Working with SSH Servers, Clients, and Keys 来了解它。
  • Ansible 安装在您的本地计算机上。 例如,如果您运行的是 Ubuntu 18.04,则可以按照 如何在 Ubuntu 18.04 上安装和配置 Ansible 一文的 Step 1 安装 Ansible。 这将使 ansibleansible-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 管理的每个节点,并将它们组织到 中。 然后,在运行 ansibleansible-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_hostansible_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 目录,稍后设置 etcdetcdctl 二进制文件的文件权限。
  • get_url:将 gzip 压缩包下载到受管节点。
  • unarchive:从 gzip 压缩包中提取和解压缩 etcdetcdctl 二进制文件。
  • lineinfile:在 .profile 文件中添加一个条目。

要应用这些更改,请按 CTRL+X 然后按 Y 关闭并保存 playbook.yaml 文件。 然后,在终端上,再次运行相同的 ansible-playbook 命令:

ansible-playbook -i hosts playbook.yaml

输出的 PLAY RECAP 部分将仅显示 okchanged

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 到其中一个受管节点并运行 etcdetcdctl

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 服务器返回本地环境。

现在,我们已在所有托管节点上成功安装了 etcdetcdctl。 在下一步中,我们将添加更多任务来运行 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

您会发现 enabledactive (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 会更快,它允许您使用 putgetdel 子命令,分别。

确保您仍在 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 来标识每个成员; 但是,人类可读的名称允许我们在配置文件和命令行中更轻松地引用它。 在这里,我们将使用主机名作为成员名称(即,etcd1etcd2etcd3)。
  • 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 - newexisting

为了确保一致性,etcd 只能在大多数节点都健康时做出决策。 这称为建立 quorum。 换句话说,在三成员集群中,如果两个或更多成员健康,则达到法定人数。

如果 initial-cluster-state 参数设置为 newetcd 将知道这是一个正在引导的新集群,并允许成员并行启动,而无需等待 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 属性的值不同。 在这里,我们使用 absentdirectory 项对列表进行迭代,以确保先删除数据目录,然后再重新创建数据目录。

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 SecureHTTPS,它是 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 模块运行 ipsed 命令,获取被管节点的私有 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_privatekeyopenssl_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 集群的一部分,请知道还有其他可用工具,例如 kubespraykubeadm。 有关后者的更多详细信息,您可以阅读 如何在 Ubuntu 18.04 上使用 Kubeadm 创建 Kubernetes 集群。

最后,本教程使用了许多工具,但无法深入每个工具。 在下文中,您将找到对每个工具进行更详细检查的链接: