Ansible入门

Linux教程 / 2021-05-08

Playbook部分内容参考自:

​ Wiki《YAML》:https://zh.wikipedia.org/wiki/YAML

​ 菜鸟教程《YAML入门教程》:https://www.runoob.com/w3cnote/yaml-intro.html

​ 阮一峰的网络日志《YAML语言教程》:https://www.ruanyifeng.com/blog/2016/07/yaml.html

一、Ansible简述

Ansible是一款Linux/Unix上基于Python研发的自动化工具,可以快速批量操作多台主机,也是热度极高的自动化运维工具。

它具有以下特点:

1.1、Agentess

基于SSH连接,因此不需要在受控机上安装agent也可以实现批量操作多台主机。

1.2、模块化管理

自带的功能模块众多,搭配使用Playbook可以实现非常强大的功能。

1.3、幂等性

即任务执行一次和执行多次结果是一样,不会因为重复执行而带来意外。比如说多次执行安装同一款程序,会自动检测,如已安装会被跳过。

1.4、Ansible架构

二、Ansible基本使用

本次学习系统环境为Manjaro21(ArchLinux系列桌面发行版)、ansible版本为3.3.0-2,受控机为Centos7.9;因为Ansible是基于Python研发,因此你的系统上必须要有Python 2.7+或 3+版本。

OS:5.10.34-1-MANJARO
ansible:3.3.0-2
Python:3.9.4

2.1、ansible语法

ansible HOST-PATTERN -m MOD_NAME -a MOD_ARGS -f FORKS -C -u USERNAME -c CONNECTION

2.2、ansible常用选项

先来看看ansible常用选项:

-C, --check  # 仅测试,并不产生实际改变。

-f,  # 单次操作主机数量,太多会卡。

-i, # 指定主机清单,默认为/etc/ansible/hosts,需要手动创建文件。

--list-hots  # 列出当前可用主机

--syntax-check,  # 检查指定的playbook语法

-t,  # 日志输出位置

ansible-doc -l,列出所有模块

2.3、ansible远程控制前戏

建议使用密钥方式连接主机,以下仅为本地环境有用。因为在云服务器上,我们创建机器前就可以选择添加密钥进去了,不需要下面这些操作。

指定root连接

我们指定使用root来连接每台主机,这需要修改ansible的配置文件。

sudo sed -i 's/#remote_user = root/remote_user = root/g' /etc/ansible/ansible.cfg
生成密钥
[sanxi@sanxi-aero15sa ~]$ ssh-keygen -t rsa -P ''
Generating public/private rsa key pair.
Enter file in which to save the key (/home/sanxi/.ssh/id_rsa): 
Created directory '/home/sanxi/.ssh'.
Your identification has been saved in /home/sanxi/.ssh/id_rsa
Your public key has been saved in /home/sanxi/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:6yGMH5GEOg8pr5U4lRVNBAz6QGkIT4O4mTa+0XZGsa4 sanxi@sanxi-aero15sa
The key's randomart image is:
+---[RSA 3072]----+
|=o=oo=o          |
|=* ..+.          |
|++. o +          |
|+= = + .         |
|+ @ o o S        |
| B B * . .       |
|o B * + o        |
| = E . + .       |
|.     . .        |
+----[SHA256]-----+
安装except
sudo pacman -S expect
创建主机清单
cat << EOF >> host.txt
> 192.168.56.101
> 192.168.56.102
> EOF
创建批量添加密钥脚本

此脚本需要传递两个位置参数,第一个是主机清单,第二个是公钥,公钥这里也可以直接写死。清单的格式最好是txt,必须是一行一个IP地址,没有任何多余信息。

#!/bin/bash


SSH_KEY_PATH=$2
USER='root'
PASSWD='sanxi666.'
PORT=22

cat $1 | while read ip;do  # 传递清单文件,开启while循环,并读取一行文本赋予给变量ip

expect << EOF  # 开启自动交互
spawn ssh-copy-id -i $SSH_KEY_PATH -p $PORT -f $USER@$ip  # 传递密钥至远程主机
expect {  # # 第一次问yes/no,第二次是要输入密码。
  "yes/no" { send "yes\n"; exp_continue }  # 捕获到"yes/no",则自动回答yes并回车,exp_continue继续。
  "password" { send "$PASSWD\n"  }  # 捕获到"password",则自动输入密码并回车。
}

expect eof  # 结束捕获文本
EOF  # 结束expect

done  # 清单文件读取完毕,结束循环。

2.4、远程操作多台主机

[sanxi@sanxi-aero15sa ~]$ ansible all -m ping 
192.168.56.102 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}
192.168.56.101 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}

三、ansible常用模块

查看当前可用模块

ansible-doc -l

3.1、user用户管理

用于操作目标主机用户组,也可以跟useradd一样跟很多选项,用得最多的也就是添加用户。

[sanxi@sanxi-aero15sa ~]$ ansible all -m user -a "name=sanxi"
192.168.56.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "comment": "",
    "create_home": true,
    "group": 1000,
    "home": "/home/sanxi",
    "name": "sanxi",
    "shell": "/bin/bash",
    "state": "present",
    "system": false,
    "uid": 1000
}
192.168.56.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "comment": "",
    "create_home": true,
    "group": 1000,
    "home": "/home/sanxi",
    "name": "sanxi",
    "shell": "/bin/bash",
    "state": "present",
    "system": false,
    "uid": 1000
}

3.2、group属组管理

用于操控目标主机的属组,其它选项不多演示。

这里因为用的是root远程,因此不会改变,因为本来就是root组。

[sanxi@sanxi-aero15sa ~]$ ansible all -m group -a "name=root"
192.168.56.101 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "gid": 0,
    "name": "root",
    "state": "present",
    "system": false
}
192.168.56.102 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "gid": 0,
    "name": "root",
    "state": "present",
    "system": false
}

3.3、copy拷贝文件

  • src,源目录/文件,这里会自动递归赋值;如果目录后面跟斜线则代表将该目录下所有文件拷贝至目标目录,目标目录必须事先存在;如果不带斜线,则代表将目录本身及其所属子文件/目录全部拷贝至远端,此时可以不指定目标路径,ansible会自动创建跟src一模一样的路径。
  • dest,目标目录,如果src是目录,则dest必须是目录。
  • content,直接在主控机上生成文件传给目标
[sanxi@sanxi-aero15sa ~]$ ansible all -m copy -a "src=/home/sanxi/light.sh dest=/root/"
192.168.56.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "checksum": "7171014f9362c0f094416ad965dd6004c8a1537a",
    "dest": "/root/light.sh",
    "gid": 0,
    "group": "root",
    "md5sum": "664b593efd967b9cf6f14d60230945a5",
    "mode": "0644",
    "owner": "root",
    "secontext": "system_u:object_r:admin_home_t:s0",
    "size": 76,
    "src": "/root/.ansible/tmp/ansible-tmp-1620376911.816549-28366-175745153098492/source",
    "state": "file",
    "uid": 0
}
192.168.56.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "checksum": "7171014f9362c0f094416ad965dd6004c8a1537a",
    "dest": "/root/light.sh",
    "gid": 0,
    "group": "root",
    "md5sum": "664b593efd967b9cf6f14d60230945a5",
    "mode": "0644",
    "owner": "root",
    "secontext": "system_u:object_r:admin_home_t:s0",
    "size": 76,
    "src": "/root/.ansible/tmp/ansible-tmp-1620376911.816788-28368-217374409506253/source",
    "state": "file",
    "uid": 0
}

3.4、fetch下载文件

跟copy相反,fetch是从远程主机拷贝文件到本地,而且只能拷贝其中一台主机而不能指定多台主机。

  • fail_on_missing,不存在则报错并退出。
[sanxi@sanxi-aero15sa ~]$ ansible 192.168.56.101 -m fetch -a "src=/root/sanxi.txt dest=/home/sanxi/"
192.168.56.101 | CHANGED => {
    "changed": true,
    "checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "dest": "/home/sanxi/192.168.56.101/root/sanxi.txt",
    "md5sum": "d41d8cd98f00b204e9800998ecf8427e",
    "remote_checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "remote_md5sum": null
}

3.5、command执行命令

在远程主机上执行命令,ansible默认的模块就是command,所以可以省略-m command直接用-a "命令"

[sanxi@sanxi-aero15sa ~]$ ansible all -a "ls"
192.168.56.101 | CHANGED | rc=0 >>
anaconda-ks.cfg
light.sh
sanxi.txt
192.168.56.102 | CHANGED | rc=0 >>
anaconda-ks.cfg
light.sh

3.6、shell执行命令

在远程主机上的shell上执行命令,支持shell特性,如管道等,shell模块比command强很多。

  • chdir,在执行命令前先切换到指定目录再执行
[sanxi@sanxi-aero15sa ~]$ ansible all -m shell -a "ls | rm -f light.sh"
192.168.56.101 | CHANGED | rc=0 >>

192.168.56.102 | CHANGED | rc=0 >>

3.7、file设定文件属性

注意,file操作的是目标主机,而不是将控制端的文件数据传过去处理。

​ state,设定文件或目录的属性,state的属性非常多,具体要看帮助文档,这里只演示创建目录和软链接。

创建目录
[sanxi@sanxi-aero15sa ~]$ ansible all -m file -a "path=/root/test/haha state=directory"
192.168.56.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "gid": 0,
    "group": "root",
    "mode": "0755",
    "owner": "root",
    "path": "/root/test/haha",
    "secontext": "unconfined_u:object_r:admin_home_t:s0",
    "size": 6,
    "state": "directory",
    "uid": 0
}
创建软链接
[sanxi@sanxi-aero15sa ~]$ ansible 192.168.56.101 -m file -a "src=/root/sanxi.txt path=/root/test/sanxi.txt state=link"192.168.56.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "dest": "/root/test/sanxi.txt",
    "gid": 0,
    "group": "root",
    "mode": "0777",
    "owner": "root",
    "secontext": "unconfined_u:object_r:admin_home_t:s0",
    "size": 15,
    "src": "/root/sanxi.txt",
    "state": "link",
    "uid": 0
}
验证结果
[sanxi@sanxi-aero15sa ~]$ ansible 192.168.56.101 -a "ls -l test/"
192.168.56.101 | CHANGED | rc=0 >>
total 0
drwxr-xr-x. 2 root root  6 May  7 16:11 haha
lrwxrwxrwx. 1 root root 15 May  7 16:17 sanxi.txt -> /root/sanxi.txt

3.8、cron定期任务

跟linux上面的crontab一样的用处,这里罗列一下常用参数:

  • month
  • day
  • hour
  • minute
  • job,定期执行的命令
  • name,任务名称,可以不指定,当state=present时,必须指定。
  • state,是否要求任务必须指定job和环境变量,absent是不需要,present是必须要。
  • env,管理crontab环境变量
[sanxi@sanxi-aero15sa ~]$ ansible all -m cron -a "hour=*/1 job='ls -l ~/' name=ls state=present"
192.168.56.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "envs": [],
    "jobs": [
        "ls"
    ]
}
192.168.56.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "envs": [],
    "jobs": [
        "ls"
    ]
}

3.9、yum包管理器

执行RedHat系列yum操作,ansible还提供了Debian系列的apt等其它对应平台的模块。我这次实验将虚拟机改为桥接网络,因此IP地址发生变更。

  • name,包名
  • update_cache,更新yum源缓存。
  • state,
    • 安装:present、installed、latest,这三个都可以。
    • 卸载:absent、removed,这两个都可以。
[sanxi@sanxi-aero15sa ~]$ ansible all -m yum -a "name=vim state=installed"

3.10、service服务管理

  • name,服务名
  • enabled,开机自启
  • state,
    • 启动:started
    • 停止:stoped
    • 重启:restarted
    • 重载:reload
ansible all -m service -a "name=nginx state=started enabled=true"

此处因为输出信息过多,我就没贴执行结果,直接来验证吧!

[sanxi@sanxi-aero15sa ~]$ ansible all -m shell -a "ss -tnl | grep 80"
192.168.31.232 | CHANGED | rc=0 >>
LISTEN     0      128          *:80                       *:*                  
LISTEN     0      128       [::]:80                    [::]:*                  
192.168.31.173 | CHANGED | rc=0 >>
LISTEN     0      128          *:80                       *:*                  
LISTEN     0      128       [::]:80                    [::]:* 

3.11、script执行脚本

复制本地脚本到远程主机执行,脚本后面可以直接跟参数

[sanxi@sanxi-aero15sa 文档]$ ansible all -m script -a "./echo.sh sanxi"
192.168.31.173 | CHANGED => {
    "changed": true,
    "rc": 0,
    "stderr": "Shared connection to 192.168.31.173 closed.\r\n",
    "stderr_lines": [
        "Shared connection to 192.168.31.173 closed."
    ],
    "stdout": "hello sanxi\r\n",
    "stdout_lines": [
        "hello sanxi"
    ]
}
192.168.31.232 | CHANGED => {
    "changed": true,
    "rc": 0,
    "stderr": "Shared connection to 192.168.31.232 closed.\r\n",
    "stderr_lines": [
        "Shared connection to 192.168.31.232 closed."
    ],
    "stdout": "hello sanxi\r\n",
    "stdout_lines": [
        "hello sanxi"
    ]
}

四、Playbook入门

像上面都是得自己手动执行ansible命令,有些麻烦,能不能简便点,一次搞定?当然可以啦!

PlayBook是ansible的核心组件,顾名思义我们就是导演,提前写好剧本(playbook),然后安排演员(主机)按照写好的剧本来演戏,当然了,这里演员是不能临场发挥的。。。

4.1、YAML简述

以下内容摘自wiki百科,原文链接:https://zh.wikipedia.org/wiki/YAML

YAML是"YAML Ain't a Markup Language"(YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)[3],但为了强调这种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重命名。

4.2、YAML基本数据结构

YAML以严格的空白字符缩进所著称,用缩进来表示层级关系;缩进多少个空格不重要,重要的是一定要对齐。

它有以下几种基本数据结构:

对象

也叫散列表,跟Python字典差不多,以键值对形式存在,冒号后面要加个空格。

Python:
  WebFramework: Djano
  CrawlerFramework: Scrapy
数组

也叫清单,同级别缩进的以-开头的行构成一个数组,也可以看作成Python的列表,还可以写在同一行。

- Java
- GO
- C
# 上下两种表达方式是一致的
['Java', 'GO', 'C']

如果子成员也是一个数组,那么可以缩进一个空格表示

- Python:
 - asyncio
 - requests
复合结构

即将数组和对象组合起来

Python:
 - asyncio
   await: sleep
   async: async
标量

标量在YAML中表示为最基本、不可分割的单位,这点跟SOL中原子性有一点点相似。它可以是:

  • 字符串
string: 字符串
  • 布尔值

可以是true、True、TRUE;false、False、FALSE

is_exist: true
is_vaild: false
  • 整数
China: 1
  • 浮点数
salary: 233.233
  • Null

Null用波浪号~表示

money: ~
  • 时间

时间使用ISO 8601格式,时间和日期之间使用T连接,最后使用+代表时区

datetime: 2021-05-07T21:11:11+08:00
  • 日期

日期必须使用ISO 8601格式,即yyyy-MM-dd。

date: 2021-05-07

了解到这就算可以了,我们的真正目的是学会编写playbook。

4.3、playbook字段

playbook使用YAML编写,它由以下字段组成:

4.3.1、主机清单hosts

运行指定任务的目标主机,可以是IP地址也可以是主机名,可以是一个或多个,用数组表示。

- hosts:
  - 192.168.31.173  #
  - 192.168.31.232

更推荐直接写主机组,而不是写IP地址

- hosts: centos
4.3.2、远端用户remoute_user

在远程主机上执行任务的用户

- remote_user: root
4.3.3、任务列表task

要执行的任务列表,它主要由name和action组成;name即任务名称,可以随意起;action是要执行的操作,多数用为模块和对应参数。

任务之间是串行的,按照剧本由上到下一个任务一个任务地执行下去;任务中的主机是并行的,即按照任务来给所有主机一次性发起执行指令。

- tasks:
  - name: install redis
    yum: name=redis state=installed
  - name: start redis
    service: name=redis state=started
  - name: echo info
    shell: echo "Hello,World!"
4.3.4、控制者handlers

由特定条件触发的任务,即接收到其它任务的通知后触发,那么如何接收通知呢?在任务里面安排一个眼线notify即可!

notify

在需要监视的任务后面加上notify,它的名称必须和handlers的name一致,当该notify所在的任务被执行后,就会触发handlers的执行

  tasks:
    - name: install redis
      yum: name=redis state=installed
    - name: modify configuration
      copy: src=/etc/redis/redis.conf dest=/etc/redis/
      notify: restart redis  # 必须和handlers的name一致
    - name: start redis
      service: name=redis state=started
    - name: echo info
      shell: echo "Hello,World!"
handlers
handlers:
  name: restart redis  # # 必须和notify保持一致
  service: name=redis state=restarted
4.3.5、标签tags

在执行playbook时使用-t选项,指定tag,就可以做到只运行tag所在的task,非常适合局部更新操作。

tasks:
  - name: install redis
    yum: name=redis state=installed
  - name: modify configuration
    copy: src=/etc/redis/redis.conf dest=/etc/redis/
    notify: restart redis
    tags: setting redis  # 打标签
4.3.6、变量variables

variables,跟shell脚本的变量一样,可以用setup模块查看目标主机上的各种系统变量信息,这些变量信息可以直接被playbook所识别,这涉及到模板语法,下面会讲到。

variables来源有几种方式:

setup模块取变量

使用时,可以用点号来连接不同层级的ansible变量

ansible 192.168.31.173 -m setup  # 获取的信息量非常多,所以没有贴出来
手动设定变量

直接在playbook中自定义变量,调用变量用双大括号,这种引用方式跟Django的模板语法引用变量是一模一样的。

- hosts: centos
  vars:  # 设定变量组
    var1: sanxi  # 变量
    var2: andy  # 变量
  remote_user: root
  tasks:
    - name: echo text
      shell: echo "Hello,{{ var1 }}" >> echo.txt  # 双大括号引用变量

其实还有几种方式,感觉不实用就没放出来,最重要的是变量与模板的搭配使用!

4.3.7、语法检查

使用--syntax-check来检查YAML文件的语法是否有语法错误

[sanxi@sanxi-aero15sa scratches]$ ansible-playbook --syntax-check study.yml 

playbook: study.yml 
4.3.8、列出主机清单

列出即将执行任务的主机清单

[sanxi@sanxi-aero15sa scratches]$ ansible-playbook --list-hosts study.yml 

playbook: study.yml

  play #1 (centos): centos      TAGS: []
    pattern: ['centos']
    hosts (2):
      192.168.31.232
      192.168.31.173
4.3.9、执行playbook

直接跟要执行的YAML文件即可

ansible-playbook study.yaml
4.3.10、主机清单通配
清单通配

主机清单也可以使用主机组的通配逻辑,定义一个范围而不是一个一个写,比如有两台主机是:

  • 192.168.31.50
  • 192.168.31.51
  • 192.168.31.52

那么这几台就可以直接写为:192.168.31.[50, 52]

清单参数

ssh默认监听22端口,如果是别的端口直接在主机后面冒号隔开并加上端口号:

[centos]
192.168.31.173:702
192.168.31.232:702

五、Playbook模板

进入playbook模板学习,这很重要。

5.1、Jinja2基础

ansible模板依赖Python模板语法之:jinja2,因为是Jinja2是基于Python的,所以支持的数据类型都是Python中支持i的数据类型。

凡是使用Jinja2语法的文件都需要以.j2结尾。

5.1.1、数据类型:
字符串使用单引号或双引号
数字整数、浮点数。
列表[item1, item2, item3]
元组(item1, item2, item3),其实元组就是只读列表。
字典{key1:value1, key2:value2},键值对形式。
布尔值true/false
5.1.2、算数运算
运算符功能
+加法
-减法
/除法
//取商
%取模/取余
**次方
5.1.3、比较运算
==等于
!=不等于
>大于
>=大于等于
<小于
<=小于等于
5.1.4、逻辑运算
and
or
not

5.2、模板基本使用

5.2.1、更改本地nginx配置

先在本地安装好nginx,然后改nginx的默认配置,这就是用了模板语法,用变量替代固定值,这样就灵活了很多。

ansible_processor_vcpus是用setup模块取得的ansible变量;http_port是自定义变量

mv nginx.conf{,.j2}  # jinja2仅识别文件后缀名为.j2的文件
sed -i 's/ 1;/ {{ ansible_processor_vcpus }};/g' nginx.conf.j2
sed -i 's/ 80;/ {{ http_port  }};/g' nginx.conf.j2
5.2.2、编写YAML

在使用模板时,我们需要学习template这个新字段的使用。

- hosts: centos
  vars:
    http_port: 8080  # 注意,这里自定义的变量在nginx.conf有用到
  remote_user: root
  tasks:
    - name: install nginx
      yum: name=nginx state=installed
    - name: nginx settings
      template: src=/home/sanxi/nginx.conf.j2 dest=/etc/nginx/nginx.conf  # 必须使用template字段
      notify: restart nginx
    - name : start nginx
      service: name=nginx state=started

  handlers:
    - name: restart nginx
      service: name=nginx state=restarted
5.2.3、检查语法

没有报错就是最好的结果

[sanxi@sanxi-aero15sa scratches]$ ansible-playbook --syntax-check study.yml 

playbook: study.yml
5.2.4、执行playbook

我这里因为第一次执行时有一台网络故障,重新配好后,第二次执行那一台成功的机器因为幂等性就被跳过了。

[sanxi@sanxi-aero15sa scratches]$ ansible-playbook study.yml 

PLAY [centos] *************************************************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************************************************
ok: [192.168.31.232]
ok: [192.168.31.173]

TASK [install nginx] ******************************************************************************************************************************************************
ok: [192.168.31.232]
ok: [192.168.31.173]

TASK [nginx settings] *****************************************************************************************************************************************************
ok: [192.168.31.232]
changed: [192.168.31.173]

TASK [start nginx] ********************************************************************************************************************************************************
ok: [192.168.31.232]
ok: [192.168.31.173]

RUNNING HANDLER [restart nginx] *******************************************************************************************************************************************
changed: [192.168.31.173]

PLAY RECAP ****************************************************************************************************************************************************************
192.168.31.173             : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.31.232             : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

5.2.5、确认结果

可以看到,两台机器上的nginx监听端口已经被变成了8080,演戏成功!

[sanxi@sanxi-aero15sa scratches]$ ansible centos -m shell -a "ss -tnl | grep :8080"
192.168.31.173 | CHANGED | rc=0 >>
LISTEN     0      128          *:8080                     *:*                  
192.168.31.232 | CHANGED | rc=0 >>
LISTEN     0      128          *:8080                     *:* 

六、playbook进阶

6.1、条件测试when

大家知道,我们的服务器可能是RedHat系列的CentOS,也可能是Debian系列的Ubuntu,这就决定了我们安装程序员无法使用同一款软件包管理器,因为RedHat系列是yum、dnf,而Debian系列是apt,难道我们就为了这个要写不同的剧本吗?这不是很麻烦吗?

为此ansible提供了新字段when,只有当when里面定义的条件成立,when所在的任务才会被执行,来看看代码!

先手动用setup取得受控机上的系统变量名称,每一次执行ansible远程控制主机第一步就是主机报告各自系统信息变量给ansible主控机,所以一定要提前确保这名称是正确的!!!

- hosts: all  # 因为要用when,所以这里的主机范围要注意
  vars:
    http_port: 8080
  remote_user: root
  tasks:
    - name: install nginx in centos
      yum: name=nginx state=installed
      when: ansible_distribution == "CentOS"  # 当检测到此变量是CentOS时触发该任务
    - name: install nginx in ubuntu
      apt: name=nginx state=installed
      when: ansible_distribution == "Ubuntu"  # 当检测到Ubuntu时触发

6.2、循环item

有时候我们需批量安装多个程序包或者添加多个用户等操作,这些操作的命令都是一样的,只是参数的名称不一样,如果也要手动一条一条地写无疑是一种重复劳动!

为此,ansible也为我们提供了循环执行某项任务的功能,关键字:item,而后用with_items要迭代的元素列表,列表内可以套字符串,也可以是字典。

YAML编写
- hosts: centos
  remote_user: root
  tasks:
    - name: add users
      user: name={{ item }}
      when: ansible_distribution == "CentOS"
      with_items:
        - sanxi
        - andy
        - xixi
开始演戏
[sanxi@sanxi-aero15sa scratches]$ ansible-playbook study.yml 

PLAY [centos] *************************************************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************************************************
ok: [192.168.31.232]
ok: [192.168.31.173]

TASK [add users] **********************************************************************************************************************************************************
ok: [192.168.31.232] => (item=sanxi)
ok: [192.168.31.173] => (item=sanxi)
changed: [192.168.31.232] => (item=andy)
changed: [192.168.31.173] => (item=andy)
changed: [192.168.31.232] => (item=xixi)
changed: [192.168.31.173] => (item=xixi)

PLAY RECAP ****************************************************************************************************************************************************************
192.168.31.173             : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.31.232             : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
确认成果
[sanxi@sanxi-aero15sa scratches]$ ansible centos -m shell -a "tail -3 /etc/shadow"
192.168.31.232 | CHANGED | rc=0 >>
nginx:!!:18754::::::
andy:!!:18754:0:99999:7:::
xixi:!!:18754:0:99999:7:::
192.168.31.173 | CHANGED | rc=0 >>
nginx:!!:18754::::::
andy:!!:18755:0:99999:7:::
xixi:!!:18755:0:99999:7:::

七、角色roles

7.1、roles简述

playbook中最最最重要的部分上场!一部戏,演员才是最重要的,不是嘛!

此前我们编写playbook时,所有的字段都写在同一个YAML文件,不利于扩展和灵活,而roles就是扮演着一个个的角色,将原本一个文件以特定的层级目录结构进行拆分,这样就实现了playbook的模块化。

7.2、roles结构

roles默认目录为以下所示,也可以手动指定位置。

roles_path = ~/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles

一个合乎规范的role的目录结构应该是这样的:

[sanxi@sanxi-aero15sa roles]$ ls
mariadb
[sanxi@sanxi-aero15sa roles]$ pwd
/home/sanxi/.ansible/roles
[sanxi@sanxi-aero15sa roles]$ tree mariadb/
mariadb/
├── default  # 设定默认变量
│   └── main.yml  # 设定默认变量时使用此固定的文件。
├── files  # 存放copy、script等模块调用的文件
├── handlers  # 存放handlers控制器
│   └── main.yml  # 至少应该存在一个名为main.yml文件,其它的handlers文件需要在此通过include进行导入。
├── meta  # 定义当前角色的特殊设定及其依赖关系
│   └── main.yml  # 至少存在一个main.yml文件,其它的meta文件需要在此include导入。
├── tasks  # 存放任务
│   └── main.yml  # 同上至少。。。
├── templates  # 存放template模块查找所需要的模板文件
└── vars  # 存放各种变量信息
    └── main.yml  # 同上至少。。。

7 directories, 5 files

7.3、roles编写

因为使用了roles,所以可以使用相对路径了,ansible会自己去对应目录找文件,需要用到对应字段的去对应目录下的mian.yml编写对应代码即可。

tasks/main.yml代码

务必记得不要用--syntax-check检测语法,一定会报错的。

同时这也是使用item实现多文件拷贝至不同目录的演示。

- name: install mariadb
  yum: name=mariadb state=installed
  when: ansible_distribution == "CentOS"
- name: setting mariadb
  copy: src={{ item.src }} dest={{ item.dest }}
  with_items:
    - { src: 'my.cnf', dest: '/etc/my.cnf' }
    - { src: 'server.cnf', dest: '/etc/my.cnf.d/server.cnf' }
    - { src: 'client.cnf', dest: '/etc/my.cnf.d/client.cnf' }
  notify: restart mariadb
  tags: setting mariadb
- name: start mariadb
  service: name=mariadb state=started
handlers/main.yml代码
- name: restart mariadb
  service: name=mariadb state=restarted
files目录
[sanxi@sanxi-aero15sa mariadb]$ ls files/
client.cnf  my.cnf  server.cnf

7.4、调用角色

先提醒一下,ansible-playbook有个大坑,大家不要去使用--syntax-check去检测roles下面的各个main.yml,就算写得没问题也会报错,只需确保调用角色的yml语法正常就可以了。

角色的调用有三种:

7.4.1、直接调用

角色必须以数组形式存在,就算只有一个也是一样。

- hosts: centos
  remote_user: root
  roles:
    - mariadb  # 必须是数组形式,可以是一个或多个。
7.4.2、调用并传递变量

role指定角色,后面的K/V键值对用于传递变量给角色,角色里可以调用该变量。

- hosts: centos
  remote_user: root
  roles:
    - { name: mariadb, username: mariadb }
7.4.3、条件调用

可以在满足when条件的情况下调用角色

- hosts: centos
  remote_user: root
  roles:
    - { role: mariadb, when: "ansible_distribution == 'CentOS' " }

7.5、ansible配置文件

ansible配置文件的配置项非常多,但是默认配置就足够我们日常使用了,我们来看看ansible配置文件中需要关注的地方:

默认项
[defaults]
inventory       = /etc/ansible/hosts  # 定义主机清单位置
#library         = ~/.ansible/plugins/modules:/usr/share/ansible/plugins/modules
#module_utils    = ~/.ansible/plugins/module_utils:/usr/share/ansible/plugins/module_utils
#remote_tmp      = ~/.ansible/tmp
#local_tmp       = ~/.ansible/tmp
forks           = 5  # 定义一次操作多少台主机,因为太多会卡。
#poll_interval   = 0.001
#ask_pass        = False
#transport       = smart
角色读取目录

默认是这样的,可以手动改。

# Paths to search for roles, colon separated
roles_path = ~/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles
默认远端用户

默认使用连接受控机的用户,如果该用户没有root权限,可能会引发很多问题,可以通过设定合适的sudo权限避免。

# Default user to use for playbooks if user is not specified
# Uses the connection plugin's default, normally the user currently executing Ansible,
# unless a different user is specified here.
#
remote_user = root
默认模块

默认模块是command,可以改成shell,这样就不用使用-m shell了,直接用-a指定命令即可。

# Default module to use when running ad-hoc commands
module_name = command
默认shell

控制远端主机使用的shell,可以改成bash,但大多数情况默认即可,不用改。

# Use this shell for commands executed under sudo.
# you may need to change this to /bin/bash in rare instances
# if sudo is constrained.
#
executable = /bin/sh
日志输出位置
# Logging is off by default unless this path is defined.
log_path = /var/log/ansible.log
世间微尘里 独爱茶酒中