ansible管理變量和事實與實施任務控制
在 Ansible 中,變量和事實(Facts)就像給劇本(Playbook)配備的 “信息工具箱”,讓你的自動化配置管理更靈活、更智能。
變量:提前準備的 “預設信息”
變量就像你出門前準備的清單,提前可以提前預先定義好各種信息,用的時候直接拿出來用。
-
怎么定義?
-
在 Playbook 里直接寫
:就像把清單貼在劇本首頁
- hosts: webserversvars:app_port: 8080 # 定義應用端口變量app_name: "myapp" # 定義應用名稱變量tasks:- name: 啟動應用command: "/opt/{{ app_name }}/start --port {{ app_port }}"
-
單獨放變量文件
:就像把清單放進文件夾,更整潔
# vars/app_vars.yml app_port: 8080 app_name: "myapp"
然后在 Playbook 里引用:
- hosts: webserversvars_files:- vars/app_vars.yml
-
命令行傳變量
:臨時改清單,比如臨時換個端口
ansible-playbook deploy.yml -e "app_port=9090"
-
-
變量的小技巧:
- 可以用
{{ 變量名 }}
在任務中調用,像插卡一樣靈活 - 支持條件判斷,比如 “如果是生產環境,端口用 443;測試環境用 8080”
- 可以用
事實(Facts):自動收集的 “系統情報”
事實就像 Ansible 派出去的 “偵察兵”,會自動收集目標主機的信息(比如 IP 地址、操作系統、內存大小等),不用你手動問。
-
怎么看收集到的情報?
跑個命令讓偵察兵匯報:ansible webservers -m setup
會得到一堆信息,比如:
ansible_facts['os_family']
:操作系統類型(比如 RedHat、Debian)ansible_facts['default_ipv4']['address']
:主機 IP 地址ansible_facts['memory_mb']['total']
:總內存(MB)
-
在 Playbook 里用事實:
比如根據操作系統選不同的安裝命令:- hosts: alltasks:- name: 安裝nginx(Debian系統)apt: name=nginx state=presentwhen: ansible_facts['os_family'] == "Debian"- name: 安裝nginx(RedHat系統)yum: name=nginx state=presentwhen: ansible_facts['os_family'] == "RedHat"
-
小提示:
- 事實默認會自動收集,如果想關掉(比如加快執行速度),可以在 Playbook 里加
gather_facts: no
- 可以自定義事實(local facts),把自己關心的信息存到目標主機的
/etc/ansible/facts.d/
目錄,Ansible 會自動讀取
- 事實默認會自動收集,如果想關掉(比如加快執行速度),可以在 Playbook 里加
總結:變量 vs 事實
- 變量:你主動告訴 Ansible 的信息(提前設定)
- 事實:Ansible 主動從目標主機收集的信息(動態獲取)
兩者結合,就像給 Ansible 裝上了 “大腦”—— 既知道你提前安排的計劃,又能根據實際情況靈活調整,讓配置管理既聰明又高效~
管理 VARIABLES
變量簡介
ansible 利用變量來存儲數據,以便在Ansible項目文件中重復引用,有利于簡化項目的創建和維護,降低出錯率。我們在playbook中可以針對如用戶、軟件包、服務、文件等進行變量定義。
變量命名規則
- 只能包含字母、數字和下劃線(如包含空格、點、$符號都為非法變量名)
- 只能以字母開頭
變量范圍和優先級
ansible項目文件中多個位置支持定義變量,主要包含三個基本范圍:
- Global scope:從命令行或 Ansible 配置設置的變量。
- Play scope:在play和相關結構中設置的變量。
- Host scope:由清單、事實(fact)收集或注冊的任務,在主機組和個別主機上設置的變量。
優先級從高到低順序:Global -> Play -> Host。
在多個級別上定義了相同名稱的變量,則采用優先級別最高的變量。
Play scope
在 Ansible 里,“Play scope”(劇本作用域)可以理解為一個 Play(劇本)的 “管轄范圍” 和 “生效邊界”。它就像給這個 Play 劃了個圈,圈里的規則、變量、設置只在這個范圍內起作用,不會跑到圈外去影響其他 Play。
舉個生活例子:你家里有客廳和臥室兩個區域(相當于兩個 Play)。在客廳里你規定 “看電視音量不能超過 30”(這是客廳 Play 的變量 / 設置),這個規則只在客廳生效,到了臥室就不算數了 —— 這就是每個 Play 有自己獨立的 scope。
具體來說,Play scope 包含這些 “圈內要素”:
- 目標主機:通過
hosts
指定的主機 / 主機組,只有這些主機受這個 Play 管理 - 變量:在 Play 的
vars
或vars_files
里定義的變量,只在當前 Play 的任務中可用 - 提權設置:當前 Play 里的
become
相關配置,不會影響其他 Play - 事實收集:
gather_facts
的開關狀態,只控制當前 Play 是否收集主機信息
比如一個 Playbook 里有兩個 Play:
- name: 管理web服務器 # Play 1hosts: webserversvars:app: "nginx" # 這個變量只在Play 1里有效tasks:- name: 安裝nginxyum: name={{ app }} state=present- name: 管理數據庫服務器 # Play 2hosts: dbserversvars:app: "mysql" # 這個變量只在Play 2里有效tasks:- name: 安裝mysqlyum: name={{ app }} state=present
這里兩個 Play 的app
變量互不干擾,因為它們各自有獨立的 scope。
簡單說,Play scope 就是 Ansible 里的 “楚河漢界”,讓每個 Play 在自己的地盤里按自己的規則干活,互不打擾~
vars 聲明
在 Ansible 中,vars
就像給 Playbook 準備的 “變量口袋”,用來提前存放各種需要反復使用的信息。聲明變量的方式靈活多樣,就像你可以把東西放在口袋、抽屜或專門的收納盒里一樣。
1. 直接在 Play 里聲明(最直觀)
就像把常用物品直接揣在口袋里,隨用隨拿。在 Play 的vars
塊里定義變量,只在當前 Play 中生效。
- name: 部署web應用hosts: webserversvars:app_name: "blog" # 應用名稱app_port: 8080 # 運行端口max_connections: 100 # 最大連接數tasks:- name: 創建應用目錄file:path: "/opt/{{ app_name }}" # 引用變量state: directory
2. 單獨放變量文件(更整潔)
如果變量太多,就像東西太多需要用抽屜分類收納。把變量寫在單獨的 YAML 文件里,再在 Playbook 中引用。
# 變量文件:vars/app_settings.yml
app_name: "blog"
app_port: 8080
db_host: "db.example.com"
在 Playbook 中調用這個文件:
- name: 部署web應用hosts: webserversvars_files:- vars/app_settings.yml # 引入外部變量文件tasks:- name: 配置數據庫連接lineinfile:path: "/opt/{{ app_name }}/config.ini"line: "db_host = {{ db_host }}"
3. 命令行臨時聲明(應急用)
就像臨時從口袋里掏出個備用物品,適合臨時修改變量值,不用改 Playbook 本身。用-e
參數傳遞:
# 臨時把端口改成9090運行
ansible-playbook deploy.yml -e "app_port=9090"
4. 主機 / 組變量(按目標分類)
如果不同主機需要不同變量(比如 web 服務器和數據庫服務器配置不同),可以在inventory
目錄下創建專門的變量文件,Ansible 會自動對應。
目錄結構通常是這樣:
inventory/├── hosts # 主機清單├── group_vars/ # 組變量(對整個組生效)│ ├── webservers.yml # web服務器組的變量│ └── dbservers.yml # 數據庫服務器組的變量└── host_vars/ # 主機變量(對單臺主機生效)└── web01.yml # 給web01主機單獨的變量
比如group_vars/webservers.yml
里寫:
app_type: "nginx"
log_path: "/var/log/nginx"
變量使用小技巧
-
引用變量時用雙大括號
{{ 變量名 }}
,比如{{ app_name }}
-
變量名可以包含字母、數字和下劃線(不能以數字開頭)
-
支持嵌套,比如:
app:name: "blog"port: 8080
引用時用
{{ app.name }}
{{ app.port }}
簡單說,vars
聲明就是給 Ansible 提前 “備課”—— 把需要用的信息整理好,用的時候直接喊名字就能調出來,不用重復寫死,既靈活又好維護~
Host scope
主機變量應用于主機和主機組。主機變量優先級高于主機組變量。
主機清單中定義
較舊的做法是直接在清單文件中定義。不建議采用,但仍可能會遇到。
[servers]
node1 user=laoma
node2[servers:vars]
user=laowang
目錄分層結構定義
在項目目錄中創建如下目錄:
-
group_vars,定義主機組變量。目錄中文件名可以直接使用
主機組名
或者主機組名.yaml
。 -
host_vars,定義主機變量。目錄中文件名可以直接使用
主機名
或者主機名.yaml
。
主機連接特殊變量
詳情參考:主機連接特殊變量。
-
ansible_connection,與主機的連接類型,可以是 smart、ssh 或 paramiko。默認為smart。
-
ansible_host,要連接的主機的名稱,默認值就是主機清單名稱。
-
ansible_port,ssh 端口號,如果不是 22。
-
ansible_user,ssh 用戶名。
-
ansible_ssh_pass,要使用的 ssh 密碼。切勿以純文本形式存儲此變量,始終使用保管庫。
-
ansible_ssh_private_key_file,ssh 使用的私鑰文件。如果使用多個密鑰并且您不想使用 SSH 代理,這很有用。
-
ansible_ssh_common_args,此設置始終附加到 sftp、scp 和 ssh 的默認命令行。
-
ansible_sftp_extra_args,此設置始終附加到默認的 sftp 命令行。
-
ansible_scp_extra_args,此設置始終附加到默認的 scp 命令行。
-
ansible_ssh_extra_args,此設置始終附加到默認的 ssh 命令行。
-
ansible_become,等效于 ansible_sudo 或 ansible_su,允許強制提權。
-
ansible_become_method,允許設置權限提升方法。
-
ansible_become_user,等效于 ansible_sudo_user 或 ansible_su_user,允許設置您通過權限升級成為的用戶。
-
ansible_become_pass,等效于 ansible_sudo_pass 或 ansible_su_pass,允許您設置權限提升密碼(切勿以純文本形式存儲此變量;始終使用保管庫。請參閱變量和保管庫)。
數組變量
除了將與同一元素相關的配置數據(軟件包列表、服務列表和用戶列表等)分配到多個變量外,管理員也可以使用數組變量,將多個值存儲在同一變量中。
示例:
user1_first_name: Bob
user1_last_name: Jones
user1_home_dir: /users/bjones
user2_first_name: Anne
user2_last_name: Cook
user2_home_dir: /users/acook
改寫如下:
users:bjones:first_name: Boblast_name: Joneshome_dir: /users/bjonesacook:first_name: Annelast_name: Cookhome_dir: /users/acook
數組變量引用方式一:
# Returns 'Bob'
users.bjones.first_name
# Returns '/users/acook'
users.acook.home_dir
數組變量引用方式二:
# Returns 'Bob'
users['bjones']['first_name']
# Returns '/users/acook'
users['acook']['home_dir']
引用方式總結:
- 如果使用方法一**.分隔符**引用的關鍵字與python的功能函數同名,例如discard、copy、add,那么就會出現問題。方法二[‘’]引用方式可以避免這種錯誤。
- 盡管兩種方法都可以使用,為了減少排故難度,Ansible中統一使用其中一種方法。
示例1:
---
- name: test vars statement in playhosts: node1vars: users:laoma:user_name: laomahome_path: /home/laomalaowang:user_name: laowanghome_path: /home/laowangtasks:- name: add user {{ users.laoma.user_name }}user:name: '{{ users.laoma.user_name }}'home: "{{ users.laoma.home_path }}"- name: debug laowangdebug: msg: >username is {{ users['laowang']['user_name'] }}home_path is {{ users['laowang']['home_path'] }}
示例2:
---
- name: test vars statement in playhosts: node1vars: users:- user_name: laoma1home_path: /home/laoma1- user_name: laoma2home_path: /home/laoma2tasks:- name: add user {{ users.0.user_name }}user:name: "{{ users.0.user_name }}"home: "{{ users.0.home_path }}"- name: debug {{ users[1].user_name }}debug: msg: "{{ users[1].user_name }}"
register 語句
**register 語句捕獲任務輸出。**輸出保存在一個臨時變量中,稍后在playbook中可用于調試用途或者達成其他目的。
示例:
---
- name: Installs a package and prints the resulthosts: node1tasks:- name: Install the packageyum:name: httpdstate: installedregister: install_result- debug: var: install_result
在 Ansible 里,register
就像給任務裝了個 “記錄儀”,能把任務執行的結果(比如命令輸出、狀態信息)存起來,方便后面的任務 “回看” 或 “利用” 這些結果。
打個比方:就像你讓同事去查一個文件的大小,他回來告訴你 “文件有 100MB”——register
就相當于把這句話記在筆記本上,你后面可以根據這個結果決定 “要不要備份”(如果大于 50MB 就備份)。
基本用法:記錄任務結果
在任務里加register: 變量名
,就會把結果存到這個變量里。比如記錄ls
命令的輸出:
- name: 查看/tmp目錄內容command: ls /tmpregister: tmp_files # 把結果存到tmp_files變量里- name: 打印剛才的結果debug:var: tmp_files # 顯示變量內容
運行后會看到tmp_files
里包含很多信息:命令是否成功(success
)、輸出內容(stdout
)、錯誤信息(stderr
)等。
實用場景:根據結果做判斷
最常用的是結合when
條件,根據記錄的結果決定下一步操作。
比如:檢查某個進程是否存在,存在就重啟,不存在就啟動:
- name: 檢查nginx進程command: pgrep nginxregister: nginx_statusignore_errors: yes # 即使命令失敗(進程不存在)也不終止Playbook- name: 如果進程存在,就重啟nginxservice:name: nginxstate: restartedwhen: nginx_status.rc == 0 # rc=0表示命令成功(進程存在)- name: 如果進程不存在,就啟動nginxservice:name: nginxstate: startedwhen: nginx_status.rc != 0 # rc≠0表示命令失敗(進程不存在)
這里nginx_status.rc
是命令的返回碼(rc
即 return code),0 代表成功,非 0 代表失敗。
常用的結果字段
register
變量里有很多有用的 “子信息”,常用的有:
stdout
:命令的標準輸出(比如ls
列出的文件)stderr
:命令的錯誤輸出(如果命令失敗)rc
:返回碼(0 = 成功,非 0 = 失敗)changed
:任務是否改變了系統狀態(布爾值)failed
:任務是否失敗(布爾值)
比如只想看命令輸出的內容:
- name: 查看系統版本command: cat /etc/os-releaseregister: os_info- name: 打印系統版本信息debug:msg: "系統版本:{{ os_info.stdout }}" # 只取stdout部分
簡單說,register
就是 Ansible 里的 “記事貼”—— 讓任務之間能 “傳遞消息”,根據前面的結果動態決定后面的操作,讓 Playbook 變得更智能、更靈活~
MAGIC 變量
magic 變量由 Ansible 自動設置,可用于獲取與特定受管主機相關的信息。
假設當前清單內容為:
controller[webs]
node1
node2[dbs]
node3
node4
最常用四個 Magic 變量:
-
inventory_hostname,包含清單中配置的當前受管主機的主機名稱。這可能因為各種原因而與FACTS報告的主機名稱不同。
[laoma@controller web]$ ansible node1 -m debug -a 'var=inventory_hostname' node1 | SUCCESS => {"inventory_hostname": "node1" }
-
group_names,列出當前受管主機所屬的所有主機組。
[laoma@controller web]$ ansible node1 -m debug -a 'var=group_names' node1 | SUCCESS => {"group_names": ["webs"] }
-
groups,列出清單中的所有組,以及組中含有的主機。
[laoma@controller web]$ ansible node1 -m debug -a 'var=groups' node1 | SUCCESS => {"groups": {"all": ["workstation","node1","node2","node3","node4"],"dbs": ["node3","node4"],"ungrouped": ["controller"],"webs": ["node1","node2"]} }
-
hostvars,包含所有受管主機的變量,可用于獲取另一臺受管主機的變量的值。如果還沒有為受管主機收集FACTS,則它不會包含該主機的 FACTS。
例如:
hostvars.controller.group_names
在 Ansible 里,“MAGIC 變量”(魔法變量)就像自帶的 “萬能鑰匙”,不需要你手動定義,Ansible 會自動生成并提供這些變量,幫你快速獲取 inventory(主機清單)里的各種信息,讓 Playbook 更靈活地處理主機間的關系。
它們之所以叫 “魔法變量”,是因為你不用聲明就能直接用,就像憑空出現的工具,專門解決和主機清單相關的問題。
最常用的幾個 “魔法變量”:
1. inventory_hostname
:當前主機的 “身份證”
返回當前正在處理的主機在 inventory 里的名字(不是主機的 hostname,而是你在清單里寫的名字)。
比如 inventory 里寫著 web01.example.com
,那這個變量就返回它。
- name: 顯示當前主機名debug:msg: "正在處理的主機:{{ inventory_hostname }}"
2. groups
:主機組的 “花名冊”
返回所有主機組的列表,以及每個組里的主機。比如想知道 webservers
組有哪些主機:
- name: 顯示web服務器組的所有主機debug:msg: "web組主機:{{ groups['webservers'] }}"
如果想獲取所有主機組的名字,用 groups.keys()
。
3. group_names
:當前主機的 “所屬群組”
返回當前主機所在的所有組(列表形式)。比如一臺主機既在 webservers
組又在 prod
組,這個變量就會返回這兩個組名。
- name: 顯示當前主機所屬的組debug:msg: "我屬于這些組:{{ group_names }}"
4. hostvars
:其他主機的 “信息庫”
可以獲取其他主機的變量或 facts 信息,相當于 “跨主機查資料”。
比如想獲取 db01
主機的 IP 地址(需要先收集過 facts):
- name: 顯示db01的IPdebug:msg: "數據庫IP:{{ hostvars['db01']['ansible_default_ipv4']['address'] }}"
5. play_hosts
:當前 Play 的 “任務清單”
返回當前 Play 中正在處理的所有主機(受 hosts
字段限制的主機列表)。
- name: 顯示當前Play要處理的所有主機debug:msg: "本次任務涉及主機:{{ play_hosts }}"
為什么需要魔法變量?
它們就像 Ansible 內置的 “導航系統”,幫你在復雜的主機清單中定位信息:
- 比如跨主機通信(web 服務器需要知道數據庫服務器的 IP)
- 比如根據主機所在組執行不同任務
- 比如動態獲取當前處理的主機信息
管理 SECRETS
Ansible Vault 簡介
Ansible可能需要訪問密碼或API密鑰等敏感數據,此信息可能以純文本形式存儲在清單變量或其他Ansible文件中。任何有權訪問Ansible文件的用戶或存儲這些Ansible文件的版本控制系統都能夠訪問此敏感數據。
這顯然存在安全風險。Ansible隨附的 Ansible Vault 可以加密任何由Ansible使用的結構化數據文件,包括清單變量、playbook中含有的變量文件、在執行playbook時作為參數傳遞的變量文件,以及Ansible角色中定義的變量。
變量管理推薦做法
- 包含敏感變量的文件可通過 ansible-vault 命令進行保護。
- 敏感變量和所有其他變量保存在相互獨立的文件中。
- 管理組變量和主機變量的首選方式是在項目目錄中創建子目錄。
可為每個主機組或受管主機使用獨立的目錄。這些目錄可包含多個變量文件,它們都由該主機組或受管主機使用。
管理 FACTS
FACTS 介紹
FACTS 是 Ansible 在受管主機上自動檢測到的變量,默認保存在內容中,只存在于本次playbook執行期間。
FACTS含有主機相關的信息,可以像play中的常規變量一樣使用。
受管主機的 facts 包括:
? 主機名稱 ? 內核版本 ? 網絡接口 ? IP地址 ? 操作系統版本 ? 各種環境變量
? CPU數量 ? 提供的或可用的內存 ? 可用磁盤空間
借助 facts,可以方便地檢索受管主機的狀態,并根據該狀態確定要執行的操作。
例如:
- 可以根據當前內核版本的FACTS運行條件任務,以此來重新啟動服務器。
- 可以根據通過FACTS報告的可用內存來自定義 MySQL 配置文件。
- 可以根據FACTS的值設置配置文件中使用的 IPv4 地址。
通常,每個play在執行第一個任務之前會先自動收集FACTS。
查看 FACTS 內容
示例1:查看所有變量
---
- name: Dump factshosts: node1tasks:- name: Print all factsdebug:var: ansible_facts
示例2:查看單個變量
---
- hosts: node1tasks:- name: Print Ansible factsdebug: msg: >The default IPv4 address of {{ ansible_fqdn }}is {{ ansible_default_ipv4.address }}
部分 FACTS
FACT | VARIABLE |
---|---|
短主機名 | ansible_facts[‘hostname’] |
完全限定的域名 | ansible_facts[‘fqdn’] |
主要IPv4地址(基于路由) | ansible_facts[‘default_ipv4’][‘address’] |
所有網絡接口的名稱列表 | ansible_facts[‘interfaces’] |
/dev/vdal磁盤分區的大小 | ansible_facts[‘devices’][‘vda’][‘partitions’]['vda1][‘size’] |
DNS服務器列表 | ansible_facts[‘dns’][‘nameservers’] |
當前運行的內核的版本 | ansible_facts[‘kernel’] |
setup 和 gather_facts 模塊
setup 和 gather_facts 模塊都可以用來收集facts:
-
gather_facts 模塊,只能用來收集facts。
-
setup 模塊,除了用來收集facts,還提供額外選項:
-
filter 選項,用于查看特定facts值。
-
gather_subset 選項,用于控制收集facts范圍
-
關閉 FACTS 收集
關閉 FACTS 收集部分原因:
- 不使用任何FACTS
- 希望加快play速度或減小play在受管主機上造成的負載
- 受管主機因為某種原因而無法運行setup模塊
- 需要安裝一些必備軟件后再收集FACTS
Ansible配置文件設置
[defaults]
gathering = explicit
play中設置
---
- name: Fact dumphosts: node1gather_facts: no
即使關閉以后,也可以隨時使用setup模塊收集facts。
實施任務控制
編寫循環任務
利用循環,管理員無需編寫多個使用同一模塊的任務。例如,確保存在五個用戶,不需要編寫五個任務,而是只需編寫一個任務來對含有五個用戶的列表迭代。
Ansible支持使用 loop 關鍵字對一組項目迭代任務。您可以配置循環以利用列表中的各個項目、列表中各個文件的內容、生成的數字序列或更為復雜的結構來重復任務。
簡單循環
簡單循環對一組項目迭代任務。loop關鍵字添加到任務中, 將應對其迭代任務的項目列表取為值。循環變量item保存每個迭代過程中使用的值。
示例:
---
- name: add several usershosts: node1gather_facts: notasks:- name: add user janeuser:name: "jane"groups: "wheel"state: present- name: add user joeuser:name: "joe"state: presentgroups: "wheel"
使用loop循環改寫:
- name: test loophosts: node1gather_facts: notasks:- name: add usersuser:name: "{{ item }}"groups: "wheel"state: presentloop:- jane- joe
這個 Playbook 的作用是在node1
主機上批量創建兩個用戶(jane 和 joe),并將他們加入wheel
組。
name: test loop
:這是 Play 的名稱,用于標識這個 Play 的用途hosts: node1
:指定在node1
這臺主機上執行gather_facts: no
:關閉 facts 收集,加快執行速度(因為這個任務不需要系統信息)- 任務部分使用了
loop
關鍵字,后面跟著一個用戶列表[jane, joe]
在循環過程中,Ansible 會自動將列表中的每個元素依次賦值給item
變量,然后執行user
模塊:
- 第一次循環:
item = jane
,創建用戶 jane 并加入 wheel 組 - 第二次循環:
item = joe
,創建用戶 joe 并加入 wheel 組
user
模塊的參數說明:
name: "{{ item }}"
:用戶名,這里引用循環變量groups: "wheel"
:指定用戶所屬的附加組(wheel 組通常用于 sudo 權限)state: present
:確保用戶存在(如果不存在則創建)
執行這個 Playbook 后,目標主機上會新增 jane 和 joe 兩個用戶,且都屬于 wheel 組。這種循環方式非常適合需要批量執行相同操作的場景,避免了重復編寫任務代碼。
循環散列或字典列表
在以下示例中,列表中的每個項實際上是散列或字典。
示例中的每個散列或字典具有兩個鍵,即name和groups,當前item循環變量中每個鍵的值可以分別通過item.name和item.groups變量來檢索。
示例:
- name: test loophosts: node1gather_facts: notasks:- name: add usersuser:name: "{{ item.name }}"groups: "{{ item.groups }}"state: presentloop: - name: janegroups: wheel- name: joegroups: root
改寫為:
---
- name: add several usershosts: node1gather_facts: novars:users:- name: janegroups: wheel- name: joegroups: roottasks:- name: add users user:name: "{{ item.name }}"state: presentgroups: "{{ item.groups }}" loop: "{{ users }}"
Register 與 Loop
示例:
---
- name: Loop Register Testhosts: node1gather_facts: notasks:- name: Looping Echo Taskshell: "echo This is my item: {{ item }}"loop:- one- tworegister: result- name: Show result variabledebug:var: result- name: Show result variable stdoutdebug:msg: "STDOUT from previous task: {{ item.stdout }}"loop: "{{ result.results }}"
編寫條件任務
Ansible可使用conditionals在符合特定條件時執行任務或play。
例如,管理員可利用條件來區分不同的受管節點,并根據它們所符合的條件來分配功能角色。 Playbook變量、注冊的變量和ANSIBLE FACTS都可通過條件來進行測試。可以使用比較字符串、數字數據和布爾值的運算符。
用例:
- 定義變量min_memory,判斷被管理節點可用內存是否滿足該值。
- 捕獲命令輸出,判定task是否執行完成,以便決定是否進行下一步操作。
- 被管理節點上收集到的網絡facts,判定是否適合哪種綁定(bonding或者trunking)。
- 根據CPU的數量決定如何調優web服務器。
- Registered變量與預定義的變量對比,判斷是否有變化。例如文件的MD5值。
when 語句
ansible playbook 中使用 when 來運行條件任務。
when 用于有條件地運行任務,取要測試的條件作為值。如果條件滿足,則運行任務。若條件不滿足,則跳過任務。
注意:通常的慣例是將可能存在的任何when關鍵字放在任務名稱和模塊(及模塊參數)的后面。原因是任務是YAML散列/字典,when 語句只是任務中的一個鍵,就如任務的名稱以及它所使用的模塊一樣。
常見判斷
操作 | 示例 |
---|---|
等于(值為字符串) | ansible_machine == “x86_64” |
等于(值為數字) | max_memory == 512 |
小于 | min_memory < 128 |
大于 | min_memory > 256 |
小于等于 | min_memory <= 256 |
大于等于 | min_memory >= 512 |
不等于 | min_memory != 512 |
變量存在 | min_memory is defined |
變量不存在 | min_memory is not defined |
布爾變量值是1、True或yes的求值為真。 | memory_available |
布爾變量值是0、False或no的求值為假。 | memory_available |
memory_available變量值為真,最終結果為假。 | not memory_available |
第一個變量的值存在,作為第二個變量的列表中的值 | ansible_distribution in supported_distros |
布爾值變量判斷
示例:
---
- name: test hosts: node1gather_facts: novars:run_my_task: truetasks:- name: test whendebug:msg: "Hello run my task"when: run_my_task
示例中的when語句導致:任務僅在run_my_task為 true時運行。
變量是否定義判斷
變量判斷:
- defined == not undefined 變量定義返回真
- undefined == not defined 變量未定義返回真
- none 變量定義了,但是值是空值,返回真
示例1:
---
- hosts: node1gather_facts: novars:username: laomatasks:- debug:msg: "var: username is defined"when: username is defined
示例2:判斷受管主機是否具有相應設備。
---
- name: create and use lvhosts: node1tasks:- name: Create a logical volume of 4000mlvol:vg: researchlv: datasize: 4000when: ansible_lvm.vgs.research is defined- debug:msg: Volume group does not existwhen: ansible_lvm.vgs.research is not defined
文件屬性判斷
- file:如果路徑是一個普通文件,返回真
- directory:如果路徑是一個目錄,返回真
- link:如果路徑是一個軟連接,返回真
- mount:如果路徑是一個掛載點,返回真
- exist:如果路徑是存在,返回真
示例:
---
- hosts: node1gather_facts: novars:file_name: /etc/hoststasks:- debug:msg: "{{ file_name }} is regular file"when: file_name is file
任務執行結果判斷
- succeeded,通過任務的返回信息判斷,任務執行成功返回真。
- failed,通過任務的返回信息判斷,任務執行失敗返回真。
- changed,通過任務的返回信息判斷,任務執行狀態為changed返回真。
- skipped,通過任務的返回信息判斷,任務沒有滿足條件跳過執行,返回真。
示例:
---
- hosts: node1gather_facts: novars:doshell: "yes"tasks:- shell: cat /etc/hostsregister: resultignore_errors: truewhen: doshell == "yes"- name: successdebug:msg: successwhen: result is succeeded- name: faileddebug:msg: failedwhen: result is failed- name: changeddebug:msg: changedwhen: result is changed- name: skippeddebug:msg: skipwhen: result is skipped
其他測試:
- 設置doshell: “no”
- 設置shell: cat /etc/hosts-no-exist
in 和 not in 判斷
示例1:給用戶添加組
---
- name: test hosts: node1gather_facts: novars:username: devopssupergroup: wheeltasks:- name: gather user informationshell: id {{ username }}register: result- name: Task run if user is in supergroupsuser:name: "{{ username }}"groups: "{{ supergroup }}"append: yeswhen: supergroup not in result.stdout
示例2:給用戶添加多個組
---
- name: test hosts: node1gather_facts: novars:username: devopssupergroups: - wheel- roottasks:- name: gather user informationshell: id {{ username }}register: result- name: Task run username is in supergroupsuser:name: "{{ username }}"groups: "{{ item }}"append: yeswhen: item not in result.stdoutloop: "{{ supergroups }}"
Ansible Handlers
Ansible Handlers 功能
Ansible的模塊的設計是可以多次執行的,當被管理節點是預期狀態時,是不會做任何更改的。然而,有時候執行了一個任務,還需要進一步執行下一個任務。
例如,更改了服務配置文件之后,需要重新加載配置文件才能生效。Handlers是由其他任務通知執行的任務,可看做inactive任務,通過notify調用。
示例:
---
- name: deploy web serverhosts: node1tasks:- name: install packagesyum:name: httpdstate: presentnotify:- enable and restart apache- name: install httpd-manualyum:name: httpd-manualstate: presentnotify:- enable and restart apache- debug: msg: last task in taskshandlers:- name: enable and restart apacheservice:name: httpdstate: restartedenabled: yes
在 Ansible 中,Handlers(處理器) 就像一個 “待命的助手”,專門用來處理那些 “只有在系統狀態發生變化時才需要執行” 的操作。它有點像 “觸發器”—— 只有當某個任務真正改變了系統狀態(比如修改了配置文件),Handlers 才會被觸發執行。
為什么需要 Handlers?
舉個例子:當你修改了 Nginx 的配置文件(nginx.conf
),只有在配置文件真的被改動時,才需要重啟 Nginx 服務。如果配置文件沒變化,重啟操作就是多余的。
Handlers 正是為這種場景設計的:它能 “監聽” 任務是否導致了系統狀態變更(通過changed: true
標識),只有變更發生時才執行指定操作,避免無效重復執行。
基本用法:定義和觸發 Handlers
Handlers 的使用分為兩步:定義 Handlers 和 在任務中通知(notify)Handlers。
- name: 配置Nginx并按需重啟hosts: webserverstasks:- name: 修改Nginx配置文件copy:src: ./nginx.confdest: /etc/nginx/nginx.confnotify: # 當這個任務導致狀態變更時,通知Handlers- 重啟Nginx服務 # 這里的名稱要和Handlers中定義的一致handlers: # 定義Handlers(放在Play的handlers塊中)- name: 重啟Nginx服務 # 名稱要和notify中的完全匹配service:name: nginxstate: restarted
執行邏輯:
- 執行 “修改 Nginx 配置文件” 任務:
- 如果配置文件有變化(
changed: true
),則標記 “重啟 Nginx 服務” 這個 Handler 為 “待執行” - 如果配置文件無變化(
changed: false
),則不通知 Handler
- 如果配置文件有變化(
- 當 Play 中所有任務執行完畢后,Ansible 會統一執行所有被標記為 “待執行” 的 Handlers。
Handlers 的特點
-
延遲執行:Handlers 不會在被通知后立即執行,而是等到當前 Play 中所有任務都執行完才統一運行。
-
冪等性保障:即使多個任務通知同一個 Handler,它也只會執行一次。例如:
tasks:- name: 修改配置文件Acopy: src=a.conf dest=/etc/nginx/notify: 重啟Nginx服務- name: 修改配置文件Bcopy: src=b.conf dest=/etc/nginx/notify: 重啟Nginx服務
無論兩個任務是否都觸發了變更,“重啟 Nginx 服務” 只會執行一次,避免多次重啟。
-
名稱唯一:Handlers 通過
name
標識,notify
必須嚴格匹配名稱(包括大小寫),否則無法觸發。
常見使用場景
Handlers 最適合處理 “配置變更后需要生效的操作”,例如:
- 服務重啟(Nginx、MySQL、Apache 等)
- 重新加載配置(
systemctl reload
) - 重建緩存(如
systemctl daemon-reload
) - 重新編譯(當源碼或 Makefile 被修改時)
簡單說,Handlers 就像 “按需執行的收尾工作”—— 平時待命,只有當系統真的發生了需要它處理的變化時,才會出手完成必要的后續操作,讓 Playbook 更高效、更符合實際運維邏輯。
處理 Errors
Errors 介紹
Ansible評估各任務的返回代碼,從而確定任務是成功還是失敗。通常而言, 當某個主機執行任務失敗時,Ansible將立即終止該主機繼續執行play,其他主機可以繼續執行play。
---
- name: testhosts: node1,node2tasks:- name: show /etc/myhostsshell: cat /etc/myhosts- name: echo enddebug:msg: echo end
ignore_errors
您可能希望即使在任務失敗時也繼續執行play。例如,您或許預期特定任務有可能會失敗,并且希望通過有條件地運行某項其他任務來恢復。
ignore_errors可以定義在以下位置:
- 定義在 play 中,則play中所有任務忽略錯誤。
- 定義在 task 中,則特定task忽略錯誤。
示例:
---
- name: testhosts: node1tasks:- name: install a not exist packageyum:name: notexitpackagestate: presentignore_errors: yesregister: result- name: debug install resultdebug:msg: notexitpackage is not exitwhen: result is failed
fail 模塊
fail 模塊,執行該任務,任務必定 failed。
示例:
- name: test fail modulehosts: node1gather_facts: notasks:- debug:msg: task1- fail:- debug:msg: task3
提示:fail模塊本身也可以配置when判斷,實現說明情況下任務是失敗的。
failed_when
指明什么條件下,判定任務執行失敗。
示例:
- name: test failed_whenhosts: node1tasks:- shell: /root/adduserregister: command_resultfailed_when: "'failed' in command_result.stdout"
環境準備:
[root@node1 ~]# cat /root/adduser
#!/bin/bash
useradd devops &> /dev/null
if [ $? -eq 0 ];thenecho add user devops success
elseecho add user devops failed
fi
[root@node1 ~]# chmod +x /root/adduser
以上示例:
- 當devops用戶不存在時,shell模塊跳過執行。
- 當devops用戶存在時,shell模塊執行失敗。
以上示例可改寫為fail模塊和when語句聯合使用:
- name: test fail modulehosts: node1tasks:- shell: /root/adduserregister: command_result- fail:msg: "add user devops failed"when: "'failed' in command_result.stdout"
changed_when
指明什么條件下,判定任務執行結果為changed。
示例1:
- name: changed_whenhosts: node1tasks:- name: upgrade-databaseshell: /usr/local/bin/upgrade-databaseregister: resultchanged_when: "'Success' in result.stdout"notify:- restart_databasehandlers:- name: restart_databaseservice:name: mariadbstate: restarted