Ansible 自动化运维

2019-05-09  本文已影响0人  花泽菠菜xqc

Ansible 不完全手册

Ansible

认识 andsible

最早是 厄休拉*勒古恩 在 1966 年的小说 《罗卡农的星球》中创造了 Ansible 这个词,用于表示一种能在浩瀚宇宙中即时通信的装置。

Ansible 的创始人 Michael DeHaan 想用这个词来比喻能控制远端大量主机的服务器。

特点

  1. 安装部署简单
  2. 学习曲线很平坦
  3. 支持多台主机并行管理
  4. 无代理,无需在客户机上安装额外软件,使用的是 SSH 协议通信
  5. 非 root 账户也可运行
  6. 不仅仅支持 python ,也可运行使用任何动态语言开发的模块

ansible 是以款自动化管理工具,ansible 公司,同时在 ansible 的基础之上,开发了基于 Web 界面友好的 Ansible Tower IT 自动化管理工具。

管理方式和架构

管理方式

image

Ansible 管理系统由控制主机和一组被管理的节点组成。

控制主机通过 SSH 控制被控节点,被管节点的 IP 等信息,在控制主机的 Ansible 的资源清单(inventory)里进行分组管理

Ansible 通常使用 Ansible 的脚本文件来进行具体的管理配置。这个脚本文件称为 playbook(剧本), playbook 里存放着被作用的主机和这些主机需要执行的任务列表,任务列表是顺序执行的。

ansible
1.  ansible 只是一个运维管理工具而已,这个工具需要依赖操作系统的  ssh 
   
   所以想使用 这个工具 ,必须先让 ansible 这个工具的主机和 被控制(机器)节点 的 ssh 连通性。

   a. ansible 这台主机必须有 自己的 密钥对儿
     ssh-keygen

   b. 需要 ansible 这台主机和被控制节点的免密登录
     ssh-copy-id  被控制节点的用户@被控制节点的ip

ansible 的操作
2.  把被控制节点的相关信息(IP、 可被解析的主机名) 添加到 ansible 的资源清单中

    资源清单文件 默认是
     yum 安装的方式
    /etc/ansible/hosts


3.  让谁  用什么模块   干什么事/ 达到某种状态

    ansible    host1    -m  shell  -a "ls /tmp"





















## 安装

[Ansible 官方文档安装指南](https://links.jianshu.com/go?to=http%3A%2F%2Fdocs.ansible.com%2Fansible%2Flatest%2Fintro_installation.html)
yum   install ansible

控制主机应该有这些模块

paramiko / PyYAML / Jinja2 / httplib2

$ git clone https://github.com/ansible/ansible.git
$ cd ./ansible
$ make rpm
$ sudo rpm -Uvh ./rpm-build/ansible-*.noarch.rpm

可以能需要安装一些软件依赖包

报错信息:

AsciiDoc 'a2x' command is not installed but is required to build

解决办法:

yum install asciidoc

File not found by glob:

这种报错和可能的原因是目前系统使用的默认版本和 rpm 使用的 python 版本不一致,建议放弃,或者使其一致。 

任务执行模式

两种: ad-hoc 和 playbook

Ansible 内置模块都是等幂的。

同一个任务执行都此,得到的效果会是一样的。比如说,创建一个用户或这目录,被控主机假如没有则创建,假如已存在,ansible 则什么也不做。

==工作中最后自己编排适用于自己环境的 playbook,而不是尝试重用通用的 playbook。学习别人的 playbook,主要是看别人是如何实现的==

配置 Ansible 环境

Ansible 执行命令时,会按照预定配置的顺序查找以下配置文件

  1. ANSIBLE_CONFIG 先检查环境变量,以及这个环境变量指向的配置文件
  2. ./ansible.cfg 接着会检查执行命令的当前目录下的 ansible.cfg 配置文件
  3. ~/.ansible.cfg 之后会检查当前用户家目录下的 .ansible.cfg 这个隐藏文件
  4. /etc/ansible/ansible.cfg 最后才会检查用软件包管理工具安装 ansible 时自动产生的配置文件

假如你是通过 GitHub 安装的,ansible.cfg 配置文件会在 example 目录下,把它拷贝到 /etc/ansbile 目录下即可。

ansible.cfg 配置文件常用参数

# 配置资源清单文件的路径
inventory = /etc/ansible/hosts

# 存放 Ansible 模块的目录
library = /usr/share/ansible/

# 默认进程数
forks = 5

# 默认执行命令的用户,这个参数也可以在 playbook 中重新设置
sudo_user = root

# 被管主机的端口
remote_port = 22

# SSH 连接超时时间,单位是 秒
timeout = 60

# Ansible 自己的日志文件完整路径,记录 Ansible 输出的内容。Ansible 默认不记录日志
log_path = /var/log/ansible.log

==指定注意的是: 执行 ansible 的用户要有在被控主机上写入日志的权限,模块会调用被管主机的 syslog 来记录日志。==

使用公钥认证

假如第一次连接被控节点,控制主机默认会检查被控节点的公钥。想禁用的话设置如下配置项,两种方式任选一种:

  1. 在 ansible.cfg 配置文件中配置
# 在 defaults 域下配置
[defaults]
host_key_checking = False

  1. 直接在控制主机上配置 环境变量
export  ANSIBLE_HOST_KEY_CHECKING=False

配置 Linux 主机 SSH 无密码访问

# ssh-keygen -f ~/.ssh/id_rsa -N ""
# ssh-copy-id -i ~/.ssh/id_rsa.pub  root@172.16.153.129

172.16.153.129 是被控节点

蜻蜓点水

测试连通性和 ansible 可用性

  1. 先在 /etc/ansible/hosts 文件中配置 被控主机
# 单个主机
172.16.153.129

# 主机组
[openstack]
192.168.2.10
192.168.2.20
192.168.2.30
192.168.2.40

➜  ~ grep host_key_checking /etc/ansible/ansible.cfg
host_key_checking = False

➜  ~ ansible openstack -m ping
192.168.2.10 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.2.40 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.2.30 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.2.20 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

测完后,我把检查密钥的配置项进行了注释

➜  ~ grep host_key_checking /etc/ansible/ansible.cfg
#host_key_checking = False

这样就检查第一次连接的主机,但是我们再次连接刚才我们测试的主机就不会再次检查了。
因为刚才在建立连接的过程中,被控主机的公钥已经存放到我们控制主机的一个文件里了。

~/.ssh/known_hosts   # 这里存放了受信任的公钥

获取帮助

[ansible@ansible ~]$ ansible-doc -h
Usage: ansible-doc [options] [module...]

Options:
  -a, --all             Show documentation for all modules
  -h, --help            show this help message and exit
  -l, --list            List available modules
  -M MODULE_PATH, --module-path=MODULE_PATH
                        specify path(s) to module library (default=None)
  -s, --snippet         Show playbook snippet for specified module(s)
  -v, --verbose         verbose mode (-vvv for more, -vvvv to enable
                        connection debugging)
  --version             show program's version number and exit

[ansible@ansible ~]$ ansible-doc --version
ansible-doc 2.3.2.0
[ansible@ansible ~]$ ansible-doc -l |wc -l
1039             # 太过分了!!! 2.3.2 已经支持一千多个模块了。

 ansible-doc yum

 ansible-doc yum -s

命令行的调试

使用 -v 或者 -vvv 会输出更详细的信息

ansible  openstack  -m ping -vvv -u  ansible --sudo

Ansible 组件介绍

Inventory (资源清单)

是存放被控节点主机的信息的一个文件

文件格式: INI

默认是 /etc/ansible/hosts

在命令行里执行命令 ansbile 和命令 ansible-playbook 时,可以使用 -i 参数临时指定:

[ansible@ansible ~]$ ansible openstack -m ping -i ./inventory.file

当然也可以定义环境变量: ANSIBLE_HOSTS 声明

定义被控主机和主机组

# 单个主机ip 和这个主机的 ssh 密码
172.16.153.129  ansible_ssh_pass='ansible'

# 单个主机,指定 SSH 端口
badwolf.example.com:5309

# 也可以使用变量来指定,其中 jumper 是这个主机的别名,可以使用这个别名对其操作
jumper ansible_port=5555 ansible_host=192.0.2.50

# 主机名或者FQDN,主机名和 FQDN 必须要可以被控制主机解析
ansible

# 定义了一个 openstack 组,成员 ip 是 192.168.2.10,20,30 和 40
[openstack]
192.168.2.[10:40]

# 定义一个组,成员192.168.2.20,21,22
[computes]
192.168.2.[20:22]

# 定义一个组的变量
[openstack:vars]
ansible_ssh_pass='ansible'

# openstack组下面有个子组,子组名叫 computes,注意这个组必须在此文件中已被定义
[openstack:children]
computes

ansible_ssh_pass 是 Inventory 的内置参数

==虽然可以在资源清单中定义主机组或者主机的变量,但是不建议在这里定义,最佳实战是主机或者主机组要与它们的变量分开文件存放,这些内容安排在后面的变量章节中==

==Ansible 内部有两个默认组:all(包含所有主机) and ungrouped(不属于任何组的主机)==

测试

使用小写字母 o 参数可以输出更漂亮格式的信息

[ansible@ansible ~]$ ansible 192.168.2.21 -m ping -o
192.168.2.21 | SUCCESS => {"changed": false, "ping": "pong"}
[ansible@ansible ~]$ ansible openstack -m ping -o
192.168.2.21 | SUCCESS => {"changed": false, "ping": "pong"}
192.168.2.22 | SUCCESS => {"changed": false, "ping": "pong"}
192.168.2.20 | SUCCESS => {"changed": false, "ping": "pong"}
192.168.2.30 | SUCCESS => {"changed": false, "ping": "pong"}
192.168.2.10 | SUCCESS => {"changed": false, "ping": "pong"}
192.168.2.40 | SUCCESS => {"changed": false, "ping": "pong"}

使用多个 Inventory 文件

  1. 定义一个文件夹,里面存放不同的 inventory 文件

注意此目录下的文件的扩展名仅仅支持:空、.yml、.ymal、.json

[ansible@ansible ~]$ sudo mkdir /etc/ansible/Inventory
[ansible@ansible ~]$ cat /etc/ansible/Inventory/openstack
[openstack]
192.168.2.10
192.168.2.30
192.168.2.40

[computes]
192.168.2.20
192.168.2.21
192.168.2.22

[openstack:children]
computes

[ansible@ansible ~]$ cat /etc/ansible/Inventory/hosts.yml
172.16.153.129
ansible

  1. 再在 /etc/ansible/ansible.cfg 文件中的 [defaults] 配置块中 配置 inventory 的值指向 这个 文件夹
image
  1. 验证配置
    --list-hosts 列出主机或者主机组信息
[ansible@ansible ~]$ ansible computes --list-hosts
  hosts (3):
    192.168.2.20
    192.168.2.21
    192.168.2.22

==动态 Inventory==

在目前互联网环境下的实际生产环境中,inventory 更多的是动态获取的,比如从 CMDB 系统或者 Zabbix 监控系统中拉取所有的主机信息,之后使用 Ansible 进行管理。

动态 inventory 配置

  1. 在 ansible.cfg 文件中的 inventory 值定义为一个可执行脚本,这个脚本可以是任何编程言编写的。
[ansible@ansible ~]$ grep ^inventory /etc/ansible/ansible.cfg
inventory      = /etc/ansible/hosts.py

但是这个脚本必须支持以下参数:

  1. 编辑这个脚本
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import json
import argparse

def lists():
    """
    indent 定义输出时的格式缩进的空格数
    """
    dic = {}
    host_list = [ '192.168.2.{}'.format(str(i) ) for i in range(20,23) ]
    hosts_dict = {'hosts': host_list}
    dic['openstack'] = hosts_dict

    return json.dumps(dic,indent=4)

def hosts(name):
    dic = {'ansibl_ssh_pass': '12345'}

    return json.dumps(dic)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('-l', '--list', help='host list', action='store_true')
    parser.add_argument('-H', '--host', help='hosts vars')
    args = vars(parser.parse_args())

    if args['list']:
        print( lists() )
    elif args['host']:
        print( hosts(args['host']) )
    else:
        parser.print_help()

[ansible@ansible ~]$ sudo chmod 655 /etc/ansible/hosts.py

[ansible@ansible ~]$ ansible -i /etc/ansible/hosts.py openstack -m ping -o
192.168.2.30 | SUCCESS => {"changed": false, "ping": "pong"}
192.168.2.20 | SUCCESS => {"changed": false, "ping": "pong"}
192.168.2.10 | SUCCESS => {"changed": false, "ping": "pong"}
192.168.2.40 | SUCCESS => {"changed": false, "ping": "pong"}

由于前面我们设定好了 ansible.cfg 文件中的 inventory 的值为一个资源池(可动态获取主机信息的可执行文件),所以可以直接执行。

[ansible@ansible ~]$ ansible openstack -m ping -o
192.168.2.30 | SUCCESS => {"changed": false, "ping": "pong"}
192.168.2.20 | SUCCESS => {"changed": false, "ping": "pong"}
192.168.2.10 | SUCCESS => {"changed": false, "ping": "pong"}
192.168.2.40 | SUCCESS => {"changed": false, "ping": "pong"}

使用混合模式的 inventory

如果在 Ansible 中 -i 给出的位置是一个目录,或者像上一节 使用多个 Inventory 文件 中的情况,在 ansible.cfg 中这样配置了 inventory 的值是一个资源池。

这样使用的就是混合云了

~, .orig, .bak, .ini, .cfg, .retry, .pyc, .pyo

这个忽略的列表可以在 ansible.cfg 文件中配置,或者使用此项 ANSIBLE_INVENTORY_IGNORE 配置环境变量。

inventory_ignore_extensions

  1. 配置 /etc/ansible/ansible.cfg
[ansible@ansible ~]$ grep ^inventory /etc/ansible/ansible.cfg
inventory      = /etc/ansible/Inventory/

  1. 静态 inventory 文件
[ansible@ansible ~]$ cat /etc/ansible/Inventory/openstack
[openstack]
192.168.2.10
192.168.2.30
192.168.2.40

[openstack:children]
computes           
# 注意这里定义的组名,并没有在这个文件中定义此组的主机,我将会在动态 inventory 文件中定义。
# 关注验证结果,会是正常的,这样证明了使用了混合 inventory 是成功的。

  1. 动态 inventory 脚本文件
[ansible@ansible ~]$ ls -l /etc/ansible/Inventory/hosts.py
-rw-r-xr-x 1 root root 840 Nov 18 14:19 /etc/ansible/Inventory/hosts.py

[ansible@ansible ~]$ cat /etc/ansible/Inventory/hosts.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import json
import argparse

def lists():
    """
    indent 定义输出时的格式缩进的空格数
    """
    dic = {}
    host_list = [ '192.168.2.{}'.format(str(i) ) for i in range(20,23) ]
    hosts_dict = {'hosts': host_list}
    dic['computes'] = hosts_dict  # 静态文件中的组,在这里定义了主机信息

    return json.dumps(dic,indent=4)

def hosts(name):
    dic = {'ansibl_ssh_pass': '12345'}

    return json.dumps(dic)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('-l', '--list', help='host list', action='store_true')
    parser.add_argument('-H', '--host', help='hosts vars')
    args = vars(parser.parse_args())

    if args['list']:
        print( lists() )
    elif args['host']:
        print( hosts(args['host']) )
    else:
        parser.print_help()

[ansible@ansible ~]$ ansible  openstack -m ping -o
192.168.2.20 | SUCCESS => {"changed": false, "ping": "pong"} # 动态 inventory 脚本文件获取的主机
192.168.2.22 | SUCCESS => {"changed": false, "ping": "pong"} # 动态 inventory 脚本文件获取的主机
192.168.2.30 | SUCCESS => {"changed": false, "ping": "pong"}
192.168.2.10 | SUCCESS => {"changed": false, "ping": "pong"}
192.168.2.21 | SUCCESS => {"changed": false, "ping": "pong"} # 动态 inventory 脚本文件获取的主机
192.168.2.40 | SUCCESS => {"changed": false, "ping": "pong"}
[ansible@ansible ~]$ ansible  computes -m ping -o
192.168.2.21 | SUCCESS => {"changed": false, "ping": "pong"}
192.168.2.22 | SUCCESS => {"changed": false, "ping": "pong"}
192.168.2.20 | SUCCESS => {"changed": false, "ping": "pong"}

动态组和静态组

  1. 假如声明的子组是静态组,还必须在静态库存文件中定义这些子组,就是需要包含具体的 host,否则将返回一个错误。下面是一个错误的例子,我在静态 inventory 文件为组 opentack 定义了一个子组 networks ,但是我没有在这个静态 inventory 文件中定义这个子组 networks 及其成员。也没有在动态脚本中定义它

[ansible@ansible Inventory]$ cat openstack

[openstack]

192.168.2.10
192.168.2.30
192.168.2.40

[openstack:children]
networks
computes


* 所以 ansible 报错了。

[ansible@ansible Inventory]$ ansible openstack -m ping -o
ERROR! Attempted to read "/etc/ansible/Inventory/openstack" as YAML: 'AnsibleUnicode' object has no attribute 'keys'
Attempted to read "/etc/ansible/Inventory/openstack" as ini file: /etc/ansible/Inventory/openstack:7: Section [openstack:children] includes undefined group: networks


   ==经过验证,在静态 inventory 文件中定义这个子组为空,也不会报错==

[图片上传失败...(image-29f9c3-1543815968457)]

For example:

# 这里是静态 inventory 文件
[tag_Name_staging_foo] 

[tag_Name_staging_bar]

[staging:children]  
tag_Name_staging_foo
tag_Name_staging_bar

Ad-Hoc 命令

所谓 ad-hoc 命令是什么呢?

这其实是一个概念性的名字,是相对于写 Ansible playbook 来说的。两者之间的关系类似于在命令行敲入shell命令和 写shell scripts两者之间的关系

Ansible 命令都是并发执行的,默认的并发数是 ansible.cfg 中的 forks 值来控制的。

运行 ansible 命令行时可以用 -f 指定并发数。

基本格式:

ansible    <pattern_goes_here>   -m    <module_name>    -a    <arguments>

示例:

ansible  computes  -m   shell   -a  "ls  /tmp" -f 10

ansible有许多模块,默认是 command,也就是命令模块,我们可以通过 -m 选项来指定不同的模块.

在 Ad-Hoc 中使用默认的 command 模块执行命令时,-m 参数 可省略,但是不支持管道。想支持的话就是用 shell 模块。

使用异步模式

==2.3.2 版本没有返回 job_id==

File Transfer

ansible test  -m  copy -a  "src=/path/to/sourcefile    dest=/path/to/destinationfile"

==在 playbook 里也可以用 template==

[ansible@ansible ~]$ ansible test -m copy -a "src=./add_ip.sh dest=/home/elk/add_ip_3.sh owner=elk group=elk mode=444" -o --sudo

[ansible@ansible ~]$ sudo ls -l /home/elk/
total 4
-r--r--r-- 1 elk elk 354 Nov 20 08:29 add_ip_3.sh

[ansible@ansible ~]$ ansible test -m file -a  "dest=/home/elk/add_ip_3.sh  owner=ansible mode=644 group=ansible" -o  --sudo
172.16.153.129 | SUCCESS => {"changed": true, "gid": 501, "group": "ansible", "mode": "0644", "owner": "ansible", "path": "/home/elk/add_ip_3.sh", "size": 354, "state": "file", "uid": 501}
[ansible@ansible ~]$ sudo ls -l /home/elk/
total 4
-rw-r--r-- 1 ansible ansible 354 Nov 20 08:29 add_ip_3.sh

[ansible@ansible ~]$ ansible test -m file -a "dest=/home/elk/a/b/c/ state=directory" -o --sudo

state=file         代表拷贝后是文件;
state=link         代表最终是个软链接;
state=directory    代表文件夹;
state=hard         代表硬链接;
state=touch        代表生成一个空文件;
state=absent       代表删除

Managing Packages

$ ansible webservers -m yum  -a  "name=httpd  state=present" --sudo -o

$ ansible webservers -m yum -a "name=httpd-2.2.15 state=present"  --sudo -o

$ ansible webservers -m yum -a "name=acme state=absent" --sudo -o

Users and Groups

使用 user 模块可以方便的创建账户,删除账户,或是管理现有的账户:

# 创建用户
## 首先应该创建一个 MD5 加密的密文,ansible 的 user 模块的 password 不接收明文密码
$ [ansible@ansible ~]$ echo test |openssl passwd -1 -stdin
$1$QzUtKCgG$q8jXR8iaImWNjk5DKtnYf1       # 把输出的密文复制给下面的命令
$ ansible test -m user -a  'name=test password="$1$QzUtKCgG$q8jXR8iaImWNjk5DKtnYf1" ' --sudo -o
# 删除用户
$ ansible all -m user -a "name=foo state=absent"  --sudo  -o

Managing Services

# 启动服务
$ ansible webservers -m service -a "name=httpd state=started"  --sudo  -o

# 重启服务
$ ansible webservers -m service -a "name=httpd state=restarted"  --sudo  -o

# 停止服务
$ ansible webservers -m service -a "name=httpd state=stopped"   --sudo  -o

获取主机的信息

# 获取全部信息
$ ansible test -m setup

# 获取指定信息,下面是获取 绑定网卡的地址
$ ansible test -m setup |grep bond

[ansible@ansible ~]$ ansible test -m yum -a "name=ruby-json,facter  state=installed" --sudo  -o
[ansible@ansible ~]$ ansible test -m facter

import json
import subprocess
res = subprocess.getoutput("ansible 172.16.153.130 -m setup")
if 'SUCCESS' in res:
    res_dic_str = s.split('SUCCESS =>')[1]
    res_dic_obj = json.loads(res_dic_str)

for k,v in res_dic_obj['ansible_facts'].items():
    print(k,'==',v)    


Playbook 详解

Ansible 的 playbook 文件格式使用的是 YAML 语法。

YAML 语法参考 -->> 官网

简单来说,playbooks 是一种简单的配置管理系统与多机器部署系统的基础.与现有的其他系统有不同之处,且非常适合于复杂应用的部署.

Playbooks 可用于声明配置,更强大的地方在于,在 playbooks 中可以编排有序的执行过程,甚至于做到在多组机器间,来回有序的执行特别指定的步骤.并且可以同步或异步的发起任务

基本的 YAML 语法

对于 Ansible, 每一个 YAML 文件都是从一个列表开始. 列表中的每一项都是一个键值对, 通常它们被称为一个 “哈希” 或 “字典”. 所以, 我们需要知道如何在 YAML 中编写列表和字典.

所有的 YAML 文件(无论和 Ansible 有没有关系)首行都应该是 ---. 这是 YAML 格式的一部分, 表明一个 YAML 文件的开始.

---
# 一个美味水果的列表
- Apple
- Orange
- Strawberry
- Mango

---
# 一位职工的记录
name: Example Developer
job: Developer
skill: Elite

---
create_key: yes
needs_agent: no
knows_oop: True
likes_emacs: TRUE
uses_cvs: false

---
# 一位职工记录
name: Example Developer
job: Developer
skill: Elite
employed: True
foods:
    - Apple
    - Orange
languages:
    ruby: Elite
    python: Elite
# 相当于 {'name': "Example Developer",'job': 'Developer', 'skill': 'Elite', 'employed': True, 'foods': ['Apple', 'Orange'], 'languages': {'ruby': 'Elite', 'python': 'Elite'}}    

需要额外注意的地方

foo: ""somebody said I should put a colon here: so I did""

foo: "{{ variable }}"

编写 Ansible playbooks 掌握以上 YAML 基本的语法就可以了,下面就来讨论一下 playbook 的语法

Playbook 基本语法

Playbook 其实并不是那么神秘,要理解 playbook 很简单。

想理解 playbook ,首先需要懂得 Ansible 的命令行模式,即 Ad-Hoc 命令。

对于 ansible 来说记住 3 句话,对应了 ansible 实现功能的基本组成部分

  1. 针对的目标是谁?
  2. 让目标做什么,其中包含了具体怎么做?
  3. 假如在做的过程中,有变化了,再做什么?

下面看一个 Ad-Hoc 命令的示例:

ansible webservers -m copy  -a  "src=httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf"

# webservers 就是目标

# -m  copy  就是做什么

# -a  "src=httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf"  就是具体怎么做

对于第 3 项,在命令行里,是用再此执行另外一条命令来实现的

ansible webservers -m service -a "name=httpd state=restarted"

想象一下这种情况,当你想对目标主机安装一个服务,并且配置一下这个服务的配置文件,之后启动这个服务时。你会发现,你需要在命令行里执行一系列的 Ad-Hoc 命令。

于是你会想要有一种方法把这些整合在其一,一起执行,并且在执行的过程中加入一些逻辑判断,就像是写 shell 脚本一样。这种方法就是 playbook。

在 ansible 中,playbook 的内容,一般有三部分组成:

* hosts
* tasks
* handlers

hosts 就是目标

tasks(任务列表)  就是让目标做什么以及怎么做;一个 task 也称为一个 play,本质是对 ansible 一个模块的调用。

handlers   就是当执行过程中,目标的某些方面发生了改变,就会被触发的动作,这些动作也是一个一个的 task

下面通过一个简单的 playbook 来具体说明一下基本的语法

---     # 表示这个文件是 YMAL 文件
- hosts: webservers   # 指定了 webservers 这个主机组为 这个 playbook 的目标主机,也可以用 all 表示所有主机,其实支持 Ad-Hoc 模式的所有参数
  vars:               # 表示下面是目标主机的变量
    http_port: 80
    max_clients: 200
  remote_user: root   # 远程被控主机的用户名,用这个用户去执行下面所用的 tasks
  tasks:              # 表示下面是这个 playbook 的一个或者多个任务的集合
  - name: ensure apache is at the latest version       # 给每个具体任务起的名字
    yum: pkg=httpd state=latest                        # 第一个具体的任务,是安装最新版的 httpd 软件包,调用了 Ansible 的 yum 模块
  - name: write the apache config file
    template: src=/srv/httpd.j2 dest=/etc/httpd.conf   # 第二个具体的任务拷贝配置文件
    notify:           # 触发的意思,假如 httpd.conf 配置文件的 MD5 值有变化,就会触发下面定义的 handlers
    - restart apache  # 这个是触发的具体事件名字
  - name: ensure apache is running
    service: name=httpd state=started   # 第三个具体的任务,启动 httpd 服务
  handlers:
    - name: restart apache      # 这个 name 的值需要和上面 notify 的值一致
      service: name=httpd state=restarted   # 定义具体 handlers 的状态,重启 httpd 服务

Playbook 基本结构分解

一个基本的 playbook 的机构包括:

Hosts

---
- hosts: 172.16.153.129,ansible   # 这些主机是必须在资源配置文件(Inventory)中定义好了

---
- hosts: 172.16.153.129,ansible
  remote_user: root

---
- hosts: 172.16.153.129,ansible
  remote_user: root
  tasks:
  - name: ping cmd
    ping:
    remote_user: ansible   # ansible 是远程被控主机的用户名

---
- hosts: webservers
  remote_user: yourname
  sudo: yes
  tasks:
    - service: name=nginx state=started
      remote_user: yourname
      sudo: yes

---
- hosts: webservers
  remote_user: yourname
  sudo: yes
  sudo_user: postgres

Tasks 列表

每个 playbook 都会有一个 tasks 列表,列表中的每个 task 会作用于所有的 hosts 值中声明的主机。

每个 task 的目标是执行一个 ansible 的模块。

tasks 列表中的 task 执行是按照从上向下顺序执行的。

假如在 hosts 中的某一个 host 执行某一个 task 失败,此 host 将会从整个 playbook 的 rotation(循环) 中移除. 如果发生执行失败的情况,请修正 playbook 中的错误,然后重新执行即可.

每一个 task 必须有一个名称 name,这样在运行 playbook 时,从其输出的任务执行信息中可以很好的辨别出是属于哪一个 task 的。

声明一个 task 使用格式:”module: options”

基本的 task 的定义,service moudle 使用 key=value 格式的来表示 moudle 的参数,这也是大多数 moudle 使用的参数格式:

tasks:
  - name: make sure apache is running
    service: name=httpd state=running

command 和 shell

tasks:
  - name: disable selinux
    command: /sbin/setenforce 0
  - name: test shell moudle
    shell: /bin/ls /  

在执行命令时,可以忽略执行命令中的错误

tasks:
  - name: run this command and ignore the result
    shell: /usr/bin/somecommand
    ignore_errors: True

tasks:
  - name: Copy ansible inventory file to client
    copy: src=/etc/ansible/hosts dest=/etc/ansible/hosts
            owner=root group=root mode=0644

tasks:
  - name: create a virtual host file for {{ vhost }}
    template: src=somefile.j2 dest=/etc/httpd/conf.d/{{ vhost }}

变量需要提前在 vars 里定义,后面会讲到如何定义变量

notify 在每一个任务执行结束时会被触发,并且即使多个 task 指定了同一个 notify action ,触发条件达到时, notify action 只会被执行一次。

下面示例中是当文件有改变时,重启 2 个服务

- name: template configuration file
  template: src=template.j2 dest=/etc/foo.conf
  notify:
     - restart memcached
     - restart apache

==ntify 列表中每个值,需要和 Handlers 中的 name 值相同。==

Handlers: 在发生改变时执行的操作

Handlers 也是一些 task 的列表,通过名字来引用,如果没有被 notify,handlers 不会执行.不管有多少个通知者进行了 notify,等到 play 中的所有 task 执行完成之后,handlers 也只会被执行一次.

handlers:
    - name: restart memcached
      service:  name=memcached state=restarted
    - name: restart apache
      service: name=apache state=restarted

==Handlers 最佳的应用场景是用来重启服务,或者触发系统重启操作.除此以外很少用到.==
==handlers 会按照声明的顺序执行==

默认 handlers 会在 ‘pre_tasks’, ‘roles’, ‘tasks’, 和 ‘post_tasks’ 之间自动执行.

tasks:
   - shell: some tasks go here
   - meta: flush_handlers
   - shell: some other tasks

执行 playbook

ansible-playbook playbook.yml -f 10

ansible-playbook  -i  /etc/ansible/Inventory/hosts.yml  playbook.yml -f 10

ansible-playbook -i /etc/ansible/Inventory/hosts.yml test.yaml --list-host

如果你想看到执行成功的 modules 的输出信息,使用 --verbose 或者 -v(否则只有执行失败的才会有输出信息)

ansible-playbook -i /etc/ansible/Inventory/hosts.yml test.yaml -v

==可以使用 -vvv 或者 -vvvv 查看更多的输出信息==

Playbook 角色(Roles) 和 Include 语句

当我们刚开始学习运用 playbook 时,可能会把 playbook 写成一个很大的文件,到后来可能你会希望这些文件是可以方便去重用的,就像你在写脚本时,会把 变量写在一个文件中,静态文件放在一个目录中,函数写在一个文件中,所以想管理好 playbook 就需要重新去组织这些文件。

使用 include 语句引用 tasks 是将 tasks 从其他文件拉取过来。

因为 handlers 也是 tasks,所以你也可以使用 include 语句去引用 handlers 文件。

Playbook 同样可以使用 include 引用其他 playbook 文件中的 play。这时被引用的 play 会被插入到当前的 playbook 中,当前的 playbook 中就有了一个更长的的 play 列表。

Roles 的概念来自于这样的想法:通过 include 包含文件并将它们组合在一起,组织成一个简洁、可重用的抽象对象。

可以认为主要是用于管理多 playbook,本质是对日常使用的 playbook 的目录结构进行一些规范。

每次你写 playbook 的时候都应该使用 Roles。

下面先介绍 Include 语句

Inclued

---
# possibly saved as tasks/foo.yml

- name: placeholder foo
  command: /bin/foo

- name: placeholder bar
  command: /bin/bar

tasks:
  - include: tasks/foo.yml

tasks:
  - include: wordpress.yml wp_user=timmy
  - include: wordpress.yml wp_user=alice
  - include: wordpress.yml wp_user=bob

假如有这样的设定:

---
# file: group_vars/all
asdf: 10

---
# file: group_vars/os_CentOS
asdf: 42

可以这样引用

- hosts: all
  tasks:
    - include_vars: "os_{{ ansible_distribution }}.yml"
    - debug: var=asdf

适用于 1.4 及之后的版本

tasks:
 - { include: wordpress.yml, wp_user: timmy, ssh_keys: [ 'keys/one.txt', 'keys/two.txt' ] }

假如你定义了一个重启 httpd 服务的 handlers,并且想要重用他,可以这么做

  1. 先创建这个 handlers 文件,名字叫 handers.yml
---
# this might be in a file like handlers/handlers.yml
- name: restart apache
  service: name=apache state=restarted

  1. 引用它
    然后在你的主 playbook 文件中,在一个 play 的最后使用 include 包含 handlers.yml
handlers:
  - include: handlers/handlers.yml

==Include 语句可以和其他非 include 的 tasks 和 handlers 混合使用。但是这种情况下,include 的优先级最高==

- name: this is a play at the top level of a file
  hosts: all
  remote_user: root

  tasks:

  - name: say hi
    tags: foo
    shell: echo "hi..."

- include: load_balancers.yml
- include: webservers.yml
- include: dbservers.yml

接下来,我们就来谈一谈如何很好利用 include 语句,并且更好的来组织 playbook

Role

.
├── fooserver.yml
├── roles
│   ├── common           # 一个 role
│   │   ├── defaults     # 放置一个 role 的默认变量文件
│   │   ├── files        # 一般放置静态文件
│   │   ├── handlers     # 发生改变时做的 handlers 文件
│   │   ├── meta         # 放置 role 的依赖文件
│   │   ├── tasks        # 放置 task 文件
│   │   ├── templates    # 放模板文件
│   │   └── vars         # 放置变量文件
│   └── webservers       # 另一个 role
│       ├── defaults
│       ├── files
│       ├── handlers
│       ├── meta
│       ├── tasks
│       ├── templates
│       └── vars
├── site.yml              # Roles 的入口文件,也是一个 playbook
└── webserver.yml

---
- hosts: webservers
  roles:
     - common
     - webservers

假如这个 playbook 为 'http' 这个角色定义的,那么有如下意义:

如果 roles 目录下有文件不存在,这些文件将被忽略。比如 roles 目录下面缺少了 ‘vars/’ 目录,这也没关系。

---
# This is  site.yml file 
- hosts: webservers
  roles:
    - { role: some_role, when: "ansible_os_family == 'RedHat'" }

它的工作方式是:将条件子句应用到 role 中的每一个 task 上。

---

- hosts: webservers

  pre_tasks:
    - shell: echo 'hello'

  roles:
    - { role: some_role }

  tasks:
    - shell: echo 'still busy'

  post_tasks:
    - shell: echo 'goodbye'

要创建默认变量,只需在 roles 目录下添加 defaults/main.yml 文件。这些变量在所有可用变量中拥有最低优先级,可能被其他地方定义的变量(包括 inventory 中的变量)所覆盖。

角色依赖(Role Dependencies)

New in version 1.3.

“角色依赖” 使你可以自动地将其他 roles 拉取到现在使用的 role 中。”角色依赖” 保存在 roles 目录下的 meta/main.yml 文件中。这个文件应包含一列 roles 和 为之指定的参数,下面是在 roles/myapp/meta/main.yml 文件中的示例:

---
dependencies:
  - { role: common, some_parameter: 3 }
  - { role: apache, port: 80 }
  - { role: postgres, dbname: blarg, other_parameter: 12 }

更多参考 https://ansible-tran.readthedocs.io/en/latest/docs/playbooks_roles.html

Galzxy

Ansible 的 Galaxy 是 Ansible 官方一个分析 role 的功能平台。

网址

https://galaxy.ansible.com/list#/roles

安装(下载)一个 role

[图片上传失败...(image-45cf85-1543815968457)]

默认安装在以下目录下

/etc/ansible/roles/

使用方式和自己写的 role 一样

变量与引用

==In Ansible 1.2 or later the group_vars/ and host_vars/ directories can exist in the playbook directory OR the inventory directory. If both paths exist, variables in the playbook directory will override variables set in the inventory directory.==
假如变量同时存在于 playbook 目录和 inventory 目录,playbook 目录的变量优先

合法的变量名

在使用变量之前最好先知道什么是合法的变量名. 变量名可以为字母,数字以及下划线.变量始终应该以字母开头. “foo_port”是个合法的变量名.”foo5”也是. “foo-port”, “foo port”, “foo.port” 和 “12”则不是合法的变量名.

内置变量

首先需要了解的是默认变量,Ansible 定义了一些在 playbook 中永远可以访问的变量。

hostvars            --->  是一个字典, key 是 ansible 主机的名字,value 是这台主机的所有变量名和相应的变量值
inventory_hostname  --->  当前主机被 Ansible 识别的名字
group_names         --->  列表, 列表中存放了当前主机所属的所有主机组名
groups              --->  字典, key 是 ansible 的主机组名,value 是这个主机组所包含的所有主机,主机组包含了 all 和 ungrouped 分组。{"all":[...],"webservers":[...],"ungrouped":[...]}
play_hosts          --->  列表, 元素是当前 play 涉及到的目标主机的 inventory 主机名
ansible_version     --->  字典, Ansible 的版本信息

Ansible 变量优先级

==不要把事情搞复杂==

但是还是有必要告诉你变量的优先级,以满足你的好奇心。

基本原则有高到低是:

在哪里定义变量

Ansbile 中定义变量非常灵活

172.16.153.129  key=hosts_value  username=ansible

[openstack]
192.168.2.[10:30]

[openstack:vars]
ansbible_python_interpreter=/usr/bin/python3.6
key=openstack

可以编辑一个 playbook 来验证一下

---
- hosts: all
  gather_facts: False
  tasks:
  - name: display Host Variable from hostfile
    debug: msg="The {{ inventory_hostname }} Vaule is [ {{ key }} ], username is [{{ username }} ]"

---
- hosts: all
  gather_facts: False
  vars:
      key: "openstack_value"
      username: "ansible"
  tasks:
  - name: display Host Variable from hostfile
    debug: msg="The {{ inventory_hostname }} Vaule is [ {{ key }} ], username is [{{ username }} ]"

- hosts: app_servers
  vars:
       app_path: "{{ base_path }}/22"

可在一个文件中存放变量,之后在 playbook 中使用 vars_files 来引用它。
这个文件可以是 YAML 格式或者是 JSON 格式

➜  ~ head var.{yml,json}
==> var.yml <==
key: "Ansible value"
username: ansible

==> var.json <==
{"key": "josn value","username": "ansible"}

引用

➜  ~ head variale.yml
---
- hosts: all
  gather_facts: False
  vars_files:
  - var.json
  #- var.yml
  tasks:
  - name: display Host Variable from hostfile
    debug: msg="The {{ inventory_hostname }} Vaule is [ {{ key }} ], username is [{{ username }} ]"

通过创建 host_vars 目录和 group_vars 目录来分别对应主机和主机组进行变量的定义。
host_vars 目录下,存放的是以 Asnsible 的每个 inventory 主机名为文件名的 YAML 格式的文件。
group_vars 目录下,存放的是以 Asnsible 的每个主机组名为文件名的 YAML 格式的文件。

这两个目录存放的位置:

1\. Asnsible 的 ansible.cfg 配置文件中 inventory 项定义的目录下
2\. 或者可以建立在 playbook 文件的同级目录下

默认的情况下,/etc/ansible/ 目录下的结构

➜  ~ tree /etc/ansible
/etc/ansible
├── ansible.cfg
├── group_vars
│   └── http
├── hosts
├── host_vars
│   └── 172.16.153.129   # 在这个文件中定义针对 172.16.153.129  这台主机的变量
├── Inventory
    ├── hosts.py
    ├── hosts.yml
    └── openstack

➜  ~ ansible-playbook variale.yml -e "key=KEY username=Ansible"

在命令行里也可以引用文件,文件格式同样支持 YAML 和 JSON

➜  ~ ansible-playbook variale.yml -e "@/root/var.yml"
➜  ~ ansible-playbook variale.yml -e "@/root/var.json"

➜  ~ cat register.yml
---
- hosts: all
 gather_facts: no
 tasks:
 - name: register a variable
   shell: hostname
   register: info

 - name: display variable
   debug: msg="The variable is {{ info }}"

输出的部分信息

TASK [display variable] ********************************************************
ok: [172.16.153.129] => {
    "msg": "The variable is {'stderr_lines': [], u'changed': True, u'end': u'2017-12-03 11:36:07.667813', u'stdout': u'ansible', u'cmd': u'hostname', u'rc': 0, u'start': u'2017-12-03 11:36:07.598862', u'stderr': u'', u'delta': u'0:00:00.068951', 'stdout_lines': [u'ansible']}"
}
ok: [192.168.2.20] => {
    "msg": "The variable is {'stderr_lines': [], u'changed': True, u'end': u'2017-12-03 11:36:07.595477', u'stdout': u'ansible', u'cmd': u'hostname', u'rc': 0, u'start': u'2017-12-03 11:36:07.483411', u'stderr': u'', u'delta': u'0:00:00.112066', 'stdout_lines': [u'ansible']}"
}

可以看出输出的 info 变量的值是一个字典,我们想要的信息在 stdout 这个键的值,可以使用 python 标准的方式访问 info["stdout"],也可以使用点儿的方式 info.stdout

➜  ~ cat register.yml
***略***
  - name: display variable
    debug: msg="The variable is {{ info.stdout }}"
    #debug: msg="The variable is {{ info['stdout'] }}"

在 playbook 中使用 vars_prompt,可以实现让用户输入变量的值,并且可以定义某个变量为私有的,当定义一个变量为私有时,用户输入此变量的值的时候,不会显示在屏幕上

定义

➜  ~ cat vars_prompt.yml
---
- hosts: all
  gather_facts: no
  vars_prompt:
  - name: "username"                 # 变量的 key
    prompt: "Please input username"  # 提示信息
    private: no                      # 公有属性
  - name: "passwd"
    prompt: "Please input password"
    default: 'good'                  # 变量的默认值
    private: yes                     # 私有属性
  tasks:
  - name: display one value
    debug: msg="one value is {{ username  }}"
  - name: display two value
    debug: msg="two value is {{ passwd  }}"

测试使用

➜  ~ ansible-playbook  var_prompt.yml -l 172.16.153.129
Please input username: ansible
Please input password [good]:

PLAY [all] *********************************************************************

TASK [display one value] *******************************************************
ok: [172.16.153.129] => {
    "msg": "one value is ansible"
}

TASK [display two value] *******************************************************
ok: [172.16.153.129] => {
    "msg": "two value is upsa"
}

PLAY RECAP *********************************************************************
172.16.153.129             : ok=2    changed=0    unreachable=0    failed=0

条件

when 语句

有的 task 的执行,是需要某一个变量的值。
比如,假如系统是 RedHat 就使用 yum 模块安装软件包;假如系统是 Ubuntu 就使用 apt 模块安装软件

➜  ~ cat when.yml
---
- hosts: 172.16.153.129
  tasks:
  - name: "Install a packge"
    yum: name=httpd state=present
    when: ansible_os_family == "RedHat"
  - name: display os family
    debug: var=ansible_os_family

一系列的Jinja2 “过滤器” 也可以在when语句中使用, 但有些是Ansible中独有的. 比如我们想忽略某一错误,通过执行成功与否来做决定,我们可以像这样:

==待进一步核实验证==

# 注意这个示例中并没有使用 name,而是直接使用模块 
➜  ~ cat when_filter.yml
---
- hosts: 172.16.153.129
  gather_facts: no           # 不获取 facts 信息,默认是获取的
  tasks:
  - command: /bin/ls /a
    register: result
    ignore_errors: True      # 假如此 task 失败,继续执行下面的 task
  - shell: /bin/ls /root
    when: result|failed      # 假如 result 是失败的,执行此 task
  - file: dest=/a/b/ state=directory
    when: result|success         # 假如 result 是成功的,执行此 task
  - file: dest=/root/yan.txt state=file
    when: result|skipped        # 假如 result 是被忽略的,执行此 task
  - debug: msg="Then result is {{ result }}"

---
- hosts: openstack
  tasks:
  - name: Host 172.16.153.129 run this task
    debug: msg="{{ ansible_default_ipv4.address }}"
    when: ansible_default_ipv4.address == "192.168.2.10"

  - name: memtotal < 128 M and processor_cores == 2 run this task
    debug: msg="{{ ansible_fqdn }}"
    when: ansible_memtotal_mb < 128 and asible_processor_cores == 2

  - name: all host run this task
    shell: hostname
    register: info

  - name: Hostname is  ansible Machie run this task
    debug: msg="{{ ansible_fqdn }}"
    when: info['stdout'] == "ansible"

  - name: Hostname is startswith M run this task
    debug: msg="{{ ansible_fqdn }}"
    when: info.stdout.startswith('M')

执行结果中的 skipping 代表此台主机没有执行此 task

# 定义变量
vars:
    epic: true   

# 条件判断
tasks:
    - shell: echo "This certainly is epic!"
      when: epic
      # 或者下面这样
      when: not epic

tasks:
    - when: foo is defined
    - when: bar is not defined

tasks:
    - command: echo {{ item }}
      with_items: [ 0, 2, 4, 6, 8, 10 ]
      when: item > 5

---
- hosts: all
  remote_user: root
  vars_files:
    - "vars/common.yml"
    - [ "vars/{{ ansible_os_family }}.yml", "vars/os_defaults.yml" ]
  tasks:
  - name: make sure apache is running
    service: name={{ apache }} state=running

这个具体事怎么工作的呢?
如果操作系统是’CentOS’, Ansible导入的第一个文件将是’vars/CentOS.yml’,紧接着 是’/var/os_defaults.yml’,如果这个文件不存在.而且在列表中没有找到,就会报错.
如果操作系统是 Debian,最先查看的将是’vars/Debian.yml’而不是’vars/CentOS.yml’, 如果没找到,则寻找默认文件’vars/os_defaults.yml’ 很简单.

如果使用这个条件性导入特性,你需要在运行playbook之前安装facter 或者 ohai.当然如果你喜欢, 你也可以把这个事情推给Ansible来做:

# for facter
ansible -m yum -a "pkg=facter state=present"
ansible -m yum -a "pkg=ruby-json state=present"

# for ohai
ansible -m yum -a "pkg=ohai state=present"

下面的例子展示怎样根据不同的系统,例如CentOS,Debian制作一个配置文件的模版:

- name: template a file
   template: src={{ item }} dest=/etc/myapp/foo.conf
   with_first_found:
     - files:
        - {{ ansible_distribution }}.conf
        - default.conf
       paths:
        - search_location_one/somedir/
        - /opt/other_location/somedir/

循环

标准 loops

---
- hosts: all
  gather_facts: no
  tasks:
  - name: Install  package
    yum: pkg={item} state=latest
    with_items:
    - httpd
    - vim

---
- hosts: all
  gather_facts: no
  remote_users: root
  tasks:
  - name: add several users
    user: name={{ item }} state=present groups=wheel
    with_items:
    - testuser1
    - testuser2

with_items 是固定的变量名,是一个列表的名字,Ansible 默认会对这个列表循环,循环的每个元素变量名是 item,可以对 item 的使用来引用列表里的每个元素。

with_items: "{{somelist}}"

- name: add several users
  user: name={{ item.name }} state=present groups={{ item.groups }}
  with_items:
    - { name: 'testuser1', groups: 'wheel' }
    - { name: 'testuser2', groups: 'root' }

==如果同时使用 when 和 with_items (或其它循环声明),when 会作用于每个循环的元素==

嵌套循环

主要实现的是 一个对多或者多对多的合并

在这里我想用一个更简单方式演示, debug 模块

---
- hosts: test
  gather_facts: no
  tasks:
  - name: debug log
    debug: msg="name ---> {{ item[0] }} vaule ---> {{ item[1] }} vv --> {{item[2]}}"
    with_nested:
        - ['A','B']
        - ['a1','a2','a3']
        - ['b1','b2','b3']

上面的嵌套循环等同于下面的普通 for 嵌套循环

for item0 in ['A','B']:
    for item1 in ['a1','a2','a3']:
        for item2 in ['b1','b2','b3']:
            print("name ---> {} vaule ---> {} vv --> {}".format(item0,item1,item2))

同样可以使用预定义好的变量。看下面这个使用的示例:

- name: here, 'users' contains the above list of employees
  mysql_user: name={{ item[0] }} priv={{ item[1] }}.*:ALL append_privs=yes password=foo
  with_nested:
    - "{{users}}"
    - [ 'clientdb', 'employeedb', 'providerdb' ]

使用 with_dict 进行哈希 loops

with_dict 可以对 python 字典格式(需要 yml.load 之后)的变量进行循环

哈希循环也叫散列循环,标准的循环要求,最外层必须是 python 的 list 数据类型

哈希循环支持更丰富的数据结构

比如有这样的变量

---
users:
  alice:
    name: Alice Appleworth
    shell: bash
    telephone: 123-456-7890
  bob:
    name: Bob Bananarama
    shell: zsh
    telephone: 987-654-3210

可以这样循环使用它

tasks:
  - name: Print phone records
    debug: msg="User {{ item.key }} is {{ item.value.name }} ({{ item.value.telephone }}), shell is --> {{ item.value.shell }}"
    with_dict: "{{users}}"

使用 with_fileglob 对文件列表使用循环

with_fileglob 可以以非递归的方式对某个目录下的特定文件进行循环,所有它是支持模糊匹配的.如下面所示:

---
- hosts: all

  tasks:

    # first ensure our target directory exists
    - file: dest=/etc/fooapp state=directory

    # copy each file over that matches the given pattern
    - copy: src={{ item }} dest=/etc/fooapp/ owner=root mode=600
      with_fileglob:
        # 这里匹配的是目录下的所有文件,也可以 *.yml 来匹配 yml 结尾的文件
        - /playbooks/files/fooapp/* 

扁平化列表

---
- hosts: ansible
  gather_facts: no
  vars:
      packages_base:
          - [ 'foo-package', 'bar-package' ]
      packages_apps:
          - [ ['one-package', 'two-package' ]]
          - [ ['red-package'], ['blue-package']]
  tasks:
      - name: flattened loop demo
        debug: msg="{{ item }}"
        with_flattened:
            - "{{packages_base}}"
            - "{{packages_apps}}"

使用 with_random_choice 进行随机循环

---
- hosts: test
  gather_facts: no
  tasks:
  - debug: msg="name ---> {{ item }}"
    with_random_choice:
        - "ansible1"
        - "ansible2"
        - "ansible3"
        - "ansible4"

使用 until 条件判断 loops

通过判断一个条件,第某个 task 执行多少次

---
- hosts: all
  gather_facts: no
  tasks:
  - name: debug loops
    shell: cat  /root/Ansbile
    register: host
    untill: host.stdout.startswith("Master")
    retries: 5  # 总共执行 5 次
    delay: 5    # 每次执行的间隔是 5 秒

使用 with_fist_found 进行文件优先匹配 loops

不经常用,这个用法以及更多的 loops 请参考官方文档: https://ansible-tran.readthedocs.io/en/latest/docs/playbooks_loops.html

Playbook lookups

playbook 的 lookups 是让 Ansible 可以从外部拉取数据信息赋值给 Anssible 变量的一种方式。
这个实现的办法就是 Ansible 的 lookups 插件。

目前 Ansible 已经自带的很多的 lookups 插件,接下来只介绍一些常用的。

* file      --> 可以从一个文件中获取数据
* password  --> 可以把传入的内容进行加密处理
* pipe      --> 就是利用了 Python 的 subprocess.Popen 执行命令,之后把执行命令的结果赋值给变量
* redis_kv  --> 从 Redis 数据库中获取数据
* template  --> 和 file 类似,都是读取文件,但是 template 支持读取 jinja 模板文件,并且 loops.j2 文件是每台主机自己的信息,不是控制主机的信息。

示例:

---
- hosts: ansible
  gather_facts: no
  vars:
      contents: "{{ lookup('file', '/etc/sysconfig/ne---
- hosts: ansible
  gather_facts: no
  vars:
      contents: "{{ lookup('file', '/etc/sysconfig/entwork') }}"
  tasks:
      - name: debug loops
        debug: msg="The contents is  {% for i in contents.split('\n') %} {{ i }} {% endfor %}" twork') }}"
  tasks:
      - name: debug loops
        debug: msg="The contents is  {% for i in contents.split('\n') %} {{ i }} {% endfor %}"    

---
- hosts: ansible
  gather_facts: no
  vars:
      contents: "{{ lookup('password', 'aa') }}"
  tasks:
      - name: debug loops
        debug: msg="The contents is  {{ contents }}"

---
- hosts: ansible
  gather_facts: no
  vars:
      contents: "{{ lookup('pipe', 'df -P ') }}"
  tasks:
      - name: debug loops
        debug: msg="The contents is  {% for i in contents.split() %} {{ i }} {% endfor %}"

vars:
    contents: "{{ lookup('redis_kv', 'redis: //localhost:6379, somekey') }}"
tasks:
      - name: debug loops
        debug: msg="The contents is  {% for i in contents.split('\n') %} {{ i }} {% endfor %}"    

模板文件内容

➜  ~ cat lookups.j2
worker_processes {{ ansible_processor_cores }}
IPaddress {{ ansible_ent0.ip4.address }}

使用

---
- hosts: ansible
  gather_facts: yes
  vars:
      contents: "{{ lookup('template', './lookups.j2') }}"
  tasks:
      - name: debug loops
        debug: msg="The contents is  {% for i in contents.split() %} {{ i }} {% endfor %}"  

Jinja2 filter

Ansible 默认支持 Jinja2 语言的内置 filter,下面介绍一些常用的。

---
- hosts: all
  gather_facts: no
  vars:
      list: [1,2,3,4,5]
      one: "1"
      str: "string"
  tasks:
      - name: run commands
        shell: df -h
        register: info

      - name: debug pprint filter
        debug: msg="{{ info.stdout | pprint }}"

      - name: debug conditionals filter
        debug: msg="The run commands status is changed"
        when: info | changed

      - name: debug int capitalize filter
        debug: msg="The int value {{ one | int }} The lower value is {{ str | capitalize }}"

      - name: debug default filter
        debug: msg="The Variable value is {{ ansible | default('ansible is not define') }}"
        # default 是假如 ansible 变量没有定义,则采用 default 内的的值作为 ansible 变量的值

      - name: debug  list max and min filter
        debug: msg="The list max value is {{ list | max }} The list min value is {{ list | min }}"

      - name: debug join filter
        debug: msg="The join filter value is {{ list | join("+") }}"

      - name: debug replace and regex_replace filter
        debug: msg="The replace value is {{ str | replace('t','T') }} The regex_replace value is {{ str | regex_replace('.*str(.*)$','\\1') }}"
上一篇 下一篇

猜你喜欢

热点阅读