管理大项目
1.jinja2模板
Ansible将jinja2模板系统用于模板文件,Ansible还使用jinja2语法来引用playbook中的变量。jinja2要搭配使用template模块
- {% EXPR %}用于表达式或逻辑(如循环)。
- {{ EXPR }}则用于向最终用户输出表达式或变量的结果。
- {# COMMENT #}语法括起不应出现在最终文件中的注释。
1.1 jinja2模板的构建及部署模板文件
构建jinja2模板:
jinja2模板由多个元素组成:数据、变量和表达式。在呈现jinja2模板时,这些变量和表达式被替换为对应的值。模板中使用的变量可以在playbook的vars部分中指定。可以将受管主机的事实用作模板中的变量。
可以使用ansible system_hostname -i inventory_file -m setup命令来获取与受管主机相关的事实。
使用jinja2模板的时候要搭配使用template
模块,不能使用copy
模块,因为copy
拷贝模板的时候不会转变变量,原封不动的复制过去,而template
会转变完变量后才复制过去。
[root@localhost ansible]# vim abc.j2 //创建一个模板文件
{# 这是一个注释信息,只有在本机才可以看见#}
this system hostanem: {{ ansible_hostname }} //他会自动引用事实变量中的主机名
this system menmory is: {{ ansible_memtotal_mb}} MB //他会自动引用事实中主机总内存关键字
[root@localhost ansible]# vim test.yml
---
- name: jinja2
hosts: all
tasks:
- name:
template: //template拷贝模板文件到对面主机
src: abc.j2 //指定模板文件
dest: /etc/motd //motd意思是“当天的提示信息”,通常在用户成功登录到Linux后出现
[root@localhost etc]# cat motd
this system hostanem: localhost //这个系统的名称
this system menmory is: 1800 MB //这个系统的总内存是1800MB,也就2G
1.2 jinja2中使用循环和条件判断
判断:
[root@localhost ansible]# cat abc.j2
{% if ansible_fqdn == "akl1" %} //if 如果,进行判断
echo "正确"
{% elif ansible_fqdn == "akl2" %} //elif:否则如果
echo "没有找到akl1"
{% else %} //else其他的,既不是akl1又不是akl2则输出一下内容
echo "没有找到akl1"
{% endif% }
//判断主机名,如果判断成功将输出正确,如果判断错误则输出“没有找到akl1”
循环:
格式:
{% for 变量 in 变量%}
{{ 变量 }}
{% endfor%}
user变量替换为users变量中包含的所有值,一行一个值。
{% for user in users %}
{{ user }}
{% endfor %}
使用for语句逐一运行users变量中的所有值,将myuser替换为各个值,但值为root时除外。
{# for statement #}
{% for myuser in users if not myuser == "root" %}
User number {{ loop.index }} - {{ myuser }}
{% endfor %}
//loop.index变量扩展至循环当前所处的索引号。它在循环第一次执行时值为1,每一次迭代递增1
下列for语句时,文件中将列出清单myhosts组内的所有主机。
{% for myhost in groups['myhosts'] %}
{{ myhost }}
{% endfor %}
name变量替换为names变量中包含的所有值,一行一个值。
[root@localhost ansible]# cat abc.j2 //模板文件的内容
{% for name in names%}
{{ name }}
{% endfor %}
[root@localhost ansible]# cat test.yml
---
- name: jinja2
hosts: all
vars_files:
- ./names.yml //定义变量的文件位置
tasks:
- name:
template:
src: ./abc.j2 //模板文件位置
dest: /tmp/abc //复制到对面的位置
[root@localhost ansible]# cat names.yml //定义变量文件
names:
- 111
- 222
- 333
[root@localhost abc]# cat abc //在对面主机查看复制过来的内容
111
222
333
2.清单管理
清单文件是用来记录哪些主机是受本机的ansible控制的。可以是一台主机,也可以是一组主机。
root@localhost ansible]# cat inventory
//主机,这里的主机可以写成ip方式也可以写成主机名方式
web.example.com
data.example.com
//主机组,是多个主机的相同主机的集合。
[httpd]
labhost1.example.com //既属于httpd组也属于mysql组
labhost2.example.com //既属于httpd组也属于php组
[mysql]
labhost1.example.com
test1.example.com
[php]
labhost2.example.com
test2.example.com
//这个主机组又包含上面两个主机组,所以这个组一共有4台服务器
[datacenter:children]
mysql
php
2.1 指定主机
- 使用组指定主机
- hosts: lab //指定Ansible将对属于该组的成员的主机执行操作
- hosts: all //匹配清单中的所有受管主机
- hosts: ungrouped //清单中不属于任何其他组的所有受管主机
- 使用通配符来匹配多个主机
- hosts: '*' //和all一样,是用来匹配所有主机的
也可使用*字符匹配包含特定子字符串的受管主机或组
---
- hosts: '*.example.com'
//匹配2.0网段的所有主机
---
- hosts: '192.168.2.*'
//匹配所以以datacenter 开头的主机
---
- hosts: 'datacenter*'
- 列表
可以通过逻辑列表来引用清单中的多个条目。主机模式的逗号分隔列表匹配符合任何这些主机模式的所有主机。
如果提供受管主机的逗号分隔列表,则所有这些受管主机都将是目标:
---
- hosts: labhost1.example.com,test2.example.com,192.168.2.2
如果提供组的逗号分隔列表,则属于任何这些组的所有主机都将是目标:
---
- hosts: lab,datacenter1
也可以混合使用受管主机、主机组和通配符:
---
- hosts: 'lab,data*,192.168.2.2'
如果列表中的某一项以与符号(&)开头,则主机必须与该项匹配才能匹配主机模式。它的工作方式类似于逻辑AND
//匹配既属于lab组又属于datacenter组的主机
---
- hosts: lab,&datacenter1
3.配置并行方式(forks、serial)
3.1 并行(forks)
当Ansible处理playbook时,会按顺序运行每个play。确定play的主机列表之后,Ansible将按顺序运行每个任务。通常,所有主机必须在任何主机在play中启动下一个任务之前成功完成任务。
理论上,Ansible可以同时连接到play中的所有主机以执行每项任务。这非常适用于小型主机列表。但如果该play以数百台主机为目标,则可能会给控制节点带来沉重负担。
Ansible所进行的最大同时连接数由Ansible配置文件中的forks参数控制。默认情况下设为5,这可通过以下方式之一来验证。
[root@localhost ~]# grep forks /etc/ansible/ansible.cfg
#forks = 5 //一次性最多同时执行5台主机的任务
简单的说就是,当forks值为5时,一次性最多同时执行5台主机的任务。如果有10台主机将会被分为2组,当第一组的5个主机执行完第一个任务后,才会执行第二组5个主机的第一个任务,然后再回到第一组主机执行第二个任务完成后再去第二组执行第二个任务,知道play执行结束。
3.2 串行(serial)
通常,当Ansible运行play时,它会确保所有受管主机在启动任何主机进行下一个任务之前已完成每个任务。在所有受管主机完成所有任务后,将运行任何通知的处理程序。
但是,在所有主机上运行所有任务可能会导致意外行为。例如,如果play更新负载均衡Web服务器集群,则可能需要在进行更新时让每个Web服务器停止服务。如果所有服务器都在同一个play中更新,则它们可能全部同时停止服务。
避免此问题的一种方法是使用serial关键字,通过play批量运行主机。在下一批次启动之前,每批主机将在整个play中运行。
在下面的示例中,Ansible一次在两个受管主机上执行play,直至所有受管主机都已更新。Ansible首先在前两个受管主机上执行play中的任务。如果这两个主机中的任何一个或两个都通知了处理程序,则Ansible将根据这两个主机的需要运行处理程序。在这两个受管主机上执行完play时,Ansible会在接下来的两个受管主机上重复该过程。Ansible继续以这种方式运行play,直到所有受管主机都已更新。
---
- name: Rolling update
hosts: webservers
serial: 2 //在forks基础上,一次最多只能两台主机执行任务且必须要前两台执行成功,重启后开能执行下一个任务。
tasks:
- name: latest apache httpd package is installed
yum:
name: httpd
state: latest
notify: restart apache
handlers:
- name: restart apache
service:
name: httpd
state: restarted
简单的理解就是,当并行设置为5(forks ) ,串行(serial)为2时,此时有10台机器要重启服务。那么就会把这10台主机分成2两组 每一组又5台主机,先在第一组的前两台主机执行第一个任务,当这两个主机执行完成并启动后 才在到第一组的3 4 台主机执行第一个任务,直到第一组执行完成,才会去第二组的前两台主机执行第一个任务
forks是控制同时可以对几台主机执行任务,而serial是在并行的基础上控制一次可以让几台主机执行完成任务后才会去执行一下主机任务。
4.包含和导入文件
如果playbook很长或很复杂,我们可以将其分成较小的文件以便于管理。
可采用模块化方式将多个playbook组合为一个主要playbook,或者将文件中的任务列表插入play。这样可以更轻松地在不同项目中重用play或任务序列。
4.1 包含或导入文件
Ansible可以使用两种操作将内容带入playbook。可以包含内容,也可以导入内容。
包含内容是一个动态操作。在playbook运行期间,尚未执行到包含的内容时可以进行修改,修改完成后playbook会运行修改之后的内容。
导入内容是一个静态操作。在运行开始之前,已经将内容导入playbook。
4.2 导入playbook
import_playbook
指令将包含play列表的外部文件导入playbook。换句话说,可以把一个或多个额外playbook导入到主playbook中。
由于导入的内容是一个完整的playbook,因此import_playbook
功能只能在playbook的顶层使用,不能在play内使用。
导入两个额外playbook到主playbook:
//第一个playbook下载httpd
[root@ansible apache]# cat install.yml
---
- hosts: apache
tasks:
- name: install apache
yum:
name: httpd
state: latest
//第二个playbook配置httpd
[root@ansible apache]# cat config.yml
---
- hosts: apache
vars: //定义变量,修改端口号
- port: 8080
tasks:
- name: config httpd
template:
src: files/httpd.conf.j2 //指定jinja2模板文件路径(相对路径)
dest: /etc/httpd/conf/httpd.conf
//config.yml中定义了变量,所以需要在jinja2模板中引用
[root@ansible apache]# vim files/httpd.conf.j2
......
Listen {{ port }} //引用变量
......
//主playbook,导入其他playbook
[root@ansible apache]# cat main.yml
- name: install
import_playbook: install.yml //主playbook与其他playbook同级,直接写名称即可
- name: config
import_playbook: config.yml
[root@ansible apache]# tree
.
├── config.yml
├── files
│ └── httpd.conf.j2
├── install.yml
└── main.yml
还可以使用导入的playbook在主playbook中交替play:
---
- hosts: apache
gather_facts: no
tasks:
- name: install
yum:
name: httpd
state: latest
//导入配置httpd的playbook
- name: config
import_playbook: config.yml
首先运行install,然后运行导入的playbook。
4.3 导入和包含任务
可以将任务文件中的任务列表导入或包含在play中。任务文件是包含一个任务平面列表的文件。
在任务文件中只指定需要运行的操作,不指定运行的主机。好处是可以在其他服务器运行任务,只需要在主playbook中导入任务即可。
- name:
yum:
name: httpd
state: latest
- name:
yum:
name: mariadb
state: latest
4.3.1 导入任务文件
使用import_tasks
功能将任务文件静态导入playbook内的play中。
//主playbook任务,导入任务tasks
[root@ansible apache]# cat main.yml
---
- name:
hosts: apache
tasks:
- import_tasks: install.yml //导入任务文件
- import_tasks: config.yml
- name:
service:
name: httpd
state: started
//任务文件install.yml
[root@ansible apache]# cat install.yml
- name: install apache
yum:
name: httpd
state: latest
//任务文件config.yml
[root@ansible apache]# cat config.yml
- name: config httpd
template:
src: files/httpd.conf.j2
dest: /etc/httpd/conf/httpd.conf
由于import_tasks在解析playbook时静态导入任务,因此对其工作方式有一些影响。
- 使用import_tasks功能时,导入时设置的when等条件语句将应用于导入的每个任务
- 无法将循环用于import_tasks功能
- 如果使用变量来指定要导入的文件的名称,则将无法使用主机或组清单变量
4.3.2 包含任务文件
可以使用include_tasks功能将任务文件动态导入playbook内的play中。
[root@ansible apache]# cat main.yml
---
- name:
hosts: apache
tasks:
- include_tasks: install.yml //包含任务文件
- include_tasks: config.yml
- name:
service:
name: httpd
state: started
在play运行并且这部分play到达前,include_tasks功能不会处理playbook中的内容。Playbook内容的处理顺序会影响包含任务功能的工作方式。
- 使用include_tasks功能时,包含时设置的when等条件语句将确定任务是否包含在play中
- 如果运行ansible-playbook --list-tasks以列出playbook中的任务,则不会显示已包含任务文件中的任务。将显示包含任务文件的任务。相比之下,import_tasks功能不会列出导入任务文件的任务,而列出已导入任务文件中的各个任务
- 不能使用ansible-playbook --start-at-task从已包含任务文件中的任务开始执行playbook
- 不能使用notify语句触发已包含任务文件中的处理程序名称。可以在包含整个任务文件的主playbook中触发处理程序,在这种情况下,已包含文件中的所有任务都将运行
4.3.3 任务文件的用例
请参考下面的示例,在这些情景中将任务组作为与playbook独立的外部文件来管理或许有所帮助:
- 如果新服务器需要全面配置,则管理员可以创建不同的任务集合,分别用于创建用户、安装软件包、配置服务、配置特权、设置对共享文件系统的访问权限、强化服务器、安装安全更新,以及安装监控代理等。每一任务集合可通过单独的自包含任务文件进行管理
- 如果服务器由开发人员、系统管理员和数据库管理员统一管理,则每个组织可以编写自己的任务文件,再由系统经理进行审核和集成
- 如果服务器要求特定的配置,它可以整合为按照某一条件来执行的一组任务。换句话说,仅在满足特定标准时才包含任务
- 如果一组服务器需要运行某一项/组任务,则它/它们可以仅在属于特定主机组的服务器上运行
4.4 为外部play和任务定义变量
使用Ansible的导入和包含功能将外部文件中的play或任务合并到playbook中极大地增强了在Ansible环境中重用任务和playbook的能力。为了最大限度地提高重用可能性,这些任务和play文件应尽可能通用。变量可用于参数化play和任务元素,以扩大任务和play的应用范围。
对软件包和服务元素进行参数化,则任务文件也可用于安装其他服务
[root@ansible apache]# cat install.yml
- name:
yum:
name: "{{ package }}" //创建一个变量,定义变量在总的playbook中
state: latest
[root@ansible apache]# cat main.yml
---
- name:
hosts: apache
tasks:
- import_tasks: install.yml //导入任务文件
vars: //这里的定义变量是针对上面的安装
package: httpd