Ansible文件部署与大项目多主机管理
Ansible 部署文件到受管主机
实验环境
[wsh@controller ansible ✔]$ pwd
/home/wsh/ansible
[wsh@controller ansible ✔]$ ls
ansible.cfg inventory
[wsh@controller ansible ✔]$ cat ansible.cfg inventory
[defaults]
inventory = ./inventory
remote_user = wsh[privilege_escalation]
become=True
become_method=sudo
become_user=root
become_ask_pass=False
[controllers]
controller[nodes]
node[1:4]
修改文件并将其复制到主机
红帽Ansible引擎本身内置附带了大型模块集合(模块库),为了便于整理、记录和管理这些模块,已根据文档中的功能以及在系统上安装的时间将它们分成多个组。
Files 模块库包含的模块允许您完成与Linux文件管理相关的大多数任务,如创建、复制、编辑和修改文件的权限和其他属性,下表列出了常用的Files模块库中使用的模块。
- file:设置权限、所有权、SELinux上下文以及常规文件、符号链接、硬链接和目录的时间戳等属性。此模块还可以创建或删除常规文件、符号链接、硬链接和目录。
- sefcontext,设置持久selinux上下文。
- lineinfile:确保特定行位于某个文件中,或使用反向引用正则表达式来替换现有行。
- replace:查找文件中行,一次性替换成对应内容。
- blockinfile:插入、更新或删除多行文本块。
- stat:检索文件的状态信息,类似于Linux stat命令。
- copy:将文件从本地或远程计算机复制到受管节点上的某个位置。
- synchronize:围绕rsync命令的一个程序,可加快和简化常见任务。
- fetch:用于从远程计算机获取文件到控制节点。
file 模块
**示例1:**创建文件或修改文件属性
---
- hosts: node1gather_facts: notasks:- name: Touch a file and set permissionsfile:path: /tmp/testfileowner: laomagroup: wheelmode: 0640state: touch
mode选项: 必须使用前导0 (‘0644’ or ‘01777’) 或者引起来 (‘‘644’’ or ‘‘1777’’) 。如果直接写640,则会640当做10进制,并转换成二进制1 010 000 000,最终文件权限是-w- — --T。
**示例2:**创建目录
---
- hosts: node1gather_facts: notasks:- name: create directoryfile:path: /webdevowner: apachegroup: apachemode: 0755state: directory
**示例3:**删除文件
---
- hosts: node1gather_facts: notasks:- name: delete file file:path: /tmp/testfilestate: absent
sefcontext 模块
selinux库中添加默认规则。
示例:
---
- hosts: node1gather_facts: notasks:- sefcontext:target: '/samba(/.*)?'setype: samba_share_tstate: present
注意:
- sefcontext模块是修改selinux 库规则,并不会立刻应用于文件,relabel时候才会生效。
- file模块会立刻应用于文件。
lineinfile 模块
示例1:确保文件中存在特定行
---
- hosts: node1gather_facts: notasks:- name: add linelineinfile:path: /tmp/testfileline: 'Add this line to file'state: present
还可以在特定位置插入:
-
insertbefore,最后一个匹配到前插入
--- - hosts: node1gather_facts: notasks:- name: add linelineinfile:path: /etc/httpd/conf/httpd.confline: 'Listen 82'insertbefore: 'Listen 80'state: present
-
insertafter,最后一个匹配到后插入
--- - hosts: node1gather_facts: notasks:- name: add linelineinfile:path: /etc/httpd/conf/httpd.confline: 'Listen 82'insertafter: 'Listen 80'state: present
示例2:替换文本行
匹配
regexp
中的内容将整行替换为line
中的内容
---
- hosts: node1gather_facts: notasks:- name: replace linelineinfile:path: /tmp/testfileregexp: 'Add'line: 'replace'state: present
---
- hosts: node1gather_facts: notasks:- name: add linelineinfile:path: /etc/httpd/conf/httpd.confline: '#Listen 80'regexp: '^Listen 80'state: present
示例3:替换成多行文本
匹配regexp
中的内容将整行替换为line
中的多行文本
---
- hosts: node1gather_facts: notasks:- name: add linelineinfile:path: /tmp/testfileline: |line 1line 2regexp: 'replace'state: present
replace 模块
该模块使用正则表达式匹配内容,将匹配的内容替换成指定的内容。匹配的多个地方都会被替换掉。
示例:
---
- hosts: node1gather_facts: notasks:- name: replace multi linereplace:path: /tmp/testfileregexp: '^Hello World.*'replace: 'Hello Laoma'
blockinfile 模块
示例:将文本块添加到现有文件
---
- hosts: node1gather_facts: notasks:- name: add block lines to fileblockinfile:path: /tmp/testfileblock: |line 1 in fileline 2 in fileaaline 3 in file sssstate: present
添加的内容如下,会在第一行和最后一行额外添加注释。
# BEGIN ANSIBLE MANAGED BLOCK
line 1 in file
line 2 in fileaa
line 3 in file sss
# END ANSIBLE MANAGED BLOCK
stat 模块
**stat 模块检索文件的信息,类似于Linux stat命令。**参数提供检索文件属性、确定文件校验和等功能。
stat 模块返回一个包含文件状态数据的值的散列字典,允许您使用单独的变量引用各条信息。
示例:
---
- hosts: node1gather_facts: notasks:- stat:path: /tmp/testfilechecksum_algorithm: md5register: result- debug:msg: "/tmp/testfile md5 is {{ result.stat.checksum }}"- debug:var: result
copy 模块
示例1:将控制节点上文件拷贝到受管理节点,类似于Linux中scp命令。
---
- hosts: node1gather_facts: notasks:- name: copy /tmp/testfile to remote nodecopy:src: /tmp/testfiledest: /tmp
说明:此模块假定设置了force: yes,强制覆盖远程文件,类似scp命令。如果设置force: no, 则不会出现覆盖。
示例2:写入字符串到文件
---
- hosts: node1gather_facts: notasks:- name: write string into /tmp/testfilecopy:content: "hello world\n"dest: /tmp/testfile
synchronize 模块
synchronize 模块是一个围绕 rsync 工具的打包程序,它简化了playbook中的常见文件管理任务。
rsync工具必须同时安装在本地和远程主机上。默认情况下,在使用synchronize模块时, “本地主机”是同步任务的源主机, 而 “目标主机”是synchronize连接到的主机。
示例1:同步文件
---
- hosts: node1gather_facts: notasks:- name: synchronize filesynchronize:src: /tmp/testfiledest: /tmp/
示例2:同步目录
---
- hosts: node1gather_facts: noremote_user: roottasks:- name: synchronize directorysynchronize:src: /etc/sysconfigdest: /tmp/
fetch 模块
从受管节点检索文件,例如将被管理节点文件先取到控制节点,然后用于分发到其他节点。诸如SSH公钥之类的文件。
示例:
---
- hosts: node1gather_facts: notasks:- name: fetch file from remote nodefetch:src: /tmp/testfiledest: /tmp
文件保存在:/tmp/node1/tmp/testfile。
[wsh@controller ansible ✔]$ tree /tmp/node1
/tmp/node1
└── tmp└── testfile1 directory, 1 file
使用JINJA2模板部署文件
JINJA2 模板介绍
Jinja2 模板是功能强大的工具,可用于自定义要在受管节点上部署的配置文件。 创建Jinja2 模板后,可以通过template模块部署到受管节点上, 该模块支持将控制节点中的本地文件转移到受管节点。
示例1:部署web服务器,主页内容显示为Welcome to HOSTNAME。HOSTNAME为受管主机完全主机名。
playbook内容如下:
---
- name: Enable intranet serviceshosts: node1tasks:- name: ensure latest version of httpd yum:name: httpdstate: latest- name: test html page is installed# template模块: 复制文件到受管主机时,根据jinja2语法替换template:# 指定 Jinja2 模板来源src: index.html.j2# 指定要在目标主机上创建的文件dest: /var/www/html/index.html- name: httpd enabled and runningservice:name: httpdenabled: truestate: restarted
...
index.html.j2 内容如下:
Welcome to {{ ansible_fqdn }}
剧本执行完成后,index.html内容如下:
Welcome to node1.laoma.cloud
template模块,与copy模块类似,允许指定已部署文件的所有者(拥有该文件的用户) 、组、权限和 SELinux上下文 。
示例2:推送 ssh 服务配置文件。 sshd_config.j2内容如下:
# {{ ansible_managed }}
# DO NOT MAKE LOCAL MODIFICATIONS TO THIS FILE AS THEY WILL BE LOST
Port {{ ssh_port }}
ListenAddress {{ ansible_facts['default_ipv4']['address'] }}HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
SyslogFacility AUTHPRIV
PermitRootLogin {{ root_allowed }}
AllowGroups {{ groups_allowed }}AuthorizedKeysFile /etc/.rht_authorized_keys .ssh/authorized_keys
PasswordAuthentication {{ passwords_allowed }}
playbook 参考
---
- name: config sshd servicehosts: node1vars:ssh_port: 1022root_allowed: "yes"groups_allowed: wheelpasswords_allowed: "yes"ansible_managed: "Ansible managed"tasks:- name: config sshd servicetemplate:src: sshd_config.j2dest: /root/sshd_config
Jinja2 模板语法
Jinja2 模板由多个元素组成:数据、变量和表达式。在呈现Jinja2模板时, 这些变量和表达式被替换为对应的值。模板中使用的变量可以在playbook的vars部分中指定,也可以使用受管主机FACTS。
变量和逻辑表达式置于分隔符之间:
- {{ EXPR }},用于装载表达式,比如变量,运算表达式,比较表达式。
- {% EXPR %},用于装载控制语句,比如if,for等。
- {# #},用于装载注释,模板文件中的注释不会包含在最终生成文件中。
for 语句
Jinja2使用for语句来提供循环功能。
示例1:
---
- name: test templatehosts: node1vars:users:- tom- jack- Snoopy- lucytasks:- name: test templatetemplate:src: testfile.j2dest: /tmp/testfile
...
testfile.j2内容如下:
{% for user in users %}
{{ user }}
{% endfor %}
for用于声明循环,{% endfor %} 表示结束。user变量会遍历users变量中所有值。
生成的/tmp/testfile内容如下:
tom
jack
Snoopy
lucy
**示例2:**testfile.j2内容如下
{# for statement #}
{% for user in users %}
{{ loop.index }} - {{ user }}
{% endfor %}
loop.index代表当前循环的索引号,从1开始到最后循环体的数量。例如循环体有12个,那么loop.index代表1,2,3,…,12。
生成的/tmp/testfile内容如下:
1 - tom
2 - jack
3 - Snoopy
4 - lucy
**示例3:**部署myhosts
创建 playbook:
-
使用模板文件hosts.j2在dev主机组中的主机上生成文件/etc/myhosts。
-
hosts.j2内容如下:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
-
针对每个受管节点包含一行内容:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain610.1.8.10 controller.laoma.cloud controller 10.1.8.11 node1.laoma.cloud node1 10.1.8.12 node2.laoma.cloud node2 10.1.8.13 node3.laoma.cloud node3 10.1.8.14 node4.laoma.cloud node4
注:清单主机名称的显示顺序不重要。
inventory 内容如下:
[controllers]
controller[dev]
node1[test]
node2[prod]
node3
node4
答案如下:
---
- name: /etc/myhosts is up to datehosts: alltasks:- name: Deploy /etc/myhoststemplate:src: hosts.j2dest: /etc/myhostswhen: inventory_hostname in groups.dev
hosts.j2内容如下:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6{% for server in groups.all %}
{{ hostvars[server].ansible_default_ipv4.address }} {{ hostvars[server].ansible_fqdn }} {{ hostvars[server].ansible_hostname }}
{% endfor %}
说明:
- hostvars是magic 变量,通过该变量引用其他受管主机facts。
- [server]不能使用单引号,因为server作为变量处理,替换为all中主机。
- play 必须针对所有主机执行,收集所有主机facts,因为模版需要引用所有主机的facts。
- 只部署到dev主机组,使用when语句。groups.dev是magic变量,该变量获取dev主机组中主机清单。
**示例4: **num 变量引用方括号中列表
{% for num in [3,1,7,8,2] %}
{{ num }}
{% endfor %}
生成的文件内容如下:
3
1
7
8
2
示例5: range(3)代表 0 1 2,循环三次。
{% for num in range(3) %}
{{ num }}
{% endfor %}
生成的文件内容如下:
0
1
2
**示例6:**range(1,4,2),从1开始,到4结束,步长2。
{% for num in range(1,4,2) %}
{{ num }}
{% endfor %}
生成的文件内容如下:
1
3
if 语句
Jinja2使用if语句来提供条件控制。如果满足某些条件, 则在部署文件中放置一行。if语句判断条件同ansible中when语句。
示例1: 只要变量finished不是假值,那么输出finished变量值。
{% if finished %}
{{ finished }}
{% endif %}
示例2: 如果PORT定义了端口,那么使用该值,否则使用默认3306。
{% if PORT is defined %}
bind-address=0.0.0.0:{{ PORT }}
{% else %}
bind-address=0.0.0.0:3306
{% endif %}
示例3:
{% if 条件1 %}
...
{% elif 条件2 %}
...
{% elif 条件n %}
...
{% else %}
...
{% endif %}
{% elif 条件N %}可以多次使用。
示例4: for和if配合使用
{% for num in [7,1,5,3,9] if num>3 %}
{{ num }}
{% endfor %}
等同于:
{% for num in [7,1,5,3,9] %}
{% if num>3 %}
{{ num }}
{% endif %}
{% endfor %}
生成的文件内容如下:
7
5
9
表达式
示例1:
{{ 1 == 1 }}
{{ 2 != 2 }}
{{ 2 > 1 }}
{{ 2 >= 1 }}
{{ 2 < 1 }}
{{ 2 <= 1 }}
生成的文件内容如下:
True
False
True
True
False
False
**示例2: **
jinja2 test
{{ (2 > 1) or (1 > 2) }}
{{ (2 > 1) and (1 > 2) }}
{{ not true }}
{{ not True }}
{{ not false }}
{{ not False }}
生成的文件内容如下:
jinja2 test
True
False
False
False
True
True
**示例3: **
{{ 3 + 2 }}
{{ 3 - 4 }}
{{ 3 * 5 }}
{{ 2 ** 3 }}
{{ 7 / 5 }}
{{ 7 // 5 }}
{{ 17 % 5 }}
生成的文件内容如下:
5
-1
15
8
1.4
1
2
**示例4: **
jinja2 test
{{ 1 in [1,2,3,4] }}
{{ 1 not in [1,2,3,4] }}
生成的文件内容如下:
jinja2 test
True
False
模版注释
{# #},用于装载注释,模板文件中的注释不会包含在最终生成文件中。
示例:
{# 此处是注释 #}
{% for num in [7,1,5,3,9] %}
{% if num>3 %}
{{ num }}
{% endif %}
{% endfor %}
生成的文件内容如下:
7
5
9
ansible_managed 变量
为避免系统管理员修改Ansible部署的文件,最好在模板顶部包含注释,指示不应手动编辑该文件。
在/etc/ansible/ansible.cfg配置文件[defaults]块中有如下默认配置:
[defaults]
ansible_managed = Ansible managed
可使用ansible_managed变量来引用“Ansible managed” 字符串。
要想在jinja2 模板内,使用ansible_managed变量代表的字符串,使用下列语法:
{{ ansible_managed }}
filter 过滤器
Jinja2还提供filter,对输出的结果进行格式化输出。
- “{{ output | to_json }}”,使用JSON格式输出。
- “{{ output | to_yaml }}”,使用YAML格式输出。
- “{{ output | to_nice_json }}”,使用人们更加可读的JSON格式输出。
- “{{ output | to_nice_yaml }}”,使用人们更加可读的YAML格式输出。
- “{{ output | from_json }}”,把output当做JSON格式解析。
- “{{ output | from_yaml }}”,把output当做YAML格式解析。
字符串处理过滤器:
- “{{ testvar | upper }}” ,将字符串转换成纯大写。
- “{{ testvar | lower }}” ,将字符串转换成纯小写。
- “{{ testvar | capitalize }}”,将字符串变成首字母大写,之后所有字母纯小写。
- “{{ testvar | reverse }}”,将字符串反转。
- “{{ testvar | first }}”,返回字符串的第一个字符。
- “{{ testvar | last }}”,返回字符串的最后一个字符。
- “{{ testvar | trim }}”,将字符串开头和结尾的空格去除。
- “{{ testvar | center(width=30) }}”,将字符串放在中间,并且设置字符串的长度为30,字符串两边用空格补齐30位长。
- “{{ testvar | length }}”,返回字符串长度,length与count等效,可以写为count。
- “{{ testvar | list }}”,将字符串转换成列表,每个字符作为一个元素。
列表处理过滤器:
- “{{ testvar | first }}”,返回列表中的第一个值。
- “{{ testvar | last }}”,返回列表中的最后一个值。
- “{{ testvar | min }}”,返回列表中最小的值。
- “{{ testvar | max }}”,返回列表中最大的值。
- “{{ testvar | sort }}”,将列表升序排序输出。
- “{{ testvar | sort(reverse=true) }}”,将列表降序排序输出。
- “{{ testvar | sum }}”,返回纯数字非嵌套列表中所有数字的和。
- “{{ testvar | join }}”,将列表中的元素合并成一个字符串。
- “{{ testvar | join(’ , ') }}”,将列表中的元素使用指定分隔符合并成一个字符串。
- “{{ testvar | random }}”,从列表中随机返回一个元素。
- “{{ testvar | unique }}”,去掉列表中重复的元素,重复的元素只留下一个。
数字处理过滤器:
- {{ ‘a’ | int }}",将对应的值转换成int类型,如果无法转换,默认返回0。使用int(default=6)或者int(6)时,如果无法转换则返回指定值6。
- “{{ ‘a’ | float }}”,将对应的值转换成浮点型,如果无法转换,默认返回’0.0’。使用float(default=8.88)或者float(8.88)时,如果无法转换则返回默认值’8.88’。
- “{{ testvar4 | abs }}”,获取对应数值的绝对值。
- “{{ 12.5 | round }}”,四舍五入。
- “{{ 3.1415926 | round(5) }}”,取小数点后五位。
- “{{ 100 | random }}”,从0到100中随机返回一个随机数。
- “{{ 10 | random(start=5) }}”,从5到10中随机返回一个随机数。
- “{{ 15 | random(step=5) }}”,从0到15中随机返回一个随机数,这个随机数是5的倍数。
- “{{ 15 | random(start=5,step=3) }}”,从5到15中随机返回一个随机数,步长为3。
Ansible管理大项目
实验环境
[wsh@controller ansible ✔]$ pwd
/home/wsh/ansible
[wsh@controller ansible ✔]$ ls
ansible.cfg inventory
[wsh@controller ansible ✔]$ cat ansible.cfg inventory
[defaults]
inventory = ./inventory
remote_user = wsh[privilege_escalation]
become=True
become_method=sudo
become_user=root
become_ask_pass=False
[controllers]
controller[nodes]
node[1:4]
利用主机模式选择主机
ansible 命令语法:
ansible host-pattern -m module [-a 'module arguments'] [-i inventory]
-
host-pattern 用于指定ad-hoc命令的目标主机。
-
host-pattern 也适用于playbook中hosts声明的主机对象。
host-pattern是inventory中定义的主机或主机组,可以为ip、hostname、inventory中的group组名、具有“,”或“*”或“:”等特殊字符的匹配型字符串,host-pattern是必须项,不可忽略。
优先使用主机模式匹配主机,而不是在play的任务中设置复杂的when语句。
本小节使用的 inventory 如下:
localhost
server[lab]
node1
node2[test]
node3
node4[datacenter1]
node1
node3[datacenter2]
node2
node4[datacenter:children]
datacenter1
datacenter2[new]
10.1.8.10
10.1.8.11
单个主机匹配
[wsh@controller ansible ✔]$ ansible --list-hosts node1hosts (1):node1[wsh@controller ansible ✔]$ ansible --list-hosts 10.1.8.10hosts (1):10.1.8.10
单个主机组匹配
[wsh@controller ansible ✔]$ ansible --list-hosts labhosts (2):node1node2
[wsh@controller ansible ✔]$ ansible --list-hosts datacenterhosts (4):node1node3node2node4
[wsh@controller ansible ✔]$ ansible --list-hosts ungroupedhosts (2):localhostserver
[wsh@controller ansible ✔]$ ansible --list-hosts allhosts (8):localhostservernode1node2node3node410.1.8.1010.1.8.11
模糊匹配
*****通配符在Ansible表示0个或多个任意字符,主要应用于一些模糊规则匹配。
# 匹配所有主机,all或*号功能相同。
[wsh@controller ansible ✔]$ ansible --list-hosts '*'hosts (8):node1node3node2node4localhostserver10.1.8.1010.1.8.11
[wsh@controller ansible ✔]$ ansible --list-hosts '10.*'hosts (2):10.1.8.1010.1.8.11
[wsh@controller ansible ✔]$ ansible --list-hosts 'ser*r'hosts (1):server
逻辑或匹配
如我们希望同时对多个主机或多个主机组同时执行,相互之间用“:”(冒号)或者“,”(逗号)分隔即可,类似于取两个集合的并集。
[wsh@controller ansible ✔]$ ansible --list-hosts 'node1,10.1.8.11'hosts (2):node110.1.8.11
[wsh@controller ansible ✔]$ ansible --list-hosts 'lab,datacenter1' hosts (3):node1node2node3
[wsh@controller ansible ✔]$ ansible --list-hosts 'lab,data*,10.1.8.11'hosts (5):node1node2node3node410.1.8.11
逻辑与匹配
逻辑与用(,&)或者(:&)表示,匹配两个集合的交集。
[wsh@controller ansible ✔]$ ansible --list-hosts 'lab,&datacenter1'hosts (1):node1
逻辑非匹配
逻辑非用(,!)表示,用于排除特定主机或主机组。
[wsh@controller ansible ✔]$ ansible --list-hosts 'datacenter,!node1'hosts (3):node3node2node4
多条件匹配
Ansible也支持多条件的复杂组合。
[wsh@controller ansible ✔]$ ansible --list-hosts 'datacenter,new,!node1'hosts (5):node3node2node410.1.8.1010.1.8.11
域切割匹配
Ansible底层基于Python,Python字符串域切割的示例如下:
str = ‘12345678’
通过[0]即可获取数值1。
该功能在Ansible中也支持:
[wsh@controller ansible ✔]$ ansible --list-hosts 'datacenter'hosts (4):node1node3node2node4# 获取第1个元素
[wsh@controller ansible ✔]$ ansible --list-hosts 'datacenter[0]'hosts (1):node1# 获取第1-2个元素
[wsh@controller ansible ✔]$ ansible --list-hosts 'datacenter[0:1]'hosts (2):node1node3# 获取最后1个元素
[wsh@controller ansible ✔]$ ansible --list-hosts 'datacenter[-1]'hosts (1):node4
配置并行
playbook 执行顺序
当Ansible处理playbook时:
- 按playbook中定义顺序运行。
- 每个play中所有主机分批次执行第一个任务,直到所有批次主机执行完该任务。
- 然后play中所有主机分批次再执行下一个任务,直到所有批次主机执行完所有任务。
- 以此类推,直到所有主机执行完所有任务,ansible才会释放shell。
理论上, Ansible可以同时连接到 play 中的所有主机以执行每项任务,适用于小型主机列表。但如果该play以数百台主机为目标, 则控制主机负载比较大。
配置 forks
Ansible 所进行的最大同时连接数由Ansible配置文件中的forks参数控制。
默认值为 5。
[wsh@controller ansible ✔]$ ansible-config dump|grep FORKS
DEFAULT_FORKS(default) = 5[wsh@controller ansible ✔]$ grep forks /etc/ansible/ansible.cfg
# forks = 5
示例:
-
清单内容如下:
controller[webs] node1 node3[dbs] node2 node4
-
剧本内容如下:
--- - name: connectionhosts: alltasks:- name: conneciton 1shell: sleep 5- name: conneciton 2debug: msg: connection 2
验证:每一批2个主机执行同一个任务。
[wsh@controller ansible ✔]$ ansible-playbook playbook.yaml -f 2
结果:
- 第一个任务 controller、node1同时完成,然后node3、node2同时完成,最后node4完成。
- 第二个任务 似乎 5台主机同时完成。
解释:第一个任务执行需要5秒,所以看起来比较明显。第二个任务执行速度非常块,所以感知不到先后顺序。
forks 的默认值设置得非常保守:
- 如果受管主机是Linux主机,则大多数任务将在受管主机上运行,并且控制主机的负载较少。在这种情況下,您通常可以将forks的值设置得更高,可能接近100 ,然后性能就会提高。
- 如果控制主机管理网络设备,路由器和交换机,则大多数模块在控制主机上运行而不在网络设备上运行,这会增加控制主机上的负载,因此其支持forks数量增加的能力将显著低于仅管理Linux主机的控制主机。
配置 serial
Ansible运行play时,所有受管主机按顺序执行完所有任务,然后运行通知的处理程序。
在所有主机上运行所有任务可能会导致意外行为。例如,更新负载平衡Web服务器集群,则可能需要在进行更新时让每个Web服务器停止服务。如果所有服务器都在同一个play中更新,则它们可能全部同时停止服务;如果某个任务失败,这将导致整个playbook运行失败。
避免此问题的一种方法是使用serial关键字,先让一批主机执行完play中所有任务,再让下一批主机执行完play中所有任务。以此类推,直到所有主机分批次执行play完成。
示例剧本内容如下:
---
- name: connection hosts: allserial: 2tasks: - name: conneciton 1shell: sleep 5 - name: conneciton 2debug: msg: connection 2
serial关键字也可以指定为百分比。此百分比应用于play中的主机总数,以确定滚动更新批处理大小。主机数不能小于1。
配置 async
ansible 默认行为:必须等当前任务执行完成,才能执行下一个任务。
有些操作需要很长时间才能完成,例如下载一个大文件,重启服务器等。使用异步并行模式,ansible可以很快在受管主机上这些执行命令,但是需要等待命令执行完成才能将主机置于相应状态。
Ansible使用async触发异步并行运作任务:
- async:async的值是ansible等待运行这个任务的最大超时值(如果执行超时任务会强制中断导致失败)。
- poll:ansible检查这个任务是否完成的时间间隔。ansible poll_interval 默认值是 15。
示例1: 任务执行失败,在规定时间内容任务没有执行完成。
---
- name: connectionhosts: node1tasks:- name: conneciton shell: sleep 10async: 5poll: 2
示例2: 放入后台下载,立刻执行下一个任务。
---
- name: connectionhosts: node1tasks:- name: downloadget_url: url: http://192.168.48.100/ISOS/openEuler-24.03-LTS-x86_64-dvd.isodest: /home/laomaasync: 100poll: 0
示例3: ansible 默认行为,等该任务执行完,再执行下一个任务。
---
- name: connectionhosts: node1tasks:- name: conneciton shell: sleep 10async: 0poll: 2
wait_for 模块
使用 wait_for 模块检查之前的任务是否达到预期状态。
示例1: 测试文件是否存在
---
- name: test wait forhosts: node1tasks:- shell: sleep 10 && touch /tmp/hello# async时间要大于sleep的时间async: 20poll: 0register: out- name: wait for create /tmp/hellowait_for:path: /tmp/hellostate: presentdelay: 5timeout: 30sleep: 2
选项说明:
- delay,设置检测前延迟时间。
- timeout,设置检测超时时间。
- sleep,设置检测时间间隔。
示例2: 测试主机端口是否打开
---
- name: test wait_forhosts: node1,node2tasks:- name: reboot node1shell: shutdown -r now "Ansible updates triggered"async: 1poll: 0when: inventory_hostname == "node1"- name: wait for node1 come backwait_for:host: node1port: 22state: starteddelay: 10sleep: 2timeout: 300when: inventory_hostname == "node2"
async_status 模块
使用 async_status 模块检查之前的任务是否运行完成。
示例:
---
- name: test async_statushosts: node1tasks:- shell: sleep 10 async: 20poll: 0register: out- name: wait forasync_status:# 通过任务的 ansible_job_id 属性跟踪任务jid: "{{ out.ansible_job_id }}"register: job_result# 根据当前任务执行结果的finished值,判断跟踪任务是否执行完成until: job_result.finishedretries: 30delay: 2
选项说明:
- retries,设置重试次数,默认值为3。
- delay,设置检测时间间隔,默认5秒检测一次。
Including 和 importing 文件
如果playbook很长或很复杂,可以将其分成较小的文件以便于管理。
采用模块化方式将多个playbook组合为一个main playbook或者将文件中的任务列表插入play。这样可以更轻松地在不同项目中重用play或任务。
ansible重用内容主要有两种方式:
- 使用任何 include 关键字任务(include_tasks、include_role 等),它将是动态的。
- 使用任何 import 关键字任务(import_playbook、import_tasks、import_role等),它将是静态的。
- 只使用include的任务(用于 task 级别和 Playbook 级别)仍然可用,此功能将在 2.12 版中删除。
[wsh@controller ansible ✔]$ ansible-doc -l|grep -e ^import -e ^include
import_playbook Import a playbook
import_role Import a role into a pl...
import_tasks Import a task list
include Include a play or task ...
include_role Load and execute a role
include_tasks Dynamically include a t...
include_vars Load variables from fil...
playbook 级别
import_playbook 支持导入外部playbooks。
- 导入的内容是完整的playbook,只能在play级别使用。
- 导入的多个playbooks,则按导入顺序执行。
示例:
-
主剧本内容如下
- name: prepare the web serverimport_playbook: pre_web.yml- name: prepare the vsftpd serverimport_playbook: pre_vsftpd.yml- name: prepare the databse serverimport_playbook: pre_db.yml
-
pre_web.yml 内容如下:
cat > pre_web.yml << EOF - name: Play webhosts: node1tasks:- name: install httpdyum:name: httpdstate: present EOF
-
pre_vsftpd.yml 内容如下:
cat > pre_vsftpd.yml << EOF - name: Play vsftpdhosts: node1tasks:- name: install vsftpdyum:name: vsftpdstate: present EOF
-
pre_db.yml 内容如下:
cat > pre_db.yml << EOF - name: Play dbhosts: node1tasks:- name: install mariadb-serveryum:name: mariadb-serverstate: present EOF
task 级别
示例:
-
主剧本内容如下:
--- - name: Install web serverhosts: node1tasks:- name: import a task fileimport_tasks: tasks.yaml#include: tasks.yaml#include_tasks: tasks.yaml
-
tasks.yaml 内容如下:
- name: Install the httpdyum:name: httpdstate: present- name: Starts httpdservice:name: httpdstate: started
任务文件用例
在这些情景中将任务组作为与playbook独立的外部文件来管理或许有所帮助:
- 如果新服务器需要全面配置,则管理员可以创建不同的任务集合,分别用于创建用户、安装软件包、配置服务、配置特权、设置对共享文件系统的访问权限、强化服务器、安装安全更新,以及安装监控代理等。如果一组服务器需要运行某一项/组任务,则它们可以仅在属于特定主机组的服务器上运行。
- 如果服务器由不同部门管理,例如开发人员、系统管理员和数据库管理员,则每个部门可以编写自己的任务文件,再由系统经理进行审核和集成。
- 如果服务器要求特定的配置,它可以整合为按照某一条件来执行的一组任务。换句话说,仅在满足特定标准时才包含任务。
我们可以创建专用目录存储任务文件, playbook 就可以从该目录包含任务文件。这便能够构建复杂的 playbook,同时简化其结构和组件的管理。
include_vars 模块
导入外部yaml格式的变量文件。
示例:
-
主剧本内容如下:
--- - name: Install web application packageshosts: node1tasks:- name: Includes variables.yml include_vars: variables.yml- name: Debugs the variables includeddebug:msg: >"{{ packages['web_package'] }} and {{ packages.db_package }} have been included"
-
variables.yml 内容如下:
--- packages:web_package: httpddb_package: mariadb-server