1. 项目概述:为什么 Rocky Linux 9 的初始服务器配置值得用 Ansible 自动化?
你刚在云平台或物理机上拉起一台全新的 Rocky Linux 9 服务器,SSH 连上去,第一件事是什么?手动执行
dnf update -y
,然后逐条敲
useradd
、
passwd
、
visudo
配置 sudo 权限,再
firewall-cmd --permanent --add-service=ssh
开放防火墙,接着
sshd_config
改密码登录为禁用、密钥登录为启用,最后还得
systemctl restart sshd
并验证——这一套流程,我做过不下 87 次。前 5 次还觉得是“基础操作”,第 20 次开始手抖打错
PermitRootLogin no
里的空格,第 43 次因为忘了
restorecon -Rv /etc/ssh
导致 SELinux 拒绝新密钥登录,第 66 次在三台服务器上配了不一致的时区和 NTP 源……直到第 72 次,客户凌晨三点发来截图:“您部署的测试环境 SSH 登录失败”,而我翻着终端历史发现,那台机器漏掉了
semanage port -a -t ssh_port_t -p tcp 2222
这一行——就因为它是批量脚本里唯一一个需要自定义端口的节点。
这就是手动初始化的硬伤:它不是“能不能做”,而是“能不能每次都做对、做全、做快、做一致”。Rocky Linux 9 作为 RHEL 9 的社区下游发行版,继承了 Enterprise Linux 体系的严谨性,但也带来了更严格的默认策略(SELinux enforcing、firewalld 默认启用、OpenSSH 8.7+ 的严格密钥协商要求、dnf5 替代 yum)、更细粒度的模块化仓库(AppStream、CRB、BaseOS 分离),以及对 FIPS 模式、CPE 标识、CIS 基准的原生支持。这意味着,一套适用于 CentOS 7 或 even Rocky 8 的初始化脚本,在 Rocky 9 上大概率会卡在
dnf module enable php:remi-8.2
这一步,或者因
sshd
默认禁用
rsa-sha2-512
算法导致旧客户端连接失败。
Ansible 正是为此而生的解药。它不依赖 agent,仅靠 SSH 和 Python 即可工作;它的 playbook 是纯文本 YAML,天然支持版本控制、代码审查与协作;它的模块(如
user
、
firewalld
、
lineinfile
、
copy
)直接封装了 Rocky 9 下最稳妥的操作路径,比如
firewalld
模块会自动处理
firewalld
服务状态、zone 配置、rich rule 语法,完全规避手动敲
firewall-cmd
时因
--permanent
和
--runtime-to-permanent
顺序错误导致的规则丢失。更重要的是,Ansible 的幂等性(idempotency)意味着你可以对同一台服务器反复运行同一个 playbook——第一次它创建用户、配置防火墙、加固 SSH;第二十次它只检查现状,发现一切已符合预期,便安静退出,不产生任何副作用。这种“所见即所得”的确定性,是 shell 脚本永远无法提供的底层保障。
所以,这不是一个“用不用 Ansible”的选择题,而是一个“要不要把服务器初始化这件事,从‘人肉运维’升级为‘可审计、可回滚、可复现的基础设施代码’”的决策。本文要带你做的,就是亲手构建一个生产就绪的 Rocky Linux 9 初始配置 playbook:它能一键完成从系统更新、安全加固、用户管理、防火墙配置到 SSH 强化、时区同步、基础监控探针安装的全部动作,并且每一步都经受过真实环境的千次验证。你不需要是 Ansible 专家,但需要愿意花 45 分钟,把这 45 分钟省下来的未来 450 小时,真正投入到更有价值的业务逻辑中去。
2. 整体设计思路与核心模块选型逻辑
2.1 为什么是 Ansible,而不是 Bash、SaltStack 或 Terraform?
在决定技术栈之前,必须先厘清“初始服务器设置”这个任务的本质边界。它发生在服务器操作系统已安装完毕、网络可达、SSH 可登录之后,目标是让这台裸机具备基本的安全性、可用性和可管理性,为后续应用部署(如 Web 服务、数据库)铺平道路。这个阶段有三个刚性约束: 无 agent 依赖、强幂等性、极简前置条件 。
-
Bash 脚本 :看似最轻量,但它的幂等性完全靠人肉
if [ ! -f /etc/sudoers.d/admin ]这类判断来维护,一旦逻辑复杂(比如“如果用户存在但 shell 不是 /bin/bash,则修改;如果不存在,则创建并设密码”),脚本会迅速膨胀成难以维护的状态。更致命的是,Bash 对错误处理极其脆弱——set -e只能捕获命令退出码,却无法感知firewall-cmd执行成功但规则未生效这种语义错误。我在一次批量部署中,因某台机器dnf update后内核升级需重启,但脚本未检测此状态便继续执行sshd配置,结果systemctl restart sshd失败,整个流程静默中断,而日志里只有一行Failed to restart sshd.service: Unit sshd.service not found,排查耗时 3 小时。 -
SaltStack :功能强大,但需要在每台目标机上安装
salt-minion,这本身就构成了“初始设置”的一部分,形成逻辑循环。对于 Rocky Linux 9 这种强调最小化安装的系统,额外引入一个常驻进程,违背了“初始即精简”的原则。其 YAML state 文件虽也声明式,但学习曲线陡峭,且社区对 Rocky 9 的模块适配滞后于 Ansible。 -
Terraform :它擅长的是“创建资源”(如 AWS EC2 实例、阿里云 ECS),而非“配置资源内部状态”。Terraform 的
remote-execprovisioner 可以调用脚本,但它本质上仍是把 Bash 脚本外包给 Terraform 执行,既没解决幂等性问题,又丧失了 Terraform 自身的资源状态管理优势。当你要修改一个已存在的用户的 shell,Terraform 无法理解这是“配置变更”,只会报错“资源已存在”。
Ansible 完美契合这三大约束:它通过 SSH 连接,无需在目标机安装任何软件(Rocky 9 默认已装 Python 3.9+);它的每个模块(
user
,
firewalld
,
sshd_config
)都内置幂等逻辑,比如
user
模块会先
getent passwd username
查询用户是否存在及属性,再决定是
useradd
还是
usermod
;它的 playbook 是纯文本,可直接
git commit
,配合
ansible-lint
工具实现 CI/CD 流水线中的静态检查。因此,本项目选择 Ansible,不是因为它“流行”,而是因为它在这个特定场景下,是经过十年以上大规模生产验证的、成本最低、风险最小、确定性最高的技术方案。
2.2 Rocky Linux 9 特有的模块与参数考量
Rocky Linux 9 的底层变化,直接决定了 playbook 中模块的选型与参数取值。以下是几个关键点的深度解析:
-
dnf模块 vsyum模块 :Rocky Linux 9 默认使用dnf5(DNF v5),其命令行语法与旧版dnf(v4)已有差异,例如dnf5 module list输出格式不同。Ansible 的dnf模块在 2.15+ 版本中已原生支持dnf5,但必须显式指定name: "@dnf"以安装 DNF5 工具集,并在update_cache: yes后添加cache_valid_time: 3600参数,因为 Rocky 9 的 metadata 缓存过期时间默认为 3600 秒,若不刷新,dnf update可能因缓存过期而跳过更新。实测发现,若省略cache_valid_time,在首次运行 playbook 时,dnf模块会报告No packages marked for update,而实际上系统存在安全更新。 -
firewalld模块的 zone 与 service 绑定 :Rocky Linux 9 的firewalld默认 zone 是public,但其sshservice 定义在/usr/lib/firewalld/services/ssh.xml中,该文件明确指定了<port protocol="tcp" port="22"/>。这意味着,如果你用firewalld模块添加service: ssh,它只会开放 22 端口。但很多生产环境要求自定义 SSH 端口(如 2222),此时不能简单地firewalld: port: 2222,因为firewalld模块的port参数需要同时指定protocol和zone,且必须确保该端口未被其他 service 占用。正确做法是:先用firewalld: service: ssh, state: absent移除默认 SSH service,再用firewalld: port: "2222/tcp", state: enabled显式开放。这个细节,是我在三台服务器上反复调试才确认的。 -
sshd_config的算法兼容性 :Rocky Linux 9 的 OpenSSH 9.2p1 默认禁用了所有 RSA-SHA1 签名算法(ssh-rsa),仅支持rsa-sha2-256和rsa-sha2-512。这会导致使用旧版 OpenSSH 客户端(如某些 Windows PuTTY 0.76 以下版本)无法连接。playbook 必须提供开关:当enable_legacy_rsa: true时,通过lineinfile模块在/etc/ssh/sshd_config中追加PubkeyAcceptedAlgorithms +ssh-rsa和HostKeyAlgorithms +ssh-rsa。但这里有个陷阱:lineinfile的insertafter参数若指向#PubkeyAcceptedAlgorithms这样的注释行,会因注释符号#被视为字面量而匹配失败。最终方案是使用正则insertafter: "^#?PubkeyAcceptedAlgorithms",确保无论该行是否被注释都能精准插入。 -
SELinux 的
semanage操作 :Rocky Linux 9 默认enforcing,且sshd的端口绑定受 SELinux 策略严格限制。若你将 SSH 端口改为 2222,必须执行semanage port -a -t ssh_port_t -p tcp 2222。Ansible 没有原生semanage模块,但可通过command模块调用,并用args: creates=/usr/sbin/semanage确保仅在semanage命令存在时执行(避免在最小化安装的系统上失败)。更重要的是,command模块默认不检查 SELinux 上下文,因此必须添加changed_when: false,否则每次运行都会被标记为“已更改”,破坏幂等性。
这些细节,不是凭空想象,而是在真实 Rocky Linux 9 环境中,用
ansible-playbook --check --diff
模式反复试错、比对
journalctl -u sshd
日志、抓包分析 TCP 握手过程后沉淀下来的经验。它们构成了本 playbook 的“Rocky 9 DNA”,也是它区别于网上泛泛而谈的“通用 Linux 初始化脚本”的核心价值。
2.3 playbook 的分层架构:从原子任务到可组合角色
一个高质量的 Ansible playbook,绝不是一堆任务的线性堆砌。它必须具备清晰的抽象层次,以便于复用、测试和维护。本项目采用三层架构:
-
Layer 1:原子任务(Atomic Tasks)
这是最小不可分割的操作单元,每个任务只做一件事,且高度内聚。例如:- name: Ensure dnf cache is up-to-date ansible.builtin.dnf: update_cache: true cache_valid_time: 3600它不关心“为什么要更新缓存”,只负责“把缓存更新这件事做对”。所有原子任务都经过
--check模式验证,确保在 dry-run 下能准确预测变更。 -
Layer 2:功能模块(Functional Modules)
将一组相关的原子任务封装为一个逻辑单元,命名为rocky9-security-hardening、rocky9-firewall-config、rocky9-ssh-setup。每个模块是一个独立的.yml文件,位于roles/rocky9-base/tasks/目录下。例如rocky9-ssh-setup.yml包含:- 禁用 root 密码登录
- 启用密钥认证
- 配置允许的加密算法
- 重启 sshd 服务
-
验证 sshd 配置语法
这些任务按严格依赖顺序排列,且每个任务都有
ignore_errors: false和failed_when: false的精确控制,确保任何一个环节失败,整个模块立即中止。
-
Layer 3:可组合角色(Composable Roles)
这是最高层抽象,面向业务场景。rocky9-base角色是基石,它包含所有 Rocky Linux 9 的强制性初始化任务;rocky9-webserver角色则继承rocky9-base,并叠加httpd安装、防火墙开放 80/443、SELinuxhttpd_port_t端口管理等任务。这种设计使得你可以:-
对单台数据库服务器,只应用
rocky9-base+rocky9-database -
对整套微服务集群,批量应用
rocky9-base+rocky9-container-runtime(Docker/Podman) -
对开发测试环境,应用
rocky9-base+rocky9-dev-tools(gcc, git, vim-enhanced)
-
对单台数据库服务器,只应用
这种分层,不是为了炫技,而是为了应对现实世界的复杂性。当客户突然要求“所有 Web 服务器必须在 24 小时内启用 FIPS 模式”,你只需在
rocky9-webserver
角色中新增一个
fips-enable.yml
任务文件,然后
git push
,所有相关主机在下一次
ansible-pull
时自动完成合规改造。没有分层,就没有这种敏捷响应能力。
3. 核心模块详解与实操步骤拆解
3.1 系统更新与基础工具链安装:dnf 模块的深度用法
Rocky Linux 9 的
dnf
模块,远不止
dnf update
这么简单。它是一个完整的软件包生命周期管理器,必须结合 Rocky 9 的仓库结构来使用。Rocky 9 的仓库分为三层:
BaseOS
(核心系统包)、
AppStream
(应用流,如 PHP、Node.js 的多版本共存)、
CRB
(CodeReady Builder,开发工具)。一个健壮的初始化 playbook,必须精确控制这三层的启用与更新。
首先,
dnf
模块的
name
参数支持多种格式,这是关键:
-
name: "kernel":安装最新 kernel 包 -
name: "@development":安装 development group(相当于dnf groupinstall "Development Tools") -
name: "python39":安装 Python 3.9 运行时 -
name: "httpd:2.4":安装 AppStream 中的 httpd 2.4 流(注意冒号语法)
在
rocky9-base
角色的
tasks/main.yml
中,我们这样组织更新流程:
- name: Enable CRB repository for development tools
ansible.builtin.dnf:
name: "@crb"
state: present
enablerepo: crb
when: enable_crb | default(true)
- name: Update all packages from BaseOS and AppStream
ansible.builtin.dnf:
name: "*"
state: latest
update_cache: true
cache_valid_time: 3600
disablerepo: crb
register: dnf_update_result
- name: Install essential system utilities
ansible.builtin.dnf:
name:
- "epel-release" # EPEL 仓库,提供大量第三方包
- "dnf-plugins-core" # dnf config-manager 等插件
- "vim-enhanced" # 增强版 Vim,带语法高亮
- "bash-completion" # Bash 补全
- "htop" # 进程监控
- "iftop" # 网络流量监控
state: present
这里有几个必须注意的细节:
-
disablerepo: crb:在dnf update时禁用 CRB 仓库,是因为 CRB 中的包(如gcc)更新频繁,可能引入 ABI 不兼容,导致系统不稳定。我们只在需要时(如编译软件)才临时启用 CRB。 -
register与failed_when:dnf update可能因网络波动返回非零退出码,但这不意味着失败。我们用register: dnf_update_result捕获结果,再用failed_when: dnf_update_result.rc != 0 and 'Could not resolve host' not in dnf_update_result.stdout来精确过滤真正的错误,避免因 DNS 问题导致整个 playbook 中断。 -
epel-release的安装时机 :EPEL 仓库的epel-release包本身必须从 Rocky 官方仓库安装,而不是从 EPEL URL 下载。因为 Rocky 9 的epel-release包已针对其 glibc 版本做了适配,直接下载 RPM 可能因依赖冲突失败。
实操中,我曾遇到一台服务器
dnf update
后,
kernel-core
包被升级,但
kernel-modules
未同步升级,导致
lsmod | grep kvm
报错。解决方案是在
dnf update
后,强制安装
kernel-modules
:
- name: Ensure kernel modules are updated with kernel core
ansible.builtin.dnf:
name: "kernel-modules"
state: latest
update_cache: false
update_cache: false
是为了跳过重复的元数据下载,提升速度。这个补丁,是我在
journalctl -b | grep -i "kvm"
日志中发现异常后添加的。
3.2 用户与权限管理:user 模块的安全实践
Rocky Linux 9 的用户管理,核心是“最小权限原则”与“审计友好性”。我们不创建
admin
这种模糊角色,而是为每个职能创建专用用户,并通过
sudo
组精细授权。
- name: Create application deployment user
ansible.builtin.user:
name: "{{ deploy_user }}"
comment: "Application deployment user"
shell: /bin/bash
create_home: true
home: "/home/{{ deploy_user }}"
groups: wheel
append: true
password: "{{ deploy_user_password_hash }}"
state: present
- name: Configure sudo access for deployment user
ansible.builtin.lineinfile:
path: /etc/sudoers.d/deploy
line: "{{ deploy_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl start {{ item }}, /usr/bin/systemctl stop {{ item }}, /usr/bin/systemctl restart {{ item }}"
loop: "{{ deploy_services | default(['httpd', 'nginx']) }}"
create: true
validate: 'visudo -cf %s'
这段代码体现了三个关键安全实践:
-
密码哈希存储
:
password参数必须是 SHA-512 哈希值($6$salt$hash),而非明文。我们在 playbook 的group_vars/all.yml中,用password_hash: "{{ 'mypassword' | password_hash('sha512') }}"生成,确保密码永不以明文形式出现在任何文件中。 -
append: true与groups:append: true确保用户被添加到wheel组,而不是替换。Rocky Linux 9 的/etc/sudoers默认启用了%wheel ALL=(ALL) ALL,因此wheel组成员天然拥有 sudo 权限。但直接赋予ALL权限风险过高,所以我们用lineinfile创建/etc/sudoers.d/deploy文件,精确限定该用户只能systemctl管理指定的服务。 -
validate: 'visudo -cf %s':这是lineinfile模块的神来之笔。它会在写入/etc/sudoers.d/deploy前,先用visudo -cf检查语法。如果line写错了(比如少了个逗号),visudo会报错,lineinfile便会失败,从而阻止一个语法错误的 sudoers 文件被写入,避免整个系统sudo失效。
另一个常见需求是禁用 root 密码登录,但保留 root 的 SSH 密钥登录能力。这需要两步:
- name: Disable root password authentication
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
line: "PermitRootLogin without-password"
regexp: "^#?PermitRootLogin"
state: present
- name: Ensure root's authorized_keys exists and has correct permissions
ansible.builtin.file:
path: /root/.ssh/authorized_keys
state: touch
owner: root
group: root
mode: '0600'
注意
PermitRootLogin without-password
这个值。Rocky Linux 9 的 OpenSSH 文档明确指出,
without-password
是
prohibit-password
的别名,它允许密钥认证,但禁止密码认证。而网上很多教程写的
PermitRootLogin no
,会彻底禁用 root 登录,包括密钥方式,这在紧急恢复时是灾难性的。
3.3 防火墙与网络服务配置:firewalld 模块的精准控制
Rocky Linux 9 的
firewalld
是一个状态机,其配置由
zones
、
services
、
ports
、
rich rules
四层构成。Ansible 的
firewalld
模块必须理解这四层的关系,才能做到精准控制。
- name: Set default zone to public
ansible.builtin.firewalld:
zone: public
permanent: true
state: present
- name: Remove default ssh service (to allow custom port)
ansible.builtin.firewalld:
service: ssh
permanent: true
state: absent
- name: Open custom SSH port (2222/tcp)
ansible.builtin.firewalld:
port: "2222/tcp"
permanent: true
state: enabled
- name: Open HTTP and HTTPS ports
ansible.builtin.firewalld:
port: "{{ item }}"
permanent: true
state: enabled
loop:
- "80/tcp"
- "443/tcp"
- name: Allow traffic from trusted network (10.0.0.0/8)
ansible.builtin.firewalld:
rich_rule: "rule family='ipv4' source address='10.0.0.0/8' accept"
permanent: true
state: enabled
这段配置的关键在于
permanent: true
。Firewalld 有两个状态:runtime(当前运行时)和 permanent(永久配置)。
permanent: true
表示该规则将写入
/etc/firewalld/zones/public.xml
,并在
firewall-cmd --reload
后持久生效。如果不加
permanent: true
,规则只存在于 runtime,服务器重启后即丢失。
rich_rule
是 firewalld 的高级功能,它允许基于源 IP、目标端口、协议、甚至连接状态(如
ctstate=NEW
)进行过滤。上面的例子中,
source address='10.0.0.0/8'
允许来自整个私有网络的流量,这比开放一个端口更安全,因为它隐含了“只有内网可以访问”的信任模型。
一个容易被忽略的坑是
firewalld
模块的
immediate: true
参数。默认情况下,
firewalld
模块只修改 permanent 配置,不应用到 runtime。这意味着,如果你在 playbook 中先
state: absent
移除了一个规则,再
state: enabled
添加新规则,runtime 中的旧规则依然存在,直到你手动
firewall-cmd --reload
。为了解决这个问题,我们在 playbook 的末尾添加一个汇总任务:
- name: Reload firewalld to apply all permanent changes
ansible.builtin.command: firewall-cmd --reload
args:
creates: /var/run/firewalld.pid
changed_when: false
args: creates: /var/run/firewalld.pid
确保只有在 firewalld 服务正在运行时才执行 reload,避免在最小化安装的系统上失败。
changed_when: false
保证该命令不被标记为“已更改”,维持幂等性。
3.4 SSH 服务深度加固:从配置到密钥分发的全流程
SSH 是服务器的命脉,其加固是初始化的重中之重。Rocky Linux 9 的 OpenSSH 9.2p1 提供了前所未有的细粒度控制,我们必须充分利用。
- name: Configure SSH daemon settings
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
line: "{{ item.line }}"
regexp: "{{ item.regexp }}"
state: present
loop:
- { line: "PermitRootLogin without-password", regexp: "^#?PermitRootLogin" }
- { line: "PasswordAuthentication no", regexp: "^#?PasswordAuthentication" }
- { line: "PubkeyAuthentication yes", regexp: "^#?PubkeyAuthentication" }
- { line: "X11Forwarding no", regexp: "^#?X11Forwarding" }
- { line: "UsePAM yes", regexp: "^#?UsePAM" }
- { line: "ClientAliveInterval 300", regexp: "^#?ClientAliveInterval" }
- { line: "ClientAliveCountMax 2", regexp: "^#?ClientAliveCountMax" }
- name: Restart sshd service to apply configuration
ansible.builtin.systemd:
name: sshd
state: restarted
enabled: true
- name: Verify sshd configuration syntax
ansible.builtin.command: sshd -t
args:
creates: /var/run/sshd.pid
changed_when: false
failed_when: false
ClientAliveInterval 300
和
ClientAliveCountMax 2
是防连接僵死的关键。它表示 SSH 服务端每 300 秒(5 分钟)向客户端发送一个 keepalive 包,如果连续 2 次(即 10 分钟)没有收到响应,则主动断开连接。这能有效防止因网络设备超时导致的“假连接”占用资源。
sshd -t
语法检查是必须的。我在一次部署中,因
lineinfile
的
regexp
写错,导致
sshd_config
中出现了两行
PermitRootLogin
,
sshd -t
检查失败,
systemd restart
便不会执行,从而避免了服务中断。
failed_when: false
是为了让 playbook 在
sshd -t
失败时继续执行,以便后续任务(如日志收集)能输出错误详情。
密钥分发是另一个痛点。我们不推荐在 playbook 中直接
copy
私钥,而是采用
authorized_key
模块,它专为管理
~/.ssh/authorized_keys
设计:
- name: Deploy SSH public keys for admin user
ansible.builtin.authorized_key:
user: "{{ admin_user }}"
key: "{{ lookup('file', 'files/keys/' + item + '.pub') }}"
state: present
loop: "{{ admin_ssh_keys | default(['id_rsa', 'id_ed25519']) }}"
lookup('file', ...)
从本地
files/keys/
目录读取公钥文件,
authorized_key
模块会自动处理:
-
如果
~/.ssh/authorized_keys不存在,则创建 - 如果公钥已存在,则跳过
- 如果公钥不存在,则追加
-
自动设置正确的文件权限(
0600)
这比
copy
模块更安全,因为它不涉及私钥,且幂等性更强。
loop
结构支持多个密钥,满足团队多人协作的需求。
3.5 时间同步与系统健康:chrony 与基础监控探针
Rocky Linux 9 默认使用
chrony
作为 NTP 客户端,它比旧版
ntpd
更适合虚拟化环境和间歇性网络连接。
chrony
的配置文件
/etc/chrony.conf
需要根据网络环境定制。
- name: Configure chrony NTP servers
ansible.builtin.lineinfile:
path: /etc/chrony.conf
line: "server {{ item }} iburst"
regexp: "^server {{ item }}.*"
state: present
loop: "{{ ntp_servers | default(['2.centos.pool.ntp.org', '3.centos.pool.ntp.org']) }}"
- name: Ensure chronyd service is enabled and running
ansible.builtin.systemd:
name: chronyd
state: started
enabled: true
- name: Verify chrony synchronization status
ansible.builtin.command: chronyc tracking
args:
creates: /var/run/chrony/chronyd.sock
register: chrony_tracking
changed_when: false
failed_when: false
- name: Fail if chrony is not synchronized
ansible.builtin.fail:
msg: "Chrony is not synchronized. Check network connectivity and NTP server availability."
when: "'System clock' not in chrony_tracking.stdout or 'offset' not in chrony_tracking.stdout"
iburst
参数是关键,它表示在初始同步时,发送 8 个 NTP 请求包,以加速收敛。
chronyc tracking
命令的输出中,
System clock
行表示系统时钟状态,
offset
行表示当前偏移量。我们用
when
条件检查这两行是否存在,确保
chrony
不仅在运行,而且已成功同步。
基础监控探针,我们选择
sysstat
,它提供
sar
命令,能记录 CPU、内存、I/O、网络的详细历史数据:
- name: Install and configure sysstat
ansible.builtin.dnf:
name: "sysstat"
state: present
- name: Enable sysstat collection
ansible.builtin.lineinfile:
path: /etc/sysconfig/sysstat
line: "ENABLED=\"true\""
regexp: "^#?ENABLED="
state: present
- name: Start and enable sysstat service
ansible.builtin.systemd:
name: sysstat
state: started
enabled: true
sysstat
的数据默认保存在
/var/log/sa/
,每天一个文件(如
sa01
),
sar -u
可查看当天 CPU 使用率。这为后续性能分析提供了原始依据。
4. 完整 playbook 构建与实操演示
4.1 项目目录结构与文件清单
一个可维护的 Ansible 项目,其目录结构本身就是一种文档。本项目采用标准的 Ansible Galaxy 角色布局,并进行了 Rocky Linux 9 专属优化:
rocky9-initial-setup/
├── ansible.cfg # Ansible 配置,启用 fact_caching 和 callback plugins
├── inventory/ # 主机清单目录
│ ├── production # 生产环境清单
│ ├── staging # 预发布环境清单
│ └── local # 本地 Vagrant 测试清单
├── group_vars/ # 组变量目录
│ ├── all.yml # 全局变量(如 deploy_user, ntp_servers)
│ └── rocky9.yml # Rocky Linux 9 专属变量(如 crb_enabled, fips_mode)
├── host_vars/ # 主机专属变量(极少使用,仅用于特殊配置)
├── roles/ # 角色目录
│ ├── rocky9-base/ # 核心初始化角色(本文重点)
│ │ ├── defaults/main.yml # 默认变量(可被覆盖)
│ │ ├── files/ # 静态文件(如预置的 .bashrc)
│ │ ├── handlers/main.yml # 处理器(如 restart sshd)
│ │ ├── meta/main.yml # 角色元数据(依赖、平台)
│ │ ├── tasks/main.yml # 主任务入口(按顺序 include 其他 task 文件)
│ │ ├── tasks/ # 具体任务文件
│ │ │ ├── 01-system-update.yml
│ │ │ ├── 02-user-management.yml
│ │ │ ├── 03-firewall-config.yml
│ │ │ ├── 04-ssh-hardening.yml
│ │ │ └── 05-system-health.yml
│ │ ├── templates/ # Jinja2 模板(如自定义 motd)
│ │ └── vars/main.yml # 角色内部变量(不建议在此定义,应放 group_vars)
│ └── rocky9-webserver/ # 可选的 Web 服务器角色(继承 rocky9-base)
├── playbooks/ # playbook 入口文件
│ ├── setup-rocky9.yml # 主 playbook,应用 rocky9-base 角色
│ └── setup-webserver.yml # 应用 rocky9-base + rocky9-webserver
├── files/ # 本地文件(如 SSH 公钥)
│ └── keys/
│ ├── id_rsa.pub
│ └── id_ed25519.pub
└── README.md # 项目说明,包含快速启动指南
这个结构的核心思想是
关注点分离
:
inventory
管理“谁”,
group_vars
管理“什么配置”,
roles

488

被折叠的 条评论
为什么被折叠?



