Ansible 劇本(Playbook)
Ansible Playbook 基礎介紹
Playbook 是 ansible 用於配置,部署,和管理被控節點的劇本。
- playbook類似Linux的shell腳本,用於實現和管理大量的、規律的、複雜的操作任務
- playbook方便代碼和配置的重用、移植性好,同時也易於管理
Playbook格式
# playbook文件由YMAL語言編寫,需遵循yaml格式要求
1. 第一行以 "---" 開始,表明YMAL文件的開始(非playbook強制要求,沒有也能通過語法檢查)
2. 列表元素以”-”開頭,後面帶有一個空格,然後元素內容
3. 對象的鍵與值以":"和一個空格來分隔
4. 相同層次內容必須保持相同的縮進和對齊,否則會報錯
5. 在同一行中,“#” 之後的內容表示註釋
6. 文件名應該以.yml或.yaml結尾
# 編寫完成後可以通過 --syntax-check 子命令檢查語法錯誤
ansible-playbook --syntax-check sample.yaml
Playbook組成
- 一個Playbook可以包括一個或多個Play。
- 一個Play由Host的無序集合與Task的有序列表組成。
- 每一個Task僅由一個模塊構成。
# play
- 一個Playbook包括一個或多個Play
- 一個Play由Host的無序集合與Task的有序列表組成
# 目標定義部分:host
- 執行playbook的主機信息
# 變量定義部分:variable
- 執行playbook需要的變量
- 設置的ansible配置變量,例如gather_facts
# 任務定義部分:tasks & module
- 在目標主機執行的任務列表和調用的模塊
- Play的主體部分,Task列表中的各任務按次序逐個在Hosts中的指定主機上執行
- 每一個Task僅由一個模塊構成
- 建議爲每一個Task設置一個Name,便於在運行Playbook時從輸出結果辨別task信息
- Ansible的自帶模塊Command模塊和Shell模塊無需使用key=value格式,可以直接編寫要執行的命令
# 觸發器定義部分:handlers
- task執行完成後需要調用的任務,通常結合關鍵字notify
- 由通知者進行的Notify,如果沒有被Notify,則Handlers不會執行,如果被Notify了,則Handlers被執行
- 不管有多少個通知者進行了Notify,等到Play中的所有Task都執行完成之後,Handlers也只會被執行一次
- 實質上也是Task的列表
Ansible Playbook 示例
[root@test01 ansible-test]# pwd
/root/ansible-test
[root@test01 ansible-test]#
[root@test01 ansible-test]# ll
total 4
drwxr-xr-x 2 root root 24 Oct 17 09:47 conf
-rw-r--r-- 1 root root 922 Oct 14 11:24 sample.yaml
[root@test01 ansible-test]#
[root@test01 ansible-test]# tree
.
├── conf
│ └── httpd.conf
└── sample.yaml
1 directory, 2 files
[root@test01 ansible-test]#
[root@test01 ansible-test]# cat conf/httpd.conf
MaxClients {{ maxClients }}
Listen {{ httpd_port }}
[root@test01 ansible-test]#
[root@test01 ansible-test]# cat sample.yaml
- hosts: ta
remote_user: vipxf
gather_facts: no
vars:
- package: vim
tasks:
- name: install vim
yum: name={{ package }} state=latest
- name: install configuration file
become: yes
become_method: su
become_flags: "-"
become_user: root
template: src=/root/ansible-test/conf/httpd.conf dest=/root/test-template.conf
notify:
- check-hostname-date
- name: copy file
copy: src=/root/ansible-test/conf/httpd.conf dest=/home/vipxf/test-copy.conf
handlers:
- name: check-hostname-date
shell: hostname && date
ignore_errors: True
- hosts: ta
remote_user: vipxf
gather_facts: no
become: yes
become_method: su
become_flags: "-"
become_user: root
vars:
- newgroup: testgroup
- newuser: testuser
tasks:
- name: create new group
group: name={{ newgroup }} system=yes
- name: create new user
user: name={{ newuser }} group=testgroup system=yes
[root@test01 ansible-test]#
[root@test01 ansible-test]# cat /etc/ansible/hosts
[ta]
172.20.8.247 ansible_ssh_port=2222 ansible_ssh_user=vipxf ansible_ssh_pass=Anliven09!
[ta:vars]
httpd_port=80 maxClients=10
[root@test01 ansible-test]#
Ansible playbook 示例說明
- hosts: ta # 指定執行指定任務的主機,可以通過一個或多個由冒號分隔主機組
remote_user: vipxf # 指定在遠程主機執行任務的用戶,也能用在task中
gather_facts: no
vars:
- package: vim
tasks: # 任務列表,在指定主機上按順序執行各任務
- name: install vim # 每個任務的名稱,用於輸出執行結果
yum: name={{ package }} state=latest
- name: install configuration file
become: yes
become_method: su
become_flags: "-"
become_user: root
template: src=/root/ansible-test/conf/httpd.conf dest=/root/test-template.conf
notify: # 此任務狀態爲changed時採取的操作,在handlers中定義
- check-hostname-date
- name: copy file
copy: src=/root/ansible-test/conf/httpd.conf dest=/home/vipxf/test-copy.conf
handlers: # 觸發器,在被指定任務通知和所有任務完成的情況下只執行一次,實質上也是按序執行的任務列表
- name: check-hostname-date
shell: hostname && date
ignore_errors: True # 如果命令或腳本的退出碼不爲零,使用ignore_errors來忽略錯誤信息
- hosts: ta # playbook文件可以有多個play
remote_user: vipxf
gather_facts: no
become: yes
become_method: su
become_flags: "-"
become_user: root
vars:
- newgroup: testgroup
- newuser: testuser
tasks:
- name: create new group
group: name={{ newgroup }} system=yes
- name: create new user
user: name={{ newuser }} group=testgroup system=yes
相關命令
# Ansible具有冪等性,會自動跳過沒有變化的部分
# 建議使用絕對路徑來執行playbook的yaml文件
# 語法檢查 和 預測試 不能保證結果絕對正確,實際的運行測試是有必要的
# 特別注意yml文件的內容格式(空格、縮進)、特殊字符等,必要時重寫重建
--------------------------------------------------------------------------------
## 列出主機列表、任務列表和標籤列表
ansible-playbook --list-hosts --list-tasks --list-tags sample.yaml
--------------------------------------------------------------------------------
## 檢查內容語法錯誤
ansible-playbook --syntax-check sample.yaml
## 預測試(不改變目標主機的任何設置)
ansible-playbook --check sample.yaml --ask-become-pass
ansible-playbook -C sample.yaml --ask-become-pass
--------------------------------------------------------------------------------
## 選擇playbook開始執行的任務
ansible-playbook sample.yaml --start-at-task="common" # 從名稱爲common的任務開始執行playbook
## 詳細模式下執行playbook,根據提示輸入root密碼
ansible-playbook -v sample.yaml --ask-become-pass
## verbose mode (-vvv for more, -vvvv to enable connection debugging)
ansible-playbook -vvv sample.yaml --ask-become-pass
返回結果
# 一般以紅色、黃色、綠色來表示執行結果
- 綠色:執行成功,未更改目標主機狀態
- 黃色:執行成功,對目標主機完成變更,目標主機狀態有變化
- 紫色:執行結果出現警告或提示信息
- 紅色:執行失敗,結果出現異常
# 在playbook執行後,會明確列出任務執行狀態的彙總信息
# 任務執行狀態包括:ok、changed、unreachable、failed、skipped、rescued、ignored
結果示例
[root@test02 ~]# ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:50:56:a1:07:78 brd ff:ff:ff:ff:ff:ff
inet 172.20.8.247/24 brd 172.20.8.255 scope global noprefixroute ens192
valid_lft forever preferred_lft forever
inet6 fe80::250:56ff:fea1:778/64 scope link
valid_lft forever preferred_lft forever
[root@test02 ~]#
[root@test02 ~]# vim -h | head -n 1
VIM - Vi IMproved 7.4 (2013 Aug 10, compiled Jun 14 2019 10:43:10)
[root@test02 ~]#
[root@test02 ~]# pwd
/root
[root@test02 ~]# ll test-template.conf
-rw-r--r-- 1 root root 38 Oct 17 18:18 test-template.conf
[root@test02 ~]#
[root@test02 ~]# ll /home/vipxf/test-copy.conf
-rw-rw-r-- 1 vipxf vipxf 66 Oct 17 18:18 /home/vipxf/test-copy.conf
[root@test02 ~]#
[root@test02 ~]# cat /etc/group |grep test
testgroup:x:995:
testuser:x:994:
[root@test02 ~]# cat /etc/passwd |grep test
testuser:x:997:995::/home/testuser:/bin/bash
[root@test02 ~]#
Ansible Playbook 條件判斷(When)
when語句可以將變量、facts或此前任務的執行結果作爲指定task是否執行的前提條件,也可以判斷變量是否被定義。
示例-1:when
- hosts: ta
remote_user: vipxf
gather_facts: yes
tasks:
- name: test
shell: echo "test"
when: ansible_os_family == "RedHat" and ansible_distribution_version == "7.8" # 可以使用facts、playbook 或Inventory中定義的變量
- name: test1
shell: echo "step1"
register: result # 將執行結果定義爲變量
- name: test2
shell: echo "step2"
when: result.rc == 0 # 基於先前任務的結果來執行
- name: test3
shell: echo "step3"
when: result.stderr != ""
- name: test4
shell: echo "step4"
when: result.changed == "true"
示例-2:結合fail語句
test.sh
[vipxf@test02 ~]$ pwd
/home/vipxf
[vipxf@test02 ~]$
[vipxf@test02 ~]$ ll test.sh
-rw-rw-r-- 1 vipxf vipxf 127 Oct 21 00:11 test.sh
[vipxf@test02 ~]$
[vipxf@test02 ~]$ cat test.sh
#!/bin/bash
if [ "$1" = "Anliven" ];then
echo "Success"
else
echo "Failed"
fi
[vipxf@test02 ~]$
when2.yaml
[root@test01 ansible-test]# cat when2.yaml
- hosts: ta
remote_user: vipxf
gather_facts: yes
vars:
- teststring: Failed
tasks:
- name: test1
shell: sh /home/vipxf/test.sh {{ testvar }}
register: result
- name: test2
shell: echo "rc 0"
when: result.rc == 0 # 基於先前任務的結果來執行
- name: test3
shell: echo "Success"
when: '"Success" in result.stdout' # 對stdout結果進行判斷
- name: test4
fail: msg="Check Failed" # 任務報錯並拋出msg信息
when: result.stdout == (( teststring )) # 在when使用playbook中定義的變量
[root@test01 ansible-test]#
[root@test01 ansible-test]# ansible-playbook -v when2.yaml -e testvar=Anliven
Using /etc/ansible/ansible.cfg as config file
PLAY [ta] *********************************************************************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************************************************
ok: [172.20.8.247]
TASK [test1] ******************************************************************************************************************************************************************************************
changed: [172.20.8.247] => {"changed": true, "cmd": "sh /home/vipxf/test.sh Anliven", "delta": "0:00:00.003638", "end": "2022-10-21 18:05:00.273596", "rc": 0, "start": "2022-10-21 18:05:00.269958", "stderr": "", "stderr_lines": [], "stdout": "Success", "stdout_lines": ["Success"]}
TASK [test2] ******************************************************************************************************************************************************************************************
changed: [172.20.8.247] => {"changed": true, "cmd": "echo \"rc 0\"", "delta": "0:00:00.002893", "end": "2022-10-21 18:05:00.722340", "rc": 0, "start": "2022-10-21 18:05:00.719447", "stderr": "", "stderr_lines": [], "stdout": "rc 0", "stdout_lines": ["rc 0"]}
TASK [test3] ******************************************************************************************************************************************************************************************
changed: [172.20.8.247] => {"changed": true, "cmd": "echo \"Success\"", "delta": "0:00:00.002632", "end": "2022-10-21 18:05:01.172659", "rc": 0, "start": "2022-10-21 18:05:01.170027", "stderr": "", "stderr_lines": [], "stdout": "Success", "stdout_lines": ["Success"]}
TASK [test4] ******************************************************************************************************************************************************************************************
skipping: [172.20.8.247] => {"changed": false, "skip_reason": "Conditional result was False"}
PLAY RECAP ********************************************************************************************************************************************************************************************
172.20.8.247 : ok=4 changed=3 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
[root@test01 ansible-test]#
[root@test01 ansible-test]# ansible-playbook -v when2.yaml -e testvar="test"
Using /etc/ansible/ansible.cfg as config file
PLAY [ta] *********************************************************************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************************************************
ok: [172.20.8.247]
TASK [test1] ******************************************************************************************************************************************************************************************
changed: [172.20.8.247] => {"changed": true, "cmd": "sh /home/vipxf/test.sh test", "delta": "0:00:00.003612", "end": "2022-10-21 18:06:49.907767", "rc": 0, "start": "2022-10-21 18:06:49.904155", "stderr": "", "stderr_lines": [], "stdout": "Failed", "stdout_lines": ["Failed"]}
TASK [test2] ******************************************************************************************************************************************************************************************
changed: [172.20.8.247] => {"changed": true, "cmd": "echo \"rc 0\"", "delta": "0:00:00.002721", "end": "2022-10-21 18:06:50.338001", "rc": 0, "start": "2022-10-21 18:06:50.335280", "stderr": "", "stderr_lines": [], "stdout": "rc 0", "stdout_lines": ["rc 0"]}
TASK [test3] ******************************************************************************************************************************************************************************************
skipping: [172.20.8.247] => {"changed": false, "skip_reason": "Conditional result was False"}
TASK [test4] ******************************************************************************************************************************************************************************************
fatal: [172.20.8.247]: FAILED! => {"changed": false, "msg": "Check Failed"}
PLAY RECAP ********************************************************************************************************************************************************************************************
172.20.8.247 : ok=3 changed=2 unreachable=0 failed=1 skipped=1 rescued=0 ignored=0
[root@test01 ansible-test]#
changed_when 與 failed_when
# changed_when
# 在條件成立時,將對應任務的執行狀態設置爲changed
# 先執行task,並對task返回的值進行判斷,當滿足changed_when指定的條件時說明是執行成功的
# 默認情況下執行命令完成的主機狀態都爲changed
- name: all host run this task
shell: hostname
register: info
changed_when: '"webserver01" in info.stdout' # 輸出包含某個特定字符才能將主機狀態設爲changed
# failed_when
# 在條件成立時,將對應任務的執行狀態設置爲失敗
# 當執行失敗後會將信息存在register的stderr中,通過判斷指定的字符是否在stderr中來確定是否真的失敗
# 其實是ansible的一種錯誤處理機制:由fail模塊組合了when條件語句的效果
- name: this command prints FAILED when it fails
command: echo "FAILED"
register: command_result
failed_when: "'FAILED' in command_result.stdout"
Ansible Playbook 循環迭代(with_items)
用來在playbook中實現循環迭代的功能。
支持元素列表、文件名(with_fileglob)、複合(with_together)、步進(with_sequence)、隨機(with_random_choice)、until等多種類型循環。
- name: test with_items
hosts: ta
remote_user: vipxf
gather_facts: yes
tasks:
- name: test1
shell: echo {{ item }} # 通過引用item變量來迭代
with_items:
- one
- two
- three
- name: test2
shell: echo {{ item.name }} with {{ item.value }} # 通過字典來迭代
with_items:
- { name: 'one', value: '111'}
- { name: 'two', value: '222'}
- { name: 'three', value: '333'}
- name: test3
copy: src={{ item }} dest=~/ mode=664 owner=vipxf group=vipxf # 把文件名作爲變量循環
with_fileglob:
- /playbook/files/*
Ansible Playbook 角色(Roles)
- 角色(roles)用於層次性、結構化地組織playbook
- 通過規範的目錄存儲結構將變量、文件、任務、模板以及觸發器等放置於單獨目錄
- 通過獨立的role目錄將不同的playbook任務單一化,從而實現高效的代碼複用
- 在playbook中調用roles,根據層次型結構自動轉載roles的變量文件、tasks以及handlers等要素信息
- 一般用於基於主機構建服務和進程的場景
- 可以在 /etc/ansible/ansible.cfg 中設置 roles_path 來設置roles文件路徑
創建role
1. 創建 roles 名稱的目錄
2. 在 roles 目錄中分別創建各角色命名的目錄,如webserver等
3. 在每個角色命名的目錄中分別創建defaults、files、handlers、meta、tasks、templates和vars等目錄;如果爲空目錄則不被引用
4. 在roles的整體編排文件playbook中調用各角色
目錄結構說明
site.yml # playbook文件,roles的整體編排文件
roles/ # 定義各角色的總目錄
common/ # 角色role模塊名,在playbook中需要調用時使用的名稱
default/ # 默認變量文件目錄(定義此角色的默認變量),至少包含一個main.yml文件,在main.yml文件中可以用include指令將其他yml文件包含進來
files/ # 存放由copy或script等模塊調用的文件
handlers/ # 觸發器文件目錄,至少包含一個main.yml文件,在main.yml文件中可以用include指令將其他yml文件包含進來
meta/ # 此角色的元數據(特殊設定及其依賴關係),至少包含一個main.yml文件,在main.yml文件中可以用include指令將其他yml文件包含進來
tasks/ # 任務文件目錄,至少包含一個main.yml文件,在main.yml文件中可以用include指令將其他yml文件包含進來
templates/ # 模板文件目錄,template模塊會自動在此目錄中尋找
vars/ # 變量文件目錄,至少包含一個main.yml文件,在main.yml文件中可以用include指令將其他yml文件包含進來
webserver/ # 角色 webserver 目錄
default/
files/
handlers/
meta/
tasks/
templates/
vars/
調用方式
在playbook中可以通過關鍵字 roles 來調用角色role。
- hosts: webserver
remote_user: anliven
roles: # 使用關鍵字roles調用角色role
- common # 調用role
- webserver # 調研多個role
- hosts: webserver
remote_user: anliven
roles:
- common
- { role: foo_app_instance, dir:'/opt/a',port:5000} # 可以向roles傳遞參數
- { role: foo_app_instance, dir:'/opt/b',port:5001}
- hosts:webserver
remote_user: anliven
roles:
- { role: some_role, when: "ansible_so_family == 'RedHat" } # 指定調用roles的條件
Ansible Playbook 標籤(Tags)
- 通過標籤(tags)可以選擇運行或跳過playbook中的指定任務
- 在playbook爲任務定義一個"標籤",在通過ansible-playbook命令執行此playbook時使用 --tags 或 --skip-tags選項選擇運行或跳過這個任務
- 可以使用系統特殊標籤,也可以自定義標籤
- 可以爲同一個任務設定多個標籤,也可以爲不同的任務設定相同的標籤
語法格式
# 格式1
tags:
- testtag
- t2
# 格式2
tags: testtag,t2
# 格式3
tags: ['testtag','t2']
特殊標籤
always 除非--skip-tags選項指定,否則 always 標籤的task會一直執行
never 除非--tags選項指定,否則 never 標籤的task都不會執行
tagged 不包括never的所有標籤
untagged 所有無標籤和always標籤
all 包括非never標籤和無標籤
選項
# 如果執行 ansible-playbook 時不指定標籤,則會執行所有非 never 標籤的任務
--tags "tag1,tag2..." 執行指定標籤和always標籤的tasks
--tags always 只執行always標籤的tasks
--tags all 執行所有非never標籤和無標籤的tasks
--tags never 執行always和never標籤的tasks
--tags tagged 執行所有標籤的tasks,但不包括never標籤的tasks
--tags untagged 執行所有無標籤和always標籤的tasks
--skip-tags "tag1,tag2..." 跳過指定標籤的tasks
--list-tags 查看playbook中哪些tags會被執行
tags示例
tasks:
- name: install package
yum: name={{ packagename }} state=latest
tags:
- always
- name: copy configuration file
copy: src=/root/conf/httpd.conf dest=/etc/httpd/conf/httpd.conf
tags: conf,http
命令示例
ansible-playbook nginx_tags.yaml --tags "testtag,t2"
Ansible Playbook 調試(Debug)
- Print statements during execution
- 在Ansible Playbook中常使用debug模塊,可以在Playbook執行過程打印調試信息
- 結合when條件語句一起使用時,可以調試特定條件下的執行過程
注意:在setup模塊中查詢出來的變量,直接可以在debug中直接作爲變量引用。
# msg
輸出自定義信息,如果不指定或不寫msg的話,默認也會輸出“null”
# var
- 指定要打印的變量名,與msg參數互斥,二者只能有一個
- var參數中的變量不需要使用{{}}表達式,而msg中需要
# verbosity
- debug的調試級別,默認0是全部顯示,級別調整到3是忽略內容不顯示
- 在命令中使用-vvv參數,可以在設置爲3情況下仍然顯示debug內容
示例
tasks:
- name: Host run this task
debug: 'msg="{{ ansible_fqdn }} and {{ ansible_default_ipv4.address }}"' # 打印必要信息
when: ansible_memtotal_mb < 500 and ansible_processor_cores == 2 # 結合when使用
- name: all host run this task
shell: hostname
register: info
- name: Hostname is webserver01 Machie run this task
debug: 'msg="{{ ansible_fqdn }}"'
when: info['stdout'].startswith('Success')
- name: Show debug info
debug: var=info verbosity=1 # 打印var變量信息,調試級別爲1