如何使用Ansible自动化多个Ubuntu 22.04服务器的初始设置

引言

Ansible是一种开源软件工具,用于自动化服务器和应用程序的配置管理、部署和供应。您可以使用Ansible自动化执行一个或多个服务器上的任务,或者在多个服务器上运行分布式应用程序。对于多服务器配置,为每个服务器完成初始服务器设置可能耗时很长,而使用Ansible可以通过自动化剧本加快这个过程。

Ansible是无需代理的,因此您无需在要运行Ansible的服务器上安装任何Ansible组件。这些服务器是Ansible主机,必须运行Python 3和OpenSSH,这两者都预先安装在Ubuntu 22.04和所有Linux发行版上。Ansible控制节点是将发起自动化的机器,它可以运行任何兼容的类Unix操作系统,或者如果已安装了Windows子系统用于Linux(WSL),则可以运行Windows。

在本教程中,您将使用Ansible自动化多个Ubuntu 22.04服务器的初始服务器设置。您将对所有服务器执行以下初始设置任务:

  • Updating installed packages
  • Adding a non-root user with admin privileges
  • Enabling SSH access for that non-root user
  • Enabling the firewall
  • Changing the port for SSH access and using the firewall to protect against brute-force attacks and boost the overall security of the servers
  • Disabling remote login for the root account
  • Making sure critical services are active
  • Removing package dependencies that are no longer required

因为您将使用Ansible来运行一个包含每个任务的全面playbook,这些任务将使用一个命令完成,而无需您单独登录服务器。在初始服务器设置之后,您可以运行一个可选的次要playbook来自动化服务器管理。

先决条件

完成此教程,您需要以下物品:

  • Ansible installed on a machine that will act as your control node, which can be your local machine or a remote Linux server. To install Ansible, follow Step 1 of How To Install and Configure Ansible on Ubuntu 22.04, and you can refer to the official Ansible installation guide as needed for other operating systems.

    If your control node is a remote Ubuntu 22.04 server, be sure to set it up using the Initial Server Setup and create its SSH key pair as well.
    Git installed on the control node. Follow the How To Install Git tutorials for popular Linux distributions.
    (Optional) In Step 5, you will use Ansible Vault to create an encrypted password file for your hosts’ users. Ansible Vault uses vi as its default editor. If your control node is a Linux machine and you prefer using nano, use the section on Setting the Ansible Vault Editor in the How To Use Ansible Vault tutorial to change the text editor linked to the EDITOR environment shell variable. This tutorial will use nano as the editor for Ansible Vault.

  • Two or more Ubuntu 22.04 servers and the public IPv4 address of each server. No prior setup is required as you’ll use Ansible to automate setup in Step 5, but you must have SSH access to these servers from the Ansible control node mentioned above. If you are using DigitalOcean Droplets, you’ll find the IPv4 address in each server’s Public Network section of the Networking tab in your dashboard.

    If your control node is a remote Ubuntu 22.04 server, be sure to use ssh-copy-id to connect the key pair to the hosts.

第一步— 在控制节点上修改SSH客户端配置文件

在这一步中,你将修改控制节点的SSH客户端配置文件中的指令。在进行此更改后,将不再需要手动确认远程机器的SSH密钥指纹,因为它们将被自动接受。对每台远程机器手动接受SSH密钥指纹可能会很繁琐,因此这个修改解决了使用Ansible自动化多个服务器的初始设置时的扩展问题。

虽然您可以使用Ansible的known_hosts模块来自动接受单个主机的SSH密钥指纹,但本教程处理的是多个主机,因此在控制节点上(通常是您的本地机器)修改SSH客户端配置文件更为有效。

首先,在控制节点上启动终端应用程序,然后使用nano或您喜欢的文本编辑器打开SSH客户端配置文件。

  1. sudo nano /etc/ssh/ssh_config

找到包含StrictHostKeyChecking指令的行。删除注释,并将其值更改为以下内容:

/etc/ssh/ssh_config 可以用下面的另外一种中文表达方式:

ssh 配置文件位于 /etc/ssh/ssh_config

...
   StrictHostKeyChecking accept-new
...

保存并关闭文件。您不需要重新加载或重启SSH守护程序,因为您只修改了SSH客户端配置文件。

Note

注意:如果您不希望将StrictHostKeyChecking的值从ask永久更改为accept-new,您可以在第7步运行playbook后将其恢复为默认值。尽管更改该值意味着您的系统将自动接受SSH密钥指纹,但如果指纹发生变化,它将拒绝来自同一主机的后续连接。这个特性意味着accept-new的更改并不像将该指令的值更改为no那样具有安全风险。

现在,您已经更新了SSH指令,下一步将开始进行Ansible配置。

第二步 – 配置Ansible主机文件

Ansible主机文件(也称为清单文件)包含有关Ansible主机的信息。此信息可能包括组名、别名、域名和IP地址。该文件默认位于/etc/ansible目录中。在此步骤中,您将添加先决条件部分所创建的Ansible主机的IP地址,以便您可以针对它们运行您的Ansible剧本。

首先,使用nano或你钟爱的文本编辑器打开hosts文件。

  1. sudo nano /etc/ansible/hosts

在文件的介绍性评论之后,添加以下几行内容:

/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地址。

初始和持续是Ansible主机的示例组名称。选择能够清楚了解主机用途的组名称。以这种方式对主机进行分组,可以将它们作为一个单元来处理。主机可以属于多个组。本教程中的主机已分配到两个不同的组,因为它们将在两个不同的playbook中使用:初始组用于第6步的初始服务器设置,持续组用于第8步的后续服务器管理。

hostN的公共IP地址是每个Ansible主机的IP地址。请确保用将参与自动化的服务器的IP地址替换host1的公共IP地址和随后的行。

当您完成对文件的修改后,请保存并关闭它。

在清单文件中定义主机可以帮助您指定要使用Ansible自动化设置的主机。在下一步中,您将克隆包含示例剧本的存储库,以实现多服务器设置的自动化。

步骤3 — 克隆Ansible Ubuntu初始服务器设置存储库

在这个步骤中,您将从GitHub克隆一个包含此自动化所需文件的示例仓库。

这个仓库包含了一个样例多服务器自动化的三个文件:initial.yml,ongoing.yml,和vars/default.yml。initial.yml文件是包含着对Ansible主机进行初始设置的plays和tasks的主要playbook。ongoing.yml文件包含了对主机进行初始服务器设置后的持续维护的任务。vars/default.yml文件包含了在第6步和第8步两个playbooks中将被调用的变量。

要克隆这个仓库,请输入以下命令:

  1. git clone https://github.com/do-community/ansible-ubuntu.git

或者,如果您已将SSH密钥添加到您的GitHub账户中,您可以使用以下方式克隆存储库:

  1. git@github.com:do-community/ansible-ubuntu.git

你现在的工作目录下会有一个名为ansible-ubuntu的文件夹。进入它:

  1. cd ansible-ubuntu

在接下来的教程中,那将是你的工作目录。

在这一步骤中,您通过Ansible获取了自动化多个Ubuntu 22.04服务器的示例文件。为了准备与您的主机相关的信息文件,您接下来将更新vars/default.yml文件以使其适用于您的系统。

第四步 – 修改Ansible变量

这个手册将引用一些自动化所需的信息,这些信息可能需要随时间更新。将这些信息放在一个变量文件中,并在手册中调用这些变量,比在手册内部硬编码更高效。在此步骤中,您将修改变量文件vars/default.yml中的变量,以符合您的偏好和设置需求。

首先,使用nano或您喜欢的文本编辑器打开文件。

  1. nano vars/default.yml

你将会审核文件内容,其中包括以下变量:

默认.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。

Note

注意:如果你的控制节点正在运行一个Linux发行版,请选择一个大于1023的数字,并在/etc/services中使用grep命令进行查找。例如,运行grep 5995 /etc/services以检查5995是否被使用。如果没有输出,则说明该端口不存在于该文件中,你可以将其分配给变量。如果你的控制节点不是一个Linux发行版,而且你不知道在你的系统上如何找到其相当于的信息,你可以参考服务名称和传输协议端口号注册表。

copy_local_key变量引用了控制节点的SSH公钥文件。如果该文件的名称是id_rsa.pub,则在该行中不需要进行任何更改。否则,请将其更改为与您的控制节点的SSH公钥文件匹配。您可以在控制节点的~/.ssh目录下找到该文件。当您在第5步中运行主要的playbook并创建具有sudo权限的用户后,Ansible控制器将把公钥文件复制到用户的主目录中,这样您就可以在服务器进行初始化设置之后使用该用户通过SSH进行登录。

当你修改完文件后,请保存并关闭它。

现在你已经在vars/default.yml文件中为变量赋值,当执行第6步和第8步的playbook时,Ansible将能够调用这些变量。在下一步中,你将使用Ansible Vault来创建并保护每个主机上将要创建的用户的密码。

第五步-使用Ansible Vault创建加密密码文件。

使用 Ansible Vault 可以创建和加密文件和变量,这些文件和变量可以在 playbooks 中引用。使用 Ansible Vault 可以确保在执行 playbook 时敏感信息不以明文形式传输。在此步骤中,您将创建和加密一个包含变量的文件,这些变量的值将用于为每个主机的 sudo 用户创建密码。通过以这种方式使用 Ansible Vault,可以确保在初始服务器设置期间和之后的 playbook 中,密码不会以明文方式引用。

仍然在ansible-ubuntu目录中,使用以下命令创建并打开一个保险库文件:

  1. ansible-vault create secret

当提示时,输入并确认一个密码,用于加密秘密文件。这就是保险库密码。在执行第6步和第8步的playbook时,您将需要保险库密码,因此请记住它。

输入并确认保险库密码后,秘密文件将在与shell的EDITOR环境变量链接的文本编辑器中打开。将以下行添加到文件中,同时替换type_a_strong_password_here和type_a_salt_here的值:

~/ansible-ubuntu/secret 的中文原文如下:
  1. password: type_a_strong_password_here
  2. password_salt: type_a_salt_here

`password`变量的值将是在每个主机上创建的sudo用户的实际密码。`password_salt`变量使用盐作为其值。盐是任何长且随机的值,用于生成哈希密码。您可以使用字母或字母数字字符串,但仅使用数字字符串可能无效。在生成哈希密码时添加盐使得猜测密码或破解哈希更加困难。在执行步骤6和步骤8中的playbooks时,这两个变量将被调用。

Note

注意:在测试中,我们发现仅使用数字字符组成的盐在步骤6和步骤8中运行playbook时会出现问题。然而,仅使用字母字符组成的盐是有效的。同时,使用字母和数字的组合也是有效的。请记住这一点,在指定盐值时要考虑这些情况。

当您完成修改文件时,请保存并关闭它。

您现在已经创建了一个包含变量的加密密码文件,这些变量将用于为主机上的sudo用户创建密码。在下一步中,您将通过运行主Ansible playbook来自动完成在第2步中指定的服务器的初始设置。

第六步 — 在你的Ansible主机上运行主要的Playbook。

在这一步骤中,您将使用Ansible自动化初始服务器设置,数量与您在清单文件中指定的服务器相同。您将首先检查主要剧本中定义的任务。然后,您将对主机执行该剧本。

一个Ansible playbook由一个或多个play组成,每个play包含一个或多个任务。你将对你的Ansible主机运行的示例playbook包含了两个play,共有14个任务。

在运行剧本之前,您将审查其设置过程中涉及的每个任务。首先,使用nano或您喜欢的文本编辑器打开文件。

  1. nano initial.yml

剧目1: 演出

文件的第一部分包含以下关键字,这些关键字会影响剧本的行为。

initial.yml的初始设置。
- name: Initial server setup tasks
  hosts: initial
  remote_user: root
  vars_files:
    - vars/default.yml
    - secret
...

名称是戏剧的一个简短描述,它将在戏剧运行时显示在终端中。hosts关键字指示戏剧的目标主机。在这种情况下,传递给关键字的模式是你在第2步中在/etc/ansible/hosts文件中指定的主机组名称。你使用remote_user关键字告诉Ansible控制器要使用的用户名来登录主机(在这种情况下是root)。vars_files关键字指向包含戏剧在执行任务时将引用的变量的文件。

通过这个设置,Ansible 控制器会尝试通过 SSH 的 22 端口以 root 用户身份登录到主机。对于每个成功登录的主机,它会报告一个“ok”的响应。否则,它会报告服务器不可访问,并开始执行与成功登录的主机相关的 play 的任务。如果您要手动完成此设置,这个自动化过程会取代使用 ssh root@host-ip-address 登录到主机。

关键词部分之后是一个按顺序执行的任务列表。与戏剧一样,每个任务都以一个名称开始,该名称提供了任务将完成的简要描述。

任务1:更新缓存

在playbook中的第一个任务是更新软件包数据库。

初始.yml
...
- name: update cache
  ansible.builtin.apt:
    update_cache: yes
...

这个任务将使用ansible.builtin.apt模块更新软件包数据库,这也是为什么它被定义为update_cache: yes。该任务实现了与登录到Ubuntu服务器并输入sudo apt update相同的功能,通常是更新所有已安装的软件包之前的准备工作。

任务2:更新所有已安装的软件包

剧本中的第二个任务是更新软件包。

初始.yml
...
- name: Update all installed packages
  ansible.builtin.apt:
    name: "*"
    state: latest
...

就像第一个任务一样,这个任务也使用了ansible.builtin.apt模块。在这里,你通过使用通配符(名称:”*”)和状态为最新(state:latest)来确保所有已安装的软件包都是最新的,这相当于登录到你的服务器上并运行sudo apt upgrade -y命令。

任务3:确保NTP服务正在运行。

在playbook中的第三个任务是确保网络时间协议(NTP)守护进程处于活动状态。

初始的.yml文件
...
- name: Make sure NTP service is running
  ansible.builtin.systemd:
    state: started
    name: systemd-timesyncd
...

这个任务调用ansible.builtin.systemd模块来确保systemd-timesyncd,即NTP守护程序正在运行(状态:已启动)。当您希望确保您的服务器保持相同的时间时,可以运行这样的任务,这可以帮助在这些服务器上运行分布式应用程序。

任务4: 确保我们有一个sudo组

剧本中的第四个任务是验证是否存在sudo群组。

初始.yml
...
- name: Make sure we have a 'sudo' group
  ansible.builtin.group:
    name: sudo
    state: present
...

此任务调用ansible.builtin.group模块来检查主机上是否存在名为sudo的组(状态:出现)。因为您的下一个任务依赖主机上存在sudo组,所以此任务会检查sudo组的存在,以确保下一个任务不会失败。

任务5:创建一个具有sudo特权的用户

播放手册中的第五个任务是使用sudo特权创建您的非root用户。

最初.yml
...
- 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内置的user模块在每个主机上创建一个用户,并将sudo组附加到用户的组中。用户的名称是从vars/default.yml中指定的create_user变量的值派生而来。此任务还确保为用户创建一个主目录,并指定正确的shell。

使用密码参数和在步骤5中设置的密码和盐的组合,调用SHA-512密码哈希算法的函数为用户生成了一个哈希密码。与秘密保险库文件配对,密码永远不会以明文形式传递给控制器。通过使用update_password,可以确保只有在创建用户的第一次才设置哈希密码。如果重新运行playbook,密码将不会重新生成。

任务6: 为远程用户设置授权密钥

剧本中的第六个任务为您的用户设定了关键。

初始的.yml文件
...
- 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的值是在前一个任务中在主机上创建的用户名,而key指向要复制的密钥。这两个变量在var/default.yml文件中定义。这个任务的效果与手动运行ssh-copy-id命令相同。

任务7:禁止root用户的远程登录

在手册中的第七个任务是禁用根用户的远程登录。

初始.yml
...
- 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的值进行替换。此任务确保在运行此playbook后,无法通过root账户进行远程登录。只有通过任务6创建的用户账户才能成功远程登录。通过禁用远程root登录,您确保只有普通用户才能登录,并且需要特权升级方法(通常是sudo)才能获取管理员权限。

任务8:更改SSH端口

第八个任务在手册中改变了SSH端口。

最初的.yml文件
...
- 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守护程序的配置文件中搜索以正则表达式开头的行,并将其值更改为line参数的值。 SSH侦听的新端口号将是您在第4步中分配给ssh_port变量的端口号。在此播放结束时重新启动主机后,将无法通过端口22登录主机。

任务9: UFW – 允许SSH连接

在播放手册中的第九个任务允许SSH流量通过。

最初的.yml文件。
...
- name: UFW - Allow SSH connections
  community.general.ufw:
    rule: allow
    port: "{{ ssh_port }}"
...

在这里,您调用 community.general.ufw 模块来允许 SSH 流量通过防火墙。请注意,SSH 的端口号不是 22,而是您在第四步的 vars/default.yml 文件中指定的自定义端口号。此任务相当于手动运行 ufw allow 5995/tcp 命令。

任务10:SSH的蛮力攻击防护

第十个任务是防范暴力破解攻击。

initial.yml 的初始配置文件。
...
- name: Brute-force attempt protection for SSH
  community.general.ufw:
    rule: limit
    port: "{{ ssh_port }}"
    proto: tcp
...

再次调用community.general.ufw模块,此任务使用速率限制规则,在30秒的时间范围内拒绝连接到SSH端口的IP地址,如果连接尝试失败了六次或更多次。proto参数指向目标协议(在本例中为TCP)。

任务11:UFW – 禁止其他的传入流量并启用UFW。

第十一项任务使防火墙启用。

初始的.yml
...
- name: UFW - Deny other incoming traffic and enable UFW
  community.general.ufw:
    state: enabled
    policy: deny
    direction: incoming
...

在这个任务中,仍然依赖于community.general.ufw模块,你可以启用防火墙(状态:启用)并设置默认策略,拒绝所有传入的流量。

任务12:删除不再需要的依赖项。

在这个剧中的第十二个任务是清理软件包的依赖关系。

最初的.yml
...
- name: Remove dependencies that are no longer required
  ansible.builtin.apt:
    autoremove: yes
...

通过再次调用ansible.builtin.apt模块,此任务将删除服务器上不再需要的软件包依赖关系,这相当于手动运行sudo apt autoremove命令。

任务13:重新启动SSH守护程序

在这本操作手册中,第十三个任务是重新启动SSH。

首先.yml
...
- 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:在初始设置后重新启动主机

这个剧本在第一场剧中的最后一个任务成功完成后开始。它受以下关键字的影响:

除了初试阶段的checkbox默认值没有被修改外,其他配置文件均没有修改。
...
- 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关键字的模式是在第二步中在/etc/ansible/hosts文件中指定的初始组名。因为您将无法再使用默认的SSH端口22登录到主机,所以端口关键字指向在第四步配置的自定义SSH端口。

在第一次播放中,Ansible控制器以root用户身份登录到主机上。通过第一次播放禁用了远程root登录,现在您需要指定Ansible控制器应以哪个用户身份登录。remote_user关键字指示Ansible控制器以在第一次播放的第5个任务创建的sudo用户身份登录到每个主机上。

“使用become关键字指定在指定的主机上执行任务时使用特权升级。该关键字指示Ansible控制器在需要时以root特权执行主机上的任务。在这种情况下,控制器将使用sudo假设root特权。ansible_become_pass关键字设置特权升级密码,这是用于假设root特权的密码。在这种情况下,它指向使用Ansible Vault在第5步中配置的密码变量。”

除了指向 vars/default.yml 文件外,vars_files 关键字还指向您在第5步中配置的秘密文件,该文件告诉 Ansible 控制器在哪里找到密码变量。

在关键字部分之后,将执行这个剧本中唯一的任务。

任务14:重启所有主机。

Note

注意:虽然这是第二个剧本的第一个任务,但编号为任务14,因为Ansible控制器将其视为Play 2的Task 1,而不是playbook的Task 14。

播放手册的最后一个任务将重新启动所有主机。

初始.yml
...
- name: Reboot all hosts
  ansible.builtin.reboot:

在完成首次操作后重启主机,即可使内核或库的任何更新在您开始安装应用程序之前生效。

完整的剧本文件如下所示:

初始.yml
- 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:

当完成对文件的审核后,保存并关闭它。

Note

注意:您可以向playbook中添加新任务或修改现有任务。但是,更改YAML文件可能会损坏它,因为YAML对缩进非常敏感,所以如果选择编辑文件的任何方面,请小心操作。有关如何编写Ansible playbooks的更多信息,请关注我们的系列文章《如何编写Ansible Playbooks》。

现在您可以运行这个剧本。首先,检查语法。

  1. ansible-playbook --syntax-check --ask-vault-pass initial.yml

在第5步中,您将被提示输入您创建的保险库密码。如果验证成功后没有YAML语法错误,输出结果将是:

Output
playbook: initial.yml

您可以使用以下命令运行该文件:

  1. ansible-playbook --ask-vault-pass initial.yml

你将再次被要求输入保险库密码。在成功验证后,Ansible控制器将以root用户身份登录每个主机,并执行playbook中的所有任务。与其在每个服务器上分别运行ssh root@node-ip-address命令,Ansible会连接到/etc/ansible/hosts中指定的所有节点,然后执行playbook中的任务。

在本教程的示例主机上,Ansible大约需要三分钟来完成跨三个主机的任务。任务完成后,您将收到以下输出:

Output
PLAY 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”列中。

无法访问的计数显示了Ansible控制器无法登录的主机数量。没有任何任务失败,因此失败的数量为0。

当任务中指定的条件不满足时(通常使用when参数),任务将被跳过。在这种情况下没有任务会被跳过,但是在第八步中将会生效。

最后两列(已救援和已忽略)与针对剧本或任务的错误处理相关。

您现在已成功使用Ansible自动化了多个Ubuntu 22.04服务器的初始服务器设置,只需执行一条命令即可完成playbook中指定的所有任务。

为了确保一切如预期般工作顺利,接下来您将登录其中一个主机来验证设置。

(可选)步骤7 — 手动检查服务器设置

为了确认前一步骤结束时的播放总结输出,您可以使用之前配置的凭据登录到其中一台主机,以验证设置。出于学习目的,这些操作是可选的,因为Ansible总结报告了准确的完成情况。

首先,使用以下命令登录到其中一个主机:

  1. ssh -p 5995 sammy@host1-public-ip-address

在步骤4中,您使用“ -p”选项指向您为SSH配置的自定义端口号(5995),而在步骤6中创建的用户是sammy。如果您能够通过该端口登录到主机并使用该用户,就说明Ansible成功完成了这些任务。

登录后,检查您是否能够更新软件包数据库。

  1. sudo apt update

如果提示输入密码,并且你可以用在第5步中设置的密码进行验证,那么你可以确认Ansible成功完成了创建用户和设置用户密码的任务。

既然你知道安装手册的操作成功了,现在你可以运行第二本手册进行持续性维护。

(可选)第八步 – 使用Ansible进行主机的持续维护

在第6步中执行的初始服务器设置播放书将根据您的需求扩展到任意数量的服务器,但在初始设置之后无法管理主机。虽然您可以分别登录每台主机来运行命令,但随着您同时处理更多服务器,此过程无法扩展。作为第3步的一部分,您还提取了一个ongoing.yml播放书,可用于持续维护。在本步骤中,您将运行ongoing.yml播放书,以自动化本教程中设置的主机的持续维护。

在运行playbook之前,您将会查看每个任务。首先,使用nano或您喜欢的文本编辑器打开文件。

  1. nano ongoing.yml

与最初的设置手册不同,维护手册只包含一个操作步骤和较少的任务。

剧本1: 播放游戏

文件第一部分中的以下关键词会影响戏剧的行为方式:

持续的.yml
- hosts: ongoing
  port: "{{ ssh_port }}"
  remote_user: "{{ create_user }}"
  become: true
  vars_files:
    - vars/default.yml
    - secret
  vars:
    ansible_become_pass: "{{ password }}"
...

除了传递给”hosts”关键字的组外,这些关键字与设置playbook的第二个任务中使用的关键字是相同的。

关键词后面是按顺序执行的任务列表。和设置剧本一样,维护剧本中的每个任务都以名称开始,描述了该任务将要完成的简要概述。

任务1:更新缓存

第一个任务是更新包数据库。

进行中的.yml
...
- name: update cache
  ansible.builtin.apt:
    update_cache: yes
...

此任务将使用ansible.builtin.apt模块更新软件包数据库,这就是为什么它定义为update_cache: yes。这个任务实现的效果与登录到Ubuntu服务器并输入sudo apt update相同,这通常是安装软件包或更新所有已安装软件包的前奏。

任务2:更新所有已安装的软件包。

第二个任务是更新软件包。

正在进行中.yml (zhèngzài jìnxíng zhōng.yml)
...
- name: Update all installed packages
  ansible.builtin.apt:
    name: "*"
    state: latest
...

与第一个任务类似,这个任务也使用ansible.builtin.apt模块。在这里,你可以利用通配符(name:”*”)和state: latest来确保所有安装的软件包都是最新的,这相当于登录服务器并运行sudo apt upgrade -y命令。

任务3:确保NTP服务正在运行。

剧本中的第三项任务是确保 NTP Daemon 已设置好。

进行中的.yml
...
- name: Make sure NTP service is running
  ansible.builtin.systemd:
    state: started
    name: systemd-timesyncd
...

为了确保服务器上的活动服务保持活动状态,可能会有各种原因导致其失败,因此你希望确保这些服务保持活动状态。为了实现这一目标,使用ansible.builtin.systemd模块来确保systemd-timesyncd(NTP守护程序)保持活动状态(状态:已启动)。

任务4:UFW – 它是否正在运行?

第四个任务是检查UFW防火墙的状态。

进行中的.yml
...
- 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模块运行相同的命令,然后将输出保存(注册)到ufw_status变量中。下一个任务将查询该变量的值。

任务5:UFW-启用UFW并拒绝传入流量。

如果UFW防火墙已经停止,则第五个任务将重新启用它。

持续中.yml
...
- name: UFW - Enable UFW and deny incoming traffic
  community.general.ufw:
    state: enabled
  when: "'inactive' in ufw_status.stdout"
...

当ufw_status变量的输出中出现“inactive”一词时,该任务调用community.general.ufw模块仅启用防火墙。如果防火墙处于活动状态,则不满足when条件,任务被标记为跳过。

任务6:移除不再需要的依赖项

这本手册的第六个任务是清理软件包依赖关系。

持续中.yml
...
- name: Remove dependencies that are no longer required
  ansible.builtin.apt:
    autoremove: yes
...

通过调用ansible.builtin.apt模块来执行该任务,该模块相当于运行sudo apt autoremove命令,它会移除服务器上不再需要的软件包依赖关系。

任务7:检查是否需要重新启动

第七项任务检查是否需要重新启动。

目前正在进行的.yml
...
- 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模块执行相同操作,并将输出保存(注册)到reboot_required变量。下一个任务将查询该变量的值。

任务8:如果需要的话重新启动

第八项任务将在必要时重新启动服务器。

进行中的.yml
...
- name: Reboot if required
  ansible.builtin.reboot:
  when: reboot_required.stat.exists == true

通过查询任务 7 中的 reboot_required 变量,该任务调用 ansible.builtin.reboot 模块,仅当 /var/run/reboot-required 存在时才会重新启动主机。如果需要重新启动并成功重新启动主机,则将该任务标记为已更改。否则,Ansible 在播放摘要中将其标记为跳过。

正在进行的维护工作的完整操作手册文件将如下所示:

首先.yml
- 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

当审核完文件后,请保存并关闭它。

Note

注意:您可以向playbook添加新任务或修改现有任务。然而,改变YAML文件可能会破坏它,因为YAML对空格敏感,所以如果选择编辑文件的任何部分,请小心处理。有关使用Ansible playbook的更多信息,请关注我们的系列文章《如何编写Ansible Playbook》。

现在你可以运行这个文件了。首先,检查语法是否正确。

  1. ansible-playbook --syntax-check --ask-vault-pass ongoing.yml

在第5步中,系统会提示您输入您创建的保险库密码。如果成功验证后,YAML语法没有错误,输出将为:

Output
playbook: ongoing.yml

你现在可以用以下命令运行这个文件了。

  1. ansible-playbook --ask-vault-pass ongoing.yml

在成功验证后,Ansible控制器将以sammy(或您指定的用户名)的身份登录到每个主机上来执行playbook中的任务。与在每个服务器上逐个运行ssh -p 5995 sammy@host_ip_address命令不同,Ansible会连接到/etc/ansible/hosts中指定的持续组中的节点,然后执行playbook中的任务。

如果命令成功完成,下面的输出将会打印出来。

Output
PLAY 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参数未满足。

您可以使用此播放册来维护主机,而无需手动登录每台主机。当您在主机上构建和安装应用程序时,可以向播放册添加任务,以便您还可以使用Ansible管理这些应用程序。

结论

在本教程中,您使用Ansible来自动化多个Ubuntu 22.04服务器的初始设置。同时,您还运行了一个辅助playbook来进行这些服务器的持续维护。当您需要在分布式或集群模式下设置像Cassandra或MinIO这样的应用程序时,Ansible自动化是一个节省时间的工具。

有关Ansible的更多信息可在官方Ansible文档站点上找到。为了进一步定制您的playbook,您可以查阅一份配置管理入门以及一份配置管理101:编写Ansible Playbooks的指南。