2. Ansible Playbook
2021-02-11 本文已影响0人
随便写写咯
3 Playbook
3.1 Playbook介绍
1. playbook剧本是由一个或多个play组成的列表
2. play的主要功能在于将预定义的一组主机, 装扮成事先通过ansible中的task定义好的角色. Task实际是调用ansible的一个module, 将多个play组织在一个playbook中, 即可以让它们联合起来, 按事先编排的机制执行预定义的动作
3. playbook文件是采用YAML语言编写的
3.2 YAML语言
3.2.1 语法介绍
在单一文件第一行, 用连续三个连字号'-'开始, 还有选择性的连续三个点号(...)用来表示文件的结尾
次行开始正常写playbook的内容, 一般建议写明该playbook的功能
使用#号注释代码
缩进必须是统一的, 不能空格和tab混用
缩进的级别也必须是一致的, 同样的缩进代表同样的级别, 程序判别配置的级别是通过缩进结合换行来实现的
YAML文件内容是区别大小写的, key/value的值均需大小写敏感
多个key/value可同行写也可换行写, 同行使用逗号分隔
key后面冒号要加一个空格, 比如: key: value
value可是个字符串, 也可是另一个列表
YAML文件扩展名通常为yaml或yml
3.2.2 YAML支持的数据类型
标量: 单个的, 不可再分的值
字典: {key1: value1, key2: value2,...}
列表: [value1, value2,...]
3.2.2.1 标量
key: value
字符串, 布尔值, 整数, 浮点数, Null, 时间, 日期
name: wang
name:
admin
3.2.2.2 字典
字典由多个key与value构成, key和value之间用冒号分隔, 并且, 后面有一个空格, 所有k/v可以放在同一行, 或者每个k/v分别放在不同的行
格式:
account: {name: wang, age :30}
使用缩进:
account:
name: admin
age: 30
范例:
emp:
name: admin
job: developer
skill: java
emp: {name: admin, job: developer, skill: java}
3.2.2.3 列表
列表由多个元素组成, 每个元素放在不同行, 且元素前均用'-'打头, 并且-后面有一个空格, 或者将所有元素用[]括起来放在一行
范例:
lang:
- java
- python
- php
lang: [java, python, php]
列表中嵌套字典:
OS: [linux: redhat, windows: windows98, apple: macbook]
OS:
- linux: redhat
- windows: windows98
- apple: macbook
小案例:
---
name: John Smith
age: 41
gender: male
spouse:
name: Jane Smith
age: 37
gender: Female
Children:
- name: Jimmy Smith
age: 17
gender: Male
- {name: Jenny Smith, age: 13, gender: Female}
- {name: Hao Smith, age: 20, gender: Male}
3.3 Playbook核心组件
一个playbook由列表组成, 其中所用到的常见组件类型如下:
hosts: 执行的远程主机列表
tasks: 任务集, 由多个task的元素组成的列表实现, 每个task是一个字典
templates: 模板, 可替换模板文件中的变量并实现一些简单逻辑的文件
variables: 内置变量或自定义变量在playbook中调用
handlers和notify结合使用, 由特定条件触发的操作, 满足条件方才执行, 否则不执行
tags: 标签, 执行某条任务执行, 用于选择运行playbook中的部分代码. ansible具有幂等性, 因此, 会自动跳过没有变化的部分, 即便如此, 有些代码为测试其确实没有发生变化的时间依然会非常长. 此时, 如果确信其没有发生变化, 就可以通过tags跳过这些代码片段
一个完成的代码块功能, 最少元素需要包括name, task
3.3.1 hosts组件
hosts: playbook中的每一个play的目的都是为了让特定的主机以某个指定的用户身份去执行任务, hosts用于指定要执行任务的主机. 需要实现定义在主机清单中
192.168.1.1
192.168.1.*
www.123.com:db.123.com
websrvs:dbsrvs
websrvs:&dbsrvs
websrvs:!dbsrvs
范例:
- hosts: websrv:appsrvs
3.3.2 remote_user组件
remote_user: 可用于host和task中, 也可以通过指定其通过sudo的方式在远程主机上执行任务, 其可用于play全局或某任务
此外, 甚至可以在sudo时使用sudo_user指定sudo时切换的用户
- hosts: websrvs
remote_user: root
tasks:
- name: test connection
ping
remote_user: admin
sudo: yes # 默认的sudo用户为root
sudo_user: admin
3.3.3 task列表和action组件
playbook的主体部分是task list, task list中有一个或多个task, 各个task按次序逐个在hosts中指定的所有主机上执行, 即在所有主机上完成第一个task后, 再开始第二个task
task的目的是使用指定的参数执行模块, 而在模块参数中, 可以使用变量
模块执行是幂等的, 意味着多次执行是安全的, 因为其结果均一致
每个task都应该有其name, 用于playbook的执行结果输出, 建议其内容能清晰地描述任务执行步骤, 如果未提供name, 则action的结果将用于输出
task的两种格式
action: module agruments
module: arguments # 建议使用
注意: shell和command模块后面跟命令, 而非key=value
范例:
---
- hosts: websrvs
remote_user: root
tasks:
- name: install httpd
yum: name=httpd
- name: start httpd
service: name=httpd state=started enabled=yes
3.3.4 其他组件
某任务的状态在运行后为changed时, 可以通过"notify"通知给相应的handlers
任务可以通过"tags"打标签, 可在ansible-playbook命令上使用-t指定进行调用
3.3.5 ShellScripts vs Playbook案例
#!/bin/bash
yum install -y httpd
cp /tmp/httpd.conf /etc/httpd/conf/httpd.conf
cp /tmp/vhosts.conf /etc/httpd/conf.d
systemctl enable --now httpd
---
- hosts: websrvs
remote_user: root
tasks:
- name: "安装apache"
yum: name=httpd
- name: "复制配置文件"
copy: src=/tmp/httpd.conf dest=/etc/httpd/conf
- name: "复制配置文件"
copy: src=/tmp/vhosts.conf dest=/etc/httpd/conf.d
- name: "启动apache, 并设置开机启动"
service: name=httpd state=started enabled=yes
3.4 Playbook命令
格式:
ansible-playbook <filename.yaml> ... [option]
选项:
--syntax-check 执行语法检查, 不执行命令
-C, --check 只检测可能会发生的改变, 但不真正执行操作
--list-hosts 列出运行任务的主机
--list-tags 列出tag
--list-tasks 列出task
--limit 主机列表 只针对主机列表中的特定主机执行
-v, --verbose 显示过程
Hello World.yaml
---
- hosts: websrvs
remote_user: root
tasks:
- name: "hello world广播"
command: /usr/bin/wall "hello world"
[16:50:51 root@ansible ~]#ansible-playbook hello_world.yml
PLAY [websrvs] ***********************************************************************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************************************************
ok: [10.0.0.86]
ok: [10.0.0.85]
TASK [hello world广播] *****************************************************************************************************************************************************
changed: [10.0.0.86]
changed: [10.0.0.85]
PLAY RECAP ***************************************************************************************************************************************************************
10.0.0.85 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.0.0.86 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
3.5 Playbook 基础使用
3.5.1 利用Playbook创建MySQL用户
eg: mysql_user.yaml
[17:01:05 root@ansible ~]#ansible-playbook mysql_user.yml
---
- hosts: dbsrvs
remote_user: root
tasks:
- name: "创建MySQL用户组"
group: name=mysql gid=3306 system=yes
- name: "创建MySQL用户"
user: name=mysql uid=3306 group=mysql system=yes shell=/sbin/nologin create_home=yes home=/data/mysql
- user模块的home参数, 如果指定了路径, 那么创建账号时就会按照指定的路径给用户创建家目录, 而不是在默认的/home目录下
- user模块的create_home参数, 如果指定为no, 那么即使通过home参数指定了家目录, 最终也不会给用户创建家目录. 如果单独指定create_home=yes, 那么会在默认的/home下为用户创建家目录. 如果home和create_yes=yes, 同时指定, 那么home参数优先级高
一般想给用户自定义创建家目录, 只要指定home参数即可, 对于系统和普通用户都有效
如果不想给用户创建家目录, 就用create_home=no
3.5.2 利用Playbook安装Nginx
eg: install_nginx.yaml
gather_facts: 默认启用, 运行yml文件时, 会自动收集被管理节点的主机信息, 包括cpu和内存等. 除非要基于系统信息做管理, 否则交易关闭该功能
gather_facts: no
[17:34:37 root@ansible ~]#vim nginx.yaml
---
- hosts: websrvs
remote_user: root
gather_facts: no
tasks:
- name: "创建nginx组"
group: name=nginx gid=80
- name: "创建nginx用户"
user: name=nginx uid=80 group=nginx system=yes create_home=yes shell=/sbin/nologin
- name: "yum安装nginx"
yum: name=nginx
- name: "准备默认页面"
copy: src=/data/files/index.html dest=/usr/share/nginx/html/index.html
- name: "启动nginx服务"
service: name=nginx state=started enabled=yes
[17:32:48 root@ansible ~]#ansible-playbook nginx.yaml
PLAY [websrvs] *********************************************************************************************************************************************************************
TASK [创建nginx组] ********************************************************************************************************************************************************************
ok: [10.0.0.85]
ok: [10.0.0.86]
TASK [创建nginx用户] *******************************************************************************************************************************************************************
ok: [10.0.0.86]
ok: [10.0.0.85]
TASK [yum安装nginx] ******************************************************************************************************************************************************************
changed: [10.0.0.85]
changed: [10.0.0.86]
TASK [准备默认页面] **********************************************************************************************************************************************************************
changed: [10.0.0.86]
changed: [10.0.0.85]
TASK [启动nginx服务] *******************************************************************************************************************************************************************
changed: [10.0.0.85]
changed: [10.0.0.86]
PLAY RECAP *************************************************************************************************************************************************************************
10.0.0.85 : ok=5 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.0.0.86 : ok=5 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
验证: 80端口开启
[17:37:30 root@ansible ~]#ansible websrvs -m shell -a "ss -ntl | grep 80"
10.0.0.86 | CHANGED | rc=0 >>
LISTEN 0 128 0.0.0.0:80 0.0.0.0:*
LISTEN 0 128 [::]:80 [::]:*
10.0.0.85 | CHANGED | rc=0 >>
LISTEN 0 128 0.0.0.0:80 0.0.0.0:*
LISTEN 0 128 [::]:80 [::]:*
验证: nginx用户和组创建
[17:37:36 root@ansible ~]#ansible websrvs -m shell -a "id nginx"
10.0.0.86 | CHANGED | rc=0 >>
uid=80(nginx) gid=80(nginx) groups=80(nginx)
10.0.0.85 | CHANGED | rc=0 >>
uid=80(nginx) gid=80(nginx) groups=80(nginx)
3.5.3 Playbook安装和卸载httpd
eg: install_httpd.yaml
准备工作:
#在管理节点安装httpd, 利用yum包提供的模板httpd.conf文件
[18:12:10 root@ansible ~]#yum -y install httpd
#将配置文件拷贝到/data/files目录
[18:11:47 root@ansible ~]#cp /etc/httpd/conf/httpd.conf /data/files/
[18:12:55 root@ansible /data/files]#ls
httpd.conf
#修改配置文件, 将默认的家目录改为/data/html
[18:12:55 root@ansible /data/files]#ls
httpd.conf
[18:12:57 root@ansible /data/files]#vim httpd.conf
#DocumentRoot "/var/www/html"
DocumentRoot "/data/html"
#修改家目录权限
<Directory "/data/html">
#创建默认页面
[18:15:51 root@ansible /data/files]#echo welcome to httpd v1 > index.html
编辑yml文件:
[18:31:49 root@ansible /data/files]#vim install_httpd.yml
---
- hosts: websrvs
remote_user: root
gather_facts: no
tasks:
- name: "install httpd"
yum: name=httpd
- name: "copy conf file"
copy: src=/data/files/httpd.conf dest=/etc/httpd/conf/httpd.conf
- name: "change listen port"
lineinfile: path=/etc/httpd/conf/httpd.conf regexp='^Listen' line='Listen 8080'
- name: "创建主页目录"
file: path=/data/html state=directory
- name: "将主页面复制到/data/html"
copy: src=/data/files/index.html dest=/data/html/index.html
- name: "开启服务"
service: name=httpd state=started enabled=yes
检查语法:
此时检查语法会报错, 因为还没有安装httpd, 所以目标目录是不存在的
TASK [change listen port] **********************************************************************************************************************************************************
fatal: [10.0.0.86]: FAILED! => {"changed": false, "msg": "Destination /etc/httpd/conf/httpd.conf does not exist !", "rc": 257}
fatal: [10.0.0.85]: FAILED! => {"changed": false, "msg": "Destination /etc/httpd/conf/httpd.conf does not exist !", "rc": 257}
验证:
[18:30:33 root@ansible /data/files]#ansible websrvs -m shell -a "ss -ntl | grep 8080"
10.0.0.86 | CHANGED | rc=0 >>
LISTEN 0 128 *:8080 *:*
10.0.0.85 | CHANGED | rc=0 >>
LISTEN 0 128 *:8080 *:*
eg: remove_httpd.yaml
[18:50:56 root@ansible /data/files]#vim remove_httpd.yml
---
- hosts: websrvs
gather_facts: no
remote_user: root
tasks:
- name: "卸载httpd"
yum: name=httpd state=absent
- name: "删除apache用户"
user: name=apache state=absent
- name: "删除apache用户组"
group: name=apache state=absent
- name: "删除数据目录"
file: path=/data/html state=absent
- name: "删除配置目录"
file: path=/etc/httpd state=absent
最好先停止服务, 再删除应用, 有时虽然包删除了, 但是服务有可能继续运行
注意: ansible管理的机器最好是处于同一个版本的, 否则部署应用的时候会出现由于配置不同, 目录不同造成问题, 比如在centos 6和centos 7&8上部署httpd就会因为本身目录结构不同, 造成配置文件不通用
---
- hosts: websrvs
remote_user: root
gather_facts: no
tasks:
- {name: "安装httpd", yum: name=httpd state=present}
- {name: "拷贝配置文件", copy: src=/tmp/httpd.conf dest=/etc/httpd/conf}
- {name: "修改监听端口", replace: path=/etc/httpd/conf/httpd.conf regexp='^Listen' replace='Listen 8080'}
- {name: "启动服务, 设定开机自动启动", service: name=httpd state=started enabled=yes}
TASK [启动服务, 设定开机自动启动] ******************************************************************************************************************************
fatal: [10.0.0.236]: FAILED! => {"changed": false, "msg": "httpd: Syntax error on line 59 of /etc/httpd/conf/httpd.conf: Include directory '/etc/httpd/conf.modules.d' not found\n"} #报错是因为, CentOS6安装httpd后不会自动创建conf.modules.d目录
changed: [10.0.0.238]
3.5.4 Playbook安装MySQL-8.0
- 准备配置文件
[19:53:28 root@ansible /data/mysql]#vim my.cnf
[mysqld]
socket=/data/mysql/mysql.sock
user=mysql
innodb_file_per_table=on
skip_name_resolve=on
datadir=/data/mysql
log-bin=/data/mysql/mysql-bin
pid-file=/data/mysql/mysqld.pid
[client]
port=3306
socket=/data/mysql/mysql.sock
[mysqld]
log-error=/data/mysql/mysql-error.log
- 编辑安装mysql的yml文件
---
- hosts: mysql
remote_user: root
gather_facts: no
tasks:
- name: create mysql group
group: name=mysql gid=3306 system=yes
- name: create mysql user
user: name=mysql group=mysql system=yes uid=3306 create_home=no
- name: install mysql-server
yum: name=mysql-server
- name: copy my.cnf
copy: src=/data/mysql/my.cnf dest=/etc/my.cnf.d/mysql-server.cnf
- name: create /data/mysql
file: path=/data/mysql state=directory owner=mysql group=mysql
- name: start mysqld
service: name=mysqld state=started enabled=yes
3.6 handlers and notify
handlers本质是task list, 其中task与上述的task并没有本质不同, 主要用于当关注的资源发生变化时, 才会采取一定的操作.
而notify对应的action, 可用于在每个play的最后被触发, 这样可避免多次有改变发生时, 每次都执行指定的操作, 仅在所有的变化发生完后一次性的执行指定操作.
在notify中列出的操作称为handler, 也即notify中调用handler中定义的操作
由notify来触发handler执行, notify是触发条件, handler是触发条件后具体执行的动作
notify所在的任务, 一旦执行, 显示为黄色, 就会触发notify, 进而触发handler
notify后的名字一定要和handler后的一样
handler和notify一般用于当配置文件发生更改, 需要重启服务时, 用notify去关注配置文件的修改, 一旦执行为黄色, 才执行handler的操作
eg: 利用handler和notify来重启httpd服务
- 利用yml文件安装httpd
[20:35:48 root@ansible /data/files]#vim install_httpd.yml
---
- hosts: websrvs
remote_user: root
gather_facts: no
tasks:
- name: "创建apache组"
group: name=apache gid=80
- name: "创建apache用户"
user: name=apache uid=80 group=apache system=yes shell=/sbin/nologin
- name: "install httpd"
yum: name=httpd
- name: "copy conf file"
copy: src=/data/files/httpd.conf dest=/etc/httpd/conf/httpd.conf
- name: "change listen port"
lineinfile: path=/etc/httpd/conf/httpd.conf regexp='^Listen' line='Listen 8080'
- name: "创建主页目录"
file: path=/data/html state=directory
- name: "将主页面复制到/data/html"
copy: src=/data/files/index.html dest=/data/html/index.html
- name: "开启服务"
service: name=httpd state=started enabled=yes
- 验证
[20:36:11 root@ansible /data/files]#curl 10.0.0.85:8080
welcome to httpd v1
[20:36:19 root@ansible /data/files]#curl 10.0.0.86:8080
welcome to httpd v1
- 添加notify和handler
[20:36:21 root@ansible /data/files]#vim install_httpd.yml
---
- hosts: websrvs
remote_user: root
gather_facts: no
tasks:
- name: "创建apache组"
group: name=apache gid=80
- name: "创建apache用户"
user: name=apache uid=80 group=apache system=yes shell=/sbin/nologin
- name: "install httpd"
yum: name=httpd
- name: "copy conf file"
copy: src=/data/files/httpd.conf dest=/etc/httpd/conf/httpd.conf
- name: "change listen port"
lineinfile: path=/etc/httpd/conf/httpd.conf regexp='^Listen' line='Listen 9090'
notify: restart httpd # 一旦该task发生变化,就会触发notify定义的restart httpd
- name: "创建主页目录"
file: path=/data/html state=directory
- name: "将主页面复制到/data/html"
copy: src=/data/files/index.html dest=/data/html/index.html
- name: "开启服务"
service: name=httpd state=started enabled=yes
handlers:
- name: restart httpd # 定义了具体的操作
service: name=httpd state=restarted
- 执行playbook
RUNNING HANDLER [restart httpd] ******************************************************************************************************************************************
changed: [10.0.0.86]
changed: [10.0.0.85]
PLAY RECAP ***************************************************************************************************************************************************************
10.0.0.85 : ok=9 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.0.0.86 : ok=9 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
补充: 即使是第一次执行playbook, notify也会触发handlers的执行
notify也可以同时触发多个handlers的执行
eg:
vim nginx.yml
---
- hosts: websrvs
remote_user: root
gather_facts: no
tasks:
- name: add group nginx
group: name=nginx state=present
- name: add user nginx
user: name=nginx state=present group=nginx
- name: INSTALL Nginx
yum: name=nginx state=present
- name: config
copy: src=/root/config.txt dest=/etc/nginx/nginx.conf
notify ["Restart Nginx", "Check Nginx Process"] #or below format
- Restart Nginx
- Check Nginx Process
handlers:
- name: Restart Nginx
service: name=nginx state=restarted enabled=yes
- name: Check Nginx PROCESS
shell: killall -0 nginx &> /tmp/nginx.log
3.7 tags组件
在playbook中, 可以利用tags组件, 为特定的task指定标签, 当在执行playbook时, 可以只执行特定tags的任务, 而非整个playbook文件
尤其是后期服务维护过程中, 只是想修改配置文件或者修改页面, 那么可以指定只执行配置文件的修改, 而不去执行其余的任务
eg: 只执行配置文件和主页面的修改任务
- 准备工作
修改配置文件中httpd的用户
User daemon
Group daemon
playbook中修改端口号
lineinfile: path=/etc/httpd/conf/httpd.conf regexp='^Listen' line='Listen 9999'
修改主页面
[21:02:43 root@ansible /data/files]#echo "welcome to httpd v2" > index.html
- 修改yml文件
[21:02:52 root@ansible /data/files]#vim install_httpd.yml
---
- hosts: websrvs
remote_user: root
gather_facts: no
tasks:
- name: "创建apache组"
group: name=apache gid=80
- name: "创建apache用户"
user: name=apache uid=80 group=apache system=yes shell=/sbin/nologin
- name: "install httpd"
yum: name=httpd
- name: "copy conf file"
copy: src=/data/files/httpd.conf dest=/etc/httpd/conf/httpd.conf
notify: restart httpd # 配置文件修改, 触发handlers
tags: copy_conf_file # 打标签
- name: "change listen port"
lineinfile: path=/etc/httpd/conf/httpd.conf regexp='^Listen' line='Listen 9999'
notify: restart httpd # 配置文件修改, 触发handlers
tags: change_listen_port # 打标签
- name: "创建主页目录"
file: path=/data/html state=directory
- name: "将主页面复制到/data/html"
copy: src=/data/files/index.html dest=/data/html/index.html
notify: restart httpd # 主页面修改, 触发handlers
tags: change_homepage # 打标签
- name: "开启服务"
service: name=httpd state=started enabled=yes
handlers:
- name: restart httpd
service: name=httpd state=restarted
- 检查tags
[21:05:07 root@ansible /data/files]#ansible-playbook --list-tags install_httpd.yml
playbook: install_httpd.yml
play #1 (websrvs): websrvs TAGS: []
TASK TAGS: [change listen port, copy conf file, 将主页面复制到/data/html]
- 执行playbook
[21:14:55 root@ansible /data/files]#ansible-playbook -t change_conf_file,change_homepage,change_listen_port install_httpd.yml
[22:23:16 root@ansible /data/ansible]#ansible-playbook -t copy_conf_file,copy_index_html,change_listen_port httpd.yml
PLAY [httpd] ***************************************************************************
TASK [复制配置文件] **************************************************************************
changed: [10.0.0.82]
changed: [10.0.0.83]
TASK [复制主页面] **************************************************************************
changed: [10.0.0.82]
changed: [10.0.0.83]
TASK [change listen port] **************************************************************************
changed: [10.0.0.82]
changed: [10.0.0.83]
RUNNING HANDLER [restart apache] *******************************************************
changed: [10.0.0.83]
changed: [10.0.0.82]
PLAY RECAP *****************************************************************************
10.0.0.82 : ok=3 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.0.0.83 : ok=3 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
注意:
tags如果是多个单词, 不要用空格分隔, 执行时无法识别, 可以用下划线, 并且逗号之间不要有空格
3.8 Playbook 变量的使用
变量名必须以字母开头, 只支持字母,数字和下划线
- 定义变量:
变量名=变量值
- 案例:
httpd_port=80
- 变量的调用方式:
通过{{ variable_name }}调用变量, 且变量名前后建议加空格, 有时需要用双引号才生效"{{ variable_name }}"
- 变量的来源:
1. ansible的setup facts远程主机的所有变量都可以直接调用, 不过setup模块的变量只能在playbook中使用
2. 通过命令行指定变量, 优先级最高
ansible-playbook -e varname=value test.yml
3. 在playbook文件中定义
vars:
- var1: value1
- var2: value2
4. 在独立的变量YAML文件中定义
- hosts: all
vars_files:
- vars.yml
5. 在/etc/ansible/hosts中定义
主机(普通)变量: 针对主机组中主机单独定义, 优先级高于公共变量
组(公共变量): 针对主机组中所有主机定义统一变量
6. 在role中定义
3.8.1 使用setup模块中的变量
setup模块中的变量只能在playbook中调用
- 案例: 调用ansible_nodename变量, 针对不同的主机, 生成不同的日志文件
[21:42:44 root@ansible /data/files]#vim log.yml
---
- hosts: all
gather_facts: yes # 调用setup模块, 需要开启gather_facts, 关闭了会报错, 无法采集主机数据
remote_user: root
tasks:
- name: "创建日志文件"
file: name=/data/{{ ansible_nodename }}.log state=touch
ansible-playbook log.yml
TASK [创建日志文件] ************************************************************************************************************************************************************
changed: [10.0.0.187]
changed: [10.0.0.85]
changed: [10.0.0.86]
PLAY RECAP ***************************************************************************************************************************************************************
10.0.0.187 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.0.0.85 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.0.0.86 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[21:45:22 root@ansible /data/files]#ansible all -m shell -a "ls -l /data"
10.0.0.187 | CHANGED | rc=0 >>
total 0
-rw-r--r-- 1 root root 0 Feb 10 21:42 centos-7-6.prac.log
10.0.0.86 | CHANGED | rc=0 >>
total 0
-rw-r--r-- 1 root root 0 Feb 10 21:42 CentOS-8-6.log
10.0.0.85 | CHANGED | rc=0 >>
total 0
-rw-r--r-- 1 root root 0 Feb 10 21:42 CentOS-8-5.log
如果要在ansible命令行去调用, 不能直接写{{ variable_name }}要写成'filter="ansible_default_ipv4"
[21:50:17 root@ansible /data/files]#ansible all -m setup -a 'filter="ansible_default_ipv4"'
3.8.2 在playbook命令行定义变量, 然后在playbook中调用
- 先编写yml文件, 在文件中调用由playbook命令行定义的变量
[21:57:03 root@ansible /data/files]#vim test.yml
---
- hosts: all
remote_user: root
gather_facts: no
tasks:
- name: create file
file: name=/data/{{ prefix }}.{{ suffix }} state=touch
- 在playbook命令行中定义变量
[21:59:46 root@ansible /data/files]#ansible-playbook -e "prefix=tomcat" -e "suffix=log" test.yml
PLAY [all] ***************************************************************************************************************************************************************
TASK [create file] *******************************************************************************************************************************************************
changed: [10.0.0.86]
changed: [10.0.0.85]
changed: [10.0.0.187]
PLAY RECAP ***************************************************************************************************************************************************************
10.0.0.187 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.0.0.85 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.0.0.86 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[22:00:12 root@ansible /data/files]#ansible all -a 'ls -l /data'
10.0.0.85 | CHANGED | rc=0 >>
total 0
-rw-r--r-- 1 root root 0 Feb 10 22:00 tomcat.log
10.0.0.86 | CHANGED | rc=0 >>
total 0
-rw-r--r-- 1 root root 0 Feb 10 22:00 tomcat.log
10.0.0.187 | CHANGED | rc=0 >>
total 0
-rw-r--r-- 1 root root 0 Feb 10 22:00 tomcat.log
3.8.3 直接把变量写到yml文件里
vars:
- var1: value1
- var2: value2
[22:12:04 root@ansible /data/files]#vim test.yml
---
- hosts: all
remote_user: root
gather_facts: no
vars:
- dirname: /opt/
- basename: test.log
tasks:
- name: create file
file: name={{ dirname }}{{ basename }} state=touch
[22:12:28 root@ansible /data/files]#ansible-playbook test.yml
[22:12:47 root@ansible /data/files]#ansible all -a 'ls -l /opt'
10.0.0.85 | CHANGED | rc=0 >>
total 0
-rw-r--r-- 1 root root 0 Feb 10 22:12 test.log
10.0.0.86 | CHANGED | rc=0 >>
total 0
-rw-r--r-- 1 root root 0 Feb 10 22:12 test.log
10.0.0.187 | CHANGED | rc=0 >>
total 0
-rw-r--r-- 1 root root 0 Feb 10 22:12 test.log
3.8.4 定义单独的yml文件, 存放变量, 然后再playbook中调用
[22:19:47 root@ansible /tmp]#vim var.yml
prefix: hello
suffix: world
[22:22:47 root@ansible /data/files]#vim test.yml
---
- hosts: all
remote_user: root
gather_facts: no
vars_files:
- /tmp/var.yml
tasks:
- name: create file
file: name={{ prefix }}.{{ suffix }} state=touch # 不写路径, 那么默认会在/root目录下创建
3.8.5 在/etc/ansible/hosts文件中定义
主机(普通)变量: 主机组中对主机单独定义, 优先级高于公共变量
组(公共)变量: 针对主机组中所有主机定义统一变量
案例: 修改appsrvs组的主机名
[appsrvs]
10.0.0.187 host=187 #主机普通变量
10.0.0.85 host=85
[appsrvs:vars]
domain=client.org #apparvs组的公共变量
[22:42:36 root@ansible /data/files]#vim test.yml
---
- hosts: appsrvs
remote_user: root
gather_facts: no
tasks:
- name: "修改appsrvs组的主机名"
hostname: name={{ host }}.{{ domain }}
[22:49:06 root@ansible /data/files]#ansible appsrvs -m shell -a "hostname"
10.0.0.85 | CHANGED | rc=0 >>
85.client.org
10.0.0.187 | CHANGED | rc=0 >>
187.client.org
3.8.6 变量定义的优先级
CLI > var.yaml > yml文件中的变量
主机变量优先于组变量
setup模块提供的变量补充:
setup模块用来收集被管理节点的信息, 收集的信息以变量:value或者变量的字典格式保存, 可以在playbook中调用
想要调用变量, 只要利用ansible 10.0.0.81 -m setup, 就可以获得对方主机的所有变量 ,之后利用字典['key']取值,或者直接调用变量名即可
- 案例: 根据管理节点的ip地址生成日志文件, httpd
setup包含的变量, ansible_default_ipv4是变量名, 其内容以字典形式存储, 可以通过key取到value
"ansible_default_ipv4": {
"address": "10.0.0.81",
"alias": "eth0",
"broadcast": "10.0.0.255",
"gateway": "10.0.0.2",
"interface": "eth0",
"macaddress": "00:0c:29:1f:4a:0a",
"mtu": 1500,
"netmask": "255.255.255.0",
"network": "10.0.0.0",
"type": "ether"
},
ansible_default_ipv4['address']就可以得到ipv4地址
之后在j2模板或者playbook中都可以调用
CustomLog "logs/{{ ansible_default_ipv4['address'] }}_access_log" combined
# playbook
---
- hosts: httpd
gather_facts: yes
remote_user: root
tasks:
- name: create log file
file: path=/var/log/{{ ansible_default_ipv4["address"]}}_access_log state=touch
最后生成的日志格式:
[00:30:56 root@web1 /etc/httpd/conf]#ll /var/log/httpd/
total 12
-rw-r--r-- 1 root root 0 Feb 14 00:30 10.0.0.81_access_log
3.9 模板
模板是一个文本文件, 可以做为生成文件的模本, 并且模板文件中还可以嵌套jinja2语法
3.9.1 jinja2
template功能: 可以根据和参考模板文件, 动态生成相类似的配置文件
template文件必须存放于templates目录下, 且命名为.j2结尾
yaml/yml 文件需和templates目录平级, 目录结构如下示例:
./
nginx.yml
templates
nginx.conf.j2
3.9.2 利用模板生成nginx配置文件
- yum下载nginx到ansible节点, 获取配置文件
[23:18:35 root@ansible ~]#yum -y install nginx
[23:19:24 root@ansible ~]#cp -a /etc/nginx/nginx.conf /data/files/templates/nginx.conf.j2
- 准备templates/nginx.conf.j2文件
[23:21:35 root@ansible /data/files/templates]#vim nginx.conf.j2
修改cpu为worker_processes {{ ansible_processor_vcpus +2 }}; # 利用setup模块的变量, 根据主机情况, 生成不同的配置文件, 使nginx可以根据本机的cpu个数, 来生成worker进程数
[23:38:59 root@ansible /data/ansible]#ansible 10.0.0.82 -m setup -a 'filter=ansible_processor_vcpus'
10.0.0.82 | SUCCESS => {
"ansible_facts": {
"ansible_processor_vcpus": 1,
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false
}
- 编辑安装nginx的yml文件
[23:15:30 root@ansible /data/files]#vim temnginx.yml
---
- hosts: 10.0.0.187
remote_user: root
tasks:
- name: install nginx
yum: name=nginx
- name: template config to remote hosts
template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
- name: service start
service: name=nginx state=started
---
- hosts: websrvs
remote_user: root
tasks:
- name: template config to remote hosts
template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
- 执行playbook
可以看到生成了3个worker进程
[23:35:20 root@centos-7-6 ~]#ps aux | grep nginx
root 8040 0.0 0.2 105500 2124 ? Ss 23:34 0:00 nginx: master process /usr/sbin/nginx
nginx 8041 0.0 0.2 105968 2916 ? S 23:34 0:00 nginx: worker process
nginx 8042 0.0 0.2 105968 2916 ? S 23:34 0:00 nginx: worker process
nginx 8043 0.0 0.2 105968 2916 ? S 23:34 0:00 nginx: worker process
3.9.3 template中使用流程控制for 和 if
template中也可以使用流程控制for循环和if条件判断, 实现动态生成文件功能
案例: 利用template循环生成多虚拟主机配置文件
[16:11:31 root@Ansible /data/scripts/yml]#vim vhost.yml
---
- hosts: 10.0.0.187
remote_user: root
gather_facts: no
vars:
nginx_vhosts:
- 81
- 82
- 83
tasks:
- name: template server
template: src=for_port.j2 dest=/data/nginx.conf
[23:41:42 root@ansible /data/files]#vim templates/for_port.j2
{% for vhost in nginx_vhosts %} # %和{}之间不能有空格, 会导致vhost显示未定义报错
server {
listen {{ vhost }};
}
{% endfor %}
[23:55:14 root@ansible /data/files]#ansible 10.0.0.187 -a 'cat /data/nginx.conf'
10.0.0.187 | CHANGED | rc=0 >>
server {
listen 81;
}
server {
listen 82;
}
server {
listen 83;
}
案例: 利用if判断, 来生成配置文件
判断server_name有没有指定, 如果没有, 那么配置文件中就不生成server_name
[00:40:47 root@ansible /data/files]#vim if.yml
- hosts: 10.0.0.187
remote_user: root
vars:
nginx_vhosts:
- web1:
listen: 8080
root: "/var/www/nginx/web1"
- web2:
listen: 8080
server_name: "web2.abc.com"
root: "/var/www/nginx/web2"
- web3:
listen: 8080
server_name: "web3.abc.com"
root: "/var/www/nginx/web3"
tasks:
- name: conf template
template: src=nginx.conf5.j2 dest=/data/nginx5.conf
[00:41:22 root@ansible /data/files]#vim templates/nginx.conf5.j2
{% for vhost in nginx_vhosts %}
server {
listen {{ vhost.listen }}
{% if vhost.server_name is defined %}
server_name {{ vhost.server_name }}
{% endif %}
root {{ vhost.root }}
}
{% endfor %}
[00:44:59 root@ansible /data/files]#ansible 10.0.0.187 -a 'cat /data/nginx5.conf'
10.0.0.187 | CHANGED | rc=0 >>
server {
listen 8080
root /var/www/nginx/web1
}
server {
listen 8080
server_name web2.abc.com
root /var/www/nginx/web2
}
server {
listen 8080
server_name web3.abc.com
root /var/www/nginx/web3
}
3.10 playbook中使用when
when语句, 可以实现条件测试. 如果需要根据变量, facts或此前任务的执行结果来作为某task执行与否的前提时要用到条件测试, 通过在task后添加when子句即可使用条件测试
when是playbook的语法, 而不是jinja2的语法
范例1: 根据操作系统的发行版来执行任务
[00:45:41 root@ansible /data/files]#vim os.yml
---
- hosts: websrvs
remote_user: root
tasks:
- name: "关闭红帽主机"
command: /sbin/shutdown -h now
when: ansible_os_family == "RedHat" # 如果发行版是红帽, 则关掉主机
范例2: 根据发行版和系统版本来关闭主机
[00:57:34 root@ansible /data/files]#vim os.yml
---
- hosts: websrvs
remote_user: root
tasks:
- name: "关闭CentOS 6 和 Debian 7主机"
command: /sbin/shutdown -h now
when: (ansible_facts['distribution'] == "CentOS" and
ansible_facts['distribution_major_version' == "6"]
) or (ansible_facts['distribution'] == "Debian" and
ansible_facts['distribution_major_version'] == "7"
)
范例3: 根据操作系统的版本拷贝配置文件
[00:59:33 root@ansible /data/files]#vim conf.yml
---
- hosts: 10.0.0.187:10.0.0.85
remote_user: root
tasks:
- name: "复制配置文件到CentOS7"
copy: src=centos7.conf dest=/data/centos7.conf
when: ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_major_version'] == "7"
- name: "复制配置文件到CentOS8"
copy: src=centos8.conf dest=/data/centos8.conf
when: ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_major_version'] == "8"
[01:05:11 root@ansible /data/files]#echo 'centos7.conf' > centos7.conf
[01:05:28 root@ansible /data/files]#echo 'centos8.conf' > centos8.conf
[01:05:41 root@ansible /data/files]#ansible-playbook -C conf.yml
PLAY [10.0.0.187:10.0.0.85] **********************************************************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************************************************
ok: [10.0.0.85]
ok: [10.0.0.187]
TASK [复制配置文件到CentOS7] ****************************************************************************************************************************************************
skipping: [10.0.0.85] # 因为本任务只复制文件到CentOS8, 所以会跳过
changed: [10.0.0.187]
TASK [复制配置文件到CentOS8] ****************************************************************************************************************************************************
skipping: [10.0.0.187] # 因为本任务只复制文件到CentOS7, 所以会跳过
changed: [10.0.0.85]
PLAY RECAP ***************************************************************************************************************************************************************
10.0.0.187 : ok=2 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
10.0.0.85 : ok=2 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
3.11 playbook中使用with_items(loop)
jinja2中可以用for来进行循环, playbook中可以用with_items来循环取值
迭代: 当有需要重复性执行的任务时, 可以使用迭代机制
对迭代项的引用, 固定变量名为"item"
要在task中使用with_items给定要迭代的元素列表
ansible2.5版本后, 可以用loop来代替with_items
with_items需要是个列表, 列表中支持字符串和字段格式
with_items是可迭代对象
item是变量, 固定变量
范例1: 字符串格式, 创建多个用户
[01:19:51 root@ansible /data/files]#vim user.yml
---
- hosts: 10.0.0.85
remote_user: root
tasks:
- name: "创建admin组"
group: name=admin
- name: "创建多个用户"
user: name={{ item }} state=present groups=admin
with_items:
- admin1
- admin2
- admin3
admin1:x:1000:1001::/home/admin1:/bin/bash
admin2:x:1001:1002::/home/admin2:/bin/bash
admin3:x:1002:1003::/home/admin3:/bin/bash
范例2: 安装多个软件包
当某个模块不支持多个参数时, 就可以用with_items来迭代取值
[01:26:31 root@ansible /data/files]#vim pkg.yml
---
- hosts: 10.0.0.85
remote_user: root
tasks:
- name: "安装多个软件包"
yum: name={{ item }} state=present
with_items:
- httpd
- php-fpm
- redis
[01:20:40 root@CentOS-8-5 ~]#rpm -q httpd
httpd-2.4.37-30.module_el8.3.0+561+97fdbbcc.x86_64
[01:28:28 root@CentOS-8-5 ~]#rpm -q php-fpm
php-fpm-7.2.24-1.module_el8.2.0+313+b04d0a66.x86_64
[01:28:33 root@CentOS-8-5 ~]#rpm -q redis
redis-5.0.3-2.module_el8.2.0+318+3d7e67ea.x86_64
范例3: 迭代嵌套子变量, 在迭代中, 还可以嵌套子变量, 关联多个变量一起使用
将with_items定义成列表, 列表中嵌套字典, 迭代取值列表中的元素, 每取出一个元素, 利用key来取值, 得到元素
[01:41:44 root@ansible /data/files]#vim create_user.yml
---
- hosts: 10.0.0.85
remote_user: root
tasks:
- name: "创建用户组"
group: name={{ item }} state=present
with_items:
- nginx
- php
- mysql
- name: "针对每个组创建用户"
user: name={{ item.name }} group={{ item.group }} state=present
with_items:
- {name: "nginx", group: "nginx"}
- {name: "php", group: "php"}
- {name: "mysql", group: "mysql"}
nginx:x:1003:1004::/home/nginx:/bin/bash
php:x:1004:1005::/home/php:/bin/bash
mysql:x:1005:1006::/home/mysql:/bin/bash
3.12 管理节点过多导致的超时问题解决方法
默认情况下, 会并行管理主机, 会按照playbook定义的每个任务,来批次执行. 默认为5个主机
比如, playbook中定义了10个任务, 那么ansible会先在5个主机上执行task1, 等到task1执行完, 再另5个主机上继续执行task1, 直到task1执行完, 再继续执行task2
这种情况会导致主机处于中间状态, 因此, 可以利用serial来指定百分比, 或者具体个数, 来指定针对一部分主机, 把所有的tasks都执行完, 再执行下一部分主机
范例1: 指定每次执行的主机数值
[01:47:25 root@ansible /data/files]#vim seria1.yml
---
- hosts: 10.0.0.85
serial: 2 # 每次只同时处理2个主机, 将所有的tasks都执行完, 再选择下2个主机继续执行所有的tasks, 直到执行完所有主机
gather_facts: no
tasks:
- name: task 1
command: hostname
- name: task 2
command: ip a
范例2: 指定每次执行的主机百分比
[01:49:41 root@ansible /data/files]#vim serial2.yml
---
- hosts: 10.0.0.85
remote_user: root
gather_facts: no
serial: "20%"
tasks:
- name: "task 1"
command: hostname
- name: "task 2"
command: ip a