Playbook 结构与常用模块
核心问题:Playbook 是 Ansible 的核心——怎样组织一个结构清晰、可重复运行的 Playbook?常用模块有哪些?
Playbook 基本结构
# site.yml — 一个最小化的 Playbook
---
- name: 配置 Web 服务器 # Play 名称(描述这个 Play 做什么)
hosts: webservers # 目标主机/组
become: true # 使用 sudo 提权
gather_facts: true # 收集主机信息(默认 true)
vars: # Play 级变量
nginx_port: 80
app_user: www-data
pre_tasks: # 在 roles 之前执行
- name: 更新 APT 缓存
apt:
update_cache: true
cache_valid_time: 3600 # 1 小时内不重复更新
roles: # 引用角色(推荐方式)
- role: nginx
- role: app
tasks: # 直接写任务(小型 Playbook 可用)
- name: 确保 Nginx 在运行
service:
name: nginx
state: started
enabled: true
post_tasks: # 在 roles 之后执行
- name: 发送部署通知
debug:
msg: "部署完成:{{ inventory_hostname }}"
handlers: # 被 notify 触发的任务
- name: 重启 Nginx
service:
name: nginx
state: restarted
任务(Task)结构
tasks:
- name: 任务名称(必填,描述任务做什么) # 必填
模块名: # 模块名
参数1: 值1
参数2: 值2
register: result # 将执行结果存入变量
when: ansible_os_family == "Debian" # 条件执行
notify: 重启 Nginx # 触发 handler
tags: [nginx, config] # 打标签,支持按标签运行
ignore_errors: true # 忽略错误继续执行
changed_when: false # 强制标记为"未变更"
failed_when: result.rc != 0 # 自定义失败条件
核心模块速查
apt / yum — 软件包管理
# 安装
- name: 安装 Nginx
apt:
name: nginx
state: present # present=安装 | absent=卸载 | latest=升级到最新
# 安装多个包
- name: 安装依赖
apt:
name:
- curl
- git
- python3-pip
state: present
update_cache: true
# 从 URL 安装 .deb
- name: 安装 GitHub CLI
apt:
deb: "https://github.com/cli/cli/releases/download/v2.40.0/gh_2.40.0_linux_amd64.deb"
copy — 复制文件
# 从控制节点复制文件到被管节点
- name: 复制配置文件
copy:
src: files/nginx.conf # 相对于 Playbook 目录的路径
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
backup: true # 覆盖前备份原文件
# 直接写入内容(content)
- name: 写入 hosts 文件
copy:
content: |
127.0.0.1 localhost
10.0.1.100 db-primary
dest: /etc/hosts
mode: "0644"
template — Jinja2 模板
# 渲染模板后复制到目标(变量会被替换)
- name: 生成 Nginx 配置
template:
src: templates/nginx.conf.j2 # .j2 后缀的 Jinja2 模板
dest: /etc/nginx/sites-available/{{ domain_name }}
owner: root
mode: "0644"
notify: 重启 Nginx
# templates/nginx.conf.j2
server {
listen {{ nginx_port }};
server_name {{ domain_name }};
root {{ web_root }};
{% if ssl_enabled %}
listen 443 ssl;
ssl_certificate /etc/ssl/{{ domain_name }}.crt;
{% endif %}
location / {
proxy_pass http://127.0.0.1:{{ app_port }};
proxy_set_header Host $host;
}
}
file — 文件/目录管理
# 创建目录
- name: 创建应用目录
file:
path: /var/www/myapp
state: directory # directory | file | link | absent | touch
owner: www-data
group: www-data
mode: "0755"
# 创建软链接
- name: 创建配置链接
file:
src: /etc/nginx/sites-available/myapp
dest: /etc/nginx/sites-enabled/myapp
state: link
# 删除文件
- name: 删除临时文件
file:
path: /tmp/install.sh
state: absent
service / systemd — 服务管理
# 启动并设置开机自启
- name: 启动 Nginx
service:
name: nginx
state: started # started | stopped | restarted | reloaded
enabled: true # 开机自启
# systemd 专用(支持更多选项)
- name: 重载 systemd 并重启应用
systemd:
name: myapp
state: restarted
daemon_reload: true # 先执行 systemctl daemon-reload
enabled: true
user / group — 用户管理
- name: 创建部署用户
user:
name: deploy
state: present
shell: /bin/bash
create_home: true
groups: sudo
append: true # append=true 表示添加到组,而非替换
- name: 添加 SSH 公钥
authorized_key:
user: deploy
state: present
key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
git — 代码拉取
- name: 拉取应用代码
git:
repo: https://github.com/myorg/myapp.git
dest: /var/www/myapp
version: main # 分支、tag 或 commit hash
force: true # 覆盖本地修改
become_user: www-data
command / shell — 执行命令
# command:不经过 Shell,不支持管道/重定向(更安全)
- name: 初始化数据库
command: /usr/bin/myapp db migrate
args:
chdir: /var/www/myapp
changed_when: true
# shell:经过 Shell,支持管道(有注入风险,谨慎使用)
- name: 检查进程
shell: ps aux | grep nginx | grep -v grep
register: nginx_process
changed_when: false # 查询操作不标记为 changed
failed_when: nginx_process.rc != 0
debug — 调试输出
- name: 打印变量
debug:
var: ansible_distribution
- name: 打印自定义消息
debug:
msg: "主机 {{ inventory_hostname }} 的 IP 是 {{ ansible_host }}"
Handler:触发式任务
Handler 只在被 notify 触发、且对应任务实际发生变更时执行,且每次 play 最多执行一次(无论被 notify 多少次)。
tasks:
- name: 修改 Nginx 配置
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify:
- 验证 Nginx 配置
- 重启 Nginx
- name: 修改 SSL 证书
copy:
src: files/cert.pem
dest: /etc/ssl/cert.pem
notify: 重启 Nginx # 多次 notify 同一 handler,只执行一次
handlers:
- name: 验证 Nginx 配置
command: nginx -t
- name: 重启 Nginx
service:
name: nginx
state: reloaded # reload 比 restart 更平滑(不中断连接)
listen: 重启 Nginx # handler 可以用 listen 设置别名
完整示例:部署 Node.js 应用
---
- name: 部署 Node.js 应用
hosts: webservers
become: true
vars:
app_name: myapi
app_dir: /var/www/myapi
app_port: 3000
node_version: "20"
tasks:
- name: 安装 Node.js {{ node_version }}
shell: |
curl -fsSL https://deb.nodesource.com/setup_{{ node_version }}.x | bash -
apt-get install -y nodejs
args:
creates: /usr/bin/node # 如果 /usr/bin/node 存在则跳过(幂等)
- name: 创建应用目录
file:
path: "{{ app_dir }}"
state: directory
owner: ubuntu
mode: "0755"
- name: 拉取代码
git:
repo: https://github.com/myorg/myapi.git
dest: "{{ app_dir }}"
version: "{{ git_branch | default('main') }}"
become_user: ubuntu
notify: 重启应用
- name: 安装依赖
npm:
path: "{{ app_dir }}"
production: true
become_user: ubuntu
- name: 生成 Systemd 服务文件
template:
src: templates/app.service.j2
dest: /etc/systemd/system/{{ app_name }}.service
notify: 重启应用
- name: 启动应用服务
systemd:
name: "{{ app_name }}"
state: started
enabled: true
daemon_reload: true
handlers:
- name: 重启应用
systemd:
name: "{{ app_name }}"
state: restarted
daemon_reload: true
运行 Playbook
# 基本运行
ansible-playbook -i inventory/hosts.yml site.yml
# 检查模式(dry run,不实际执行)
ansible-playbook -i inventory/hosts.yml site.yml --check
# 只运行打了 nginx 标签的任务
ansible-playbook -i inventory/hosts.yml site.yml --tags nginx
# 跳过某些标签
ansible-playbook -i inventory/hosts.yml site.yml --skip-tags debug
# 只在指定主机上运行
ansible-playbook -i inventory/hosts.yml site.yml -l web-01.example.com
# 传递额外变量
ansible-playbook -i inventory/hosts.yml site.yml -e "git_branch=release/1.0"
# 详细输出(-v/-vv/-vvv)
ansible-playbook -i inventory/hosts.yml site.yml -vv
下一节:变量、条件与循环