iT邦幫忙

2025 iThome 鐵人賽

DAY 8
0
DevOps

不爆肝學習 Ansible 的短暫30天系列 第 8

Day08 – 條件與迴圈讓 Playbook 更聰明

  • 分享至 

  • xImage
  •  

今日目標

  • 學會用 when 來做條件判斷,避免不相關的步驟執行
  • 學會用 loop 來重複執行任務,告別複製貼上的惡夢
  • 讓 Playbook 變得更聰明,可以應對不同環境

為什麼需要條件和迴圈?

想像一下這個情況:你管理著一堆 Ubuntu 和 CentOS 的機器,現在要幫它們裝防火牆。

Ubuntu 用 ufw,CentOS 用 firewalld,如果沒有條件判斷,你就得寫兩份 Playbook,超麻煩的。

或者你要在每台機器上裝 10 個常用工具,沒有迴圈的話就要寫 10 個 task,想到就頭痛對吧?

條件判斷 (when) - 符合什麼條件就執行什麼任務

基本語法

先來看最經典的例子,根據作業系統裝不同的防火牆:

---
- 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),但是他不會知道要裝哪一種防火牆啊啊啊!!!

常用的 Facts 變數

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', '>=')

迴圈 (loop) - 重複任務的小幫手

基本用法

最簡單的例子,一次裝多個套件:

---
- 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' }

loop_control 進階技巧

- 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 }}"  # 只顯示套件名,不顯示整個字典

when + loop 結合蹦出新滋味

基本組合

---
- 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"

常見的坑

1. 字串比較大小寫

# 錯誤:大小寫不符
when: ansible_distribution == "ubuntu"

# 正確:Ubuntu 首字母大寫
when: ansible_distribution == "Ubuntu"

# 或者用 lower 過濾器
when: ansible_distribution | lower == "ubuntu"

2. 迴圈中的條件判斷位置

# 這樣會對整個 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

3. 變數未定義的錯誤

# 不安全:如果變數不存在會報錯
when: my_var == "some_value"

# 安全:先檢查是否定義
when: my_var is defined and my_var == "some_value"

# 或者用 default 過濾器
when: (my_var | default('')) == "some_value"

作業練習時間

練習 1:環境感知安裝

寫一個 Playbook,根據不同作業系統安裝對應的套件:

  • Ubuntu: nginx, ufw, software-properties-common
  • CentOS: nginx, firewalld, yum-utils

練習 2:批次用戶管理

建立一個 Playbook,批次建立開發團隊的使用者帳號:

users:
  - { name: 'john', groups: ['sudo', 'docker'] }
  - { name: 'mary', groups: ['sudo'] }
  - { name: 'bob', groups: ['docker'] }

練習 3:挑戰題

寫一個 Playbook,根據主機的記憶體大小決定要不要安裝某些服務:

  • 記憶體 >= 4GB:安裝 Docker 和 Kubernetes
  • 記憶體 >= 2GB:只安裝 Docker
  • 記憶體 < 2GB:只安裝基本工具

提示:用 ansible_memtotal_mb 這個 fact

Debug 小技巧

檢查條件是否符合:

- 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,可以讓設定檔真的再有變更時才重啟對應的服務。


上一篇
Day07 – 用變數讓 Playbook 更聰明
下一篇
Day09 - Handlers 有效率的重啟機制
系列文
不爆肝學習 Ansible 的短暫30天9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言