变量、条件与循环
High Contrast
Dark Mode
Light Mode
Sepia
Forest
2 min read370 words

变量、条件与循环

核心问题:同一个 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 定义
loopwith_items 混用 with_items 是旧语法 统一改用 loop
when 条件对字符串 "false" 判断错误 字符串 "false" 不等于布尔 false \| bool 过滤器:when: flag \| bool
循环内 notify 只触发一次 Handler 设计如此 如需每次都执行,用 command + ignore_errors 代替

下一章Ansible 实战:Role 与多环境管理