如何使用 Ansible 自动化多个 Ubuntu 22.04 服务器的初始服务器设置
作者选择了 Write for DOnations 计划。
介绍
自动化剧本。
Ansible 是无代理的,因此您无需在要运行 Ansible 的服务器上安装任何 Ansible 组件。这些服务器是 Ansible 主机,必须运行 Windows Subsystem for Linux (WSL) 已安装。
在本教程中,您将使用 Ansible 自动执行多个 Ubuntu 22.04 服务器的初始服务器设置。您将在所有服务器上完成以下初始设置任务:
- 更新安装包
- 添加具有管理员权限的非根用户
- 为该非根用户启用 SSH 访问
- 启用防火墙
- 更改 SSH 访问端口并使用防火墙来防止暴力攻击并提高服务器的整体安全性
- 禁用根帐户的远程登录
- 确保关键服务处于活动状态
- 删除不再需要的包依赖项
因为您将使用 Ansible 来运行定义每个任务的综合剧本,所以这些任务将仅使用一个命令完成,而无需您单独登录到服务器。在初始服务器设置后,您可以运行可选的辅助 playbook 来自动化服务器管理。
先决条件
要完成本教程,您需要:
- <李> Ansible 安装在将充当您的控制节点的机器上,该机器可以是您的本地机器或远程 Linux 服务器。要安装 Ansible,请根据其他操作系统的需要按照官方 Ansible 安装指南的步骤 1 进行操作。
- 如果您的控制节点是远程 Ubuntu 22.04 服务器,请务必使用创建其 SSH 密钥对进行设置。
- Git 安装在控制节点上。按照流行的 Linux 发行版的如何安装 Git 教程进行操作。
- (可选)在如何使用 Ansible Vault 教程的设置 Ansible Vault 编辑器中更改链接到
EDITOR
环境 shell 变量的文本编辑器。本教程将使用nano
作为 Ansible Vault 的编辑器。 - 如果您的控制节点是远程 Ubuntu 22.04 服务器,请务必使用
ssh-copy-id
将密钥对连接到主机。
两台或多台 Ubuntu 22.04 服务器以及每台服务器的公共 IPv4 地址。不需要事先设置,因为您将在第 5 步中使用 Ansible 自动设置,但您必须从上面提到的 Ansible 控制节点通过 SSH 访问这些服务器。如果您使用的是 DigitalOcean Droplet,您将在仪表板的“网络”选项卡的每个服务器的“公共网络”部分找到 IPv4 地址。
第 1 步 — 修改控制节点上的 SSH 客户端配置文件
在此步骤中,您将修改控制节点的 SSH 客户端配置文件中的指令。进行此更改后,系统将不再提示您接受远程计算机的 SSH 密钥指纹,因为它们将被自动接受。手动接受每台远程机器的 SSH 密钥指纹可能很乏味,因此此修改解决了使用 Ansible 自动执行多台服务器的初始设置时的扩展问题。
虽然您可以使用 Ansible 的 known_hosts
模块自动接受单个主机的 SSH 密钥指纹,但本教程处理多个主机,因此修改控制节点上的 SSH 客户端配置文件更有效(通常,您的本地计算机)。
首先,在您的控制节点上启动一个终端应用程序,然后使用 nano
或您最喜欢的文本编辑器打开 SSH 客户端配置文件:
- sudo nano /etc/ssh/ssh_config
找到包含 StrictHostKeyChecking
指令的行。取消注释并更改值,使其如下所示:
...
StrictHostKeyChecking accept-new
...
保存并关闭文件。您不需要重新加载或重新启动 SSH 守护程序,因为您只修改了 SSH 客户端配置文件。
注意:如果您不想将 StrictHostKeyChecking
的值从 ask
永久更改为 accept-new
,您可以将其恢复为默认值在第 7 步中运行 playbook 之后。虽然更改值将意味着您的系统自动接受 SSH 密钥指纹,但如果指纹发生更改,它将拒绝来自同一主机的后续连接。此功能意味着 accept-new
更改不会像将该指令的值更改为 no
那样带来安全风险。
现在您已经更新了 SSH 指令,您将开始 Ansible 配置,您将在接下来的步骤中进行配置。
第 2 步 — 配置 Ansible 主机文件
Ansible hosts
文件(也称为清单文件)包含有关 Ansible 主机的信息。此信息可能包括组名、别名、域名和 IP 地址。该文件默认位于 /etc/ansible
目录中。在此步骤中,您将添加在先决条件部分启动的 Ansible 主机的 IP 地址,以便您可以针对它们运行 Ansible 剧本。
首先,使用 nano
或您喜欢的文本编辑器打开 hosts
文件:
- sudo nano /etc/ansible/hosts
在文件中的介绍性注释之后,添加以下行:
...
host1 ansible_host=host1-public-ip-address
host2 ansible_host=host2-public-ip-address
host3 ansible_host=host3-public-ip-address
[initial]
host1
host2
host3
[ongoing]
host1
host2
host3
host1
, host2
, host3
是别名对于要在其上自动执行初始服务器设置的每个主机。使用别名可以更容易地引用别处的主机。 ansible_host
是 Ansible 连接变量,在本例中,指向目标主机的 IP 地址。
initial
和 ongoing
是 Ansible 主机的样本组名称。选择组名,这样可以很容易地了解主机的用途。以这种方式对主机进行分组可以将它们作为一个单元进行寻址。主机可以属于多个组。本教程中的主机已分配到两个不同的组,因为它们将在两个不同的剧本中使用:initial
用于第 8 步中的初始服务器设置。
hostN-public-ip-address
是每个 Ansible 主机的 IP 地址。请务必将 host1-public-ip-address
和后续行替换为将成为自动化一部分的服务器的 IP 地址。
完成修改文件后,保存并关闭它。
在清单文件中定义主机可帮助您指定将使用 Ansible 自动化设置哪些主机。在下一步中,您将使用示例剧本克隆存储库以自动化多服务器设置。
第 3 步 — 克隆 Ansible Ubuntu 初始服务器设置存储库
在此步骤中,您将从 GitHub 克隆一个示例存储库,其中包含此自动化所需的文件。
此 repo 包含用于示例多服务器自动化的三个文件:initial.yml
、ongoing.yml
和 vars/default.yml
。 initial.yml
文件是主要的剧本,其中包含您将针对 Ansible 主机运行以进行初始设置的剧本和任务。 ongoing.yml
文件包含您将针对主机运行的任务,以便在初始服务器设置后进行持续维护。 vars/default.yml
文件包含将在第 8 步的两个剧本中调用的变量。
要克隆存储库,请键入以下命令:
- git clone https://github.com/do-community/ansible-ubuntu.git
或者,如果您已将 SSH 密钥添加到 GitHub 帐户,则可以使用以下方法克隆存储库:
- git@github.com:do-community/ansible-ubuntu.git
现在,您的工作目录中将有一个名为 ansible-ubuntu
的文件夹。换成它:
- cd ansible-ubuntu
这将是本教程其余部分的工作目录。
在此步骤中,您获取了使用 Ansible 自动化多个 Ubuntu 22.04 服务器的示例文件。要准备包含特定于您的主机的信息的文件,接下来您将更新 vars/default.yml
文件以适用于您的系统。
第 4 步 — 修改 Ansible 变量
该剧本将参考一些可能需要随时间更新的自动化信息。将该信息放在一个变量文件中并在 playbook 中调用变量比在 playbook 中对它们进行硬编码更有效,因此您将修改 vars/default.yml
文件中的变量以匹配您的此步骤中的首选项和设置需求。
首先,使用 nano
或您最喜欢的文本编辑器打开文件:
- nano vars/default.yml
您将查看文件的内容,其中包括以下变量:
create_user: sammy
ssh_port: 5995
copy_local_key: "{{ lookup('file', lookup('env','HOME') + '/.ssh/id_rsa.pub') }}"
create_user
变量的值应该是将在每个主机上创建的 sudo
用户的名称。在本例中,它是 sammy
,但您可以随意命名用户。
ssh_port
变量保存您将在设置后用于连接到 Ansible 主机的 SSH 端口。 SSH 的默认端口是 22
,但更改它会显着减少对服务器的自动攻击次数。此更改是可选的,但会提高主机的安全状况。您应该选择一个介于 1024
和 65535
之间且未被 Ansible 主机上的其他应用程序使用的鲜为人知的端口。在此示例中,您使用的是端口 5995
。
注意:如果您的控制节点运行的是 Linux 发行版,请在 /etc/services
中为其选择一个高于 1023
和 grep
的数字。例如,运行 grep 5995 /etc/services
来检查是否正在使用 5995
。如果没有输出,则该文件中不存在该端口,您可以将其分配给变量。如果您的控制节点不是 Linux 发行版并且您不知道在系统上的何处可以找到它的等效项,您可以查阅服务名称和传输协议端口号注册表。
copy_local_key
变量引用您的控制节点的 SSH 公钥文件。如果该文件的名称是 id_rsa.pub
,则您无需对该行进行任何更改。否则,更改它以匹配您的控制节点的 SSH 公钥文件。您可以在控制节点的 ~/.ssh
目录下找到该文件。当您在第 5 步中运行主剧本并创建具有 sudo
权限的用户后,Ansible 控制器会将公钥文件复制到用户的主目录,这使您能够以该用户身份登录在初始服务器设置后通过 SSH。
完成修改文件后,保存并关闭它。
现在您已经为 vars/default.yml
中的变量赋值,Ansible 将能够在步骤 8 中执行剧本时调用这些变量。在下一步中,您将使用 Ansible Vault 为将在每个主机上创建的用户创建和保护密码。
第 5 步 — 使用 Ansible Vault 创建加密密码文件
Ansible Vault 用于创建和加密可在剧本中引用的文件和变量。使用 Ansible Vault 可确保在执行剧本时敏感信息不会以明文形式传输。在此步骤中,您将创建并加密一个包含变量的文件,这些变量的值将用于为每台主机上的 sudo
用户创建密码。通过以这种方式使用 Ansible Vault,您可以确保在初始服务器设置期间和之后,剧本中不会以明文形式引用密码。
仍在 ansible-ubuntu
目录中,使用以下命令创建并打开一个 vault 文件:
- ansible-vault create secret
出现提示时,输入并确认将用于加密 secret
文件的密码。这是保险库密码。在第 8 步运行 playbook 时,您将需要 vault 密码,所以不要忘记它。
输入并确认保险库密码后,secret
文件将在链接到 shell 的 EDITOR
环境变量的文本编辑器中打开。将这些行添加到文件中,替换 type_a_strong_password_here
和 type_a_salt_here
的值:
- password: type_a_strong_password_here
- password_salt: type_a_salt_here
password
变量的值将是您将在每个主机上创建的 sudo
用户的实际密码。 password_salt
变量使用salt 作为其值。 A 第 8 步。
注意:在测试中,我们发现仅由数字字符组成的盐会导致第 8 步运行剧本时出现问题。但是,仅由字母字符组成的盐会起作用。字母数字盐也应该起作用。指定盐时请记住这一点。
完成修改文件后,保存并关闭它。
您现在已经创建了一个加密的密码文件,其中包含用于为主机上的 sudo
用户创建密码的变量。在下一步中,您将通过运行主要的 Ansible 剧本来自动执行您在步骤 2 中指定的服务器的初始设置。
第 6 步 — 针对 Ansible 主机运行主要剧本
在此步骤中,您将使用 Ansible 自动执行您在清单文件中指定的服务器的初始服务器设置。您将从查看主剧本中定义的任务开始。然后,您将针对主机执行剧本。
Ansible playbook 由一个或多个 play 组成,每个 play 都有一个或多个任务。您将针对 Ansible 主机运行的示例剧本包含两个剧本,共有 14 个任务。
在运行剧本之前,您将查看其设置过程中涉及的每个任务。首先,使用 nano
或您最喜欢的文本编辑器打开文件:
- nano initial.yml
播放 1:
文件的第一部分包含以下影响播放行为的关键字:
- name: Initial server setup tasks
hosts: initial
remote_user: root
vars_files:
- vars/default.yml
- secret
...
name
是播放的简短描述,将在播放运行时显示在终端中。 hosts
关键字指示哪些主机是播放的目标。在这种情况下,传递给关键字的模式是您在第 2 步的 /etc/ansible/hosts
文件中指定的主机的组名。您使用 remote_user
关键字告诉 Ansible 控制器用于登录主机的用户名(在本例中为 root
)。 vars_files
关键字指向包含播放在执行任务时将引用的变量的文件。
通过此设置,Ansible 控制器将尝试以 root
用户身份通过 SSH 端口 22
登录主机。对于它能够登录的每个主机,它将报告一个 ok
响应。否则,它将报告服务器 unreachable
并将开始为任何可以登录的主机执行 play 的任务。如果您手动完成此设置,此自动化将取代使用 ssh root@host-ip-address
登录主机。
关键字部分之后是要按顺序执行的任务列表。与剧本一样,每个任务都以一个名称
开始,它提供了任务将要完成的内容的简短描述。
任务 1:更新缓存
剧本中的第一个任务更新包数据库:
...
- name: update cache
ansible.builtin.apt:
update_cache: yes
...
此任务将使用 ansible.builtin.apt
模块更新包数据库,这就是为什么用 update_cache: yes
定义它的原因。此任务完成与登录 Ubuntu 服务器并键入 sudo apt update
时相同的任务,通常是更新所有已安装软件包的前奏。
任务 2:更新所有已安装的包
剧本更新包中的第二个任务:
...
- name: Update all installed packages
ansible.builtin.apt:
name: "*"
state: latest
...
与第一个任务一样,这个任务也调用了 ansible.builtin.apt
模块。在这里,您确保所有已安装的包都是最新的,使用通配符指定包 (name: *
) 和 state: latest
,这等同于登录到您的服务器并运行 sudo apt upgrade -y
命令。
任务 3:确保 NTP 服务正在运行
剧本中的第三个任务确保网络时间协议 (NTP) 守护进程处于活动状态:
...
- name: Make sure NTP service is running
ansible.builtin.systemd:
state: started
name: systemd-timesyncd
...
此任务调用 ansible.builtin.systemd
模块以确保 NTP 守护进程 systemd-timesyncd
正在运行(state: started
)。当您想要确保您的服务器保持相同的时间时,您可以运行这样的任务,这有助于在这些服务器上运行分布式应用程序。
任务 4:确保我们有一个 sudo 组
剧本中的第四个任务验证是否存在 sudo
组:
...
- name: Make sure we have a 'sudo' group
ansible.builtin.group:
name: sudo
state: present
...
此任务调用 ansible.builtin.group
模块来检查主机上是否存在名为 sudo
的组 (state: present
)。因为您的下一个任务取决于主机上是否存在 sudo
组,所以此任务会检查是否存在 sudo
组,因此您可以确保下一个任务不会失败.
任务 5:创建具有 sudo 权限的用户
剧本中的第五个任务创建具有 sudo
权限的非根用户:
...
- name: Create a user with sudo privileges
ansible.builtin.user:
name: "{{ create_user }}"
state: present
groups: sudo
append: true
create_home: true
shell: /bin/bash
password: "{{ password | password_hash('sha512', password_salt) }}"
update_password: on_create
...
在这里,您通过调用 ansible.builtin.user
模块并将 sudo
组附加到用户组来在每个主机上创建一个用户。用户名源自您在 vars/default.yml
中指定的 create_user
变量的值。此任务还确保为用户创建主目录并为其分配适当的 shell。
使用 password
参数以及您在 SHA-512 加密哈希算法中设置的密码和盐的组合为用户生成哈希密码。与 secret
保险库文件配对,密码永远不会以明文形式传递给控制器。使用 update_password
,您可以确保仅在第一次创建用户时设置散列密码。如果您重新运行剧本,密码将不会重新生成。
任务 6:为远程用户设置授权密钥
剧本中的第六项任务为您的用户设置密钥:
...
- name: Set authorized key for remote user
ansible.posix.authorized_key:
user: "{{ create_user }}"
state: present
key: "{{ copy_local_key }}"
...
通过此任务,您可以通过调用 ansible.posix.authorized_key
将公共 SSH 密钥复制到主机。 user
的值为上一个任务中在hosts上创建的用户名,key
指向要复制的key。这两个变量都在 var/default.yml
文件中定义。此任务与手动运行 ssh-copy-id
命令具有相同的效果。
任务 7:禁用 root 的远程登录
剧本中的第七个任务为 root
用户禁用远程登录:
...
- name: Disable remote login for root
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
state: present
regexp: '^PermitRootLogin yes'
line: 'PermitRootLogin no'
...
接下来,调用 ansible.builtin.lineinfile
模块。此任务使用正则表达式 (regexp
) 在 /etc/ssh/sshd_config
文件中搜索以 PermitRootLogin
开头的行,然后替换它的值为 line
。此任务确保使用 root
帐户的远程登录在运行此 playbook 中的游戏后将失败。只有使用任务 6 中创建的用户帐户进行远程登录才会成功。通过禁用远程 root 登录,您可以确保只有普通用户可以登录,并且需要使用权限升级方法(通常是 sudo
)来获得管理员权限。
任务 8:更改 SSH 端口
剧本中的第八个任务更改 SSH 端口:
...
- name: Change the SSH port
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
state: present
regexp: '^#Port 22'
line: 'Port "{{ ssh_port }}"'
...
因为 SSH 侦听众所周知的端口 22
,所以它容易受到针对该端口的自动攻击。通过更改 SSH 侦听的端口,可以减少攻击主机的自动攻击次数。此任务使用相同的 ansible.builtin.lineinfile
模块在 SSH 守护进程的配置文件中搜索以 regexp
开头的行,并将其值更改为 <代码>行参数。 SSH 监听的新端口号将是您在第 4 步中分配给 ssh_port
变量的端口号。此游戏结束后重新启动主机后,您将无法登录通过端口 22
到主机。
任务 9:UFW - 允许 SSH 连接
剧本中的第九个任务允许 SSH 流量:
...
- name: UFW - Allow SSH connections
community.general.ufw:
rule: allow
port: "{{ ssh_port }}"
...
在这里,您调用第 4 步。此任务相当于手动运行 ufw allow 5995/tcp
命令。
任务 10:SSH 的暴力破解尝试保护
第十项任务防范暴力破解:
...
- name: Brute-force attempt protection for SSH
community.general.ufw:
rule: limit
port: "{{ ssh_port }}"
proto: tcp
...
再次调用 community.general.ufw
模块,此任务使用速率限制 规则
拒绝登录访问已失败六次或更多次连接尝试的 IP 地址30 秒时间范围内的 SSH 端口。 proto
参数指向目标协议(在本例中为 TCP)。
任务 11:UFW - 拒绝其他传入流量并启用 UFW
第十一任务启用防火墙:
...
- name: UFW - Deny other incoming traffic and enable UFW
community.general.ufw:
state: enabled
policy: deny
direction: incoming
...
在此任务中仍然依赖于 community.general.ufw
模块,您启用防火墙(state: enabled
)并设置默认的 policy
拒绝所有传入流量。
任务 12:删除不再需要的依赖项
本剧的第十二个任务清理包依赖:
...
- name: Remove dependencies that are no longer required
ansible.builtin.apt:
autoremove: yes
...
通过再次调用 ansible.builtin.apt
模块,此任务会删除服务器上不再需要的包依赖项,这相当于运行 sudo apt autoremove
命令手动。
任务 13:重新启动 SSH 守护程序
本剧本中的第十三项任务重新启动 SSH。
...
- name: Restart the SSH daemon
ansible.builtin.systemd:
state: restarted
name: ssh
最后一个任务调用 ansible.builtin.systemd
模块来重启 SSH 守护进程。必须重新启动才能使守护进程的配置文件中所做的更改生效。此任务与使用 sudo systemctl restart ssh
重新启动守护程序具有相同的效果。
与主机的初始连接是通过端口 22
作为 root
,但之前的任务已经更改了端口号并禁用了远程 root 登录,这就是您需要重新启动 SSH 的原因守护进程在这个阶段发挥作用。第二个游戏将使用不同的连接凭据(用户名而不是 root
和新定义的端口号不是 22
)。
玩法 2:初始设置后重启主机
此播放在播放 1 中的最后一个任务成功完成后开始。它受以下关键字的影响:
...
- name: Rebooting hosts after initial setup
hosts: initial
port: "{{ ssh_port }}"
remote_user: "{{ create_user }}"
become: true
vars_files:
- vars/default.yml
- ~/secret
vars:
ansible_become_pass: "{{ password }}"
...
传递给 hosts
关键字的模式是第 4 步中 /etc/ansible/hosts
文件中指定的 initial
组名。
在第一个游戏中,Ansible 控制器以 root
用户身份登录到主机。由于第一次播放禁用了远程 root 登录,您现在必须指定 Ansible 控制器应该登录的用户。 remote_user
关键字指示 Ansible 控制器以在第一个游戏的任务 5 中创建的 sudo
用户身份登录到每个主机。
become
关键字指定权限升级用于在定义的主机上执行任务。此关键字指示 Ansible 控制器在必要时承担在主机上执行任务的 root 权限。在这种情况下,控制器将使用 sudo
获得 root 权限。 ansible_become_pass
关键字设置权限升级密码,这是将用于承担 root 权限的密码。在这种情况下,它指向具有您在步骤 5 中使用 Ansible Vault 配置的密码的变量。
vars_files
关键字除了指向vars/default.yml
文件外,还指向你在第5步配置的secret
文件,该文件告诉 Ansible 控制器在哪里可以找到 password
变量。
在关键字部分之后是将在该剧中执行的单独任务。
任务 14:重新引导所有主机
注意:虽然这是第二个 play 的第一个任务,但它被编号为 Task 14,因为 Ansible Controller 认为它不是 Play 2 的 Task 1,而是 playbook 的 Task 14。
剧本的最终任务将重启所有主机:
...
- name: Reboot all hosts
ansible.builtin.reboot:
在完成第一次播放中的任务后重新启动主机将意味着对内核或库的任何更新都将在您开始安装应用程序之前生效。
完整的剧本文件如下所示:
- name: Initial server setup tasks
hosts: initial
remote_user: root
vars_files:
- vars/default.yml
- secret
tasks:
- name: update cache
ansible.builtin.apt:
update_cache: yes
- name: Update all installed packages
ansible.builtin.apt:
name: "*"
state: latest
- name: Make sure NTP service is running
ansible.builtin.systemd:
state: started
name: systemd-timesyncd
- name: Make sure we have a 'sudo' group
ansible.builtin.group:
name: sudo
state: present
- name: Create a user with sudo privileges
ansible.builtin.user:
name: "{{ create_user }}"
state: present
groups: sudo
append: true
create_home: true
shell: /bin/bash
password: "{{ password | password_hash('sha512', password_salt) }}"
update_password: on_create
- name: Set authorized key for remote user
ansible.builtin.authorized_key:
user: "{{ create_user }}"
state: present
key: "{{ copy_local_key }}"
- name: Disable remote login for root
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
state: present
regexp: '^PermitRootLogin yes'
line: 'PermitRootLogin no'
- name: Change the SSH port
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
state: present
regexp: '^#Port 22'
line: 'Port "{{ ssh_port }}"'
- name: UFW - Allow SSH connections
community.general.ufw:
rule: allow
port: "{{ ssh_port }}"
- name: Brute-force attempt protection for SSH
community.general.ufw:
rule: limit
port: "{{ ssh_port }}"
proto: tcp
- name: UFW - Deny other incoming traffic and enable UFW
community.general.ufw:
state: enabled
policy: deny
direction: incoming
- name: Remove dependencies that are no longer required
ansible.builtin.apt:
autoremove: yes
- name: Restart the SSH daemon
ansible.builtin.systemd:
state: restarted
name: ssh
- name: Rebooting hosts after initial setup
hosts: initial
port: "{{ ssh_port }}"
remote_user: "{{ create_user }}"
become: true
vars_files:
- vars/default.yml
- secret
vars:
ansible_become_pass: "{{ password }}"
tasks:
- name: Reboot all hosts
ansible.builtin.reboot:
查看完文件后,保存并关闭它。
注意:您可以向剧本添加新任务或修改现有任务。但是,更改 YAML 文件可能会损坏它,因为 YAML 对间距很敏感,因此如果您选择编辑文件的任何方面,请务必小心。有关使用 Ansible 剧本的更多信息,请关注我们关于如何编写 Ansible 剧本的系列文章。
现在你可以运行剧本了。首先,检查语法:
- ansible-playbook --syntax-check --ask-vault-pass initial.yml
系统将提示您输入在第 5 步中创建的保管库密码。如果身份验证成功后 YAML 语法没有错误,输出将是:
Outputplaybook: initial.yml
您现在可以使用以下命令运行该文件:
- ansible-playbook --ask-vault-pass initial.yml
系统将再次提示您输入保管库密码。身份验证成功后,Ansible 控制器将以 root
用户身份登录到每个主机,并执行剧本中的所有任务。 Ansible 不是在每个服务器上单独运行 ssh root@node-ip-address
命令,而是连接到 /etc/ansible/hosts
中指定的所有节点 然后执行剧本中的任务。
对于本教程中的示例主机,Ansible 花费了大约三分钟的时间来完成跨三台主机的任务。任务完成后,您将获得如下输出:
OutputPLAY RECAP *****************************************************************************************************
host1 : ok=16 changed=11 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host2 : ok=16 changed=11 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host3 : ok=16 changed=11 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
成功评估的每个任务和播放关键字部分都将计入 ok
列中的数字。两场比赛有 14 个任务,所有任务都成功评估,计数为 16
。在评估的任务中,只有 11
导致服务器发生变化,由 changed
列表示。
unreachable
计数显示 Ansible 控制器无法登录的主机数。所有任务均未失败,因此 failed
的计数为 0
。
当不满足任务中指定的条件时(通常使用 when
参数),任务将被跳过
。在这种情况下,没有任务被跳过
,但它将适用于第 8 步。
最后两列(rescued
和ignored
)与为游戏或任务指定的错误处理相关。
您现在已经成功地使用 Ansible 执行一个命令来自动完成多个 Ubuntu 22.04 服务器的初始服务器设置,该命令完成了剧本指定的所有任务。
要检查一切是否按预期工作,您接下来将登录到其中一台主机以验证设置。
(可选)第 7 步 — 手动检查服务器设置
要在上一步结束时确认播放回顾的输出,您可以使用之前配置的凭据登录到您的其中一台主机以验证设置。这些操作出于学习目的是可选的,因为 Ansible 回顾报告准确完成。
首先使用以下命令登录到其中一台主机:
- ssh -p 5995 sammy@host1-public-ip-address
您使用 -p
选项指向您在第 6 步中为 SSH 配置的自定义端口号。如果您能够通过该端口以该用户身份登录到主机,您就知道Ansible 成功地完成了这些任务。
登录后,检查您是否能够更新包数据库:
- sudo apt update
如果系统提示您输入密码并且可以使用您在步骤 5 中为用户配置的密码进行身份验证,则可以确认 Ansible 已成功完成创建用户和设置用户密码的任务。
既然您知道设置 playbook 按预期工作,您可以运行第二个 playbook 进行持续维护。
(可选)第 8 步 — 使用 Ansible 进行主机的持续维护
在第 3 步中执行的初始服务器设置剧本,您还提取了一个可用于持续维护的 ongoing.yml
剧本。在此步骤中,您将运行 ongoing.yml
剧本来自动执行本教程中设置的主机的持续维护。
在运行剧本之前,您将检查每项任务。首先,使用 nano
或您最喜欢的文本编辑器打开文件:
- nano ongoing.yml
与初始设置剧本不同,维护剧本只包含一个剧本和更少的任务。
播放 1:
文件第一部分中的以下关键字会影响播放的行为:
- hosts: ongoing
port: "{{ ssh_port }}"
remote_user: "{{ create_user }}"
become: true
vars_files:
- vars/default.yml
- secret
vars:
ansible_become_pass: "{{ password }}"
...
除了传递给 hosts
关键字的组外,这些关键字与设置剧本的第二次播放中使用的关键字相同。
关键字之后是要按顺序执行的任务列表。与设置 playbook 一样,维护 playbook 中的每个任务都以一个 name
开始,它提供了任务将完成的内容的简短描述。
任务 1:更新缓存
第一个任务更新包数据库:
...
- name: update cache
ansible.builtin.apt:
update_cache: yes
...
此任务将使用 ansible.builtin.apt
模块更新包数据库,这就是为什么用 update_cache: yes
定义它的原因。此任务完成与登录 Ubuntu 服务器并键入 sudo apt update
时相同的任务,这通常是安装软件包或更新所有已安装软件包的前奏。
任务 2:更新所有已安装的包
第二个任务更新包:
...
- name: Update all installed packages
ansible.builtin.apt:
name: "*"
state: latest
...
与第一个任务一样,这个任务也调用了 ansible.builtin.apt
模块。在这里,您确保所有已安装的包都是最新的,使用通配符指定包 (name: *
) 和 state: latest
,这等同于登录到您的服务器并运行 sudo apt upgrade -y
命令。
任务 3:确保 NTP 服务正在运行
剧本中的第三个任务确保设置了 NTP 守护进程:
...
- name: Make sure NTP service is running
ansible.builtin.systemd:
state: started
name: systemd-timesyncd
...
服务器上的活动服务可能因各种原因而失败,因此您要确保此类服务保持活动状态。此任务调用 ansible.builtin.systemd
模块以确保 NTP 守护进程 systemd-timesyncd
保持活动状态(state: started
)。
任务 4:UFW - 它在运行吗?
第四个任务检查 UFW 防火墙的状态:
...
- name: UFW - Is it running?
ansible.builtin.command: ufw status
register: ufw_status
...
您可以使用 sudo ufw status
命令检查 Ubuntu 上 UFW 防火墙的状态。输出的第一行将显示为 Status: active
或 Status: inactive
。此任务使用 ansible.builtin.command
模块运行相同的命令,然后将输出保存(register
)到 ufw_status
变量。该变量的值将在下一个任务中查询。
任务 5:UFW - 启用 UFW 并拒绝传入流量
第五个任务将重新启用 UFW 防火墙(如果它已停止):
...
- name: UFW - Enable UFW and deny incoming traffic
community.general.ufw:
state: enabled
when: "'inactive' in ufw_status.stdout"
...
只有当 inactive
出现在 ufw_status 的输出中时,此任务才调用
变量。如果防火墙处于活动状态,则不满足 community.general.ufw
模块启用防火墙when
条件,任务被标记为 skipped
。
任务 6:删除不再需要的依赖项
本剧本中的第六项任务是清理包依赖项:
...
- name: Remove dependencies that are no longer required
ansible.builtin.apt:
autoremove: yes
...
此任务通过调用 ansible.builtin.apt
模块删除服务器上不再需要的包依赖项,这相当于运行 sudo apt autoremove
命令。
任务 7:检查是否需要重启
第七个任务检查是否需要重启:
...
- name: Check if reboot required
ansible.builtin.stat:
path: /var/run/reboot-required
register: reboot_required
...
在 Ubuntu 上,新安装或升级的软件包将发出信号,表明需要重新启动才能使安装或升级时引入的更改生效,方法是创建 /var/run/reboot-required
文件。您可以使用 stat /var/run/reboot-required
命令确认该文件是否存在。此任务调用 ansible.builtin.stat
模块来做同样的事情,然后将输出保存(register
)到 reboot_required
变量。该变量的值将在下一个任务中查询。
任务 8:如果需要重新启动
第八项任务将在必要时重新启动服务器:
...
- name: Reboot if required
ansible.builtin.reboot:
when: reboot_required.stat.exists == true
通过查询任务 7 中的 reboot_required
变量,此任务调用 ansible.builtin.reboot
模块以仅在时
/var/run/reboot-required
存在。如果需要重新启动并且主机已重新启动,任务将标记为已更改
。否则,Ansible 在播放回顾中将其标记为 skipped
。
持续维护的完整剧本文件如下:
- hosts: ongoing
port: "{{ ssh_port }}"
remote_user: "{{ create_user }}"
become: true
vars_files:
- vars/default.yml
- secret
vars:
ansible_become_pass: "{{ password }}"
tasks:
- name: update cache
ansible.builtin.apt:
update_cache: yes
- name: Update all installed packages
ansible.builtin.apt:
name: "*"
state: latest
- name: Make sure NTP service is running
ansible.builtin.systemd:
state: started
name: systemd-timesyncd
- name: UFW - Is it running?
ansible.builtin.command: ufw status
register: ufw_status
- name: UFW - Enable UFW and deny incoming traffic
community.general.ufw:
state: enabled
when: "'inactive' in ufw_status.stdout"
- name: Remove dependencies that are no longer required
ansible.builtin.apt:
autoremove: yes
- name: Check if reboot required
ansible.builtin.stat:
path: /var/run/reboot-required
register: reboot_required
- name: Reboot if required
ansible.builtin.reboot:
when: reboot_required.stat.exists == true
查看完文件后,保存并关闭它。
注意:您可以向剧本中添加新任务或修改现有任务。但是,更改 YAML 文件可能会损坏它,因为 YAML 对间距很敏感,因此如果您选择编辑文件的任何方面,请务必小心。有关使用 Ansible 剧本的更多信息,请关注我们关于如何编写 Ansible 剧本的系列文章。
现在您可以运行该文件了。首先,检查语法:
- ansible-playbook --syntax-check --ask-vault-pass ongoing.yml
系统将提示您输入在第 5 步中创建的保管库密码。如果身份验证成功后 YAML 语法没有错误,输出将是:
Outputplaybook: ongoing.yml
您现在可以使用以下命令运行该文件:
- ansible-playbook --ask-vault-pass ongoing.yml
系统将提示您输入保管库密码。身份验证成功后,Ansible 控制器将以 sammy
(或您指定的用户名)身份登录到每个主机以执行 playbook 中的任务。 Ansible 不是在每个服务器上单独运行 ssh -p 5995 sammy@host_ip_address
命令,而是连接到节点由 /etc/ansible/hosts
中的 ongoing
组指定,然后执行 playbook 中的任务。
如果命令成功完成,将打印以下输出:
OutputPLAY RECAP *****************************************************************************************************
host1 : ok=7 changed=2 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
host2 : ok=7 changed=2 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
host3 : ok=7 changed=2 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
与初始服务器设置的播放回顾不同,此播放回顾记录了 跳过
的两个任务,因为未满足使用 when
参数为每个任务设置的条件。
您可以使用此 playbook 来维护主机,而无需手动登录到每个主机。当您在主机上构建和安装应用程序时,您可以将任务添加到 playbook,以便您还可以使用 Ansible 管理这些应用程序。
结论
在本教程中,您使用 Ansible 自动完成了多个 Ubuntu 22.04 服务器的初始设置。您还运行了一个辅助 playbook 来持续维护这些服务器。当您需要在分布式或集群模式下设置像 MinIO 这样的应用程序时,Ansible 自动化是一种节省时间的工具。
有关 Ansible 的更多信息,请参阅配置管理 101:编写 Ansible 剧本。