playbook 劇本編寫
Asible的指令碼 ---playbooks劇本
目錄一: playbooks 簡述
1.1 什麼是playbooks
Playbooks 是 Ansible的配置,部署,編排語言.他們可以被描述為一個需要希望遠端主機執行命令的方案,或者一組IT程式執行的命令集合.
在基礎層面, playbooks 可以被用來管理用於部署到遠端主機的配置檔案.在更高的層面上,playbooks 可以依次對多層式架構上的伺服器執行上線包括滾動更新在內的操作並可以將操作委託給其他主機包括在此過程中發生的與監視伺服器,負載均衡伺服器的互動操作在內.
Playbooks 的格式是YAM,語法做到最小化,意在避免 playbooks 成為一種程式語言或是指令碼,但它也並不是一個配置模型或過程的模型.
playbook 由一個或多個 ‘plays’ 組成.它的內容是一個以 ‘plays’ 為元素的列表.
在 play 之中,一組機器被對映為定義好的角色.在 ansible 中,play 的內容,被稱為 tasks,即任務.在基本層次的應用中,一個任務是一個對 ansible 模組的呼叫,這在前面章節學習過.
‘plays’ 好似音符,playbook 好似由 ‘plays’ 構成的曲譜,通過 playbook,可以編排步驟進行多機器的部署,比如在 webservers 組的所有機器上執行一定的步驟, 然後在 database server 組執行一些步驟,最後回到 webservers 組,再執行一些步驟,諸如此類.
“plays” 算是一個體育方面的類比,你可以通過多個 plays 告訴你的系統做不同的事情,不僅是定義一種特定的狀態或模型.你可以在不同時間執行不同的 plays.
1.2 playbooks的組成
playbooks本身由以下各部分組成
- Tasks:任務,即通過task呼叫ansible的模板將多個操作組織在一個playbook中執行
- Variables:變數
- Templates:模板
- Handlers:處理器,當changed狀態條件滿足時, (notify)觸發執行的操作
- Roles:角色
1.3 playbooks劇本示例
1.3.1 先修改/etc/ansible/hosts主機清單
[root@host103 ~]# vim /etc/ansible/hosts
...
#將主機ip或者主機名(要做主機名對映) 加入webservers組
[webservers]
192.168.23.105
....
1.3.2 編寫安裝並啟動httpd服務的劇本
#劇本名應該是 .yml 或者 .yaml 結尾
[root@host103 opt]# vim daemon1.yaml
--- #yaml檔案以 三個短橫槓--- 開頭,表明是一個yaml檔案。可以省略
- name: first palybook # 定義一個play 的名稱。可以省略
gather_facts: false #設定不進行facts資訊收集。這樣可以加快執行速度。預設是true。可以省略
hosts: webservers #指定要執行任務的被管理主機,多個主機組用冒號: 隔開。
remote_user: root #指定被管理主機上執行任務的使用者
tasks: #定義任務列表。任務列表中的各個任務安次序在hosts定義的主機上執行
- name: test connection #自定義任務名稱。可以省略。但是為了排查,不建議省略
ping: #使用module: [options] 格式來定義任務。這裡使用ping 模組
- name: disable selinux
command: '/sbin/setenforce 0' #command和shell 模組無需使用key=value的模式。引號可省
#忽略失敗任務。如果命令返回值不為0,就會報錯,退出playboos。所以使用此選項,忽略失敗的任務
ignore_errors: true
- name: disable firewalld
#使用module: options的格式來定義任務.option 使用key=value的格式
service: name=firewalld state=stopped enabled=no
- name: install apache
yum: name=httpd state=latest
- name: start apache service
service: name=httpd state=started enabled=yes
- name: modify httpd configuration file
#需要事先準備好/opt/httpd.conf檔案
copy: src=/opt/httpd.conf dest=/etc/httpd/conf/httpd.conf
#設定觸發器。如果以上操作後為change狀態,會通過notify 指定的名稱觸發對應名稱的handlers操作
notify: "restart httpd"
#handlers中定義的就是任務
handlers:
#設定任務名。應該和notify指定的任務名相同
- name: restart httpd
#設定執行的動作。這裡使用service模組,執行重啟httpd的動作
service: name=httpd state=restarted
#Ansible在執行完某個任務之後並不會立即去執行對應的handler,而是在當前play中所有普通任務都執行完後再去執行handler,這樣的好處是可以多次觸發notify,但最後只執行一次對應的handler,從而避免多次重啟。
1.3.3 準備/opt/httpd.conf檔案
root@host103 opt]# vim httpd.conf
#配置監聽埠和域名(正常是使用80埠)
42// Listen 8080
95// ServerName www.mynet.com:8080
1.4 執行ploybooks劇本
格式:
ansible-playbook 劇本名
補充引數:
-k (-ask-pass):用來互動輸入ssh密碼
-K (-ask-become-pass):用來互動輸入sudo密碼
-u :指定使用者
#管理主機執行playbooks劇本
[root@host103 opt]# ansible-playbook demo1.yaml
#訪問測試
[root@host105 ~]# firefox http://192.168.23.105:8080
1.5 檢查yaml檔案,和指定task開始執行
1.5.1 yaml 檔案語法檢查
#檢查yaml 檔案的語法是否正確
[root@host103 opt]# ansible-playbook demo1.yaml --syntax-check
#檢查task任務
[root@host103 opt]# ansible-playbook demo1.yaml --list-task
#檢查生效的主機
[root@host103 opt]# ansible-playbook demo1.yaml --list-hosts
1.5.2 指定從某個task開始執行
#將192.168.23.105 主機的firewalld服務開啟
[root@host105 ~]# systemctl start firewalld
[root@host105 ~]# systemctl is-active firewalld
active
#192.168.23.105 停止httpd服務,並移除httpd
[root@host105 ~]# systemctl stop httpd
[root@host105 ~]# yum -y remove httpd
#先檢視有哪些tasks
[root@host103 opt]# ansible-playbook demo1.yaml --list-task
playbook: demo1.yaml
play #1 (webservers): first palybook TAGS: []
tasks:
test connection TAGS: []
disable selinux TAGS: []
disable firewalld TAGS: []
install apache TAGS: []
start apache service TAGS: []
modify httpd configuration file TAGS: []
#指定從 install apache 這個任務開始執行
[root@host103 opt]# ansible-playbook demo1.yaml --start-at-task='install apache'
#此時,192.168.23.105主機,重新安裝了httpd,並啟動了服務
[root@host105 ~]# rpm -q httpd
httpd-2.4.6-67.el7.centos.x86_64
[root@host105 ~]# systemctl is-active httpd
active
#由於disale firewalld任務在install apache 任務之前,因此,沒有被執行
[root@host105 ~]# systemctl is-active firewalld
active
二: 定義和引用變數
2.1 定義變數
2.1.1 合法的變數名
在使用變數之前最好先知道什麼是合法的變數名. 變數名可以為字母,數字以及下劃線.變數始終應該以字母開頭.
“foo_port”是個合法的變數名.”foo5”也是.
“foo-port”, “foo port”, “foo.port” 和 “12”則不是合法的變數名.
2.1.2 playbook中定義變數的格式
可以使用vars定義變數,多個變數應該組成列表的格式。變數的格式為key: value的格式
如
- hosts: dbservers
vars:
- groupname: mysql #定義變數groupname,值為mysql
- username: nginx #定義變數username,值為nginx
2.2 使用變數
Ansible允許你使用Jinja2模板系統在playbook中引用變數.藉助Jinja你能做很多複雜的操作,
2.2.1 變數的替換
#在簡單的模板中可以如下操作,這也是變數替換的最基本形式
My amp goes to {{ max_amp_value }}
#在playbook中替換:
template: src=foo.cfg.j2 dest={{ remote_install_path }}/foo.cfg
#在上述的例子中,我們使用變數來決定檔案放置在哪裡.
#在模板中你自動會獲取在主機範圍之內的所有變數的訪問權
2.3 yaml陷阱
Ansible 使用 “{{ var }}” 來引用變數. 如果一個值以 “{” 開頭, YAML 將認為它是一個字典, 所以我們必須引用它, 像這樣:foo: "{{ variable }}"
#錯誤的例子
- hosts: app_servers
vars:
app_path: {{ base_path }}/22
#正確的例子
- hosts: app_servers
vars:
app_path: "{{ base_path }}/22"
2.4 使用Facts獲取的資訊
還有其它地方可以獲取變數,這些變數是自動發現的,而不是使用者自己設定的.
Facts通過訪問遠端系統獲取相應的資訊. 一個例子就是遠端主機的IP地址或者作業系統是什麼. 使用以下命令可以檢視哪些資訊是可用的:
ansible hostname -m setup
[root@host103 opt]# ansible webservers -m setup
[root@host103 opt]# ansible webservers -m setup -a 'filter=*ipv4'
2.5 在命令列定義變數
在命令列,可以使用 -e 指定變數。但是會覆蓋playbooks裡的變數
[root@host103 opt]#ansible-playbook demo1 -e "username=nginx"
2.6 ansible提供的變數
Ansible會自動提供給你一些變數,即使你並沒有定義過它們.這些變數中重要的有 ‘hostvars’,’group_names’,和 ‘groups’.由於這些變數名是預留的,所以使用者不應當覆蓋它們. ‘environmen’ 也是預留的.
2.6.1 hostvars變數
hostvars可以讓你訪問其它主機的變數,包括哪些主機中獲取到的facts.如果你還沒有在當前playbook或者一組playbook的任何play中訪問那個主機,那麼你可以獲取變數,但無法看到facts值. 如果資料庫伺服器想使用另一個節點的某個 ‘fact’ 值,或者賦值給該節點的一個inventory變數.可以在一個模板中甚至命令列中輕鬆實現:
{{ hostvars['test.example.com']['ansible_distribution'] }}
2.6.2groups變數
group_names 是當前主機所在所有群組的列表(陣列).所以可以使用Jinja2語法在模板中根據該主機所在群組關係(或角色)來產生變化:
{% if 'webserver' in group_names %}
# some part of a configuration file that only applies to webservers
{% endif %}
2.6.3 groups 變數
groups 是inventory中所有群組(主機)的列表.可用於列舉群組中的所有主機.例如:
{% for host in groups['app_servers'] %}
# something that applies to all app servers.
{% endfor %}
一個經常使用的正規化是找出該群組中的所有IP地址:
{% for host in groups['app_servers'] %}
{{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}
{% endfor %}
2.7 變數檔案的分隔
把playbook置於原始碼管理之下是個很好的注意,當你可能會想把playbook原始碼公開之餘還想保持某些重要的變數私有.有時你也想把某些資訊放置在不同的檔案中,遠離主playbook檔案.
你可以使用外部的變數檔案來實現:
---
- hosts: all
remote_user: root
vars:
favcolor: blue
vars_files:
- /vars/external_vars.yml
tasks:
- name: this is just a placeholder
command: /bin/echo foo
這可以保證你共享playbook原始碼時隔離敏感資料的風險.
每個變數檔案的內容是一個簡單的YAML檔案,如下所示:
---
# in the above example, this would be vars/external_vars.yml
somevar: somevalue
password: magic
2.8 變數的優先順序
* extra vars (在命令列中使用 -e)優先順序最高
* 然後是在inventory中定義的連線變數(比如ansible_ssh_user)
* 接著是大多數的其它變數(命令列轉換,play中的變數,included的變數,role中的變數等)
* 然後是在inventory定義的其它變數
* 然後是由系統發現的facts
* 然後是 "role預設變數", 這個是最預設的值,很容易喪失優先權
2.9playbook變數使用示例
[root@host103 opt]# vim demo2.yaml
- name: second playbook
#啟動facts,預設為true(或者yes).如果想要關閉,使用false或者no
gather_facts: yes
hosts: webservers
remote_user: root
#定義變數。groupname變數,值為test ; username變數,值為zhangsan
vars:
- groupname: test
- username: zhangsan
tasks:
- name: create group
#引用變數groupname
group: name={{groupname}} system=yes gid=1024
- name: create user for group
#引用變數username和groupname
user: name={{username}} system=yes uid=9527 group={{groupname}}
- name: test copy file
#從facts裡引用變數。但是要求facts必須是開啟.
#ansible_defaule_ipv4為其中的一個key。
copy: content="{{ansible_default_ipv4}}" dest=/opt/test.txt
#執行劇本
[root@host103 opt]# ansible-playbook demo2.yaml
#在192.168.23.103 上使用ansible 的setup模組,並使用filter過濾鍵值對資訊
[root@host103 opt]# ansible webservers -m setup -a 'filter=ansible_default_ipv4'
#在192.168.23.105主機檢視zhangsan使用者是否生成,組是否是test
[root@host105 opt]# id zhangsan
#檢視/opt/test.txt
[root@host105 opt]# cat /opt/test.txt
三 指定遠端主機sudo切換使用者
---
- hosts: dbservers
remote_user: zhangsan
become: yes #2.6版本以後的引數,之前是sudo,意思為切換使用者執行
become_user: root #指定sudo使用者為root
如果你需要在使用 sudo 時指定密碼,可在執行 ansible-playbook 命令時加上選項 --ask-sudo-pass
(-K). 如果使用 sudo 時,playbook 疑似被掛起,可能是在 sudo prompt 處被卡住,這時可執行 Control-C 殺死卡住的任務,再重新執行一次.
當使用 sudo_user 切換到 非root 使用者時,模組的引數會暫時寫入 /tmp 目錄下的一個隨機臨時檔案. 當命令執行結束後,臨時檔案立即刪除.這種情況發生在普通使用者的切換時,比如從 ‘bob’ 切換到 ‘timmy’, 切換到 root 賬戶時,不會發生,如從 ‘bob’ 切換到 ‘root’,直接以普通使用者或root身份登入也不會發生. 如果你不希望這些資料在短暫的時間內可以被讀取(不可寫),請避免在 sudo_user 中傳遞未加密的密碼. 其他情況下,’/tmp’ 目錄不被使用,這種情況不會發生.Ansible 也有意識的在日誌中不記錄密碼引數.
四:when 條件判斷
在Ansible中,提供的唯一一個通用的條件判斷是when指令,當when指令的值為true時,則該任務執行,否則不執行該任務。
when一個比較常見的應用場景是實現跳過某個主機不執行任務或者只有滿足條件的主機執行任務
4.1 跳過主機,或者讓滿足條件的主機執行任務
vim demo3.yaml
---
- hosts: all
remote_user: root
tasks:
- name: reboot
command: /usr/sbin/shutdown -r now
#when指令中的變數名不需要手動加上 {{}}
#次任務效果為:當主機位192.168.23.105時,執行/usr/sbin/shutdown -r now命令,重啟主機
when: ansible_default_ipv4.address == "192.168.23.105"
或
when: inventory_hostname == "<主機名>"
ansible-playbook demo3.yaml
4.2 迭代
Ansible提供了很多種迴圈結構,一般都命名為with_items,作用等同於 loop 迴圈。
[root@host103 opt]# vim demo4.yaml
---
- name: play4
hosts: webservers
gather_facts: false
tasks:
- name: create directories
#建立目錄。目錄名使用with_items裡的迴圈
file:
#由於值是{{....}} ,所以為了防止被認為是字典,要加上雙引號.
path: "{{item}}"
state: directory
#with_items迴圈,等同於loop迴圈
with_items:
- /opt/zhangsan
- /opt/lisi
- name: add users
#使用迴圈建立使用者,並新增附加組。
user:
#以下兩種方法都可以呼叫with_iptems裡的值
name: "{{item.name}}"
groups: "{{item['test_groups']}}"
with_items:
#以下兩種方法都可以
- name: test01
test_groups: aaa
- {name: test02, test_groups: bbb}
#在管理機上執行劇本
[root@host103 opt]# ansible-playbook demo4.yaml
#在被管理主機上檢視執行結果
[root@host105 opt]# ls
lisi zhangsan
[root@host105 opt]# id test01
uid=9528(test01) gid=9528(test01) 組=9528(test01),2222(aaa)
[root@host105 opt]# id test02
uid=9529(test02) gid=9529(test02) 組=9529(test02),3333(bbb
五 templates模組
Jinja是基於Python的模板引擎。Template類是Jinja的一個重要元件,可以看作是一個編譯過的模板檔案,用來產生目標文字,傳遞Python的變數給模板去替換模板中的標記。
5.1 準備模板
準備一個一 .j2 為字尾的template模板檔案,設定引用的變數
[root@host103 opt]# cp httpd.conf httpd.conf.j2
[root@host103 opt]# vim httpd.conf.j2
#42//設定監聽埠為變數。引用變數http_port的值
42// Listen {{http_port}}
#95// 設定域名為引用變數server_name的值
95 ServerName {{server_name}}
#119// 設定網站根目錄為引用root_dir的值
119// DocumentRoot "{{root_dir}}"
#131// 配置目錄訪問許可權
131// <Directory "{{root_dir}}">
5.2 修改主機清單檔案
修改主機清單檔案,使用主機變數定義一個變數名相同,而值不同的變數
[root@host103 opt]# vim /etc/ansible/hosts
#使用主機變數,對變數進行單個的定義
[webservers]
192.168.23.105 http_port=192.168.23.105:80 server_name=www.mynet.com:80 root_dir=/opt/mynet
[dbservers]
192.168.23.106 http_port=192.168.23.106:8080 server_name=www.zhi.com:8080 root_dir=/opt/zhi
5.3 編寫playbook
---
- name: for diff apache
hosts: webservers dbservers
remote_user: root
vars:
- Package: httpd
- Service: httpd
- mynet: /opt/mynet
- zhi: /opt/zhi
tasks:
- name: install httpd
yum: name={{Package}} state=latest
- name: install configure file
template: src=/opt/httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf
notify:
- restart httpd
- name: create root dir for mynet
file: path={{mynet}} state=directory
when: ansible_default_ipv4.address == "192.168.23.105"
- name: for mynet index.html
copy: content='this is mynet.com' dest={{mynet}}/index.html
when: ansible_default_ipv4.address == "192.168.23.105"
- name: create root dir for zhi
file: path={{zhi}} state=directory
when: ansible_default_ipv4.address == "192.168.23.106"
- name: for zhi index.html
copy: content='this is zhi.com' dest={{zhi}}/index.html
when: ansible_default_ipv4.address == "192.168.23.106"
- name: start httpd
service: name={{Service}} enabled=true state=started
handlers:
- name: restart httpd
service: name={{Service}} state=restarted
[root@host103 opt]# ansible-playbook demo5.yaml
#訪問測試
[root@host103 opt]# curl http://192.168.23.105:80
this is mynet.com
[root@host103 opt]# curl http://192.168.23.106:8080
this is zhi.com