Ansible命令大全【上】

一、ansible簡介與安裝
1、ansilbe特性
Agentless:不需要再被管理節點上安裝客戶端,只要有sshd即可
Serverless:在服務端不需要啓動任何服務,只需要執行命令就行
YAML:使用yaml語言定製playbook
SSH by default:默認使用ssh控制各節點
Strong multi-tier solution:可實現多級控制

2、ansible的基本組建
核心:ansible
核心模塊(Core Modules):這些都是ansible自帶的模塊
擴展模塊(Custom Modules):如果核心模塊不足以完成某種功能,可以添加擴展模塊
插件(Plugins):完成模塊功能的補充
劇本(Playbooks):把需要完成的多個任務定義在劇本中
連接插件(Connectior Plugins):ansible基於插件連接到各個主機上,雖然ansible是使用ssh連接到各個主機的。
主機羣(Host Inventory):ansible在管理多臺主機時,可以選擇只對其中的一部分執行某些操作

3、工作原理
Ansible 在管理節點將 Ansible 模塊通過SSH協議(或者 Kerberos、LDAP)推送到被管理端執行,執行完之後自動刪除,可以使用版本控制系統(git/svn)來管理自定義模塊及playbooks。

4、ansible的安裝
# 解決依賴關係:
yum install -y python36 python36-devel python36-setuptools gcc libffi-devel openssl-devel
# 下載ansible:
wget https://github.com/ansible/ansible/archive/v2.8.0.tar.gz
#解壓安裝
tar xf v2.8.0.tar.gz
cd ansible-2.8.0/
python setup.py build
python setup.py install
mkdir /etc/ansible
cp -r examples/* /etc/ansible

二、ansible的inventory
在使用Ansible來批量管理主機的時候,通常我們需要先定義要管理哪些主機或者主機組,而這個用於管理主機與主機組的文件就叫做Inventory,也叫主機清單,該文件默認位於/etc/ansible/hosts。
當然我們也可以通過修改ansible配置文件的hostfile配置項來修改默認inventory的位置。
1、簡單的主機和組
mail.yanruogu.com
[webservers]
web1.yanruogu.com
web2.yanruogu.com 
[dbservers]
db1.yanruogu.com
db2.yanruogu.com
# 中括號中的名字代表組名,可以根據自己的需求將龐大的主機分成具有標識的組,如上面分了兩個組webservers和dbservers組;  
# 主機(hosts)部分可以使用域名、主機名、IP地址表示;當然使用前兩者時,也需要主機能反解析到相應的IP地址,一般此類配置中多使用IP地址;

2、指定主機範圍
[webservers]
www[01:50].yanruogu.com
[databases]
db-[a:f].yanruogu.com
# 下面指定了從web01到web50,webservers組共計50臺主機;databases組有db-a到db-f共6臺主機

3、定義主機嵌套
# 如下示例中,production組包含兩個子組,分別爲webservers和dbservers,webservers和dbservers組分別包含若干主機
[webservers]
web1.lab.example.com
web2.lab.example.com
[dbservers]
db1.lab.example.com
db2.lab.example.com
[production:children]
webservers
dbservers

4、選擇主機和組
(1)匹配所有主機
# ansible all --list-hosts
可以通過all或者*來指定匹配所有主機,通過如下指令查看all匹配到的主機:

(2)匹配指定的主機或主機組
#ansible prod --list-hosts       #匹配prod主機組
#ansible db2.example.com --list-hosts   #匹配單臺主機
#ansible 'lb1.lab.example.com,s1.lab.example.com,db1.example.com' --list-hosts   #匹配多臺主機
#ansible 'london,boston' --list-hosts    #匹配多個組
#ansible ungrouped --list-hosts   #匹配不屬於任何組的主機

(3)通配符匹配
#ansible '*.example.com' --list-hosts  #匹配'*.example.com'所有主機
#ansible '172.25.*' --list-hosts   #匹配172.25.*開頭的主機
#ansible 's*' --list-hosts    #匹配以s開頭的主機及主機組

(4)通配符組合匹配
#ansible '*.example.com,!*.lab.example.com' --list-hosts    #匹配包含*.example.com但不包含*.lab.example.com的主機
#ansible 'prod,172*,*lab*' --list-hosts  #匹配包含prod以及172開頭、包含lab關鍵字的主機或組
#ansible 'db,&london' --list-hosts  #匹配屬於db組同時還屬於london組的主機
#ansible 'boston,london,&prod,!lb' --list-hosts  #匹配在london組或者boston組,還必須在prod組中且必須不在lb組中的主機:

(5)正則表達式
在開頭的地方使用”~”,用來表示這是一個正則表達式:
#ansible '~(s|db).*example\.com' --list-hosts  #表示匹配以s或者db開頭的,並以example.com結尾的主機信息。

(6)通過--limit明確指定主機或組
#ansible ungrouped  --limit srv1.example.com --list-hosts  #通過明確指定主機域名。
#ansible ungrouped  --limit @retry_hosts.txt --list-hosts  #通過文件來指定域名。
vi retry_hosts.txt
srv1.example.com

三、ansible的配置文件管理
ansible的配置文件名爲ansible.cfg,它一般會存在於四個地方:
(1)ANSIBLE_CONFIG:首先,Ansible命令會檢查該環境變量,及這個環境變量將指向的配置文件。
(2)./ansible.cfg:當前工作目錄,即當前執行ansible指令的目錄,如果ANSIBEL_CONFIG環境變量未定義,則優先使用該配置文件
(3)~/.ansible.cfg:當前用戶家目錄下的一個隱藏文件,如果當前工作目錄下不存在ansible.cfg配置文件,則會查找用戶家目錄下的該隱藏文件
(4)/etc/ansible/ansible.cfg:默認配置文件,如果上面兩個路徑下的ansible.cfg都不存在,則使用該文件
注意:配置文件中所有的配置項都可以通過環境變量的方式來定義,而環境變量定義的配置項具有最高優先級,會覆蓋掉所有配置文件中的配置項。

ansible.cfg的配置文件爲8段:
[defaults]:通用配置項
[inventory]:與主機清單相關的配置項
[privilege_escalation]:特權升級相關的配置項
[paramiko_connection]:使用paramiko連接的相關配置項,Paramiko在RHEL6以及更早的版本中,默認使用的ssh連接方式
[ssh_connection]:使用OpenSSH連接的相關配置項,OpenSSH是Ansible在RHEL6之後默認使用的ssh連接方式
[persistent_connection]:持久連接的配置項
[accelerate]:加速模式配置項
[selinux]:selinux相關的配置項
[colors]:ansible命令輸出的顏色相關的配置項
[diff]:定義是否在運行時打印diff(變更前與變更後的差異)

配置參數說明:
[default]
inventory      = /etc/ansible/hosts    #定義默認使用的主機清單
remote_user    = root   #ansible在操作遠程主機時,使用遠程主機上的哪個用戶身份,默認是root
ask_pass       = false  #ansible在操作遠程主機時,是否交互提示密碼驗證,默認爲true。如果使用密鑰認證的話,建議將其設置爲false
log_path       = /var/log/ansible.log  #打開該配置項,所有的命令執行後,都會將日誌輸出到/var/log/ansible.log文件。
[privilege_escalation]
become=True         #如果ansible在操作遠程主機時,使用的是遠程主機上的普通用戶,該普通用戶是否允許提權
become_method=sudo  #如果允許提權,使用何種提權方式,默認是sudo
become_user=root    #提權到哪個用戶身份,默認是root
become_ask_pass=False   #提權時,是否交互提示密碼驗證,默認爲False
[ssh_connection]
ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s  #ansible通過ssh連接遠程被管理機,這裏用於定義一些ssh連接時的參數,如-C啓用壓縮傳輸,ControlPersist用於提升性能。
host_key_checking = False #通過ssh首次連接遠程主機時,由於在本機的~/.ssh/known_hosts文件中並有fingerprint key串,ssh第一次連接的時候一般會提示輸入yes/no進行確認將key字符串加入到~/.ssh/known_hosts文件中。將此項設置爲False將跳過該確認過程。

四、Ad-hoc與命令行執行模塊
Ad-hoc定義:是指ansible下臨時執行的一條命令,並且不需要保存的命令,對於複雜的命令會使用playbook。Ad-hoc的執行依賴於模塊,ansible官方提供了大量的模塊。 如:command、raw、shell、file、cron等。
(1)ansible-doc -l 進行查看模塊信息。
(2)ansible-doc -s module來查看某個模塊的參數。
(3)ansible-doc help module來查看該模塊更詳細的信息。

Ad-hoc格式:ansible 主機或組 -m 模塊名 -a '模塊參數'  ansible參數
主機和組:是在/etc/ansible/hosts 裏進行指定的部分,當然動態Inventory 使用的是腳本從外部應用裏獲取的主機。
模塊名:可以通過ansible-doc -l 查看目前安裝的模塊,默認不指定時,使用的是command模塊,具體可以查看/etc/ansible/ansible.cfg 的 “#module_name = command ” 部分,默認模塊可以在該配置文件中進行修改;
模塊參數:可以通過 “ansible-doc -s 模塊名” 查看具體的用法及後面的參數;
ansible參數:可以通過ansible命令的幫助信息裏查看到,這裏有很多參數可以供選擇,如是否需要輸入密碼、是否sudo等。

1、低級用戶說明:
在被管理機上創建一個普通用戶ansible,並配置sudo提權:
# 創建ansible用戶
useradd ansible

# 設置ansible用戶可免密提權
vim /etc/sudoers.d/ansible
ansible    ALL=(ALL)       NOPASSWD:ALL

chmod 400 /etc/sudoers.d/ansible

mkdir /home/ansible/.ssh
chown ansible.ansible /home/ansible/.ssh
chmod 700 /home/ansible/.ssh
在管理端修改ansible.cfg配置文件如下:
[default]
remote_user = ansible
ask_pass = False

[privilege_escalation]
become=True
become_method=sudo
become_user=root
become_ask_pass=False
將管理端用戶的id_rsa.pub複製到被管理端的/home/ansible/.ssh//home/ansible/.ssh/authorized_keys文件中,並設置該文件的權限爲400
# 管理端執行:
ssh-copy-id -i .ssh/id_rsa.pub [email protected]
需要說明的是,通過上面的操作,需要爲ansible用戶先創建密碼,在實際生產當中,不建議爲該用戶設置密碼。可在安裝系統時,提前將管理端公鑰直接寫入到被管理節點的ansible用戶下

2、命令執行模塊
命令執行模塊包含如下 四個模塊:
command模塊:該模塊通過-a跟上要執行的命令可以直接執行,不過命令裏如果有帶有如下字符部分則執行不成功 “ "<", ">", "|", "&" ;
shell 模塊:用法基本和command一樣,不過其是通過/bin/sh進行執行,所以shell 模塊可以執行任何命令,就像在本機執行一樣;
raw模塊:用法和shell模塊一樣,也可以執行任意命令,就像在本機執行一樣;
script模塊:將管理端的shell 在被管理主機上執行,其原理是先將shell 複製到遠程主機,再在遠程主機上執行,原理類似於raw模塊。
raw模塊和comand、shell 模塊不同的是其沒有chdir、creates、removes參數,chdir參數的作用就是先切到chdir指定的目錄後,再執行後面的命令,這在後面很多模塊裏都會有該參數 。
(1)command模塊
free_form:要執行的linux指令
chdir:在執行指令之前,先切換到該指定的目錄
removes:一個文件名,當該文件不存在,則該選項不執行
creates:一個文件名,當該文件存在,則該命令不執行
executable:切換shell來執行指令,該執行路徑必須是一個絕對路徑

chdir示例:
# 三個命令都會返回執行成功的狀態。不過實際上只有前兩個文件會被創建成功。使用raw模塊的執行的結果文件事實上也被正常創建了,不過不是在chdir指定的目錄,而是在當前執行用戶的家目錄
ansible 192.168.1.1 -m command -a 'chdir=/tmp/ touch test.file'
ansible 192.168.1.1 -m shell -a 'chdir=/tmp/ touch test2.file'
ansible 192.168.1.1 -m raw -a 'chdir=/tmp/ touch test3.file'
creates與removes示例:
ansible 192.168.1.1 -a 'creates=/tmp/server.txt uptime' #當/tmp/server.txt文件存在時,則不執行uptime指令
ansible 192.168.1.1 -a 'removes=/tmp/server.txt uptime' #當/tmp/server.txt文件不存在時,則不執行uptime指令

(2)script模塊
#要執行的腳本文件script.sh內容如下:【在服務器端】 

#/bin/bash
ifconfig
df -hT

# 執行ansible指令:
ansible 10.212.52.252 -m script -a 'script.sh' 

(3)ping模塊
ansible test -m ping

(4)file模塊
file模塊主要用於遠程主機上的文件操作,file模塊包含如下選項:

force:需要在兩種情況下強制創建軟鏈接,一種是源文件不存在但之後會建立的情況下;另一種是目標軟鏈接已存在,需要先取消之前的軟鏈,然後創建新的軟鏈,有兩個選項:yes|no
group:定義文件/目錄的屬組
mode:定義文件/目錄的權限
owner:定義文件/目錄的屬主
path:必選項,定義文件/目錄的路徑
src:要被鏈接的源文件的路徑,只應用於state=link的情況
dest:被鏈接到的路徑,只應用於state=link的情況
state:
directory:如果目錄不存在,創建目錄
file:即使文件不存在,也不會被創建
link:創建軟鏈接
hard:創建硬鏈接
touch:如果文件不存在,則會創建一個新的文件,如果文件或目錄已存在,則更新其最後修改時間
absent:刪除目錄、文件或者取消鏈接文件

ansible 192.168.8.120 -m file -a 'path=/tmp/test.txt state=touch owner=root group=root mode=644'
ansible 192.168.8.120 -m file -a 'src=/tmp/test.txt dest=/root/test.txt state=link'
ansible 192.168.8.120 -m file -a 'path=/tmp/test.txt state=file'
ansible 192.168.8.120 -m file -a 'path=/tmp/test state=directory owner=root group=root mode=755'
ansible 192.168.8.120 -m file -a 'path=/tmp/test2/test3/aaa/bbb state=directory owner=root group=root mode=755'
ansible 192.168.8.120 -m file -a 'path=/tmp/test2 state=absent'

(5)Copy模塊
複製文件到遠程主機,copy模塊包含如下選項:
backup:在覆蓋之前將原文件備份,備份文件包含時間信息。有兩個選項:yes|no
content:用於替代"src",可以直接設定指定文件的值
dest:必選項。要將源文件複製到的遠程主機的絕對路徑,如果源文件是一個目錄,那麼該路徑也必須是個目錄
force:如果目標主機包含該文件,但內容不同,如果設置爲yes,則強制覆蓋,如果爲no,則只有當目標主機的目標位置不存在該文件時,才複製。默認爲yes
others:所有的file模塊裏的相關文件屬性選項都可以在這裏使用
src:要複製到遠程主機的文件在本地的地址,可以是絕對路徑,也可以是相對路徑。如果路徑是一個目錄,它將遞歸複製。在這種情況下,如果路徑使用"/"來結尾,則只複製目錄裏的內容,如果沒有使用"/"來結尾,則包含目錄在內的整個內容全部複製,類似於rsync。
示例如下:
ansible 192.168.8.120 -m copy -a 'src=/etc/ansible/ansible.cfg dest=/usr/local/src/ owner=root group=root mode=644'
ansible 192.168.8.120 -m copy -a 'backup=yes src=/etc/fstab dest=/usr/local/src/ansible.cfg owner=root group=root mode=644'
ansible 192.168.8.120 -m copy -a 'content="just a test!" dest=/usr/local/src/test.txt'
ansible 192.168.8.120 -m copy -a 'src=/data dest=/usr/local/src/'
ansible 192.168.8.120 -m copy -a 'src=/data/ dest=/usr/local/src/'
ansible 192.168.8.120 -m copy -a "src=/mine/sudoers dest=/etc/sudoers validate='visudo -cf %s'"

(6)Yum模塊
使用yum包管理器來管理軟件包,其選項有:
name:要進行操作的軟件包的名字,也可以傳遞一個url或者一個本地的rpm包的路徑
state:狀態(present,absent,latest)
示例如下:
ansible test -m yum -a 'name=httpd state=latest'
ansible test -m yum -a 'name="@Development tools" state=present'
ansible test -m yum -a 'name=http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm state=present'

(7)service模塊
用於管理服務
該模塊包含如下選項:

arguments:給命令行提供一些選項
enabled:是否開機啓動 yes|no
name:必選項,服務名稱
pattern:定義一個模式,如果通過status指令來查看服務的狀態時,沒有響應,就會通過ps指令在進程中根據該模式進行查找,如果匹配到,則認爲該服務依然在運行
runlevel:運行級別
sleep:如果執行了restarted,在則stop和start之間沉睡幾秒鐘
state:對當前服務執行啓動,停止、重啓、重新加載等操作(started,stopped,restarted,reloaded)
daemon_reload:針對使用systemd的系統,重新加載systemd配置,yes/no
使用示例:

ansible test -m service -a "name=httpd state=started enabled=yes"
asnible test -m service -a "name=foo pattern=/usr/bin/foo state=started"
ansible test -m service -a "name=network state=restarted args=eth0"

(8)cron模塊
用於管理計劃任務

包含如下選項:
backup:對遠程主機上的原任務計劃內容修改之前做備份
cron_file:如果指定該選項,則用該文件替換遠程主機上的cron.d目錄下的用戶的任務計劃
day:日(1-31,,/2,……)
hour:小時(0-23,,/2,……)
minute:分鐘(0-59,,/2,……)
month:月(1-12,,/2,……)
weekday:周(0-7,*,……)
job:要執行的任務,依賴於state=present
name:該任務的描述
special_time:指定什麼時候執行,參數:reboot,yearly,annually,monthly,weekly,daily,hourly
state:確認該任務計劃是創建還是刪除
user:以哪個用戶的身份執行
示例:
ansible test -m cron -a 'name="a job for reboot" special_time=reboot job="/some/job.sh"'
ansible test -m cron -a 'name="yum autoupdate" weekday="2" minute=0 hour=12 user="root"'
ansible test -m cron  -a 'backup="True" name="test" minute="0" hour="5,2" job="ls -alh > /dev/null"'
ansilbe test -m cron -a 'cron_file=ansible_yum-autoupdate state=absent'

(9)user模塊與group模塊
user模塊是請求的是useradd, userdel, usermod三個指令,goup模塊請求的是groupadd, groupdel, groupmod 三個指令。
home:指定用戶的家目錄,需要與createhome配合使用
groups:指定用戶的屬組
uid:指定用的uid
password:指定用戶的密碼
name:指定用戶名
createhome:是否創建家目錄 yes|no
system:是否爲系統用戶
remove:當state=absent時,remove=yes則表示連同家目錄一起刪除,等價於userdel -r
state:是創建還是刪除
shell:指定用戶的shell環境
使用示例:

user: name=johnd comment="John Doe" uid=1040 group=admin
user: name=james shell=/bin/bash groups=admins,developers append=yes user: name=johnd state=absent remove=yes
user: name=james18 shell=/bin/zsh groups=developers expires=1422403387
#生成密鑰時,只會生成公鑰文件和私鑰文件,和直接使用ssh-keygen指令效果相同,不會生成authorized_keys文件
user: name=test generate_ssh_key=yes ssh_key_bits=2048 ssh_key_file=.ssh/id_rsa  
需要說明的是,在指定password參數時,不能使用明文密碼,因爲後面這一串密碼會被直接傳送到被管理主機的/etc/shadow文件中,所以需要先將密碼字符串進行加密處理。然後將得到的字符串放到password中即可。

echo "123456" | openssl passwd -1 -salt $(< /dev/urandom tr -dc '[:alnum:]' | head -c 32) -stdin
$1$4P4PlFuE$ur9ObJiT5iHNrb9QnjaIB0

#使用上面的密碼創建用戶
ansible all -m user -a 'name=foo password="$1$4P4PlFuE$ur9ObJiT5iHNrb9QnjaIB0"'
不同的發行版默認使用的加密方式可能會有區別,具體可以查看/etc/login.defs文件確認,centos 6.5版本使用的是SHA512加密算法。

group示例
group: name=somegroup state=present

(10)synchronize模塊
使用rsync同步文件,其參數如下:
archive: 歸檔,相當於同時開啓recursive(遞歸)、links、perms、times、owner、group、-D選項都爲yes ,默認該項爲開啓
checksum: 跳過檢測sum值,默認關閉
compress:是否開啓壓縮
copy_links:複製鏈接文件,默認爲no ,注意後面還有一個links參數
delete: 刪除不存在的文件,默認no
dest:目錄路徑
dest_port:默認目錄主機上的端口 ,默認是22,走的ssh協議
dirs:傳輸目錄不進行遞歸,默認爲no,即進行目錄遞歸
rsync_opts:rsync參數部分
set_remote_user:主要用於/etc/ansible/hosts中定義或默認使用的用戶與rsync使用的用戶不同的情況
mode: push或pull 模塊,push模的話,一般用於從本機向遠程主機上傳文件,pull 模式用於從遠程主機上取文件
使用示例:

src=some/relative/path dest=/some/absolute/path rsync_path="sudo rsync"
src=some/relative/path dest=/some/absolute/path archive=no links=yes
src=some/relative/path dest=/some/absolute/path checksum=yes times=no
src=/tmp/helloworld dest=/var/www/helloword rsync_opts=--no-motd,--exclude=.git mode=pull

(11)filesystem模塊
在塊設備上創建文件系統
常用選項:
dev:目標塊設備
force:在一個已有文件系統 的設備上強制創建
fstype:文件系統的類型
opts:傳遞給mkfs命令的選項
示例:
ansible test -m filesystem -a 'fstype=ext2 dev=/dev/sdb1 force=yes'
ansible test -m filesystem -a 'fstype=ext4 dev=/dev/sdb1 opts="-cc"'

(12)mount模塊
配置掛載點,選項:
fstype:必選項,掛載文件的類型
name:必選項,掛載點
opts:傳遞給mount命令的參數
src:必選項,要掛載的文件
state:必選項
present:只處理fstab中的配置
absent:刪除掛載點
mounted:自動創建掛載點並掛載之
umounted:卸載
示例:
name=/mnt/dvd src=/dev/sr0 fstype=iso9660 opts=ro state=present
name=/srv/disk src='LABEL=SOME_LABEL' state=present
name=/home src='UUID=b3e48f45-f933-4c8e-a700-22a159ec9077' opts=noatime state=present

ansible test -a 'dd if=/dev/zero of=/disk.img bs=4k count=1024'
ansible test -a 'losetup /dev/loop0 /disk.img'
ansible test -m filesystem 'fstype=ext4 force=yes opts=-F dev=/dev/loop0'
ansible test -m mount 'name=/mnt src=/dev/loop0 fstype=ext4 state=mounted opts=rw'

(13)get_url 模塊
該模塊主要用於從http、ftp、https服務器上下載文件(類似於wget),主要有如下選項:
sha256sum:下載完成後進行sha256 check;
timeout:下載超時時間,默認10s
url:下載的URL
url_password、url_username:主要用於需要用戶名密碼進行驗證的情況
use_proxy:是事使用代理,代理需事先在環境變更中定義
示例:
get_url: url=http://example.com/path/file.conf dest=/etc/foo.conf mode=0440
get_url: url=http://example.com/path/file.conf dest=/etc/foo.conf sha256sum=b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c

(14)unarchive模塊
用於解壓文件,模塊包含如下選項:
copy:在解壓文件之前,是否先將文件複製到遠程主機,默認爲yes。若爲no,則要求目標主機上壓縮包必須存在。
creates:指定一個文件名,當該文件存在時,則解壓指令不執行
dest:遠程主機上的一個路徑,即文件解壓的路徑
group:解壓後的目錄或文件的屬組
list_files:如果爲yes,則會列出壓縮包裏的文件,默認爲no,2.0版本新增的選項
mode:解決後文件的權限
src:如果copy爲yes,則需要指定壓縮文件的源路徑
owner:解壓後文件或目錄的屬主
示例如下:

- unarchive: src=foo.tgz dest=/var/lib/foo
- unarchive: src=/tmp/foo.zip dest=/usr/local/bin copy=no
- unarchive: src=https://example.com/example.zip dest=/usr/local/bin copy=no
分類: Ansible

五、Playbook簡單配置
ansbile-playbook是一系列ansible命令的集合,利用yaml 語言編寫。playbook命令根據自上而下的順序依次執行。同時,playbook開創了很多特性,它可以允許你傳輸某個命令的狀態到後面的指令,如你可以從一臺機器的文件中抓取內容並附爲變量,然後在另一臺機器中使用,這使得你可以實現一些複雜的部署機制,這是ansible命令無法實現的。
playbook通過ansible-playbook命令使用,它的參數和ansible命令類似,如參數-k(–ask-pass) 和 -K (–ask-sudo) 來詢問ssh密碼和sudo密碼,-u指定用戶,這些指令也可以通過規定的單元寫在playbook 。
ansible-playbook的簡單使用方法: ansible-playbook example-play.yml 。
(1)Playbook基本語法
# cat user.yml
- name: create user
  hosts: all
  remote_user: root
  gather_facts: false  #指定在執行任務之前,是否先執行setup模塊獲取主機相關信息
  vars:
    user:"test"  #定義後續任務中會使用到的變量,如未用到,可不指定
  tasks:         #定義具體需要執行的任務
    - name: create  user
      user: name="{{ user }}"  #如果想刪除user: name="{{ user }}" state=absent remove=yes
(2)playbook簡單示例
1、創建playbook
- name: play to setup web server
  hosts: all
  tasks:
    - name: latest httpd version installed
      yum:
        name: httpd
        state: latest
        
    - name: correct index.html is present
      copy: 
        src: files/index.html
        dest: /var/www/html/index.html
        
    - name: start httpd service
      service:
        name: httpd
        state: started
        enabled: true
2、執行playbook語句
# ansible-playbook  manage_apache.yml
PLAY [play to setup web server] ************************************************************************************
TASK [Gathering Facts] *********************************************************************************************
ok: [10.1.61.187]
PLAY RECAP *****************************************************************************
10.1.61.187                : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

ansible-playbook常用選項:
(1)打印詳細信息
-v:打印任務運行結果
-vv:打印任務運行結果以及任務的配置信息
-vvv:包含了遠程連接的一些信息

(2)校驗playbook語法
# ansible-playbook --syntax-check  manage_apache.yml  

(3)測試運行playbook
# ansible-playbook -C  manage_apache.yml  #通過-C選項可以測試playbook的執行情況,但不會真的執行。

(4)多個play設置
- name: first play
  hosts: web.example.com
  tasks:
    - name: first task
      yum:
        name: httpd
        status: present
    - name: second task
      service:
        name: httpd
        state: started
    
- name: second play
  hosts: db.example.com
  tasks:
    - name: first task
      yum:
        name: mariadb-server
        status: present
    - name: second task
      service:
        name: mariadb
        state: started


六、Playbook的結構及handler語法
1、Playbook的結構說明
Target: 用於定義主機組及遠程主機組上的用戶,還包括定義通過什麼樣的方式連接遠程主機(默認ssh)
Variable: 定義playbook運行時需要使用的變量
Task: 定義將要在遠程主機上執行的任務列表
Handler: 定義task執行完成以後需要調用的任務

(1)遠程用戶和ad-hoc中的使用沒有區別,默認不定義,則直接使用ansible.cfg配置中的用戶相關的配置。也可在playbook中定義如下:
- name: /etc/hosts is up to date
  hosts: datacenter
  remote_user: automation
  become: yes
  become_mothod: sudo
  become_user: root

(2)Handler section
在Ansible Playbook中,handler事實上也是個task,只不過這個task默認並不執行,只有在被觸發時才執行。
handler通過notify來監視某個或者某幾個task,一旦task執行結果發生變化,則觸發handler,執行相應操作。
handler會在所有的play都執行完畢之後纔會執行,這樣可以避免當handler監視的多個task執行結果都發生了變化之後而導致handler的重複執行(handler只需要在最後執行一次即可)。
tasks:
  - name: template configuration file
    template: 
      src: template.j2 
      dest: /etc/foo.conf
    notify:                 #關聯後面的handlers語句。
      - restart memcached
      - restart apache
  - name: start memcached
    service:
      name: memcached
      state: started
  - name: start apache
    service
      name: httpd
      state: started
handlers:
  - name: restart memcached
    service:
      name: memcached
      state: restarted
  - name: restart apache
    service:
      name: httpd
      state: restarted

七、變量之自定義變量
1、在Inventory中定義變量
(1)在定義主機inventory時,在其前加上ansible_即成爲內置變量。
# 一般連接
ansible_ssh_host     #用於指定被管理的主機的真實IP
ansible_ssh_port     #用於指定連接到被管理主機的ssh端口號,默認是22
ansible_ssh_user     #ssh連接時默認使用的用戶名

# 特定ssh連接
ansible_connection     #SSH連接的類型:local, ssh, paramiko,在ansible 1.2之前默認是paramiko,後來智能選擇,優先使用基於ControlPersist的ssh(如果支持的話)
ansible_ssh_pass       #ssh連接時的密碼
ansible_ssh_private_key_file     #祕鑰文件路徑,如果不想使用ssh-agent管理祕鑰文件時可以使用此選項
ansible_ssh_executable    #如果ssh指令不在默認路徑當中,可以使用該變量來定義其路徑

# 特權升級
ansible_become        #相當於ansible_sudo或者ansible_su,允許強制特權升級
ansible_become_user   #通過特權升級到的用戶,相當於ansible_sudo_user或者ansible_su_user
ansible_become_pass   #提升特權時,如果需要密碼的話,可以通過該變量指定,相當於ansible_sudo_pass或者ansible_su_pass
ansible_sudo_exec     #如果sudo命令不在默認路徑,需要指定sudo命令路徑

# 遠程主機環境參數
ansible_shell_executable       #設置目標機上使用的shell,默認爲/bin/sh
ansible_python_interpreter     #用來指定python解釋器的路徑,默認爲/usr/bin/python 同樣可以指定ruby 、perl 的路徑
ansible_*_interpreter          #其他解釋器路徑,用法與ansible_python_interpreter類似,這裏"*"可以是ruby或才perl等其他語言
下面是一個簡單的示例:
# 指定了三臺主機,三臺主機的用密碼分別是P@ssw0rd、123456、45789,指定的ssh連接的用戶名分別爲root、breeze、bernie,ssh 端口分別爲22、22、3055 ,這樣在ansible命令執行的時候就不用再指令用戶和密碼等了

[test]
192.168.1.3 ansible_ssh_user=bernie ansible_ssh_port=3055 ansible_ssh_pass='456789'

(2)定義主機組變量
# test組中包含兩臺主機,通過對test組指定vars變更,相應的host1和host2相當於相應的指定了ntp_server和proxy變量參數值
[test]
host1
host2
[test:vars]
ntp_server=192.168.1.10
proxy=192.168.1.20

# 下面是一個示例,指定了一個武漢組有web1、web2;隨州組有web3、web4主機;又指定了一個湖北組,同時包含武漢和隨州;同時爲該組內的所有主機指定了2個vars變量。設定了一個組中國組,包含湖北、湖南。
[wuhan]
web1
web2
[suizhou]
web4
web3
[hubei:children]
wuhan
suizhou
[hubei:vars]
ntp_server=192.168.1.10
zabbix_server=192.168.1.10

2、在Playbook中定義變量
(1)通過vars關鍵詞
- name: use vars define invrionmemnt
  hosts: test
  user: ansible
  vars: 
    http_port: 80
    server_name: localhost
    conf_file: /etc/nginx/conf/default.conf

(2)通過vars_files關鍵詞
- hosts: all
  remote_user: root
  vars:
    favcolor: blue
  vars_files:
    - vars/external_vars.yml
    - vars/user_vars.yml

# vars/user_vars.yml示例:
users:
  bjones:
    first_name: Bob
    last_name: Jones
    home_dirs: /users/bjones
  acook:
    first_name: Anne
    last_name: Cook
    home_dirs: /users/acook
#變量的定義格式是成鍵值對出現的,鍵值對之間可以嵌套,最終形成一個大字典

(3)註冊變量
在有些時候,可能需要將某一條任務執行的結果保存下來,以便在接下的任務中調用或者做些判斷。可以通過register關鍵字來實現將某一任務結果保存爲一個變量。
#下面是個簡單的例子,將whoami命令執行的結果註冊到變量login
- name: register variables
  hosts: test
  tasks:
    - name: capture output of whoami command
      command: whoami
      register: login

(4)通過命令行設置變量
ansible-playbook release.yml --extra-vars "hosts=vipers user=starbuck"
也可以改寫成:--extra-vars '{"hosts":"vipers","user":"starbuck"}'

3、使用與調試變量
(1)變量的引用
- name: use vars define variables
  hosts: test
  vars: 
    http_port: 80
    server_name: localhost
    conf_file: /etc/nginx/conf/default.conf
    
  tasks:
    - name: print variables
      shell: echo "{{ http_port }} {{ server_name }} {{ conf_file }}"  > /tmp/text.txt

(2)變量的調試輸出
- name: register variables
  hosts: test
  tasks:
    - name: capture output of whoami command
      command: whoami
      register: login
    - debug: var=login

關於debug的更多用法說明:調試模塊,用於在調試中輸出信息
msg:調試輸出的消息
var:將某個變量傳遞給debug模塊,debug會直接將其打印輸出
verbosity:debug的級別

# Example that prints the loopback address and gateway for each host
- debug: msg="System {{ inventory_hostname }} has uuid {{ ansible_product_uuid }}"

- debug: msg="System {{ inventory_hostname }} has gateway {{ ansible_default_ipv4.gateway }}"
  when: ansible_default_ipv4.gateway is defined

- shell: /usr/bin/uptime
  register: result
- debug: var=result verbosity=2    #直接將上一條指令的結果作爲變量傳遞給var,由debug打印出result的值

- name: Display all variables/facts known for a host
  debug: var=hostvars[inventory_hostname] verbosity=4


八、變量之Fact
fact簡介:ansible有一個模塊叫setup,用於獲取遠程主機的信息,並可以將這些信息作爲變量在playbook裏進行調用。而setup模塊獲取這些信息的方法就是依賴於fact。
# ansible test -m setup
10.1.61.187 | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "10.1.61.187"
        ],
        "ansible_all_ipv6_addresses": [
            "fe80::f816:3eff:fe4f:6611"
        ],
        "ansible_apparmor": {
            "status": "disabled"
        },
        "ansible_architecture": "x86_64",
        "ansible_bios_date": "04/01/2014",
        "ansible_bios_version": "Ubuntu-1.8.2-1ubuntu1~cloud0",
        
        ...output omitted...
}
setup獲取的這些信息,都是可用於該主機的變量。

自定義fact
1. 手動設置fact
ansible除了能獲取到預定義的fact的內容,還支持手動爲某個主機定製fact。稱之爲本地fact。本地fact默認存放於被控制端的/etc/ansible/facts.d目錄下,沒有該目錄則手工創建,如果文件爲ini格式或者json格式,ansible會自動識別。以這種形式加載的fact是key爲ansible_local的特殊變量。

下面是一個簡單的示例,在ansibler主控端定義一個ini格式的custom.fact文件內容如下:
more /etc/ansible/facts.d/custom.fact
[general]
package = httpd
service = httpd
state = started

在服務器端執行
# ansible 10.1.61.187 -m setup        
10.1.61.187 | SUCCESS => {
    "ansible_facts": {
    
        ...output omitted...
        
        "ansible_local": {
            "custom": {
                "general": {
                    "package": "httpd",
                    "service": "httpd",
                    "state": "started"
                }
            }
        },

        ...output omitted...
  
}
我們可以寫一個簡單的playbook來使用這些facts:

- name: Install Apache and starts the service
  hosts: test
  tasks:
    - name: Install the required package
      yum: 
        name: "{{ ansible_facts.ansible_local.custom.general.package }}"
        state: latest
    - name: Start the service
      service: 
        name: "{{ ansible_facts.ansible_local.custom.general.service }}"
        state: "{{ ansible_facts.ansible_local.custom.general.state }}"

2. 使用set_fact模塊定義新的變量
set_fact模塊可以自定義facts,這些自定義的facts可以通過template或者變量的方式在playbook中使用。如果你想要獲取一個進程使用的內存的百分比,則必須通過set_fact來進行計算之後得出其值,並將其值在playbook中引用。
下面是一個set_fact模塊的應用示例:
- name: set_fact example
  hosts: test
  tasks:
    - name: Calculate InnoDB buffer pool size
      set_fact: innodb_buffer_pool_size_mb="{{ ansible_memtotal_mb / 2 |int }}"    
    - debug: var=innodb_buffer_pool_size_mb

執行playbook如下:
# ansible-playbook set_fact_ex.yaml 
PLAY [set_fact example] *******************************************************************************************************
TASK [Gathering Facts] *********************************************************************************************************
ok: [10.1.61.187]
TASK [Calculate InnoDB buffer pool size] *********************************************************************************
ok: [10.1.61.187]
TASK [debug] ***************************************************************************************************************
ok: [10.1.61.187] => {
    "innodb_buffer_pool_size_mb": "3911.0" }
PLAY RECAP *********************************************************************************************************************
10.1.61.187                : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
這種設置方式只在當前playbook當中有效

3、啓用fact緩存
如果在play中需要引入fact,則可以開啓fact緩存。fact緩存目前支持三種存儲方式,分別爲JSON、memcached、redis。
(1) Json文件fact緩存後端
使用JSON文件作爲fact緩存後端的時候,ansible將會把採集的fact寫入到控制主機的文件中。
ansible.cfg配置如下:
[defaults]
gathering = smart
fact_caching_timeout = 86400    #緩存時間,單位爲秒 
fact_caching = jsonfile #指定ansible包含fact的json文件位置,如果目錄不存在,會自動創建
fact_caching_connection = /tmp/ansible_fact_cache   #指定文件存儲路徑

(2)Redis fact緩存後端
使用redis作爲fact緩存後端,需要在控制主機上安裝redis服務並保持運行。需要安裝python操作redis的軟件包。
ansible.cfg配置如下:
[defaults]
gathering = smart
fact_caching_timeout = 86400 
fact_caching = redis

(3)Memcached fact緩存後端
使用memcached作爲fact緩存後端,需要在控制主機上安裝Memcached服務並保持運行,需要安裝python操作memcached的軟件包。
ansible.cfg配置如下:
[defaults]
gathering = smart
fact_caching_timeout = 86400 
fact_caching = memcached

4、關閉fact
如果不想從fact中獲取變量,或者說整個playbook當中都沒有使用到fact變量,可以通過如下方法關閉fact以提升執行效率:

- hosts: test
  gather_facts: no
也可以在ansible.cfg中添加如下配置:
或者:
[defaults]
gathering = explicit

九、使用lookup生成變量
說明:在通常情況下,所有的配置信息都會被作爲ansible的變量保存了,而且可以保存在ansible允許定義變量的各種地方,諸如vars區段,vars_files加載的文件中,以及host_vars和group_vars目錄中。但在有些時候,我們希望從諸如文本文件或者.csv文件中收集數據作爲ansible的變量,或者直接獲取某些命令的輸出作爲ansible的變量,甚至從redis或者etcd這樣的鍵值存儲中取得相應的值作爲ansible的變量。這個時候,我們就需要通過ansible的lookup插件來從這些數據源中讀取配置數據,傳遞給ansbile變量,並在playbook或者模板中使用這些數據。
ansible支持一套從不同數據源獲取數據的lookup,包括file, password, pipe, env, template, csvfile, dnstxt, redis_kv, etcd等
1、file
使用file lookup可以從文本文件中獲取數據,並在這些數據傳遞給ansible變量,在task或者jinja2模板中進行引用。下面是一個從文本文件中獲取ssh公鑰並複製到遠程主機的示例:
{{ lookup('file', '/users/breeze/.ssh/id_rsa.pub')}}

2、pipe
使用pipe lookup可以直接調用外部命令,並將命令執行的結果打印到標準輸出,作爲ansible變量。下面的例子通過pipe調用date指令拿到一個以時間數字組成的字串
- name: Flamingo | Get release version
  set_fact:
    flamingo_release_version: "{{ lookup('pipe', 'date +%Y%m%d%H%M%SZ') }}"

3、env
env lookup實際就是獲取在控制主機上的某個環境變量的值。下面是一個讀取控制機上$JAVA_HOME變量值的示例:
- name: get JAVA_HOME
  debug: msg="{{ lookup('env', 'JAVA_HOME')}}"

4、template
template lookup可以指定一個jinja2模板,然後返回這個模板中的變量被替換以後的結果。
假設我們有一個message.j2模板,內容如下:
This host runs {{ ansible_distribution }}

定義一個如下的task:
- name: print message from template
  debug: msg="{{ lookup('template', 'message.j2')}}"
輸出的msg的結果如下:
This host runs CentOS

5. csvfile
csvfile可以從.csv文件中讀取一個條目。假設我們有如下示例的名爲users.csv的文件:
username,email
lorin,[email protected]
john,[email protected]
sue,[email protected]

下面是一個使用csvfile lookkup提取sue的電子郵件地址的task示例:
- name: get sue's email
  debug: msg="{{ lookup('csvfile','sue file=users.csv delimiter=, col=1')}}"
可以看到,一共向插件傳遞了四個參數:sue, file=users.csv, delimiter=,col=1。說明如下:
第一個參數指定一個名字,該名字必須出現在其所在行的第0列,需要說明的是,如果指定的第一個參數名字在文件中出現多次,則匹配第一次出現的結果
第二個參數指定csv文件的文件名
第三個參數指定csv文件的中條目的分隔符
第四個參數指定要取得哪一列的值,這一列正是第一個參數所在行的那一列的值
如果我們想要查找的用戶存儲在名爲username的變量中,則可以使用"+"符號來連接username字串和其他的參數字串,來構建完整的參數字符串:
lookup('csvfile', username+'file=users.csv' delimiter=, col=1)

6、password
password lookup會隨機生成一個密碼,並將這個密碼寫入到參數指定的文件中。如下示例,創建一個名爲bob的mysql用戶,並隨機生成該用戶的密碼,並將密碼寫入到主控端的bob-password.txt中:
- name: create deploy mysql user
  mysql_user: name=bob password={{ lookup('password', 'bob-password,txt')}} priv=*.*:ALL state=present

十、使用vault配置加密
在使用ansible的過程中,不可避免的會存儲一些敏感信息,比如在變量文件中存儲帳號密碼信息等。ansible通過ansible-vault命令行工具來提供對敏感文件的加密和解密。
ansible-vault  #可以創建、加密、解密和查看文件。其可以加密任何ansible使用的文件,包括inventory文件,playbook中調用的變量文件等。
ansible-vault create file    #創建加密文件
ansible-vault edit file      #編輯加密文件
ansible-vault rekey file     #重置密碼
ansible-vault encrypt file   #加密已有文件
ansible-vault decrypt file   #解密文件
ansible-vault view file      #查看文件

Ansible-vault配置示例:
(1)創建一個user.yml的變量文件,內容如下:
username: "user1"
pwhash: "$1$GkTPu7we$ZZtdsLPIHkS.fmoVcn3v51"
(2)加密上面創建的變量文件:
# ansible-vault encrypt user.yml 
New Vault password: 
Confirm New Vault password: 
Encryption successful
(3)編寫playbook文件如下:
- name: create user accounts for all our servers
  hosts: test
  become: True
  remote_user: ansible
  vars_files:
    - user.yml
  tasks:
    - name: Creating user from user.yml
      user:
        name: "{{ username }}"
        password: "{{ pwhash }}"
(4)執行playbook
# ansible-playbook create_user.yml --ask-vault-pass
Vault password: 

也可以通過如下操作執行playbook:
echo redhat > vault-pass
chmod 600 vault-pass

ansible-playbook create_user.yml --vault-password-file=vault-pass

十一、Ansible lineinfile模塊詳解
1、修改匹配的行
# 將/etc/selinux/config中匹配到以'SELINUX='開頭的行,將其替換爲'SELINUX=disabled'
- name: modify selinux to disabled
  lineinfile:
    path: /etc/selinux/config
    regex: '^SELINUX='
    line: 'SELINUX=disabled'

2、在匹配行前或後添加內容
# cat /etc/http.conf
Listen 127.0.0.1:80
Listen 80
Port

(1)在匹配行前添加
在http.conf文件的Listen 80前面添加一行Listen 8080,task示例如下:
- name: add line before Listen 80
  lineinfile:
    dest: /etc/http.conf
    insertbefore: '^Listen 80'
    line: 'Listen 8080'

(2)在匹配行後添加
在http.conf文件的Port後面添加一行just a test,task示例如下:
- name: add line before Listen 80
  lineinfile:
    dest: /etc/http.conf
    insertafter: '^Port'
    line: 'just a test'

3、修改文件內容及權限
示例文件:
#cat /etc/hosts
127.0.0.1       localhost.localdomain localhost ::1       localhost6.localdomain6 localhost6
10.1.61.130     hub.dz11.com  
修改/etc/hosts,將以127.0.0.1開頭的行替換爲127.0.0.1 localhost,並將/etc/hosts的屬主和屬組都修改爲root,權限改爲644,如下:

- name: modify hosts
  lineinfile:
    dest: /etc/hosts
    regex: '^127\.0\.0\.1'
    line: '127.0.0.1 localhost'
    owner: root
    group: root
    mode: 0644

4、刪除一行內容
示例原文件:
#cat /etc/hosts
127.0.0.1       localhost.localdomain localhost ::1       localhost6.localdomain6 localhost6
10.1.61.130     hub.dz11.com
刪除以10.1.61.130開頭的行:

- name: delete a line
  lineinfile:
    dest: /etc/hosts
    regex: '^10\.1\.61'
    state: absent

5、文件存在則添加一行內容
往/etc/hosts裏添加一行10.1.61.131 test.dz11.com(多次執行,不會重複添加),示例如下:
- name: add a line
  lineinfile:
    dest: /etc/hosts
    line: '10.1.61.131 test.dz11.com'

6、如果有匹配的行則修改該行,如果不匹配則添加
示例原文件/tmp/test.txt內容如下:
# %wheel   ALL=(ALL)   ALL
下面的示例task中,匹配以%wheel開頭的行,匹配到,則執行替換,未匹配,則添加。因爲原文件中,沒有以%wheel開頭的行,所以會添加一行:
- name: add or modify a line
  lineinfile: 
    dest: /tmp/test.txt
    regex: '^%wheel'
    line: '%wheel  ALL=(ALL)       NOPASSWD: ALL'
修改後的文件如下:

#cat /tmp/text.txt
# %wheel   ALL=(ALL)   ALL
%wheel  ALL=(ALL)       NOPASSWD: ALL

7、參數backrefs,backup說明
backup: 是否備份原文件,默認爲no
backrefs:
    當backrefs爲no時,如果regex沒有匹配到行,則添加一行,如果Regx匹配到行,則修改該行
    當backrefs爲yes時,如果regex沒有匹配到行,則保持原文件不變,如果regex匹配到行,則修改該行
    backrefs默認爲no,所以上面那個示例中,我們沒有配置backrefs,而默認沒有匹配,則修改。
下面我們看一看backrefs爲yes時匹配到行的示例:

示例原文件:

# cat /tmp/testfile

# %wheel   ALL=(ALL)   ALL
%wheel  ALL=(ALL)       NOPASSWD: ALL
#?bar
task示例:

 - name: test backrefs
  lineinfile:
      backup: yes
      state: present
      dest: /tmp/testfile
      regexp: '^#\?bar'
      backrefs: yes
      line: 'bar'
修改後的文件:

# cat /tmp/testfile

# %wheel   ALL=(ALL)   ALL
%wheel  ALL=(ALL)       NOPASSWD: ALL

8、使用validate驗證文件是否正確修改
在一些場景下,我們修改完文件後,需要對文件做一下測試,用以檢查文件修改之後,是否能正常運行。如http.conf、nginx.conf等,一旦改錯,而不加以測試,可能會直接導致http服務掛掉。

可以使用validate關鍵字,在修改完成以後,對文件執行檢測:
- name: test validate
  lineinfile:
      dest: /etc/sudoers
      state: present
      regexp: '^%ADMIN ALL='
      line: '%ADMIN ALL=(ALL)'
      validate: 'visudo -cf %s'
  tags:
    - testsudo


十二、playbook循環語句
循環語句:我們在編寫playbook的時候,不可避免的要執行一些重複性操作,比如指安裝軟件包,批量創建用戶,操作某個目錄下的所有文件等。正如我們所說,ansible一門簡單的自動化語言,所以流程控制、循環語句這些編程語言的基本元素它同樣都具備。
1、loop關鍵字說明
tasks:
  - name: postfix and httpd are running
    service:
      name: "{{ item }}"
      state: started
    loop:
      - postfix
      - httpd
2、在循環語句中註冊變量01
# cat register_loop.yml 
- name: registered variable usage as a loop list
  hosts: test
  tasks:
      - name: ensure /mnt/bkspool exists     #確保目錄已經存在
        file:
          path: /mnt/bkspool
          state: directory
      - name: retrieve the list of home directories  #查看該目錄下的文件
        command: ls /home
        register: home_dirs
      - name: Show home_dirs results   #輸出目錄下的文件
        debug:
          var: home_dirs.stdout_lines
      - name: add home dirs to the backup spooler   #把目錄備份到指定的目錄下
        file: 
          path: /mnt/bkspool/{{ item }}
          src: /home/{{ item }}
          state: link
          force: yes
        loop: "{{ home_dirs.stdout_lines }}"

3、在循環中註冊變量02
- name: Loop Register test
  gather_facts: no
  hosts: test
  tasks:
    - name: Looping Echo Task
      shell: "echo this is my item: {{ item }}"
      loop:
        - one
        - two
      register: echo_results
    - name: Show echo_results variable
      debug:
        var: echo_results

4、循環語句關鍵字    描述    
with_lines           
with_sequence    
with_subelement       #遍歷子元素
with_together       #遍歷數據並行集合

(1) with_items   #簡單的列表循環
- hosts: test
  vars:
    data:
      - user0
      - user1
      - user2
  tasks:
    - name: "with_items"
      debug:
        msg: "{{ item }}"
      with_items: "{{ data }}"

(2)with_nested   #嵌套循環
tasks: 
  - name: debug loops
    debug: msg="name is {{ item[0] }}  vaule is {{ item[1] }} num is {{ item[2] }}"
    with_nested:
      - ['alice','bob']
      - ['a','b','c']
      - ['1','2','3']


(3)with_dict  #循環字典
# 假如有如下變量內容:
users:
  alice:
    name: Alice Appleworth
    telephone: 123-456-7890
  bob:
    name: Bob Bananarama
    telephone: 987-654-3210

# 現在需要輸出每個用戶的用戶名和手機號:
tasks:
  - name: Print phone records
    debug: msg="User {{ item.key }} is {{ item.value.name }} ({{ item.value.telephone }})"
    with_dict: "{{ users }}"


4. with_fileglob   #循環指定目錄中的所有文件
- hosts: test
  tasks:
    - name: Make key directory     
      file: 
        path: /root/.sshkeys 
        state: directory 
        mode: 0700 
        owner: root 
        group: root 
        
    - name: Upload public keys     
      copy: 
        src: "{{ item }}"
        dest: /root/.sshkeys
        mode: 0600 
        owner: root 
        group: root  
      with_fileglob:
        - /root/.ssh/*.pub 
        
    - name: Assemble keys into authorized_keys file     
      assemble: 
        src: /root/.sshkeys 
        dest: /root/.ssh/authorized_keys
        mode: 0600 
        owner: root 
        group: root

5. with_lines #循環一個文件中的所有行
with_lines循環結構會讓你在控制主機上執行任意命令,並對命令的輸出進行逐行迭代。假設我們有一個 文件test.txt包含如下行:
Breeze Yan
Bernie Yang
jerry Qing
我們可以通過如下方法進行逐行輸出:

- name: print all names
  debug: msg="{{ item }}"
  with_lines:
    - cat test.txt

6. with_subelement #
假如現在需要遍歷一個用戶列表,並創建每個用戶,而且還需要爲每個用戶配置以特定的SSH key登錄。變量文件內容如下:

users:
  - name: alice
    authorized:
      - /tmp/alice/onekey.pub
      - /tmp/alice/twokey.pub
    mysql:
        password: mysql-password
        hosts:
          - "%"
          - "127.0.0.1"
          - "::1"
          - "localhost"
        privs:
          - "*.*:SELECT"
          - "DB1.*:ALL"
  - name: bob
    authorized:
      - /tmp/bob/id_rsa.pub
    mysql:
        password: other-mysql-password
        hosts:
          - "db1"
        privs:
          - "*.*:SELECT"
          - "DB2.*:ALL"
playbook中定義如下:

tasks:
  - user: name={{ item.name }} state=present generate_ssh_key=yes
    with_items: "{{users}}"
  - authorized_key: "user={{ item.0.name }} key='{{ lookup('file', item.1) }}'"
    with_subelements:
     - users
     - authorized
也可以遍歷嵌套的子列表:

- name: Setup MySQL users
  mysql_user: name={{ item.0.name }} password={{ item.0.mysql.password }} host={{ item.1 }} priv={{ item.0.mysql.privs | join('/') }}
  with_subelements:
    - users
    - mysql.hosts
7. with_sequence   #生成一個自增的整數序列,可以指定起始值和結束值以及步長。參數以key=value的形式指定,format指定輸出的格式。數字可以是十進制、十六進制、八進制
- hosts: all
  tasks:
    # create groups
    - group: name=evens state=present
    - group: name=odds state=present
    # create some test users
    - user: name={{ item }} state=present groups=evens
      with_sequence: start=0 end=32 format=testuser%02d
    # create a series of directories with even numbers for some reason
    - file: dest=/var/stuff/{{ item }} state=directory
      with_sequence: start=4 end=16 stride=2    # stride用於指定步長
    # a simpler way to use the sequence plugin
    # create 4 groups
    - group: name=group{{ item }} state=present
      with_sequence: count=4

8. with_random_choice  #從列表中隨機取一個值:

- debug: msg={{ item }}
  with_random_choice:
     - "go through the door"
     - "drink from the goblet"
     - "press the red button"
     - "do nothing"

9. do-Util循環
- action: shell /usr/bin/foo
  register: result
  until: result.stdout.find("all systems go") != -1
  retries: 5
  delay: 10
重複執行shell模塊,當shell模塊執行的命令輸出內容包含"all systems go"的時候停止。重試5次,延遲時間10秒。retries默認值爲3,delay默認值爲5。任務的返回值爲最後一次循環的返回結果。


10. with_together
示例:

- hosts: webservers
  remote_user: root
  vars:
    alpha: [ 'a','b','c','d']
    numbers: [ 1,2,3,4 ]
  tasks:
    - debug: msg="{{ item.0 }} and {{ item.1 }}"
      with_together:
         - "{{ alpha }}"
         - "{{ numbers }}"
# 輸出的結果爲:
ok: [192.168.1.65] => (item=['a', 1]) => {
    "item": [
        "a",
        1
    ],
    "msg": "a and 1"
}
ok: [192.168.1.65] => (item=['b', 2]) => {
    "item": [
        "b",
        2
    ],
    "msg": "b and 2"
}
ok: [192.168.1.65] => (item=['c', 3]) => {
    "item": [
        "c",
        3
    ],
    "msg": "c and 3"
}
ok: [192.168.1.65] => (item=['d', 4]) => {
    "item": [
        "d",
        4
    ],
    "msg": "d and 4"
}

十三、Playbook條件語句
介紹:
在有的時候play的結果依賴於變量、fact或者是前一個任務的執行結果,或者有的時候,我們會基於上一個task執行返回的結果而決定如何執行後續的task。這個時候就需要用到條件判斷。
條件語句在Ansible中的使用場景:
在目標主機上定義了一個硬限制,比如目標主機的最小內存必須達到多少,才能執行該task
捕獲一個命令的輸出,根據命令輸出結果的不同以觸發不同的task
根據不同目標主機的facts,以定義不同的task
根據目標機的cpu的大小,以調優相關應用性能
用於判斷某個服務的配置文件是否發生變更,以確定是否需要重啓服務

1、when關鍵字
在ansible中,使用條件判斷的關鍵字就是when。
如在安裝包的時候,需要指定主機的操作系統類型,或者是當操作系統的硬盤滿了之後,需要清空文件等,可以使用when語句來做判斷。
when關鍵字後面跟着的是python的表達式,在表達式中你能夠使用任何的變量或者fact,當表達式的結果返回的是false,便會跳過本次的任務
---
- name: Install vim
  hosts: all
  tasks:
    - name:Install VIM via yum
      yum: 
        name: vim-enhanced 
        state: installed
      when: ansible_os_family =="RedHat"
      
    - name:Install VIM via apt
      apt: 
        name: vim 
        state: installed
      when: ansible_os_family =="Debian"
      
    - name: Unexpected OS family
      debug: msg="OS Family {{ ansible_os_family }} is not supported" fail=yes
      when: not ansible_os_family =="RedHat" or ansible_os_family =="Debian"

2、比較運算符
在上面的示例當中,我們使用了"=="的比較運算符,在ansible中,還支持如下比較運算符:
==:比較兩個對象是否相等,相等則返回真。可用於比較字符串和數字
!=:比較兩個對象是否不等,不等則爲真。
>:比較兩個對象的大小,左邊的值大於右邊的值,則爲真
<:比較兩個對象的大小,左邊的值小於右邊的值,則爲真
>=:比較兩個對象的大小,左邊的值大於等於右邊的值,則爲真
<=:比較兩個對象的大小,左邊的值小於等於右邊的值,則爲真
when: ansible_machine == "x86_64" 
when: max_memory <= 512

3、邏輯運算符
在Ansible中,除了比較運算符,還支持邏輯運算符:
and:邏輯與,當左邊和右邊兩個表達式同時爲真,則返回真
or:邏輯或,當左右和右邊兩個表達式任意一個爲真,則返回真
not:邏輯否,對表達式取反
():當一組表達式組合在一起,形成一個更大的表達式,組合內的所有表達式都是邏輯與的關係

# 邏輯或
when: ansible_distribution == "RedHat" or ansible_distribution == "Fedora"
# 邏輯與
when: ansible_distribution_version == "7.5" and ansible_kernel == "3.10.0-327.el7.x86_64"
when:
  - ansible_distribution_version == "7.5"
  - ansible_kernel == "3.10.0-327.el7.x86_64"
# 組合
when: => 
  ( ansible_distribution == "RedHat" and ansible_distribution_major_version == "7" )
  or
  ( ansible_distribution == "Fedora" and ansible_distribution_major_version == "28")

4、條件判斷與tests
在shell當中,我們可使用test命令來進行一些常用的判斷操作,如下:
# 判斷/test文件是否存在
test -e /test
# 判斷/testdir是否存在且爲一個目錄
test -d /testdir
事實上,在ansible中也有類似的用法,只不過ansible沒有使用linux的test命令,而是jinja2模板的tests。
下面是一個簡單示例:
# 通過條件語句判斷testpath的路徑是否存在
- hosts: test
  vars:
    testpath: /testdir
  tasks:
    - debug:
        msg: "file exist"
      when: testpath is exists
上面的示例中,我們使用了is exists用於路徑存在時返回真,也可以使用is not exists用於路徑不存在時返回真。也可以在整個條件表達式的前面使用not以取反:

- hosts: test
  vars:
    testpath: /testdir1
  tasks:
    - debug:
        msg: "file not exist"
      when: not testpath is exists
在ansible中,除了能夠使用exists這種tests之外,還有一些別的tests。接下來我們詳細說一說。

5、判斷變量
defined:判斷變量是否已定義,已定義則返回真
undefined:判斷變量是否未定義,未定義則返回真
none:判斷變量的值是否爲空,如果變量已定義且值爲空,則返回真
示例:
- hosts: test
  gather_facts: no
  vars:
    testvar: "test"
    testvar1:
  tasks:
    - debug:
        msg: "testvar is defined"
      when: testvar is defined
    - debug:
        msg: "testvar2 is undefined"
      when: testvar2 is undefined
    - debug:
        msg: "testvar1 is none"
      when: testvar1 is none
判斷執行結果
sucess或succeeded:通過任務執行結果返回的信息判斷任務的執行狀態,任務執行成功則返回true
failure或failed:任務執行失敗則返回true
change或changed:任務執行狀態爲changed則返回true
skip或skipped:任務被跳過則返回true

示例:
- hosts: test
  gather_facts: no
  vars:
    doshell: true
  tasks:
    - shell: 'cat /testdir/aaa'
      when: doshell
      register: result
      ignore_errors: true
    - debug:
        msg: "success"
      when: result is success
      
    - debug:
        msg: "failed"
      when: result is failure
      
    - debug:
        msg: "changed"
      when: result is change
      
    - debug:
        msg: "skip"
      when: result is skip

6、判斷路徑
file:判斷指定路徑是否爲一個文件,是則爲真
directory:判斷指定路徑是否爲一個目錄,是則爲真
link:判斷指定路徑是否爲一個軟鏈接,是則爲真
mount:判斷指定路徑是否爲一個掛載點,是則爲真
exists:判斷指定路徑是否存在,存在則爲真
特別注意:關於路徑的所有判斷均是判斷主控端上的路徑,而非被控端上的路徑

示例:
- hosts: test
  gather_facts: no
  vars:
    testpath1: "/testdir/test"
    testpath2: "/testdir"
  tasks:
    - debug:
        msg: "file"
      when: testpath1 is file
    - debug:
        msg: "directory"
      when: testpath2 is directory

7、判斷字符串
lower:判斷字符串中的所有字母是否都是小寫,是則爲真
upper:判斷字符串中的所有字母是否都是大寫,是則爲真
- hosts: test
  gather_facts: no
  vars: 
    str1: "abc"
    str2: "ABC"
  tasks:
    - debug:
        msg: "str1 is all lowercase"
      when: str1 is lower
    - debug:
        msg: "str2 is all uppercase"
      when: str2 is upper

8、判斷整除
even:判斷數值是否爲偶數,是則爲真
odd:判斷數值是否爲奇數,是則爲真
divisibleby(num):判斷是否可以整除指定的數值,是則爲真
示例:

- hosts: test
  gather_facts: no
  vars: 
    num1: 6
    num2: 8 
    num3: 15
  tasks:
    - debug: 
        msg: "num1 is an even number"
      when: num1 is even
    - debug:
        msg: "num2 is an odd number"
      when: num2 is odd
    - debug:
        msg: "num3 can be divided exactly by"
      when: num3 is divisibleby(3)

9、其他tests
(1)version
可用於對比兩個版本號的大小,或者與指定的版本號進行對比,使用語法爲version("版本號","比較操作符")
- hosts: test 
  vars: 
    ver1: 1.2 
    ver2: 1.3 
  tasks: 
    - debug: 
        msg: "ver1 is greater than ver2" 
      when: ver1 is version(ver2,">") 
    - debug: 
        msg: "system version {{ ansible_distribution_version }} greater than 7.3" 
      when: ansible_distribution_version is version("7.3","gt")
version中使用的比較運算符說明:
大於: >, gt
大於等於: >=, ge
小於: <, lt
小於等於: <=, le
等於: =, ==, eq
不等於: !=, <>, ne

(2)subset
判斷一個list是不是另一個list的子集

(3)superset
判斷一個list是不是另一個list的父集"
- hosts: test
  gather_facts: no
  vars:
    a:
      - 2
      - 5
    b: [1,2,3,4,5]
  tasks:
    - debug:
        msg: "A is a subset of B"
      when: a is subset(b)
    - debug:
        msg: "B is the parent set of A"
      when: b is superset(a)

(4)in
判斷一個字符串是否存在於另一個字符串中,也可用於判斷某個特定的值是否存在於列表中
- hosts: test
  vars:
    supported_distros:
      - RedHat
      - CentOS
  tasks:
    - debug:
        msg: "{{ ansible_distribution }} in supported_distros"
      when: ansible_distribution in supported_distros

(5)string
判斷對象是否爲一個字符串,是則爲真

(6)number
判斷對象是否爲一個數字,是則爲真
- hosts: test
  gather_facts: no
  vars:
    var1: 1
    var2: "1"
    var3: a
  tasks:
    - debug:
        msg: "var1 is a number"
      when: var1 is number
    - debug:
        msg: "var2 is a string"
      when: var2 is string
    - debug:
        msg: "var3 is a string"
      when: var3 is string
      
10、條件判斷與block
(1)block
我們在前面使用when做條件判斷時,如果條件成立則執行對應的任務。但這就面臨一個問題,當我們要使用同一個條件判斷執行多個任務的時候,就意味着我們要在某一個任務下面都寫一下when語句,而且判斷條件完全一樣。這種方式不僅麻煩而且顯得low。Ansible提供了一種更好的方式來解決這個問題,即block。
在ansible中,使用block將多個任務進行組合,當作一個整體。我們可以對這一個整體做條件判斷,當條件成立時,則執行塊中的所有任務:
- hosts: test
  tasks:
    - debug:
        msg: "task1 not in block"
    - block:
        - debug:
            msg: "task2 in block1"
        - debug:
            msg: "task3 in block1"
      when: 2 > 1
下面是一個稍微有用點兒的例子:
- hosts: test
  tasks:
    - name: set /etc/resolv.conf
      template: 
        src: resolv.conf.j2 
        dest: /etc/resolv.conf 
        owner: root 
        group: root 
        mode: 0644
    - block:
        - name: ensure /etc/resolvconf/resolv.conf.d/base file for ubuntu 16.04
          template: 
            src: resolv.conf.j2
            dest: /etc/resolvconf/resolv.conf.d/base
       
        - name: config dns for ubuntu 16.04
          template: 
            src: resolv.conf.j2
            dest: /etc/resolv.conf
      when: ansible_distribution == "Ubuntu" and ansible_distribution_major_version == "16" 
使用block注意事項:
可以爲block定義name(ansible 2.3增加的特性)
可以直接對block使用when,但不能直接對block使用loop

(2)rescue
block除了能和when一起使用之外,還能作錯誤處理。這個時候就需要用到rescue關鍵字:
- hosts: test
  tasks:
    - block:
        - shell: 'ls /testdir'
      rescue:
        - debug:
            msg: '/testdir is not exists'
在上面的例子中,當block中的任務執行失敗時,則運行rescue中的任務。如果block中的任務正常執行,則rescue的任務就不會被執行。如果block中有多個任務,則任何一個任務執行失敗,都會執行rescue。block中可以定義多個任務,同樣rescue當中也可以定義多個任務。

(3)always
當block執行失敗時,rescue中的任務纔會被執行;而無論block執行成功還是失敗,always中的任務都會被執行:

- hosts: test
  tasks:
    - block:
        - shell: 'ls /testdir'
      rescue:
        - debug:
            msg: '/testdir is not exists'
      always:
        - debug:
            msg: 'This task always executes'

11、條件判斷與錯誤處理
在上面講block的使用方法的時候,我們說block除了可以將多個任務組合到一起,還有錯誤處理的功能。接下來我們繼續說一說錯誤處理。
(1)fail模塊
在shell中,可能會有這樣的需求:當腳本執行至某個階段時,需要對某個條件進行判斷,如果條件成立,則立即終止腳本的運行。在shell中,可以直接調用"exit"即可執行退出。事實上,在playbook中也有類似的模塊可以做這件事。即fail模塊。
fail模塊用於終止當前playbook的執行,通常與條件語句組合使用,當滿足條件時,終止當前play的運行。
選項只有一個:
msg:終止前打印出信息
示例:
# 使用fail模塊中斷playbook輸出
- hosts: test
  tasks:
    - shell: echo "Just a test--error" 
      register: result
    - fail:
        msg: "Conditions established,Interrupt running playbook"
      when: "'error' in result.stdout"
    - debug:
        msg: "Inever execute,Because the playbook has stopped"
(2)failed_when
事實上,當fail和when組合使用的時候,還有一個更簡單的寫法,即failed_when,當滿足某個條件時,ansible主動觸發失敗。
# 如果在command_result存在錯誤輸出,且錯誤輸出中,包含了`FAILED`字串,即返回失敗狀態:
- name: this command prints FAILED when it fails
  command: /usr/bin/example-command -x -y -z
  register: command_result
  failed_when: "'FAILED' in command_result.stderr"
也可以直接通過fail模塊和when條件語句,寫成如下:
- name: this command prints FAILED when it fails
  command: /usr/bin/example-command -x -y -z
  register: command_result
  ignore_errors: True
- name: fail the play if the previous command did not succeed
  fail: msg="the command failed"
  when: " command_result.stderr and 'FAILED' in command_result.stderr"
ansible一旦執行返回失敗,後續操作就會中止,所以failed_when通常可以用於滿足某種條件時主動中止playbook運行的一種方式。
ansible默認處理錯誤的機制是遇到錯誤就停止執行。但有些時候,有些錯誤是計劃之中的。我們希望忽略這些錯誤,以讓playbook繼續往下執行。這個時候就可以使用ignore_errors忽略錯誤,從而讓playbook繼續往下執行。

(3)changed_when
當我們控制一些遠程主機執行某些任務時,當任務在遠程主機上成功執行,狀態發生更改時,會返回changed狀態響應,狀態未發生更改時,會返回OK狀態響應,當任務被跳過時,會返回skipped狀態響應。我們可以通過changed_when來手動更改changed響應狀態。示例如下:
- shell: /usr/bin/billybass --mode="take me to the river"
register: bass_result
changed_when: "bass_result.rc != 2"    #只有該條task執行以後,bass_result.rc的值不爲2時,纔會返回changed狀態
# this will never report 'changed' status
- shell: wall 'beep'
  changed_when: False    #當changed_when爲false時,該條task在執行以後,永遠不會返回changed狀態

11、在循環語句中使用條件語句
# 只打印大於5的值
tasks:
    - command: echo {{ item }}
      loop: [ 0, 2, 4, 6, 8, 10 ]
      when: item > 5
# 確保將mariadb-server安裝到根分區且根分區的可用空間要大於300M
- name: install mariadb-server if enough space on root
  yum: 
    name: mariadb-server
    state;拉特st
  loop: "{{ ansible_mounts }}"
  when: item.mount == "/" and item.size_available > 300000000

十四、文件管理模塊及Jinja2過濾器
1、常用文件管理模塊
(1)file  #我們在講ansible ad-hoc的時候,已經說過file模塊,在playbook中的使用也沒什麼不同,
下面給個簡單的示例:
- name: Touch a file and set permissions
  file:
    path: /path/to/file
    owner: user1
    group: group1
    mode: 0640
    state: touch

(2)synchronize  #同步模塊
synchronize模塊示例:
- name: synchronize local file to remote files
  synchronize:
    src: file
    dest: /path/to/file

(3)copy 
- name: copy a file to managed hosts
  copy:
    src: file
    dest: /path/to/file

(4)fetch #fetch模塊與copy模塊正好相反,copy是把主控端的文件複製到被控端,而fetch則是把被控端的文件複製到主控端。並且在主控端指定的目錄下,以被控端主機名的形式來組織目錄結構。
- name: Use the fetch module to retrieve secure log files
  hosts: all
  user: ansible
  tasks:
    - name: Fetch the /var/log/secure log file from managed hosts
      fetch:
        src: /var/log/secure
        dest: secure-backups
        flat: no
在主控端文件存儲的目錄樹如下:

# tree  secure-backups/
secure-backups/
└── 10.1.61.187
    └── var
        └── log
            └── secure
3 directories, 1 file

(5)lineinfile
lineinfile是一個非常有用的模塊,而且相對來說,也是用法比較複雜的模塊,可直接參考《Ansible lineinfile模塊》

(6)stat  #查看文件的狀態
stat模塊與linux中的stat命令一樣,用來顯示文件的狀態信息。
- name: Verify the checksum of a file
  stat:
    path: /path/to/file
    checksum_algorithm: md5
  register: result
  
- debug:
    msg: "The checksum of the file is {{ result.stat.checksum }}"

(7)blockinfile
圍繞着被標記的行插入、更新、刪除一個文本塊。
#cat files/test.html
<html>
  <head>
  </head>
  <body>
  </body>
</html>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章