Ansible基礎

Ansible

官網文檔:

//docs.ansible.com/ansible/latest/

//docs.ansible.com/ansible/latest/user_guide/index.html

安裝

yum安裝

配置yum源

$ cat /etc/yum.repos.d/ansible.repo
[epel]
name=epel
baseurl=//mirrors.aliyun.com/epel/7Server/x86_64/
enable=1
gpgcheck=0

安裝ansible

$ yum -y install ansible

如果有pip工具,也可以運行pip install ansible下載。使用pip下載的ansible不提供默認配置文件/etc/ansible/ansible.cfg。

安裝完成檢查

$ ansible --version
ansible 2.9.25
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/test/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /bin/ansible
  python version = 2.7.5 (default, Oct 14 2020, 14:45:30) [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)]

Ansible參數補全功能(可選)

從Ansible 2.9版本開始,ansible支持命令的選項補全功能,它依賴於python的argcomplete插件

$ yum -y install python-argcomplete			# 安裝
$ activate-global-python-argcomplete		# 激活

激活後,就可以按兩次tab補全命令和選項了。

環境配置

配置主機名解析

在master節點的/etc/hosts文件中,添加上ip主機名對應關係

$ cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.175.100 master
192.168.175.101 node1
192.168.175.102 node2

配置主機互信

ansible默認採用ssh連接,ssh連接要麼使用密碼認證,要麼使用公鑰認證。

這裡採用公鑰認證,實現免密登錄,後續使用ansible方便一些。

$ ssh-keygen -t rsa -f ~/.ssh/id_rsa -N ''
$ ssh-keyscan 192.168.175.101 >> ~/.ssh/known_hosts 2>/dev/null
$ ssh-keyscan 192.168.175.102 >> ~/.ssh/known_hosts 2>/dev/null
$ sshpass -p'hello123' ssh-copy-id [email protected]

測試:

[root@master ~]# ssh 192.168.175.101
Last login: Wed Jan  5 12:24:18 2022 from 192.168.175.1
[root@node1 ~]# exit
logout
Connection to 192.168.175.101 closed.
[root@master ~]#

發現不用輸入密碼即可直接登錄其它主機。


安裝、環境配置完成後,ansible還只能控制本機,不能實現對其它節點的批量控制。

若想實現它的真正功能,必須對它進行配置。

下面開始了解ansible的幾個重要的配置文件。

ansible的配置文件

全局配置文件

ansible默認的全局配置文件是/etc/ansible/ansible.cfg,可認為是全局配置的入口。

Ansible支持4種方式指定配置文件,它們的解析順序從上到下

  • ANSIBLE_CFG 環境變量指定的配置文件
  • ansible.cfg 當前目錄下的ansible.cfg
  • ~/.ansible.cfg 家目錄下的.ansible.cfg
  • /etc/ansible/ansible.cfg 默認全局配置文件

修改默認配置文件/etc/ansible/ansible.cfg:

$ cat /etc/ansible/ansible.cfg | grep -vE "^$|^#"
[defaults]
inventory      = /etc/ansible/hosts						# inventory文件:ansible管理的主機清單
library        = /usr/share/my_modules/
forks          = 5										# ansbile的並發連接數
sudo_user      = root
remote_port    = 22
host_key_checking = False
timeout = 10
log_path = /var/log/ansible.log

文件中其它的可配置項:

stdout_callback = debug        # 可將輸出變得人性化;默認輸出會擠在一行,配置後會換行輸出;

inventory主機文件

inventory文件默認路徑是/etc/ansible/hosts,在這裡配置目標主機,ansible便可以對其進行控制。

/etc/ansible/hosts文件里配置node主機名或ip:

$ cat hosts
[default]				#	主機分組
node1					#	192.168.175.101
node2					#	192.168.175.102

可以通過/etc/ansible/ansible.cfg文件修改inventory的默認路徑:inventory = /etc/ansible/hosts。如果將該配置指定為目錄,便可以使用多個inventory文件來管理節點,一般很少動這個。

通常,不會修改默認的路徑。如果有自定義的inventory文件,可以直接在ansible命令行中使用-i選項指定:

# ansible -i /tmp/my_inventory.ini ...
# ansible-playbook -i /tmp/my_inventory.ini ...

查看inventory

列出ansiblek可管理的所有主機

$ ansible-inventory --graph all			# 指定文件:ansible-inventory -i /etc/ansible/hosts --graph all
@all:									# all是默認的主機組,包含所有主機
  |--@default:
  |  |--node1
  |  |--node2
  |--@ungrouped:

運行ansible命令

完成上述的基本配置後,即可以開始使用ansible來批量管理主機了,這裡的管理方式為命令行方式(又稱為Ad-hoc方式)。

Ad-hoc方式運行ansible的命令格式:ansible 主機組/主機 -m 模塊 -a 參數

選項解析:

-m:	指定調用的模塊
-a:	向模塊傳遞的參數,模塊不需要則可省略;參數需要使用引號包圍

此外ansible命令還可以帶上其它選項:

-i:	指定本次的inventory路徑,指定該參數則後面不加主機組/主機
-e:	設置變量,格式為'var1="aaa" var="bbb"'
-v/vv/vvv:	命令輸出的打印級別

ansible的批量管理功能依靠各個模塊來完成,ansible提供了幾千個模塊(其中ansible團隊自己維護大約100多個核心模塊),每個模塊完成各自的作用。

下面用ping模塊和debug模塊來演示一下ansible的基礎功能。

ping模塊

ping模塊是ansible最基礎模塊之一,可用於檢測遠程主機是否在線。

命令:ansible 主機組/主機 -m ping

返回值:changed、ping

命令:ansible all -m ping

[root@master ansible]$ ansible all -m ping
node2 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}
node1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}

debug模塊

官網說明://docs.ansible.com/ansible/latest/collections/ansible/builtin/debug_module.html

debug模塊用於輸出或調試一些變量或數據。該模塊共有三個參數msg、var、verbosity。

msg:打印配置的信息
var:打印變量值
verbosity:運行級別,設置為3,則-vvv或更高才會打印輸出

命令:

ansible all -m debug -a 'msg="hello world"'

ansible all -e 'str="hello world"' -m debug -a 'var=str'

ansible all -v -m debug -a 'msg="hello world" verbosity=1'

示例:

[root@master ansible]$ ansible all -m debug -a 'msg="hello world"'
node1 | SUCCESS => {
    "msg": "hello world"
}
node2 | SUCCESS => {
    "msg": "hello world"
}
[root@master ansible]$ ansible all -e 'str="hello world"' -m debug -a 'var=str'
node1 | SUCCESS => {
    "str": "hello world"
}
node2 | SUCCESS => {
    "str": "hello world"
}

[root@master ansible]$ ansible all -m debug -a 'msg="hello world" verbosity=1'
node1 | SKIPPED
node2 | SKIPPED
[root@master ansible]$ ansible all -v -m debug -a 'msg="hello world" verbosity=1'
Using /etc/ansible/ansible.cfg as config file
node1 | SUCCESS => {
    "msg": "hello world"
}
node2 | SUCCESS => {
    "msg": "hello world"
}

playbook

playbook、play、task的關係

  • playbook中可以定義一個或多個play
  • 每個play中可以定義一個或多個task
    • 其中還可以定義兩類特殊的task:pre_tasks和post_tasks
    • pre_tasks表示執行執行普通任務之前執行的任務列表
    • post_tasks表示普通任務執行完之後執行的任務列表
  • 每個play都需要通過hosts指令指定要執行該play的目標主機
  • 每個play都可以設置一些該play的環境控制行為,比如定義play級別的變量

playbook01

編寫一個playbook並執行

playbook使用yaml語法格式組織各種playtask規則。

下面使用ping模塊和debug模塊編寫一個playbook文件如下:

# cat test.yaml
---
- name: play1								# play的名稱,非必須
  hosts: all								# 指定目標主機
  gather_facts: false						# 收集目標主機信息,默認值true,非必須
  tasks:									# tasks聲明任務列表
    - name: task1							# task任務名稱,非必須
      ping:									# 模塊
        data: "pong task1"					# 模塊參數
    - name: task2
      ping:
        data: "pong task2"
- name: play2
  hosts: all
  gather_facts: false
  tasks:
    - name: task1
      debug:
        msg: "hello task1 in play2"
    - name: task2
      debug:
        msg: "hello task2 in play2"

注意所有的-:符號後面均需要接一個空格

執行playbook的命令是ansible-playbook test.yaml

該命令同樣支持像ansile命令一樣的多個選項,如-e-i-v

$ ansible-playbook -v test.yaml			
Using /etc/ansible/ansible.cfg as config file

PLAY [play1] **********************************************************************************************************************

TASK [task1] **********************************************************************************************************************
ok: [node2] => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "ping": "pong task1"}
ok: [node1] => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "ping": "pong task1"}

TASK [task2] **********************************************************************************************************************
ok: [node1] => {"changed": false, "ping": "pong task2"}
ok: [node2] => {"changed": false, "ping": "pong task2"}

PLAY [play2] **********************************************************************************************************************

TASK [task1] **********************************************************************************************************************
ok: [node1] => {
    "msg": "hello task1 in play2"
}
ok: [node2] => {
    "msg": "hello task1 in play2"
}

TASK [task2] **********************************************************************************************************************
ok: [node1] => {
    "msg": "hello task2 in play2"
}
ok: [node2] => {
    "msg": "hello task2 in play2"
}

PLAY RECAP ************************************************************************************************************************
node1                      : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
node2                      : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

playbook文件各配置指令及含義

yaml文件中,使用-表示一個列表元素,多個key:value表示一個字典。

每個playbook使用列表來組織多個playplay內同樣使用列表來組織多個task;play和task自身則採用字典的方式組織,即多個鍵值對。

在playbook頂層使用- xxx:表示這是一個play;每個play中必須包含hoststasks指令。

hosts指令用來指定要執行該play的目標主機,可以是主機名、主機組或者其它多種方式。

tasks指令用來指定該play中包含的任務列表,每個任務使用- xxx:方式表示。

name指令用來設置play和task的名稱,值具有唯一性。

gather_facts指令用來收集目標主機的信息,由setup模塊提供。默認情況下,每個play都先執行這個特殊任務,收集完信息才開始其它任務。如果後續任務中用不到該信息,則可以禁止掉該任務,提升效率。

向模塊傳遞參數

yaml中,向模塊傳遞參數的方式總結為字符串和數組兩種方式。

還是以debug模塊為例。

數組方式如上面test.yaml文件里的傳參,即key: value的形式:

......
- name: task1
      debug:
        msg: "hello task1 in play2"
        verbosity=1

字符串方式,由於yaml的語法規則(字符串換行將自動轉換為空格),又有不同的書寫形式:

---
- name: debug
  hosts: all
  gather_facts: false
  tasks:
    - name: task1
      debug:
        msg="hello task1" verbosity=1					# 參數寫成一行
    - name: task2	
      debug:
        msg="hello task2"								# 參數寫成多行
        verbosity=1
    - name: task3
      debug: |											# 豎線|,將保留字符串的換行符,否則將自動轉換成空格,在一些模塊中很有用,如shell
        msg="hello task3"						
        verbosity=1
    - name: task4
      debug: >											# 符號>,效果和直接寫成多行一樣
        msg="hello task4"
        verbosity=1

還可以直接使用指令args指明參數:

......
- name: task1
      debug:
      args:
        msg: "hello task1 in play2"

默認的任務執行策略

/etc/ansible/ansible.cfg文件的forks配置,決定ansible執行任務的並發連接數。

假如forks配置為5,那麼ansible第一次將同時連接5個node節點執行任務。其中若有節點提前執行完任務, 則ansible會新建一個新進程,來連接下一個節點執行任務。

forks是保證最多有N個節點同時執行任務。

常見模塊

shell模塊

說明:

shell模塊接收shell命令,命令後可跟空格分割的參數列;

必須傳入自由格式的命令,或者cmd參數;

它十分類似command模塊,但是它在遠程主機上通過shell(比如/bin/bash)來運行命令;

shell模塊相比command模塊,支持解析特殊符號<>|;&等。

參數:

Parameter Choices/Defaults 說明
chdir path 運行腳本前,切換到相關目錄
cmd string 需要運行的命令,後跟可選的參數
creates path 若一個文件或目錄存在,則跳過該步驟
removes path 若一個文件或目錄不存在,則跳過該步驟
executable path 改變執行命令的解釋器,如/bin/bash、/usr/bin/expect、/usr/bin/python;絕對路徑
free_form string 自由格式的命令(即命令字符串,沒有相關參數,直接寫在shell模塊後面即可)
stdin string Set the stdin of the command directly to the specified value.
stdin_add_newline boolean Choices:noyes Whether to append a newline to stdin data.
warn boolean Choices:noyes Whether to enable task warnings.

示例:

---
- name: shell
  hosts: all
  gather_facts: no
  tasks:
    - name: task1
      shell:
        hostname
    - name: task2
      shell:
        cmd: date +"%F %T"
    - name: task3
      shell: 
        cmd: pwd
        chdir: /etc/sysconfig
    - name: task4
      shell:
        cmd: ls /tmp
        creates: /tmp
    - name: task5
      shell:
        cmd: print('hello world')
        executable: /usr/bin/python

注意chdir參數只支持下面的方式:

- name: task3
      shell: 
        cmd: pwd
        chdir: /etc/sysconfig
- name: task3
      shell: pwd					#	free_from格式需要採用args參數,顯式指定chdir參數
      args:
        chdir: /etc/sysconfig
# 下面的方式將發生錯誤
- name: task3
      shell: pwd
        chdir: /etc/sysconfig

Ad-hoc方式:

$ ansible all -m shell -a "ls -l | wc -l"
$ ansible all -v -m shell -a "ls chdir=/tmp "

//docs.ansible.com/ansible/latest/collections/ansible/builtin/shell_module.html#ansible-collections-ansible-builtin-shell-module

script模塊

說明:

script模塊接受一個腳本名稱,後面可跟空格分割的參數列;

支持自由格式的命令,或者cmd參數;

將本地腳本傳輸到遠程主機上執行;

在遠程主機上使用shell環境執行腳本;

該模塊不需要python,類似raw模塊。

參數:

Parameter Choices/Defaults 說明
chdir path 運行腳本前,切換到遠程主機的相關目錄
cmd string 需要運行的腳本路徑,後跟可選的參數
creates path 若一個文件或目錄存在,則跳過該步驟
removes path 若一個文件或目錄不存在,則跳過該步驟

示例:

- name: Run a script with arguments (free form)
  ansible.builtin.script: /some/local/script.sh --some-argument 1234

- name: Run a script with arguments (using 'cmd' parameter)
  ansible.builtin.script:
    cmd: /some/local/script.sh --some-argument 1234

- name: Run a script only if file.txt does not exist on the remote node
  ansible.builtin.script: /some/local/create_file.sh --some-argument 1234
  args:
    creates: /the/created/file.txt

- name: Run a script only if file.txt exists on the remote node
  ansible.builtin.script: /some/local/remove_file.sh --some-argument 1234
  args:
    removes: /the/removed/file.txt

- name: Run a script using an executable in a non-system path
  ansible.builtin.script: /some/local/script
  args:
    executable: /some/remote/executable

- name: Run a script using an executable in a system path
  ansible.builtin.script: /some/local/script.py
  args:
    executable: python3

Ad-hoc:

$ ansible all -m script -a "/tmp/hello.sh world"
$ ansible all -m script -a "/tmp/hello.sh world creates=/tmp"

//docs.ansible.com/ansible/latest/collections/ansible/builtin/script_module.html#ansible-collections-ansible-builtin-script-module

hostname模塊

說明:

設置遠程設備的主機名。

//docs.ansible.com/ansible/latest/collections/ansible/builtin/hostname_module.html

在playbook中設置變量

vars指令可在play或task中設置變量,可以設置一個或多個。可以採用字典或列表的形式定義變量。

字典變量的定義和引用

定義:

vars:
    foo1:
      a: hello
      b: world
    foo2:
      a: aaa
      b: bbb

引用:

使用點號或方括號,在yaml文件使用jinja2語法引用,需要加單雙引號,否則解析yaml的時候將報錯

- name: task1
  debug:
    msg: "{{ foo1.a }} {{ foo1['b'] }}"			# 注意{{}}是jinja2的語法,在yaml文件中需要使用引號引起來,單雙引號都行
- name: task2
  debug:
    var: foo1.a,foo1['b']						# debug模塊的var參數,多個值使用逗號分隔,且無需花括號,前後加不加引號均可
- name: task3
  debug:
    msg: '{{ foo2.a }} {{ foo2.b }}'

示例:

---
- name: vars play
  hosts: all
  gather_facts: no
  vars:
    foo1:
      a: hello
      b: world
    foo2:
      a: aaa
      b: bbb
  tasks:
    - name: task1
      debug:
        msg: "{{ foo1.a }} {{ foo1['b'] }}"				
    - name: task2
      debug:
        var: foo1.a,foo1['b']
    - name: task3
      debug:
        msg: '{{ foo2.a }} {{ foo2.b }}'

列表變量的定義和引用

定義:

vars:
    foo:
      - a: hello
        b: world
      - a: aaa
        b: bbb

引用:

- name: task1
  debug:
    msg: "{{ foo[0].a }} {{ foo[0].b }}"
- name: task2
  debug:
    msg: "{{foo[1]['a']}} {{foo[1]['b']}}"

引用變量時,使用點號比較方便,但如果變量名本身帶點,則盡量選擇方括號的方式。

when指令進行條件判斷

when指令是ansible提供的唯一一個通用條件指令。when指令後的變量引用不需要雙花括號,當when指令的值為true時,執行任務。

yaml文件如下:

---
- name: when
  hosts: all
  gather_facts: no
  vars:
    foo: test
  tasks:
    - name: task1
      when: foo == "test"						# when指令的變量可直接引用
      debug:
        msg: "hello"
    - name: task2
      when: foo == "dev"
      debug:
        msg: "world"

示例:

從輸出中可看出,任務task2由於條件不滿足自動跳過

[root@master ansible]# ansible-playbook -v when.yaml
Using /etc/ansible/ansible.cfg as config file

PLAY [when] ***********************************************************************************************************************

TASK [task1] **********************************************************************************************************************
ok: [node1] => {
    "msg": "hello"
}
ok: [node2] => {
    "msg": "hello"
}

TASK [task2] **********************************************************************************************************************
skipping: [node1] => {}
skipping: [node2] => {}

PLAY RECAP ************************************************************************************************************************
node1                      : ok=1    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0
node2                      : ok=1    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

多個判斷條件:

- name: task3
  when: foo == "dev" or foo == "test"			# 邏輯或
  debug:
    msg: "hello world"
- name: task4
  when: (foo == "dev") or (foo == "test")		# 支持括號括起來
  debug:
    msg: "hello world"
- name: task5
  when: (foo == "dev") and (foo == "test")		# 邏輯與
  debug:
    msg: "hello world"
- name: task6
  when:							
    - foo == "dev"								# 邏輯與的另一種組織方式
    - foo == "test"
  debug:
    msg: "hello world"
- name: task7
  when: foo != "dev"							# 邏輯否
  debug:
    msg: "hello world"

loop循環結構

loop指令中的各項元素將以item變量名進行迭代。

直接迭代列表

迭代簡單列表:

列表元素為字符串

- name: task1
  shell: "{{ item }}"
  loop:
    - hostname
    - "uptime -p"

示例:

[root@master ansible]# cat loop.yaml
---
- name: loop
  hosts: all
  gather_facts: no
  tasks:
    - name: task1
      shell: "{{ item }}"
      loop:
        - hostname
        - "uptime -p"
[root@master ansible]# ansible-playbook -v loop.yaml
Using /etc/ansible/ansible.cfg as config file

PLAY [loop] ***********************************************************************************************************************

TASK [task1] **********************************************************************************************************************
changed: [node2] => (item=hostname) => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "ansible_loop_var": "item", "changed": true, "cmd": "hostname", "delta": "0:00:00.020436", "end": "2022-01-09 15:05:17.126971", "item": "hostname", "rc": 0, "start": "2022-01-09 15:05:17.106535", "stderr": "", "stderr_lines": [], "stdout": "node2", "stdout_lines": ["node2"]}
changed: [node1] => (item=hostname) => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "ansible_loop_var": "item", "changed": true, "cmd": "hostname", "delta": "0:00:00.361005", "end": "2022-01-09 15:05:17.409020", "item": "hostname", "rc": 0, "start": "2022-01-09 15:05:17.048015", "stderr": "", "stderr_lines": [], "stdout": "node1", "stdout_lines": ["node1"]}
changed: [node2] => (item=uptime -p) => {"ansible_loop_var": "item", "changed": true, "cmd": "uptime -p", "delta": "0:00:00.006628", "end": "2022-01-09 15:05:17.610052", "item": "uptime -p", "rc": 0, "start": "2022-01-09 15:05:17.603424", "stderr": "", "stderr_lines": [], "stdout": "up 3 weeks, 2 days, 19 hours, 42 minutes", "stdout_lines": ["up 3 weeks, 2 days, 19 hours, 42 minutes"]}
changed: [node1] => (item=uptime -p) => {"ansible_loop_var": "item", "changed": true, "cmd": "uptime -p", "delta": "0:00:00.074039", "end": "2022-01-09 15:05:18.006283", "item": "uptime -p", "rc": 0, "start": "2022-01-09 15:05:17.932244", "stderr": "", "stderr_lines": [], "stdout": "up 3 weeks, 2 days, 19 hours, 42 minutes", "stdout_lines": ["up 3 weeks, 2 days, 19 hours, 42 minutes"]}

PLAY RECAP ************************************************************************************************************************
node1                      : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
node2                      : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

迭代hash哈希列表:

列表元素為字典

- name: task1
  shell:
    cmd: "{{ item.cmd }}"
    creates: "{{ item['condition'] }}"						# 兩種引用方式均可
  loop:
    - { cmd: 'hostname', condition: '/tmp' }				# 這個循環將被跳過
    - { cmd: 'uptime', condition: '/aaa' }					

示例:

[root@master ansible]# cat loop.yaml
---
- name: loop
  hosts: all
  gather_facts: no
  tasks:
    - name: task1
      shell:
        cmd: "{{ item.cmd }}"
        creates: "{{ item.condition }}"
      loop:
        - { cmd: 'hostname', condition: '/tmp' }			# 這個循環將被跳過,["skipped, since /tmp exists"],任務沒有跳過
        - { cmd: 'uptime', condition: '/aaa' }				
[root@master ansible]#
[root@master ansible]# ansible-playbook -v loop.yaml
Using /etc/ansible/ansible.cfg as config file

PLAY [loop] ***********************************************************************************************************************

TASK [task1] **********************************************************************************************************************
ok: [node2] => (item={u'cmd': u'hostname', u'condition': u'/tmp'}) => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "ansible_loop_var": "item", "changed": false, "cmd": "hostname", "item": {"cmd": "hostname", "condition": "/tmp"}, "rc": 0, "stdout": "skipped, since /tmp exists", "stdout_lines": ["skipped, since /tmp exists"]}
changed: [node2] => (item={u'cmd': u'uptime', u'condition': u'/aaa'}) => {"ansible_loop_var": "item", "changed": true, "cmd": "uptime", "delta": "0:00:00.024675", "end": "2022-01-09 15:21:23.286758", "item": {"cmd": "uptime", "condition": "/aaa"}, "rc": 0, "start": "2022-01-09 15:21:23.262083", "stderr": "", "stderr_lines": [], "stdout": " 15:21:23 up 23 days, 19:58,  2 users,  load average: 0.13, 0.14, 0.20", "stdout_lines": [" 15:21:23 up 23 days, 19:58,  2 users,  load average: 0.13, 0.14, 0.20"]}
ok: [node1] => (item={u'cmd': u'hostname', u'condition': u'/tmp'}) => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "ansible_loop_var": "item", "changed": false, "cmd": "hostname", "item": {"cmd": "hostname", "condition": "/tmp"}, "rc": 0, "stdout": "skipped, since /tmp exists", "stdout_lines": ["skipped, since /tmp exists"]}
changed: [node1] => (item={u'cmd': u'uptime', u'condition': u'/aaa'}) => {"ansible_loop_var": "item", "changed": true, "cmd": "uptime", "delta": "0:00:00.386491", "end": "2022-01-09 15:21:30.029184", "item": {"cmd": "uptime", "condition": "/aaa"}, "rc": 0, "start": "2022-01-09 15:21:29.642693", "stderr": "", "stderr_lines": [], "stdout": " 15:21:30 up 23 days, 19:58,  2 users,  load average: 0.90, 0.51, 0.35", "stdout_lines": [" 15:21:30 up 23 days, 19:58,  2 users,  load average: 0.90, 0.51, 0.35"]}

PLAY RECAP ************************************************************************************************************************
node1                      : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
node2                      : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

迭代字典

loop指令無法直接迭代字典,需要使用過濾器dict2items進行轉換,如下:

- name: Using dict2items
  ansible.builtin.debug:
    msg: "{{ item.key }} - {{ item.value }}"
  loop: "{{ tag_data | dict2items }}"
  vars:
    tag_data:
      Environment: dev
      Application: payment

上例中,通過迭代tag_data來打印它的key和value。

when指令和loop指令同時使用時,先進行循環,再在每個循環中進行條件判斷。

notify和handlers

ansible中的一個重要概念changed,它表示目標狀態是否發生改變,即本次任務是否執行、執行後是否影響結果。如果changed=1,則表示目標狀態發生改變;如果changed=0,則表示目標狀態未發生改變,或者任務沒有執行。

ansible若監視到changed=1,就會觸發notify指令所定義的handler。handler,也是一個task,只是定義在handlers中,需要notify來觸發執行。

handlers的使用與tasks使用一樣,notify和hanlders中任務名稱必須一樣

當一個play中所有任務都執行完成後,handler才會執行。好處是可以多次觸發notify,但最後只執行一次對應的handler。

示例:

---
- name: notify and handlers
  hosts: all
  gather_facts: no
  tasks:
    - name: task1
      shell: uptime
      notify: hello
  handlers:
    - name: hello
      debug:
        msg: "hello world"

執行結果:

[root@master ansible]# ansible-playbook notify.yaml

PLAY [notify and handlers] ********************************************************************************************************

TASK [task1] **********************************************************************************************************************
changed: [node2]
changed: [node1]

RUNNING HANDLER [hello] ***********************************************************************************************************
ok: [node2] => {}

MSG:

hello world
ok: [node1] => {}

MSG:

hello world

PLAY RECAP ************************************************************************************************************************
node1                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
node2                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

組織playbook

將所有play全部寫在一個yaml文件中,固然可行,但是可讀性、維護性太差。

比較好的做法是,將同類任務的play放在一個文件中。多個任務,則寫成多個文件,最後使用一個入口文件來引用這些任務文件。

假設入口文件名為main.yaml,在該文件中使用import_playbook指令引用其它playbook:

---
- import_playbook: "init_server/aaa.yaml"
- import_playbook: "init_server/bbb.yaml"
- import_playbook: "init_server/ccc.yaml"
- import_playbook: "init_server/ddd.yaml"

執行方式不變:ansible-playbook main.yaml

組織各類內容:task、handler、變量

上面介紹了使用playbook指令來引入多個playbook文件,從而提高可讀性和維護性。

其實,ansible還提供了更加規範的方式,來組織更多的內容,即rolecolllection,collection這裡暫不涉及。

ansible可組織的內容包括:

  • playbook
  • task
  • variable
  • handler(handler也是一個task,只是編寫在handlers內部)
  • role

可組織的意思是說,可將相同內容定義在同一個文件中,然後使用相關指令來引入指定文件內容。

  • 引入palybookimport_playbook
  • 引入taskhandlerimport_tasksinclude_tasks
  • 引入variablevars_filesinclude_vars
  • 引入roleimport_roleinclude_roleroles

import和include兩種引入方式有所區別,前者為靜態加載(在playbook解析的階段,內容將寫入到引入的位置),後者為動態加載(解析階段不引入,而是在執行階段才引入)。

組織tasks

可將task單獨寫在一個文件中,然後在play里使用import_tasksinclude_tasks模塊引入。

示例:

編寫一個tasks文件tasks.yaml:

---
- name: task1
  debug:
    msg: "hello"
- name: task2
  debug:
    msg: "world"

在playbook中引入:

---
- name: play1
  hosts: node1
  gather_facts: no
  tasks:
    - name: task1 & task2
      import_tasks: tasks.yaml		# include_tasks模塊也行

在循環中引入tasks文件,必須使用include_tasks指令

---
- name: play1
  hosts: node1
  gather_facts: no
  tasks:
    - name: loop tasks
      include_tasks: tasks.yaml
      loop:
        - one
        - two

組織handlers

一般情況下playbook的handlers如下,在task中使用handler的任務名來觸發

---
- name: play1
  hosts: node1
  gather_facts: no
  tasks:
    - name: task1
      shell: uptime
      notify: h1
    - name: task2
      shell: date
      notify: h2
  handlers:
    - name: h1
      debug:
        msg: "run h1"
    - name: h2
      debug:
        msg: "run h2"

可將handler單獨編寫在一個文件中,如下:

$ cat handler1.yaml
---
- name: handler1
  debug:
    msg: "run handler1"
$ cat handler2.yaml
---
- name: handler2
  debug:
    msg: "run handler2"

然後在playbook中使用import_tasksinclude_tasks指令來引入,同時在notify中修改對應的handler名:

---
- name: play1
  hosts: node1
  gather_facts: no
  tasks:
    - name: task1
      shell: uptime
      notify: handler1						#	handler1為靜態引入,notify使用handler名
    - name: task2
      shell: date
      notify: h2							#	handler2為動態引入,notify使用引入的任務名
  handlers:
    - name: h1
      import_tasks: handler1.yaml			#	import_tasks靜態引入
    - name: h2
      include_tasks: handler2.yaml			#	include_tasks動態引入

組織變量

前面已經介紹了如何在playbook中使用var指令直接設置變量,除了這個方法,ansible還支持將變量單獨放在一個文件中,然後在play中使用vars_files指令或include_vars模塊來引入該變量文件。也可以在命令行中,使用-e選項(–extra_vars)來設置變量或引入變量文件。

vars_filesplay級別的指令,用於play中,在playbook解析階段引入變量文件;

include_vars是任務模塊(類似模塊一樣),用在tasks中定義一個引入變量的任務,只有該任務執行之後,才會創建變量;

-e選項在命令行中,全局有效;

vars_files示例

變量文件varfile.yaml,變量的定義一樣,使用yaml或json格式,可採用字典或列表的形式。

---
foo:
  a: hello
  b: world

playbook文件如下,使用vars_files指令來引入:

---
- name: play1
  hosts: node1
  gather_facts: no
  vars_files:
     - varfile.yaml						#	多個變量文件,使用列表形式即可
  tasks:
    - name: task1
      debug:
        msg: "{{foo.a}} {{foo.b}}"

include_vars模塊

include_vars是模塊提供的功能,它是一個手動創建的任務,和shell、debug等模塊一樣。所以只有當任務執行完後,相關變量才會創建。

下面介紹幾個用法。

引入一個文件:

---
- name: play1
  hosts: node1
  gather_facts: no
  vars_files:
     - varfile.yaml
  tasks:
    - name: task1
      include_vars: varfile.yaml					# 引入之後,可在後續的任務中使用
    - name: task2
      debug:
        msg: "{{foo.a}} {{foo.b}}"

引入多個文件,可採用循環:

- name: task1
  include_vars: "{{ item }}"
  loop:
  	- varfile1.yaml
  	- varfile2.yaml

還可以引入目錄,使用條件和其它參數控制引入的變量文件。這裡不展開了,該模塊有很多參數和用法,具體可參考官網:

//docs.ansible.com/ansible/latest/collections/ansible/builtin/include_vars_module.html#ansible-collections-ansible-builtin-include-vars-module

-e選項

ansible-playbook命令的-e選項或–extra-vars選項也可以用來定義變量或引入變量文件:

# 定義單個變量
$ ansible-playbook -e 'var1="value1"' xxx.yml

# 定義多個變量
$ ansible-playbook -e 'var1="value1" var2="value2"' xxx.yml

# 引入單個變量文件
$ ansible-playbook -e '@varfile1.yml' xxx.yml

# 引入多個變量文件
$ ansible-playbook -e '@varfile1.yml' -e '@varfile2.yml' xxx.yml

使用role

上面將各類內容放在單獨的文件中,然後使用相關指令或模塊將其引入。ansible中,有一種更為規範的組織方式,即role

使用role,即可無需手動使用這些指令或模塊了。按照role指定的文件或目錄存放對應的內容,ansible就會自動引入。

role的文件結構

ansible-galaxy init role1命令,可以快速創建一個role框架。

$ cd /etc/ansible/roles
$ ansible-galaxy init role01
$ tree role01
role01
├── defaults
│   └── main.yml
├── files										# 外部文件,放入此處的文件,在role的各種任務中直接無需使用全路徑
├── handlers
│   └── main.yml				#	存放handler
├── meta
│   └── main.yml				# 	該role依賴的先行role。定義在此處的role將在該role運行前執行
├── README.md
├── tasks
│   └── main.yml				#	存放任務
├── templates									# 模板文件,放入此處的文件,在role的各種任務中無需使用全路徑		
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml				#	存放變量

在相應目錄及文件下編寫對應內容,然後還需要提供一個入口playbook文件。在入口playbook文件中,使用import_rolesinclude_roleroles指令來引入role。最後使用ansible-playbook命令執行入口文件,即可執行定義在role中的各種任務了。

enter.yml文件如下:

---
- name: enter for all roles
  hosts: node1
  gather_facts: no
  roles:
    - role01					# 多個role可使用列表一起列出

編寫一個role

定義role的變量

etc/ansible/roles/role01/vars/main.yml文件是role定義變量或引入變量文件的地方。

etc/ansible/roles/role01/defaults/main.yml文件是role定義默認變量的地方,優先級較低,當然也可以引入文件。

---
# vars file for role01
foo1:
  a: hello
  b: world

定義role的task

/etc/ansible/roles/role01/tasks/main.yml文件是role定義task或者引入task文件的地方。

---
# tasks file for role01
- name: task1
  debug:
    msg: "{{ foo1.a }} {{ foo1.b }}"
  notify: handler1
  changed_when: true							# 該指令使得chenged=1,觸發notify

定義role的handler

/etc/ansible/roles/role01/handles/main.yml文件是role定義handler或者引入handler文件的地方。

---
# handlers file for role01
- name: handler1
  debug:
    msg: "run handler1"

執行:

$ ansible-playbook  enter.yml

PLAY [enter for all roles] **********************************************************************************************************

TASK [role01 : task1] ***************************************************************************************************************
changed: [node1] => {}

MSG:

hello world

RUNNING HANDLER [role01 : handler1] *************************************************************************************************
ok: [node1] => {}

MSG:

run handler1

PLAY RECAP **************************************************************************************************************************
node1                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

playbook的執行順序

一個節點執行任務的順序如下:

  • 解析配置/etc/ansible/ansible.cfg
  • 解析inventory
  • gather_facts任務
  • pre_tasks任務
  • pre_tasks任務觸發的handler
  • roles指令加載的role
  • task指令中的任務
  • roles和tasks觸發的handler
  • post_tasks指令中的任務
  • post_tasks中任務觸發的handler

多個節點時,ansible在所有節點上執行完成前一個任務後,才進入下一個任務的執行流程。handlers是在所有節點上的所有任務執行完成後,開始執行。

  • 從inventory中選擇執行play的相關主機
  • 連接到遠程主機,通常使用ssh方式
  • 拷貝相關模塊到遠程主機,並開始執行

理解委託任務

委託是指將原本在一個節點上執行的任務,委託給另一個節點執行。

要想理解委託,需要先了解ansible任務的執行過程:默認情況下,ansible先選擇執行任務的主機,後連接該主機,再拷貝相關模塊,並在該遠程主機上執行模塊。

但是在任務中進行了委託後,實際連接主機和執行模塊動作將發生改變。比如將node1主機的任務委託給node2,ansible會根據hosts指令選擇遠程主機node1後,然後根據delegate_to指令,連接到node2節點,並將相關模塊拷貝到node2並在node2上執行。

以下使用dalegate_to指令做個演示

test.yaml文件:

---
- name: play1
  hosts: node1
  gather_facts: no
  tasks:
    - name: task1
      shell: hostname
      delegate_to: node2			# 委託node2執行shell模塊

執行ansible-playbook -vvv test.yaml,然後查看具體的執行日誌。

再將delegate_to指令去除,執行ansible-playbook -vvv test.yaml命令查看不委託的執行過程,兩者對比很容易發現ansible連接的主機為委託的主機。

$ ansible-playbook -vvv test.yaml

打印輸出日誌如下,也可在/var/log/ansible.log中查看。

ansible-playbook 2.9.25
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible-playbook
  python version = 2.7.5 (default, Oct 14 2020, 14:45:30) [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)]
Using /etc/ansible/ansible.cfg as config file
host_list declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
script declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
auto declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
Parsed /etc/ansible/hosts inventory source with ini plugin
Skipping callback 'actionable', as we already have a stdout callback.
Skipping callback 'counter_enabled', as we already have a stdout callback.
Skipping callback 'debug', as we already have a stdout callback.
Skipping callback 'dense', as we already have a stdout callback.
Skipping callback 'dense', as we already have a stdout callback.
Skipping callback 'full_skip', as we already have a stdout callback.
Skipping callback 'json', as we already have a stdout callback.
Skipping callback 'minimal', as we already have a stdout callback.
Skipping callback 'null', as we already have a stdout callback.
Skipping callback 'oneline', as we already have a stdout callback.
Skipping callback 'selective', as we already have a stdout callback.
Skipping callback 'skippy', as we already have a stdout callback.
Skipping callback 'stderr', as we already have a stdout callback.
Skipping callback 'unixy', as we already have a stdout callback.
Skipping callback 'yaml', as we already have a stdout callback.

PLAYBOOK: test.yaml *****************************************************************************************************************
1 plays in test.yaml

PLAY [play1] ************************************************************************************************************************
META: ran handlers

TASK [task1] ************************************************************************************************************************
task path: /etc/ansible/test.yaml:6
<node2> ESTABLISH SSH CONNECTION FOR USER: None
<node2> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=22 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/0d67807d8c node2 '/bin/sh -c '"'"'echo ~ && sleep 0'"'"''
<node2> (0, '/root\n', '')
<node2> ESTABLISH SSH CONNECTION FOR USER: None
<node2> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=22 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/0d67807d8c node2 '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo /root/.ansible/tmp `"&& mkdir "` echo /root/.ansible/tmp/ansible-tmp-1642857067.57-71643-202799162287554 `" && echo ansible-tmp-1642857067.57-71643-202799162287554="` echo /root/.ansible/tmp/ansible-tmp-1642857067.57-71643-202799162287554 `" ) && sleep 0'"'"''
<node2> (0, 'ansible-tmp-1642857067.57-71643-202799162287554=/root/.ansible/tmp/ansible-tmp-1642857067.57-71643-202799162287554\n', '')
<node1> Attempting python interpreter discovery
<node2> ESTABLISH SSH CONNECTION FOR USER: None
<node2> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=22 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/0d67807d8c node2 '/bin/sh -c '"'"'echo PLATFORM; uname; echo FOUND; command -v '"'"'"'"'"'"'"'"'/usr/bin/python'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.7'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.6'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.5'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python2.7'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python2.6'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'/usr/libexec/platform-python'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'/usr/bin/python3'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python'"'"'"'"'"'"'"'"'; echo ENDFOUND && sleep 0'"'"''
<node2> (0, 'PLATFORM\nLinux\nFOUND\n/usr/bin/python\n/usr/bin/python2.7\n/usr/libexec/platform-python\n/usr/bin/python\nENDFOUND\n', '')
<node2> ESTABLISH SSH CONNECTION FOR USER: None
<node2> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=22 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/0d67807d8c node2 '/bin/sh -c '"'"'/usr/bin/python && sleep 0'"'"''
<node2> (0, '{"osrelease_content": "NAME=\\"CentOS Linux\\"\\nVERSION=\\"7 (Core)\\"\\nID=\\"centos\\"\\nID_LIKE=\\"rhel fedora\\"\\nVERSION_ID=\\"7\\"\\nPRETTY_NAME=\\"CentOS Linux 7 (Core)\\"\\nANSI_COLOR=\\"0;31\\"\\nCPE_NAME=\\"cpe:/o:centos:centos:7\\"\\nHOME_URL=\\"//www.centos.org/\\"\\nBUG_REPORT_URL=\\"//bugs.centos.org/\\"\\n\\nCENTOS_MANTISBT_PROJECT=\\"CentOS-7\\"\\nCENTOS_MANTISBT_PROJECT_VERSION=\\"7\\"\\nREDHAT_SUPPORT_PRODUCT=\\"centos\\"\\nREDHAT_SUPPORT_PRODUCT_VERSION=\\"7\\"\\n\\n", "platform_dist_result": ["centos", "7.9.2009", "Core"]}\n', '')
Using module file /usr/lib/python2.7/site-packages/ansible/modules/commands/command.py
<node2> PUT /root/.ansible/tmp/ansible-local-71634Zeeqqp/tmpWHppzz TO /root/.ansible/tmp/ansible-tmp-1642857067.57-71643-202799162287554/AnsiballZ_command.py
<node2> SSH: EXEC sftp -b - -C -o ControlMaster=auto -o ControlPersist=60s -o Port=22 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/0d67807d8c '[node2]'
<node2> (0, 'sftp> put /root/.ansible/tmp/ansible-local-71634Zeeqqp/tmpWHppzz /root/.ansible/tmp/ansible-tmp-1642857067.57-71643-202799162287554/AnsiballZ_command.py\n', '')
<node2> ESTABLISH SSH CONNECTION FOR USER: None
<node2> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=22 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/0d67807d8c node2 '/bin/sh -c '"'"'chmod u+x /root/.ansible/tmp/ansible-tmp-1642857067.57-71643-202799162287554/ /root/.ansible/tmp/ansible-tmp-1642857067.57-71643-202799162287554/AnsiballZ_command.py && sleep 0'"'"''
<node2> (0, '', '')
<node2> ESTABLISH SSH CONNECTION FOR USER: None
<node2> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=22 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/0d67807d8c -tt node2 '/bin/sh -c '"'"'/usr/bin/python /root/.ansible/tmp/ansible-tmp-1642857067.57-71643-202799162287554/AnsiballZ_command.py && sleep 0'"'"''
<node2> (0, '\r\n{"changed": true, "end": "2022-01-22 21:11:09.433351", "stdout": "node2", "cmd": "hostname", "rc": 0, "start": "2022-01-22 21:11:09.333195", "stderr": "", "delta": "0:00:00.100156", "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": true, "strip_empty_ends": true, "_raw_params": "hostname", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin_add_newline": true, "stdin": null}}}\r\n', 'Shared connection to node2 closed.\r\n')
<node2> ESTABLISH SSH CONNECTION FOR USER: None
<node2> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=22 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/0d67807d8c node2 '/bin/sh -c '"'"'rm -f -r /root/.ansible/tmp/ansible-tmp-1642857067.57-71643-202799162287554/ > /dev/null 2>&1 && sleep 0'"'"''
<node2> (0, '', '')
changed: [node1 -> node2] => {
    "changed": true, 
    "cmd": "hostname", 
    "delta": "0:00:00.100156", 
    "end": "2022-01-22 21:11:09.433351", 
    "invocation": {
        "module_args": {
            "_raw_params": "hostname", 
            "_uses_shell": true, 
            "argv": null, 
            "chdir": null, 
            "creates": null, 
            "executable": null, 
            "removes": null, 
            "stdin": null, 
            "stdin_add_newline": true, 
            "strip_empty_ends": true, 
            "warn": true
        }
    }, 
    "rc": 0, 
    "start": "2022-01-22 21:11:09.333195"
}

STDOUT:

node2
META: ran handlers
META: ran handlers

PLAY RECAP **************************************************************************************************************************
node1                      : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

到此為止,ansible的基礎算是過了一遍,對ansible的基礎概念、用法有了一個大概的了解。

但是ansible的內容和細節還有很多,包括不限於:

  • inventory更加複雜的定義方式

  • 各種級別的指令

  • 眾多類型的變量和其作用

  • 各種條件判斷

  • 各類循環,以及其它控制流程

  • 文件如何加載解析、任務執行的順序及方式

  • 更多的模塊、插件

  • role、vault、jinja2

  • 二次開發

  • 配置文件

Tags: