运维高频模块
本章在 Playbook 编排之前,先把 Ansible 真正干活的单元——模块 吃透。你将用 ad-hoc(ansible -m) 逐个验证模块行为,再过渡到 YAML 任务写法;下一章再学如何把模块组织成 play / handler / tags。
读完本章应能:
- 说清「模块 / 参数 / 返回值 / changed」四件事
- 用声明式模块完成文件、包、用户、服务、下载解压、健康检查
- 明确
command/shell的使用边界 - 对着场景选出正确模块,而不是先写脚本
0. 模块是什么
一次 Ansible 执行的最小动作:
控制节点 → SSH → 托管节点上执行「模块代码」→ 返回 JSON(是否 changed、结果字段)
# ad-hoc:立刻验证
ansible web -m ping
ansible web -m file -a 'path=/tmp/demo state=directory mode=0755'
同一模块在 playbook 中的写法(先混个眼熟,细节见下一章):
- name: 创建目录
ansible.builtin.file:
path: /tmp/demo
state: directory
mode: "0755"
| 概念 | 含义 |
|---|---|
| 模块名 | 如 file、copy、apt;推荐 FQCN:ansible.builtin.file |
| 参数 | -a 或 YAML 字段,决定期望状态 |
| 返回值 | changed / failed / 模块自定义字段 |
| 幂等 | 已达状态再跑 → 通常 changed=false |
本章以 ad-hoc 为主 建立手感;参数稳定后再写进 YAML。不要还没摸过模块就堆长 playbook。
查文档:
ansible-doc file
ansible-doc -l | head
ansible-doc copy | less
1. 连通与事实:ping / setup
1.1 ping
不是 ICMP,而是「SSH + Python + 模块通道」自检:
ansible web -m ping
# web1 | SUCCESS => { "changed": false, "ping": "pong" }
1.2 setup(Facts)
收集主机事实,供后续判断发行版、内存、网卡等:
ansible web -m setup
ansible web -m setup -a 'filter=ansible_distribution*'
ansible web -m setup -a 'filter=ansible_memtotal_mb'
ansible web1 -m setup -a 'filter=ansible_default_ipv4*'
常用事实字段:
| 字段 | 含义 |
|---|---|
ansible_os_family | Debian / RedHat … |
ansible_distribution | Ubuntu / CentOS … |
ansible_distribution_version | 版本号 |
ansible_memtotal_mb | 内存 |
ansible_default_ipv4.address | 默认 IPv4 |
ansible_hostname / ansible_fqdn | 主机名 |
只取过滤结果可减小输出;play 里也可用 gather_facts: false 加速纯下发场景。
2. 文件与目录:file
管理目录、权限、属主、软链、删除,不负责文件内容。
# 目录
ansible web -m file -a 'path=/data/app state=directory owner=root group=root mode=0755'
# 空文件
ansible web -m file -a 'path=/var/lib/myapp/.keep state=touch mode=0644'
# 软链(current → 某 release)
ansible web -m file -a 'src=/opt/app/releases/20260730 dest=/opt/app/current state=link force=yes'
# 删除
ansible web -m file -a 'path=/var/tmp/old-artifact state=absent'
state | 用途 |
|---|---|
directory | 目录存在 |
file | 断言是普通文件(不会写内容) |
link | 符号链接 |
hard | 硬链接 |
touch | 创建空文件 / 更新时间 |
absent | 删除 |
YAML:
- name: 数据目录
ansible.builtin.file:
path: /data/app
state: directory
owner: app
group: app
mode: "0755" # 建议字符串,避免 YAML 数字坑
recurse: false
写成 mode: 0644(数字)在 YAML 里可能被解析成奇怪的十进制。统一用 "0644"。
3. 静态内容:copy
把控制节点文件(或字符串)推到目标机。
# 本地 files/motd → 远端
ansible web -m copy -a 'src=files/motd dest=/etc/motd owner=root mode=0644 backup=yes'
# 直接写内容
ansible web -m copy -a 'content="hello from ansible\n" dest=/tmp/hello.txt mode=0644'
项目里建议:
files/motd
files/certs/example.crt
YAML 关键参数:
- name: 下发证书
ansible.builtin.copy:
src: files/certs/example.crt
dest: /etc/ssl/certs/example.crt
owner: root
group: root
mode: "0644"
backup: true
# checksum: sha256:abcd...
# validate: /usr/bin/openssl x509 -noout -in %s
| 参数 | 作用 |
|---|---|
src | 控制节点路径(相对 playbook/role 的 files/) |
content | 内联文本 |
dest | 目标路径 |
backup | 覆盖前备份 |
remote_src | 源已在目标机 |
force | 是否覆盖 |
validate | 落地前校验,%s = 临时文件 |
checksum | 完整性校验 |
与 template 的选择:
- 内容固定 →
copy - 随主机/变量变化 →
template
4. 配置渲染:template
Jinja2 模板是配置管理的核心模块。
templates/nginx.conf.j2(可先放在仓库 templates/,ad-hoc 用绝对路径或下一章放进 play/role):
# {{ ansible_managed }}
worker_processes {{ nginx_worker_processes | default(1) }};
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
events {
worker_connections {{ nginx_worker_connections | default(1024) }};
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
server_tokens off;
server {
listen {{ nginx_listen_port | default(80) }} default_server;
server_name {{ nginx_server_name | default('_') }};
root {{ nginx_root | default('/var/www/html') }};
location / {
try_files $uri $uri/ =404;
}
location /healthz {
access_log off;
return 200 'ok';
add_header Content-Type text/plain;
}
}
}
YAML:
- name: 渲染 nginx 配置
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
backup: true
validate: "/usr/sbin/nginx -t -c %s"
# ad-hoc 也可以,但变量传递别扭,配置类更推荐 playbook
ansible web -m template -a 'src=/path/to/nginx.conf.j2 dest=/etc/nginx/nginx.conf validate="/usr/sbin/nginx -t -c %s"'
常用 Jinja:
{{ http_port | int }}
{{ name | default('app') }}
{{ optional | default([]) }}
{% if nginx_healthz_enable | default(true) | bool %}
...
{% endif %}
{% for h in groups['web'] | default([]) %}
# peer {{ h }} {{ hostvars[h].ansible_host | default('') }}
{% endfor %}
能做本地语法检查的组件(nginx、haproxy、named、sudoers 用 visudo -cf)都应挂 validate。失败则不覆盖目标文件,避免推坏配置。
5. 改一行 / 一小段:lineinfile / blockinfile
适合 sshd、sysctl、小开关;大段配置仍用 template。
5.1 lineinfile
SSH 配置会影响远程登录,不能只用一条 ad-hoc 命令直接改。请保留一个已经登录的终端,并使用下面带语法校验与 handler 的 playbook task。
tasks:
- name: 设置 SSH 密码登录开关
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PasswordAuthentication\s+'
line: "PasswordAuthentication {{ ssh_password_authentication | default(false) | ternary('yes', 'no') }}"
backup: true
validate: "/usr/sbin/sshd -t -f %s"
state: present
notify: Reload SSH service
handlers:
- name: Reload SSH service
ansible.builtin.service:
name: "{{ 'ssh' if ansible_os_family == 'Debian' else 'sshd' }}"
state: reloaded
要点:
- 必须写好
regexp,否则重复执行会追加多行 state: absent+regexp可删行validate失败时不会写入坏配置;成功后才由 handler reload- 改 sshd 前确保有第二条登录通道,并在新会话验证公钥登录后再关闭旧会话
- 示例读取
ssh_password_authentication;变量本身不产生变更,只有被 task 引用才会生效
5.2 blockinfile
- name: 受管 sysctl 片段
ansible.builtin.blockinfile:
path: /etc/sysctl.d/99-ansible.conf
create: true
marker: "# {mark} ANSIBLE MANAGED BLOCK net.basic"
block: |
net.ipv4.ip_forward = 0
fs.file-max = 2097152
backup: true
marker 让 Ansible 认得自己的块,二次执行是更新块而不是傻追加。
6. 查询文件状态:stat
改之前先看,常与条件配合(条件语法下一章细讲):
ansible web -m stat -a 'path=/etc/nginx/nginx.conf'
- name: 检查配置是否存在
ansible.builtin.stat:
path: /etc/nginx/nginx.conf
register: nginx_conf
# 下一章配合 when: nginx_conf.stat.exists
返回里常用:stat.exists / stat.isdir / stat.checksum / stat.mode。
7. 包管理:package / apt / dnf / yum
7.1 跨发行版:package
ansible web -m package -a 'name=curl state=present'
ansible web -m package -a 'name=jq,ca-certificates state=present'
- name: 安装通用工具
ansible.builtin.package:
name:
- curl
- jq
- ca-certificates
state: present
7.2 Debian 家族:apt
ansible web -m apt -a 'name=nginx state=present update_cache=yes cache_valid_time=3600'
ansible web -m apt -a 'name=nginx state=absent purge=yes'
- name: 安装 nginx(Debian)
ansible.builtin.apt:
name: nginx
state: present # present / absent / latest
update_cache: true
cache_valid_time: 3600
when: ansible_os_family == "Debian"
钉版本:name: nginx=1.24.*(视仓库而定)。
7.3 RedHat 家族:dnf / yum
- name: 安装 nginx(RedHat)
ansible.builtin.dnf:
name: nginx
state: present
when: ansible_os_family == "RedHat"
state | 建议 |
|---|---|
present | 默认;生产可钉版本 |
absent | 卸载 |
latest | 易漂,生产慎用 |
shell: apt-get install -y nginx 难幂等、难审计。一律走包模块;内网先配好镜像源。
8. 仓库与密钥环(补充)
偶尔需要加源(示例,按公司镜像改):
- name: 添加内部 apt 源(示意)
ansible.builtin.apt_repository:
repo: "deb https://mirrors.example.com/ubuntu jammy main"
state: present
filename: internal
when: ansible_os_family == "Debian"
RHEL 系对应 ansible.builtin.yum_repository。密钥用 get_url/copy 下发后再 apt_key(视发行版新老,新 Ubuntu 更推荐 signed-by 文件方式)。本章知存在即可,生产按平台规范选型。
9. 用户与组:user / group
ansible web -m group -a 'name=app system=yes'
ansible web -m user -a 'name=app group=app system=yes shell=/usr/sbin/nologin home=/data/app create_home=yes'
- name: app 组
ansible.builtin.group:
name: app
system: true
- name: app 用户
ansible.builtin.user:
name: app
group: app
system: true
shell: /usr/sbin/nologin
home: /data/app
create_home: true
password_lock: true
# password: "{{ 'secret' | password_hash('sha512') }}" # 需登录时再设
| 参数 | 说明 |
|---|---|
system | 系统用户 |
uid/gid | 固定 ID,集群一致时有用 |
groups | 附加组 |
remove | 删用户时是否移除家目录(配合 state=absent) |
10. SSH 公钥:authorized_key
集合:ansible.posix(缺失则安装)。
ansible-galaxy collection install ansible.posix
ansible web -m ansible.posix.authorized_key -a "user=deploy key='ssh-ed25519 AAAA... deploy@ci' state=present"
- name: 写入 deploy 公钥
ansible.posix.authorized_key:
user: deploy
key: "{{ lookup('file', 'files/ssh/deploy.pub') }}"
state: present
exclusive: false # true 会清掉未声明的 key,慎用
也可用 copy 整文件管理 authorized_keys,但多人公钥合并策略要自己定;authorized_key 更适合「声明某一把 key 存在」。
11. 服务:service / systemd
11.1 service(通用)
ansible web -m service -a 'name=nginx state=started enabled=yes'
ansible web -m service -a 'name=nginx state=reloaded'
ansible web -m service -a 'name=nginx state=restarted' # 慎用,优先 reload
state | 含义 |
|---|---|
started / stopped | 运行状态 |
reloaded | 重载配置 |
restarted | 重启进程 |
enabled/disabled | 是否开机自启(参数 enabled:) |
11.2 systemd(单元文件 / daemon-reload)
- name: 安装 unit
ansible.builtin.template:
src: myapp.service.j2
dest: /etc/systemd/system/myapp.service
mode: "0644"
- name: 启动 myapp
ansible.builtin.systemd:
name: myapp
state: started
enabled: true
daemon_reload: true
原则:能 reloaded 就不要 restarted;配置变更触发重启应放到 handler(下一章),而不是每个 task 后都 restart。
12. 下载与解压:get_url / unarchive
12.1 get_url
ansible web -m get_url -a 'url=https://example.com/pkg/app.tar.gz dest=/tmp/app.tar.gz mode=0644 checksum=sha256:abcd...'
- name: 下载制品
ansible.builtin.get_url:
url: "{{ app_tarball_url }}"
dest: /tmp/app.tar.gz
mode: "0644"
checksum: "sha256:{{ app_tarball_sha256 }}"
timeout: 60
12.2 unarchive
- name: 解压到版本目录
ansible.builtin.unarchive:
src: /tmp/app.tar.gz
dest: /opt/app/releases/{{ app_version }}
remote_src: true
creates: /opt/app/releases/{{ app_version }}/bin/app
| 参数 | 说明 |
|---|---|
remote_src | 压缩包已在目标机(常与 get_url 联用) |
creates | 目标已存在则跳过,利于幂等 |
extra_opts | 传给 tar 的额外参数 |
本地打包上传也可:remote_src: false,src 指向控制节点文件。
13. 等待与探测:wait_for / uri
13.1 wait_for
ansible web -m wait_for -a 'port=80 host=127.0.0.1 delay=2 timeout=60'
ansible web -m wait_for -a 'path=/var/run/nginx.pid timeout=30'
- name: 等待端口就绪
ansible.builtin.wait_for:
host: 127.0.0.1
port: "{{ nginx_listen_port | default(80) }}"
delay: 2
timeout: 60
state: started
13.2 uri(HTTP 检查 / 调 API)
ansible web -m uri -a 'url=http://127.0.0.1/healthz return_content=yes status_code=200'
- name: 健康检查
ansible.builtin.uri:
url: "http://127.0.0.1:{{ nginx_listen_port }}/healthz"
method: GET
status_code: 200
return_content: true
register: health
# until/retries 见下一章
也可 POST JSON、带 Header,适合探针与简易 API 调用;复杂集成仍更适合 Python。
14. 定时任务:cron
- name: 每天清理日志
ansible.builtin.cron:
name: "cleanup app logs"
user: root
minute: "30"
hour: "3"
job: "/usr/local/bin/cleanup-logs.sh >/var/log/cleanup-logs.log 2>&1"
state: present
用 name 作为唯一标识,二次执行是更新同一条,而不是盲追加。删任务:state: absent + 同名。
15. 主机名与其它常用模块(速通)
hostname
- name: 设置主机名
ansible.builtin.hostname:
name: "{{ inventory_hostname }}"
sysctl
- name: 调整 file-max
ansible.builtin.sysctl:
name: fs.file-max
value: "2097152"
state: present
reload: true
sysctl_file: /etc/sysctl.d/99-ansible.conf
mount(了解)
- name: 挂载数据盘(示意)
ansible.builtin.mount:
path: /data
src: /dev/vdb1
fstype: xfs
opts: defaults,noatime
state: mounted
磁盘分区/格式化风险高,生产变更要单独评审,本系列不展开破坏性存储操作。
git(可选)
- name: 拉取配置仓库
ansible.builtin.git:
repo: "https://git.example.com/ops/configs.git"
dest: /opt/configs
version: main
force: false
密钥与镜像权限处理好再用;许多团队更倾向 CI 构建制品 + get_url。
16. 命令类:command / shell / raw / script
16.1 对照
| 模块 | 特点 | 何时用 |
|---|---|---|
command | 不走 shell,无管道/重定向 | 简单二进制调用 |
shell | /bin/sh -c | 必须管道、通配、环境复杂时 |
raw | 几乎不依赖 Python | 引导极老/无 Python 主机 |
script | 把本地脚本拷过去执行 | 短时辅助脚本 |
ansible web -m command -a 'uptime'
ansible web -m command -a 'nginx -t'
ansible web -m shell -a 'ss -lntp | grep -W :80 || true'
16.2 必须声明变更语义
- name: 只读版本查询
ansible.builtin.command: /usr/sbin/nginx -v
register: nginx_ver
changed_when: false
failed_when: nginx_ver.rc != 0
- name: 带 creates 的初始化脚本
ansible.builtin.command: /usr/local/bin/init-node.sh
args:
creates: /var/lib/myapp/initialized
16.3 反模式
# 坏例子:难幂等、难回滚、难审计
- ansible.builtin.shell: |
apt-get update && apt-get install -y nginx
echo "x" >> /etc/nginx/nginx.conf
systemctl restart nginx
改写方向:apt + template/lineinfile + service(reload 放 handler)。
17. 调试模块:debug / assert
ansible web -m debug -a 'var=inventory_hostname'
ansible web -m debug -a 'msg=port is {{ http_port | default(80) }}'
- name: 打印变量
ansible.builtin.debug:
var: ansible_distribution
- name: 断言内存足够
ansible.builtin.assert:
that:
- ansible_memtotal_mb | int >= 1024
fail_msg: "内存不足 1G,拒绝继续"
排障神器;不要 debug 密码(即便有 Vault)。
18. 模块怎么选(决策表)
| 你想做的事 | 优先模块 |
|---|---|
| 目录/权限/软链/删除 | file |
| 固定文件内容 | copy |
| 按主机渲染配置 | template |
| 改单行/小块 | lineinfile / blockinfile |
| 看文件是否存在 | stat |
| 装软件 | package / apt / dnf |
| 用户组 | user / group |
| SSH 公钥 | ansible.posix.authorized_key |
| 启停服务 | service / systemd |
| 下载 | get_url |
| 解压 | unarchive |
| 等端口/文件 | wait_for |
| HTTP 检查 | uri |
| 定时任务 | cron |
| 内核参数 | sysctl |
| 实在没模块 | command/shell + changed_when/creates |
19. 串烧练习:不用完整 Playbook 也能验证
先用 ad-hoc 在一台机上走通(变量少的步骤):
ansible web1 -m package -a 'name=nginx state=present'
ansible web1 -m file -a 'path=/var/www/html state=directory mode=0755'
ansible web1 -m copy -a 'content="<h1>demo</h1>\n" dest=/var/www/html/index.html mode=0644'
ansible web1 -m service -a 'name=nginx state=started enabled=yes'
ansible web1 -m uri -a 'url=http://127.0.0.1/ return_content=yes'
配置文件再用 template(建议直接进入下一章写成 play,带 validate 与变量)。
20. 练习与验收
练习 A:file/copy
创建 /data/app/{bin,conf,logs},下发一个 motd,二次执行无 changed。
练习 B:包与服务
安装 nginx,started+enabled,用 uri 或 wait_for 验证 80 端口。
练习 C:lineinfile
在实验机改某一非关键配置行(不要先动 sshd),确认 regexp 正确、不重复追加。
练习 D:command 边界
写一条 changed_when: false 的版本查询;再故意写一条会每次 changed 的 shell,观察差异后删掉它。
验收清单
- 会用
ansible-doc <module>查参数 - 能区分 copy / template / lineinfile
- 装包与服务不用 shell
- 理解
changed含义,会处理只读 command
下一章:Playbook 核心 —— 把模块编排成 play,加上 handler、when、loop、tags。