when
來做條件判斷,避免不相關的步驟執行loop
來重複執行任務,告別複製貼上的惡夢想像一下這個情況:你管理著一堆 Ubuntu 和 CentOS 的機器,現在要幫它們裝防火牆。
Ubuntu 用 ufw
,CentOS 用 firewalld
,如果沒有條件判斷,你就得寫兩份 Playbook,超麻煩的。
或者你要在每台機器上裝 10 個常用工具,沒有迴圈的話就要寫 10 個 task,想到就頭痛對吧?
先來看最經典的例子,根據作業系統裝不同的防火牆:
---
- name: Install firewall based on OS
hosts: all
become: yes
tasks:
- name: Install ufw on Ubuntu/Debian
package:
name: ufw
state: present
when: ansible_os_family == "Debian"
- name: Install firewalld on CentOS/RHEL
package:
name: firewalld
state: present
when: ansible_os_family == "RedHat"
- name: Start firewall service
service:
name: "{{ 'ufw' if ansible_os_family == 'Debian' else 'firewalld' }}"
state: started
enabled: yes
等等!可是第四天的時候不是有說 package 會自動判斷要用什麼套件管理器安裝套件嗎?為什麼這邊還要用 when 來判斷作業系統呢?
這邊要注意 package 的確會幫我們在不同作業系統上使用不同的套件管理器 (ex:apt、dnf),但是他不會知道要裝哪一種防火牆啊啊啊!!!
Ansible 有個蠻好用的特性就是會自動收集每台機器的資訊,但相對會讓任務執行速度變得比較慢,不過這個設定是可以自由開關的,所以就根據各位的使用場景做選擇。
# 如果想看機器有哪些 facts 可以使用這個指令
ansible all -i inventory.ini -m setup | grep ansible_
常用的 facts:
ansible_os_family
: 作業系統家族 (Debian, RedHat, etc.)ansible_distribution
: 具體發行版 (Ubuntu, CentOS, etc.)ansible_distribution_version
: 版本號ansible_hostname
: 主機名ansible_architecture
: 架構 (x86_64, arm64, etc.)檢查變數是否定義:
- name: Install custom package if defined
package:
name: "{{ custom_package }}"
state: present
when: custom_package is defined
檢查檔案是否存在:
- name: Check if config exists
stat:
path: /etc/myapp/config.yml
register: config_file
- name: Create default config if not exists
copy:
src: default_config.yml
dest: /etc/myapp/config.yml
when: not config_file.stat.exists
多重條件組合:
- name: Install Docker on modern Ubuntu
package:
name: docker.io
state: present
when:
- ansible_distribution == "Ubuntu"
- ansible_distribution_version is version('18.04', '>=')
最簡單的例子,一次裝多個套件:
---
- name: Install multiple tools
hosts: all
become: yes
tasks:
- name: Install common development tools
package:
name: "{{ item }}"
state: present
loop:
- curl
- git
- vim
- htop
- tree
- wget
效能小技巧:其實 package 模組可以直接接受列表,像這樣會更快:
- name: Install tools (faster way) package: name: [curl, git, vim, htop, tree, wget] state: present
建立多個使用者:
- name: Create multiple users
user:
name: "{{ item.name }}"
group: "{{ item.group }}"
shell: "{{ item.shell | default('/bin/bash') }}"
loop:
- { name: 'alice', group: 'developers', shell: '/bin/zsh' }
- { name: 'bob', group: 'ops' }
- { name: 'charlie', group: 'developers' }
複製多個設定檔:
- name: Deploy config files
copy:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
mode: "{{ item.mode | default('0644') }}"
loop:
- { src: 'nginx.conf', dest: '/etc/nginx/nginx.conf', mode: '0644' }
- { src: 'ssl.conf', dest: '/etc/nginx/ssl.conf', mode: '0600' }
- { src: 'logrotate.conf', dest: '/etc/logrotate.d/nginx' }
- name: Install packages with custom loop variable
package:
name: "{{ pkg.name }}"
state: present
loop:
- { name: 'nginx', desc: 'Web server' }
- { name: 'mysql', desc: 'Database' }
loop_control:
loop_var: pkg # 把 item 改名為 pkg
label: "{{ pkg.name }}" # 只顯示套件名,不顯示整個字典
---
- name: Install OS-specific tools
hosts: all
become: yes
tasks:
- name: Install Debian/Ubuntu tools
package:
name: "{{ item }}"
state: present
loop:
- ufw
- net-tools
- software-properties-common
when: ansible_os_family == "Debian"
- name: Install RedHat/CentOS tools
package:
name: "{{ item }}"
state: present
loop:
- firewalld
- net-tools
- yum-utils
when: ansible_os_family == "RedHat"
- name: Create environments based on server role
file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- /var/log/webapp
- /var/cache/webapp
- /etc/webapp
when: "'web' in group_names"
- name: Setup database directories
file:
path: "{{ item }}"
state: directory
mode: '0700'
owner: mysql
loop:
- /var/lib/mysql-backup
- /var/log/mysql-slow
when: "'db' in group_names"
# 錯誤:大小寫不符
when: ansible_distribution == "ubuntu"
# 正確:Ubuntu 首字母大寫
when: ansible_distribution == "Ubuntu"
# 或者用 lower 過濾器
when: ansible_distribution | lower == "ubuntu"
# 這樣會對整個 loop 做判斷(推薦)
- name: Install packages
package:
name: "{{ item }}"
loop: [vim, git]
when: ansible_os_family == "Debian"
# 這樣會對每個 item 做判斷(通常不是你想要的)
- name: Install packages conditionally
package:
name: "{{ item }}"
loop:
- { name: vim, os: 'Debian' }
- { name: git, os: 'RedHat' }
when: item.os == ansible_os_family
# 不安全:如果變數不存在會報錯
when: my_var == "some_value"
# 安全:先檢查是否定義
when: my_var is defined and my_var == "some_value"
# 或者用 default 過濾器
when: (my_var | default('')) == "some_value"
寫一個 Playbook,根據不同作業系統安裝對應的套件:
建立一個 Playbook,批次建立開發團隊的使用者帳號:
users:
- { name: 'john', groups: ['sudo', 'docker'] }
- { name: 'mary', groups: ['sudo'] }
- { name: 'bob', groups: ['docker'] }
寫一個 Playbook,根據主機的記憶體大小決定要不要安裝某些服務:
提示:用
ansible_memtotal_mb
這個 fact
檢查條件是否符合:
- name: Debug OS info
debug:
msg: "OS: {{ ansible_os_family }}, Version: {{ ansible_distribution_version }}"
- name: Test condition
debug:
msg: "Condition met!"
when: ansible_os_family == "Debian"
檢查迴圈變數:
- name: Debug loop items
debug:
var: item
loop: [one, two, three]
今天學完條件跟回圈的用法,明天來玩另外一個 Handlers,可以讓設定檔真的再有變更時才重啟對應的服務。