变量、条件与循环
核心问题:同一个 Playbook 怎样在不同环境(开发/生产)运行不同逻辑?怎样避免重复写相似的任务?
变量优先级(从高到低)
Ansible 变量来源众多,优先级决定哪个值生效:
最高优先级
↑
命令行 -e 参数 ansible-playbook ... -e "env=prod"
task vars(任务内定义)
block vars
role vars/main.yml
include_vars 文件
set_facts
host_vars/ 目录
group_vars/ 目录
inventory 文件中的变量
role defaults/main.yml (最容易被覆盖,设默认值用)
↓
最低优先级
实践建议:
- 默认值放 role/defaults/main.yml
- 环境差异放 group_vars/
- 敏感信息用 ansible-vault 加密,放 group_vars/
- 临时覆盖用 -e 参数
定义变量的方式
1. Play 级别变量
- name: Deploy App
hosts: webservers
vars:
app_port: 3000
app_env: production
vars_files:
- vars/common.yml # 从文件加载
- vars/{{ env }}.yml # 动态加载环境配置
2. group_vars / host_vars 目录
inventory/
├── hosts.yml
├── group_vars/
│ ├── all.yml # 适用于所有主机
│ ├── webservers.yml # 适用于 webservers 组
│ └── webservers/ # 也可以是目录(多文件)
│ ├── vars.yml
│ └── vault.yml # 加密文件
└── host_vars/
└── web-01.example.com.yml # 单台主机专用变量
# group_vars/webservers.yml
nginx_port: 80
ssl_enabled: true
domain_name: api.example.com
3. register:保存任务输出
- name: 检查应用版本
command: /var/www/myapp/bin/myapp --version
register: app_version_result
changed_when: false
- name: 打印版本
debug:
msg: "当前版本:{{ app_version_result.stdout }}"
- name: 版本文件中包含 v2
debug:
msg: "这是 v2 版本"
when: "'v2' in app_version_result.stdout"
4. set_fact:动态设置变量
- name: 设置部署目录
set_fact:
deploy_dir: "/var/www/{{ app_name }}/releases/{{ ansible_date_time.epoch }}"
current_link: "/var/www/{{ app_name }}/current"
- name: 创建目录
file:
path: "{{ deploy_dir }}"
state: directory
内置变量(Magic Variables)
| 变量 | 说明 | 示例值 |
|---|---|---|
inventory_hostname | 当前主机在 Inventory 中的名称 | web-01.example.com |
ansible_host | 实际连接 IP/主机名 | 10.0.1.100 |
ansible_user | SSH 登录用户 | ubuntu |
ansible_distribution | 操作系统发行版 | Ubuntu |
ansible_os_family | OS 家族 | Debian |
ansible_architecture | 架构 | x86_64 |
ansible_memtotal_mb | 总内存(MB) | 7973 |
groups['webservers'] | webservers 组的所有主机列表 | ['web-01', 'web-02'] |
hostvars['db-01']['ansible_host'] | 其他主机的变量 | 10.0.2.100 |
ansible_date_time.epoch | Unix 时间戳 | 1710892800 |
条件判断(when)
# 基于 OS 类型
- name: 安装 Nginx(Debian 系)
apt:
name: nginx
state: present
when: ansible_os_family == "Debian"
- name: 安装 Nginx(RedHat 系)
yum:
name: nginx
state: present
when: ansible_os_family == "RedHat"
# 基于变量值
- name: 启用 HTTPS
template:
src: ssl.conf.j2
dest: /etc/nginx/ssl.conf
when:
- ssl_enabled | bool # 转换为布尔值
- domain_name is defined # 变量存在
- env == "production" # 字符串比较
# 基于 register 结果
- name: 检查文件是否存在
stat:
path: /var/www/myapp/.env
register: env_file
- name: 生成 .env 文件(只在不存在时创建)
template:
src: .env.j2
dest: /var/www/myapp/.env
when: not env_file.stat.exists
# 基于命令返回值
- name: 检查服务是否运行
command: systemctl is-active nginx
register: nginx_status
changed_when: false
failed_when: false
- name: 启动 Nginx(仅当未运行时)
service:
name: nginx
state: started
when: nginx_status.rc != 0
循环(loop)
基本列表循环
# 安装多个包(但 apt 模块直接支持列表,更高效)
- name: 安装软件包
apt:
name: "{{ item }}"
state: present
loop:
- nginx
- git
- curl
- python3-pip
# 创建多个目录
- name: 创建目录结构
file:
path: "{{ item }}"
state: directory
mode: "0755"
loop:
- /var/www/myapp
- /var/www/myapp/logs
- /var/www/myapp/uploads
- /var/log/myapp
字典循环(loop with dict)
# 创建多个用户
- name: 创建用户
user:
name: "{{ item.name }}"
shell: "{{ item.shell }}"
groups: "{{ item.groups }}"
loop:
- { name: deploy, shell: /bin/bash, groups: www-data }
- { name: monitor, shell: /bin/false, groups: "" }
# 配置多个 Nginx 虚拟主机
- name: 生成虚拟主机配置
template:
src: vhost.conf.j2
dest: "/etc/nginx/sites-available/{{ item.domain }}"
loop: "{{ vhosts }}" # vhosts 是 group_vars 中定义的列表
notify: 重载 Nginx
# group_vars/webservers.yml
vhosts:
- domain: api.example.com
port: 3000
ssl: true
- domain: static.example.com
port: 8080
ssl: false
loop_control:控制循环行为
- name: 部署多个服务
include_tasks: deploy_service.yml
loop: "{{ services }}"
loop_control:
loop_var: service # 用 service 替代默认的 item(避免嵌套循环冲突)
label: "{{ service.name }}" # 日志中只显示名称(不显示整个字典)
pause: 5 # 每次循环之间暂停 5 秒
实战:多环境部署 Playbook
# deploy.yml
---
- name: 部署应用到 {{ target_env }} 环境
hosts: "{{ target_env }}_webservers"
become: true
vars_files:
- vars/common.yml
- "vars/{{ target_env }}.yml" # dev.yml 或 prod.yml
tasks:
- name: 确认环境
debug:
msg: "正在部署到 {{ target_env }},主机:{{ inventory_hostname }}"
- name: 拉取代码({{ git_branch }})
git:
repo: "{{ git_repo }}"
dest: "{{ app_dir }}"
version: "{{ git_branch }}"
register: git_result
notify: 重启应用
- name: 安装依赖
npm:
path: "{{ app_dir }}"
production: "{{ target_env == 'production' }}"
when: git_result.changed
- name: 数据库迁移(仅第一台主机执行)
command: "{{ app_dir }}/bin/migrate"
run_once: true # 整个 play 只执行一次
when: target_env == 'production'
handlers:
- name: 重启应用
systemd:
name: "{{ app_name }}"
state: restarted
# 部署到开发环境
ansible-playbook deploy.yml -e "target_env=dev git_branch=develop"
# 部署到生产环境
ansible-playbook deploy.yml -e "target_env=production git_branch=v1.2.3"
常见错误
| 错误 | 原因 | 解决 |
|---|---|---|
undefined variable 'foo' | 变量未定义 | 用 {{ foo \| default('') }} 提供默认值,或在 defaults/main.yml 定义 |
loop 和 with_items 混用 | with_items 是旧语法 | 统一改用 loop |
when 条件对字符串 "false" 判断错误 | 字符串 "false" 不等于布尔 false | 用 \| bool 过滤器:when: flag \| bool |
循环内 notify 只触发一次 | Handler 设计如此 | 如需每次都执行,用 command + ignore_errors 代替 |