一、playbook基本语法

---
- hosts: all
  tasks:
      - name: Install nginx package
        yum: name=nginx state=present
      - name: copy nginx.conf
        template: src=./nginx.conf.j2 dest=/etc/nginx/nginx.conf owner=root group=root mode=0644 validate='nginx -t -c %s'
        notify:
            - restart nginx service
  handlers:
      - name: restart nginx service
        service: name=nginx state=restarted
[root@localhost ~]#  cat hosts
[nginx]
192.168.198.10[1:2]
[nginx:vars]
ansible_python_interpreter=/usr/bin/python2
[root@localhost ~]#  cat nginx.conf.j2
user nginx;
worker_processes {{ ansible_processor_cores }};
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  _;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
#检查语法
ansible-playbook nginx.yaml --syntax-check
#列出task
ansible-playbook nginx.yaml --list-task
#列出主机
ansible-playbook nginx.yaml --list-hosts
#-i指定hosts文件,运行playbook
ansible-playbook -i hosts nginx.yaml -f 3
#检查nginx服务状态,并确认生成的worker_processes参数
ansible -i hosts all -m shell -a 'netstata -tpln|grep :80' -f 3
ansible -i hosts all -m shell -a 'grep worker_processes /etc/nginx/nginx.conf' -f 3
#指定task运行,也可以交互式的执行task指定--step参数即可。
ansible-playbook -i hosts nginx.yaml -f 3 --start-at-task='copy nginx.conf'

日常playbook例子

ansible telnet 模块详解 ansible state=present_ansible telnet 模块详解


ansible telnet 模块详解 ansible state=present_ansible telnet 模块详解_02


二、playbook变量与引用

1、通过inventory文件定义主机以及主机组变量

vim /etc/ansible/hosts
192.168.198.101 key=101
192.168.198.102 key=102
[nginx]
192.168.198.10[1:2]
[nginx:vars]
ansible_python_interpreter=/usr/bin/python2
vim variable.yaml
---
- hosts: all
  gather_facts: False
  tasks:
  - name: display Host variable from hostfile
    debug: msg='The {{ inventory_hostname }} Value is {{ key }}'
 ansible-playbook variable.yaml
vim hosts
192.168.198.101
192.168.198.102
[nginx]
192.168.198.10[1:2]
[nginx:vars]
ansible_python_interpreter=/usr/bin/python2
key=nginx
ansible-playbook variable.yaml

2、通过/etc/ansible/下的文件定义主机以及主机组变量

tree /etc/ansible/
.
|-ansible.cfg
|-group_vars
	|_nginx
|-hosts
|_host_vars
	|-192.168.198.101
	|_192.168.198.102
head host_vars/*
host_vars/192.168.198.101
---
key: 192.168.198.101
host_vars/192.168.198.102
---
key: 192.168.198.102
cat group_vars/nginx
---
key: NGINX
ansible-playbook /root/variable.yaml
rm -rf host_vars/*
ansible-playbook /root/variable.yaml

3、通过ansible-playbook命令行传入

ansible-playbook /root/variable.yaml -e 'key=KEY'
#还支持指定文件的方式传入变量,变量的内容支持YAML和JSON。
cat var.yaml
---
key: YAML
cat var.json
{"key":"JSON"}
ansible-playbook /root/variable.yaml -e "@var.json"
ansible-playbook /root/variable.yaml -e "@var.yaml"

4、在playbook文件内使用vars

cat variable.yaml
---
- hosts: all
  gather_facts: False
  vars:
      key: Ansible
  tasks:
  	  - name: display Host Variable from hostfile
  	    debug: msg="The {{ inventory_hostname }} Value is {{ key }}"
ansible-playbook /root/variable.yaml

4、在playbook文件内使用vars_files

cat variable.yaml
---
- hosts: all
  gather_facts: False
  vars_files:
      - var.yaml
  tasks:
  	  - name: display Host Variable from hostfile
  	    debug: msg="The {{ inventory_hostname }} Value is {{ key }}"
ansible-playbook /root/variable.yaml

6、使用register内的变量

---
- hosts: all
  gather_facts: False
  tasks:
  	- name: register variable
  	  shell: hostname
  	  register: info# 把返回的值传递给info
  	- name: display variable
  	  debug: msg='The variable is {{ info }}'#打印info变量的内容
ansible-playbook variable.yaml -l 192.168.198.101

register的输出结果都是python字典,可以指定输出的信息。

---
- hosts: all
  gather_facts: False
  tasks:
  	- name: register variable
  	  shell: hostname
  	  register: info# 把返回的值传递给info
  	- name: display variable
  	  debug: msg='The variable is {{ info['stdout'] }}'
ansible-playbook variable.yaml -l 192.168.198.101

7、使用vars_prompt传入
Ansible还支持在运行playbook的时候通过交互式的方式给定义好的参数传入变量值,只需要在playbook中定义vars_prompt的变量名和交互式提示内容即可。也可加密内容,需安装python的passlib库。

---
- hosts: all
  gather_facts: False
  vars_prompt:
        - name: "one"
          prompt: "please input one value"
          private: no
        - name: "two"
          prompt: "Please input two value"
          default: 'good'
          private: yes
  tasks:
        - name: display one value
          debug: msg="one value is {{ one }}"
        - name: display two value
          debug: msg="two value is {{ two }}"
ansible-playbook variable.yaml  -l 192.168.198.101

注意,相同的变量名会发生覆盖。
三、playbook循环
1、标准loops

vim loops.yaml
---
- hosts: all
  gather_facts: False
  tasks:
  	- name: debug loops
  	  debug: msg="name -----> {{ item }}"
  	  with_items:
  	  - one
  	  - two
#运行,-l 只执行这个主机
ansible-playbook loops.yaml -l 192.168.198.101
#with_items的值是python list数据结构,可以理解为每个task会循环读取list里面的值,然后key的名称是item,当然list里面也支持python字典。如下
vim loops.yaml
---
- hosts: all
  gather_facts: False
  tasks:
  	- name: debug loops
  	  debug: msg="name -----> {{ item.key }}  value -----> {{ item.value }}"
  	  with_items:
  	  - {key: 'one', value: 'VALUE1'}
  	  - {key: 'two', value: 'VALUE2'}
ansible-playbook loops.yaml -l 192.168.198.101

2、嵌套loops

---
- hosts: all
  gather_facts: False
  tasks:
  - name: debug loops
    debug: msg="name --> {{ item[0] }}  value --> {{ item[1] }}"
    with_nested:
    - ['A']
    - ['a','b','c']
ansible-playbook loops.yaml -l 192.168.198.101

可以通过一个Python例子来解释这个例子,定义两个list的代码如下:

In: one=['A']
In: two=['a','b','c']
In: [i + y for i in one for y in two]
Out: ['Aa','Ab','Ac']

3、散列loops

---
- hosts: all
  gather_facts: False
  vars:
  	user:
  		shencan: 
  			name: shencan
  			shell: bash
  		ruifengyun:
  			name: ruifengyun
  			shell: zsh
  tasks:
  - name: debug loops
    debug: msg='name --> {{ item.key }} value --> {{ item.value.name }} shell --> {{ item.value.shell }}'
    with_dict: user
ansible-playbook loops.yaml -l 192.168.198.101    
#fatal: [192.168.198.101]: FAILED! => {"msg": "with_dict expects a dict"}
#为什么失败了

原理,with_dict是接收一个python字典(经过yaml.load后)的格式的变量。

In: user
out: {'shencan': {'name': 'shencan','shell': 'bash'},
  		'ruifengyun':{'name': 'ruifengyun','shell': 'zsh'}}
 In: for key,value in user.items():
 			print(key,value['name'],value['shell'])
 out: shencan shencan bash
  	  ruifengyun ruifengyun zsh

4、文件匹配l
目录下指定格式的文件进行处理,这个时候直接引用with_fileglob循环取匹配我们需要处理的文件即可。

---
- hosts: all
  gather_facts: False
  tasks:
  - name: debug loops
    debug: msg="files --> {{ item }}"
    with_fileglob:
    - /root/*.yaml
#with_fileglob会匹配root目录下所有以yam结尾的文件,当做{{item}}变量。
ansible-playbook loops.yaml -l 192.168.198.101

原理,使用glob.glob(’/root/*.yaml’)取做文件模糊匹配。(os.listdir())
5、随机选择loops

---
- hosts: all
  gather_facts: False
  tasks:
  - name: debug loops
    debug: msg="files --> {{ item }}"
    with_random_choice:
    - 'ansible1'
	- 'ansible2'
	- 'ansible3'
ansible-playbook loops.yaml -l 192.168.198.101

with_random_choice就是在传入的list 中随机选择一个,与使用python random实现原理一样。

In: import random
In: list=['ansible1','ansible2','ansible3']
In: print(random.choice(list))
ansible1

6、条件判断loops
有时候执行一个task之后,我们需要检测这个task的结果是否达到了预想状态,如果没有达到我们预想的状态时,就需要退出整个playbook执行,这个时候我们就需要对某个task结果一直循环检测了,如下所示:

---
- hosts: all
  gather_facts: False
  tasks:
  - name: debug loops
    shell: cat /root/Ansible
    register: host
    until: host.stdout.startswith("Master")
    retries: 5
    delay: 5

每隔5秒执行一次cat /root/Ansible将结果register给host然后判断host.stdout.startswith的内容是否Master字符串开头,如果条件成立,此task运行完成,如果条件不成立5秒后重试,5秒后还不成立,此task运行失败。
7、文件优先loops

---
- hosts: all
  gather_facts: False
  tasks:
  - name: debug loops
    debug: msg="files --> {{ item }}"
    with_first_found:
    - '{{ansible_distribution}}.yaml'
	- 'default.yaml'
ansible-playbook loops.yaml -l 192.168.198.101

with_first_found会从list里面定义的文件从上往下一个一个匹配,匹配后就是item的值。
8、register loops

---
- hosts: all
  gather_facts: True
  tasks:
  - name: debug loops
    shell: '{{ item }}'
    with_items:
    - hostname
    - uname
    register: ret
  - name:display loops
    debug: msg='{% for i in ret.results %} {{ i.stdout }} {% endfor %}'
ansible-playbook loops.yaml -l 192.168.198.101

四、playbook lookups
1、lookups file
open是经常使用的lookups方式,打开文件把结果返回给变量。

---
- hosts: all
  gather_facts: False
  vars:
  	contents: '{{ lookup('open','/etc/sysconfig/network') }}'
  tasks:
  	- name: debug lookups
  	  debug: msg='The contents is {% for i in contents.split('\n') %}{{ i }}{% endfor %}'
ansible-playbook lookups.yaml -l 192.168.198.101

2、lookups password
password也是我们使用的一种lookups方式,对传入的内容进行加密处理。

---
- hosts: all
  gather_facts: False
  vars:
  	contents: '{{ lookup('password','ansible_book') }}'
  tasks:
  	- name: debug lookups
  	  debug: msg='The contents is {{ contents }}'
ansible-playbook lookups.yaml -l 192.168.198.101

3、lookups pipe
控制python的subprocess.Popen函数,将命令的结果传递给变量。

---
- hosts: all
  gather_facts: False
  vars:
  	contents: '{{ lookup('pipe','date +%Y-%m-%d') }}'
  tasks:
  	- name: debug lookups
  	  debug: msg=msg='The contents is {% for i in contents.split('\n') %}{{ i }}{% endfor %}'
#contents的内容就是在Ansible控制机器上执行date +%Y-%m-%d的结果。
ansible-playbook lookups.yaml -l 192.168.198.101

4、lookups redis_kv
redis_kv是从Redis数据库中get数据,需要安装redis的python库。这里在本地安装一个Redis服务器,然后给Ansible设置一个值。

redis-cli
redis 127.0.0.1:6379> set 'ansible' 'good'
redis 127.0.0.1:6379> get 'ansible'
#修改playbook并运行
---
- hosts: all
  gather_facts: False
  vars:
  	contents: '{{ lookup('redis_kv','redis://localhost:6379,ansible') }}'
  tasks:
  	- name: debug lookups
  	  debug: msg='The contents is {% for i in contents.split('\n') %}{{ i }}{% endfor %}'
ansible-playbook lookups.yaml -l 192.168.198.101

5、lookups template
template和open方式有点类似,都是读取文件,但是template在读取文件之前需要把jinja模板渲染完后再读取,下面我们制定一个jinja模板文件:

worker_processes {{ ansible_processor_cores }}
IPaddress {{ ansible_eth0.ipv4.address }}

---
- hosts: all
  gather_facts: False
  vars:
  	contents: '{{ lookup('template','./lookups.j2') }}'
  tasks:
  	- name: debug lookups
  	  debug: msg='The contents is {% for i in contents.split('\n') %}{{ i }}{% endfor %}'
ansible-playbook lookups.yaml

五、playbook conditionals
条件判断,在实际应用中经常会碰到不同的主机可能要执行不同的task。目前Ansible的所有条件方式都是使用when进行判断,条件成立就执行。

---
- hosts: all
  tasks:
  - name: Host 192.168.198.101 run this task
    debug: msg='{{ ansible_default_ipv4.address }}'
    when: ansible_default_ipv4.address == '192.168.198.101'
  - name: memtotal < 200M and processor_cores == 2 run this task
    debug: msg='{{ ansible_fqdn }}'
    when: ansible_memtotal_mb < 500 and ansible_processor_cores == 2
  - name: all host run thos task
    shell: hostname
    register: info
  - name: Hostname is python Machie run this task
    debug: msg='{{ ansible_fqdn }}'
    when: info['stdout'] == 'python'
  - name: Hostname is startswith M run this task
    debug: msg='{{ ansible_fqdn }}'
    when: info['stdout'].startswith('M')
#执行这个语句
ansible-playbook conditionals.yaml
#skipping表示跳过的主机,没有执行

六、Jinja2 filter(管道)
语法: {{ value|filter_name:参数 }}
Jinja2 filter扩展 Ansible默认支持Jinja2语言的内置filter,Jinja2官网也提供了很多filter。一下是常用的filter:

---
- hosts: all
  gather_facts: False
  vars:
  	list: [1,2,3,4,5]
  	one: '1'
  	str: 'string'
  tasks:
  - name: run commands
    shell: df -h
    register: info
  - 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 ower value is {{ str|capitalize }}'
  - name: debug default filter
    debug: msg='The Variable value is {{ ansible | default('ansible is not define') }}'
  - name: debug list max and min filter
    debug: msg='The list max valye is {{ list | max }} The list min value is {{ list | min }}'
  - name: debug ramdom filter
    debug: msg='The list random value is {{ list | random }} and generate a random value is {{ 1000 | random(1,10) }}'
  - name: debug join filter
    debug: msg='The join filter value is {{ list | join('+') }}'
  - name: msg='The replace value is {{ str |replace('t','T') }} The regex_replace value is {{ str | regex_replace('.*tr(.*)$','\\1') }}'

七、playbook内置变量
playbook默认已经内置变量,掌握这些变量就可以实现关于主机的逻辑判断。
1、groups和group_names
groups变量是一个全局变量,他会打印出Inventory文件里面的所有主机以及主机组信息,返回的是json字符串,可以直接调用{{ groups }},也可以引用其中的数据,比如docker组的host主机,{{ groups[‘docker’] }},他会返回一个list列表,group_names变量会打印当前主机所在的groups名称。如果没有定义会返回ungrouped,它返回的也是夜歌组名称的list列表。
2、hostvars
hostvars是用来调用指定主机变量,需要传入主机信息,返回结果也是一个JSON字符串,同样也可以直接引用JSON字符串内的指定信息。
3、inventory_hostname和inventory_hostname_short
inventory_hostname变量是返回Inventory文件里面定义的主机名,
inventory_hostname_short会返回Inventory文件中主机名的第一部分。
4、play_hosts和inventory_dir
play_hosts变量是用来返回当前playbook运行的主机信息,返回格式是主机list结构。
inventory_dir变量是用来返回当前playbook使用的Inventory目录。
5、应用案例
看一个Jinja2的模板文件,这个文件包含以上playbook内置变量。

=============
groups info
{{ groups }}
=============

=======docker groups info =========
{% for host in groups['docker'] %}
={{ hostvars[host]['inventory_hostname'] }} eth0 IP is {{ hostvars[host]['ansible_default_ipv4']['address'] }}
+{{ hostvars[host]['inventory_hostname'] }} groups is {{ group_names }}
-{{ hostvars[host]['inventory_hostname'] }} short is {{ inventory_hostname_short }}
*{{ play_hosts }}
@{{ inventory_dir }}
{% endfor %}
[root@localhost ~]# tree .
.
|-docker
|_hosts
[root@localhost ~]# cat docker
[docker]
172.17.42.10[1:3]
[docker:vars]
ansible_ssh_pass='123456'
[ansible:children]
docker
[root@localhost ~]# cat hosts
172.17.42.101 ansible_ssh_pass='123456'
172.17.42.102 ansible_ssh_pass='123456'
172.17.42.1 ansible_ssh_pass='123456'
[root@localhost ~]# cat template.yaml
---
- hosts: all
  tasks:
  - name: test template
    template: src=jinja.j2 dest=/tmp/cpis
#开始执行playbook
ansible-playbook template.yaml
[root@localhost ~]# cat /tmp/cpis

ansible telnet 模块详解 ansible state=present_html_03


可以参考结果来看每个变量的含义。