抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

15. Ansible文件管理模块及Jinja2过滤器

目录

常用文件管理模块

1. file

我们在讲ansible ad-hoc的时候,已经说过file模块,在playbook中的使用也没什么不同,下面给个简单的示例:

- name: Touch a file and set permissions
  file:
    path: /path/to/file
    owner: user1
    group: group1
    mode: 0640
    state: touch

2. synchronize

synchronize模块示例:

- name: synchronize local file to remote files
  synchronize:
    src: file
    dest: /path/to/file

3. copy

同样的,我们已经介绍过copy模块,示例如下:

- name: copy a file to managed hosts
  copy:
    src: file
    dest: /path/to/file

4. fetch

fetch模块与copy模块正好相反,copy是把主控端的文件复制到被控端,而fetch则是把被控端的文件复制到主控端。并且在主控端指定的目录下,以被控端主机名的形式来组织目录结构。

- name: Use the fetch module to retrieve secure log files
  hosts: all
  user: ansible
  tasks:
    - name: Fetch the /var/log/secure log file from managed hosts
      fetch:
        src: /var/log/secure
        dest: secure-backups
        flat: no

在主控端文件存储的目录树如下:

# tree  secure-backups/
secure-backups/
└── 10.1.61.187
    └── var
        └── log
            └── secure

3 directories, 1 file

参考:https://docs.ansible.com/ansible/latest/modules/fetch_module.html#fetch-module

5. lineinfile

lineinfile是一个非常有用的模块,而且相对来说,也是用法比较复杂的模块,可直接参考《Ansible lineinfile模块》

6. stat

stat模块与linux中的stat命令一样,用来显示文件的状态信息。

- name: Verify the checksum of a file
  stat:
    path: /path/to/file
    checksum_algorithm: md5
  register: result

- debug:
    msg: "The checksum of the file is {{ result.stat.checksum }}"

参考: https://docs.ansible.com/ansible/latest/modules/stat_module.html#stat-module

7. blockinfile

围绕着被标记的行插入、更新、删除一个文本块。

#cat files/test.html
<html>
  <head>
  </head>
  <body>
  </body>
</html>

#cat blockinfile_ex.yml
---
- name: blockinfile module test
  hosts: test
  tasks:
    - name: copy test.html to dest
      copy:
        src: files/test.html
        dest: /var/www/html/test.html
    - name: add block 
      blockinfile:
        marker: "<!-- {mark} ANSIBLE MANAGED BLOCK -->"
        insertafter: "<body>"
        path: /var/www/html/test.html
        block: |
          <h1>Welcome to {{ ansible_hostname }}</h1>
          <p>Last updated on {{ ansible_date_time.iso8601 }}</p>

执行后结果如下:

[root@app html]# cat test.html 
<html>
  <head>
  </head>
  <body>
<!-- BEGIN ANSIBLE MANAGED BLOCK -->
<h1>Welcome to app</h1>
<p>Last updated on 2019-05-28T15:00:03Z</p>
<!-- END ANSIBLE MANAGED BLOCK -->
  </body>
</html>

更多blockinfile用法参考:https://docs.ansible.com/ansible/latest/modules/blockinfile_module.html#blockinfile-module

Jinja2模板管理

Jinja2简介

Jinja2是基于python的模板引擎。那么什么是模板?

假设说现在我们需要一次性在10台主机上安装redis,这个通过playbook现在已经很容易实现。默认情况下,所有的redis安装完成之后,我们可以统一为其分发配置文件。这个时候就面临一个问题,这些redis需要监听的地址各不相同,我们也不可能为每一个redis单独写一个配置文件。因为这些配置文件中,绝大部分的配置其实都是相同的。这个时候最好的方式其实就是用一个通用的配置文件来解决所有的问题。将所有需要修改的地方使用变量替换,如下示例中redis.conf.j2文件:

daemonize yes
supervised systemd
pidfile /var/run/redis.pid
port 6379
logfile "/var/log/redis/redis.log"
dbfilename dump.rdb
dir /data/redis

maxmemory {{ (ansible_memtotal_mb /2) | int }}M

bind {{ ansible_eth0.ipv4.address }} 127.0.0.1

timeout 300
loglevel notice

databases 16
save 900 1
save 300 10
save 60 10000

rdbcompression yes

maxclients 10000
appendonly yes
appendfilename appendonly.aof
appendfsync everysec

那么此时,redis.conf.j2文件就是一个模板文件。{{ ansible_eth0.ipv4.address }}是一个fact变量,用于获取被控端ip地址以实现替换。

在playbook中使用jinja2

现在我们有了一个模板文件,那么在playbook中如何来使用呢?

playbook使用template模块来实现模板文件的分发,其用法与copy模块基本相同,唯一的区别是,copy模块会将原文件原封不动的复制到被控端,而template会将原文件复制到被控端,并且使用变量的值将文件中的变量替换以生成完整的配置文件。

下面是一个完整的示例:

# cat config_redis.yml 
- name: Configure Redis
  hosts: test
  tasks:
    - name: create redis group
      group:
        name: redis
        gid: 1111
    - name: create redis user
      user:
        name: redis
        uid: 1111
        group: redis
    - name: install redis
      yum:
        name: redis
        state: present
    - name: create data dir
      file:
        path: /data/redis
        state: directory
        recurse: yes
        owner: redis
        group: redis
    - name: copy redis.conf to dest
      template:
        src: templates/redis.conf.j2
        dest: /etc/redis.conf
      notify:
        - restart redis
    - name: start redis
      service:
        name: redis
        state: started
        enabled: yes
  handlers:
    - name: restart redis
      service:
        name: redis
        state: restarted

执行完成之后,我们可以看到被控端/etc/redis.conf配置文件如下:

daemonize yes
pidfile /var/run/redis.pid
port 6379
logfile "/var/log/redis/redis.log"
dbfilename dump.rdb
dir /data/redis

maxmemory 1G

bind 10.1.61.187 127.0.0.1

timeout 300
loglevel notice

databases 16
save 900 1
save 300 10
save 60 10000

rdbcompression yes

maxclients 10000
appendonly yes
appendfilename appendonly.aof
appendfsync everysec

关于template模块的更多参数说明:

  • backup:如果原目标文件存在,则先备份目标文件

  • dest:目标文件路径

  • force:是否强制覆盖,默认为yes

  • group:目标文件属组

  • mode:目标文件的权限

  • owner:目标文件属主

  • src:源模板文件路径

  • validate:在复制之前通过命令验证目标文件,如果验证通过则复制

Jinja2条件语句

在上面的示例中,我们直接取了被控节点的eth0网卡的ip作为其监听地址。那么假如有些机器的网卡是bond0,这种做法就会报错。这个时候我们就需要在模板文件中定义条件语句如下:

daemonize yes
pidfile /var/run/redis.pid
port 6379
logfile "/var/log/redis/redis.log"
dbfilename dump.rdb
dir /data/redis

maxmemory 1G

{% if ansible_bond0 is defined %}
bind {{ ansible_bond0.ipv4.address }} 127.0.0.1
{% elif ansible_eth0 is defined %}
bind {{ ansible_eth0.ipv4.address }} 127.0.0.1
{% else%}
bind 0.0.0.0
{% endif %}

timeout 300
loglevel notice

databases 16
save 900 1
save 300 10
save 60 10000

rdbcompression yes

maxclients 10000
appendonly yes
appendfilename appendonly.aof
appendfsync everysec

我们可以更进一步,让redis主从角色都可以使用该文件:

daemonize yes
pidfile /var/run/redis.pid
port 6379
logfile "/var/log/redis/redis.log"
dbfilename dump.rdb
dir /data/redis

maxmemory {{ (ansible_memtotal_mb /2) | int }}M

{% if ansible_bond0 is defined %}
bind {{ ansible_bond0.ipv4.address }} 127.0.0.1
{% elif ansible_bond0 is defined %}
bind {{ ansible_bond0.ipv4.address }} 127.0.0.1
{% else%}
bind 0.0.0.0
{% endif %}

{% if masterip is defined %}
slaveof {{ hostvars[groups.redismaster.0].ansible_bond0.ipv4.address }} {{ masterport|default(6379) }}
{% endif %}

{% if masterpass is defined %}
masterauth {{ masterpass }}
{% endif %}

{% if requirepass is defined %}
requirepass {{ requirepass }}
{% endif %}


#requirepass是配置在主节点的,masterauth是配置在从节点的,两边配置要一样从节点才能和主节点连接上进行主从复制。

timeout 300
loglevel notice

databases 16
save 900 1
save 300 10
save 60 10000

rdbcompression yes

maxclients 10000
appendonly yes
appendfilename appendonly.aof
appendfsync everysec

stop-writes-on-bgsave-error no

我们定义一个inventory如下:

all:
  children: 
    redis:
      children:
         redismaster:
              vars: 
                  requirepass: redhat
              hosts: 
                 servera: 
         redisslave:
              vars:
                  masterauth: redhat 
                  masterport: 6379
                  masterip: yes
              hosts:
                 serverb:
                 serverc:

测试

[root@servera ~]# redis-cli 
127.0.0.1:6379> auth redhat
127.0.0.1:6379> info
role:master

[root@serverb ~]# redis-cli 
127.0.0.1:6379> info
role:slave

Jinja2循环语句

定义一个inventory示例如下:

[proxy]
10.1.61.195

[webservers]
10.1.61.27
10.1.61.187

现在把proxy主机组中的主机作为代理服务器,安装nginx做反向代理,将请求转发至后面的两台webserver,即webserver组的服务器。

现在我们编写一个playbook如下:

#cat config_nginx.conf

# 还需要在ansible.cfg中开启facts缓存
- name: gather facts
  gather_facts: Fasle
  hosts: webservers
  tasks:
    - name: gather facts
      setup:

- name: Configure Nginx
  hosts: proxy
  tasks:
    - name: install nginx
      yum:
        name: nginx
        state: present
    - name: copy nginx.conf to dest
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify:
        - restart nginx
    - name: start nginx
      service:
        name: nginx
        state: started
        enabled: yes
  handlers:
    - name: restart nginx
      service:
        name: nginx
        state: restarted

模板文件 templates/nginx.conf.j2示例如下:

# cat nginx.conf.j2 
user nginx;
worker_processes {{ ansible_processor_vcpus }};
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
    worker_connections 65535;
    use epoll;
}
http {
    map $http_x_forwarded_for $clientRealIP {
        "" $remote_addr;
        ~^(?P<firstAddr>[0-9\.]+),?.*$ $firstAddr;
    }
    log_format  real_ip '{ "datetime": "$time_local", '
                        '"remote_addr": "$remote_addr", '
                        '"source_addr": "$clientRealIP", '
                        '"x_forwarded_for": "$http_x_forwarded_for", '
                        '"request": "$request_uri", '
                        '"status": "$status", '
                        '"request_method": "$request_method", '
                        '"request_length": "$request_length", '
                        '"body_bytes_sent": "$body_bytes_sent", '
                        '"request_time": "$request_time", '
                        '"http_referrer": "$http_referer", '
                        '"user_agent": "$http_user_agent", '
                        '"upstream_addr": "$upstream_addr", '
                        '"upstream_status": "$upstream_status", '
                        '"upstream_http_header": "$upstream_http_host",'
                        '"upstream_response_time": "$upstream_response_time", '
                        '"x-req-id": "$http_x_request_id", '
                        '"servername": "$host"'
                        ' }';
    access_log  /var/log/nginx/access.log  real_ip;
    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;
    include /etc/nginx/conf.d/*.conf;

    upstream web {
    {% for host in groups['webservers'] %}
       server {{ hostvars[host].ansible_ens33.ipv4.address }}:80;
    {% endfor %}
    }
    server {
        listen       80 default_server;
        server_name  _;
        location / {
            proxy_pass http://web;
        }
    }
}

for循环只能循环列表,不能循环字典,字典需要转换为列表才能循环

Jinja2过滤器

1. default过滤器

当指定的变量不存在时,用于设定默认值

简单示例:

"Host": "{{ db_host | default('lcoalhost') }}"

复杂一点儿的实例:

- hosts: 
  gather_facts: false
  vars:
    paths:
      - path: /tmp/test
        mode: '0400'
      - path: /tmp/foo
      - path: /tmp/bar
  tasks:
    - file:
        path: "{{ item.path }}"
        state: touch
        mode: "{{ item.mode | default(omit)}}"
      with_items: "{{ paths }}"

2. 字符串操作相关的过滤器

  • upper:将所有字符串转换为大写

  • lower:将所有字符串转换为小写

  • capitalize:将字符串的首字母大写,其他字母小写

  • reverse:将字符串倒序排列

  • first:返回字符串的第一个字符

  • last:返回字符串的最后一个字符

  • trim:将字符串开头和结尾的空格去掉

  • center(30):将字符串放在中间,并且字符串两边用空格补齐30位

  • length:返回字符串的长度,与count等价

  • list:将字符串转换为列表

  • shuffle:list将字符串转换为列表,但是顺序排列,shuffle同样将字符串转换为列表,但是会随机打乱字符串顺序

示例:

- hosts: test
  gather_facts: no
  vars: 
    teststr: "abc123ABV"
    teststr1: " abc "
    teststr2: "123456789"
    teststr3: "sfacb1335@#$%"
  tasks:
    - debug: 
        msg: "{{ teststr | upper }}"
    - debug:
        msg: "{{ teststr | lower }}"
    - debug:
        msg: "{{ teststr | capitalize }}"
    - debug:
        msg: "{{ teststr | reverse }}"
    - debug:
        msg: "{{ teststr|first }}"
    - debug: 
        msg: "{{ teststr|last }}"
    - debug:
        msg: "{{ teststr1 | trim }}"
    - debug:
        msg: "{{ teststr2 | center(30) }}"
    - debug: 
        msg: "{{ teststr2 | length }}"
    - debug:
        msg: "{{ teststr3 | list }}"
    - debug:
        msg: "{{ teststr3 | shuffle }}"

3. 数字操作相关的过滤器

  • int: 将对应的值转换为整数

  • float:将对应的值转换为浮点数

  • abs:获取绝对值

  • round:小数点四舍五入

  • random:从一个给定的范围中获取随机值

示例:

- hosts: test
  gather_facts: no
  vars: 
    testnum: -1
  tasks:
    - debug: 
        msg: "{{  8+('8'|int) }}"
    - debug:
        # 默认情况下,如果无法完成数字转换则返回0
        # 这里指定如果无法完成数字转换则返回6
        msg: "{{ 'a'|int(default=6) }}" 
    - debug:
        msg: "{{ '8'|float }}"
    - debug:
        msg: "{{ 'a'|float(8.88)' }}"
    - debug:
        msg: "{{ testnum|abs }}"
    - debug: 
        msg: "{{ 12.5|round }}"
    - debug:
        msg: "{{ 3.1415926 | round(5) }}"
    - debug:
        # 从0到100中随机返回一个数字
        msg: "{{ 100|random }}"
    - debug: 
        # 从5到10中随机返回一个数字
        msg: "{{ 10|random(start=5) }}"
    - debug:
        # 从4到15中随机返回一个数字,步长为3
        # 返回的随机数只可能是:4,7,10,13中的一个
        msg: "{{ 15|random(start=4,step=3) }}"
    - debug:
        # 从0到15随机返回一个数字,步长为4
        msg: "{{ 15|random(step=4) }}"

4. 列表操作相关的过滤器

  • length: 返回列表长度

  • first:返回列表的第一个值

  • last:返回列表的最后一个值

  • min:返回列表中最小的值

  • max:返回列表中最大的值

  • sort:重新排列列表,默认为升序排列,sort(reverse=true)为降序

  • sum:返回纯数字非嵌套列表中所有数字的和

  • flatten:如果列表中包含列表,则flatten可拉平嵌套的列表,levels参数可用于指定被拉平的层级

  • join:将列表中的元素合并为一个字符串

  • random:从列表中随机返回一个元素

  • shuffle

  • upper

  • lower

  • union:将两个列表合并,如果元素有重复,则只留下一个

  • intersect:获取两个列表的交集

  • difference:获取存在于第一个列表中,但不存在于第二个列表中的元素

  • symmetric_difference:取出两个列表中各自独立的元素,如果重复则只留一个

示例:

- hosts: test
  gather_facts: false
  vars:
    testlist1: [1,2,4,6,3,5]
    testlist2: [1,[2,3,4,[5,6]]]
    testlist3: [1,2,'a','b']
    testlist4: [1,'A','b',['C','d'],'Efg']
    testlist5: ['abc',1,2,'a',3,2,'1','abc']
    testlist6: ['abc',3,'1','b','a']
  tasks:
    - debug:
        msg: "{{ testlist1 | length }}"
    - debug:
        msg: "{{ testlist1 |first }}"
    - debug: 
        msg: "{{ testlist1 | last }}"
    - debug:
        msg: "{{ testlist1 | min }}"
    - debug: 
        msg: "{{ testlist1 | max }}"
    - debug:
        msg: "{{ testlist1 | sort }}"
    - debug:
        msg: "{{ testlist1 | sort(reverse=true) }}"  
    - debug:
        msg: "{{ testlist2 | flatten }}"
    - debug:
        msg: "{{ testlist2 | flatten(levels=1) }}"
    - debug:
        msg: "{{ testlist2 | flatten | max }}"
    - debug:
        msg: "{{ testlist3 | join }}"
    - debug:
        msg: "{{ testlist3 |join(',')}}"
    - debug:
        msg: "{{ testlist3 | random }}"
    - debug:
        msg: "{{ testlist3 | shuffle }}"
    - debug:
        msg: "{{ testlist4 | upper }}"
    - debug:
        msg: "{{ testlist4 | lower }}"
    - debug:
        msg: "{{ testlist5 | union(testlist6) }}"
    - debug:
        msg: "{{ testlist5 | intersect(testlist6) }}"
    - debug:
        msg: "{{ testlist5 | difference(testlist6) }}"
    - debug:
        msg: "{{ testlist5 | symmetric_difference(testlist6) }}"

5. hash和Encoding过滤器

  • hash过滤器
- hosts: test
  gather_facts: no
  tasks:
    - debug: 
        msg: "{{ 'redhat' | hash('sha1') }}"

#结果:
ok: [servera] => {
    "msg": "3c767c41afb12ada140190ed82db3fd930e2efa3"

#相当于: 
# echo -n redhat | sha1sum 
3c767c41afb12ada140190ed82db3fd930e2efa3  -
  • password_hash过滤器
- hosts: test
  gather_facts: no
  tasks:
    - user:
        name: john
        password: "{{ password }}"
      vars:
        password: "{{ 'redhat' | password_hash('sha512') }}"
  • base64加密和解密
- hosts: test
  gather_facts: no
  tasks:
    - debug: 
        msg: "{{ 'redhat' | b64encode | b64decode }}"

b64encode: 加密
b64decode: 解密

# 相当于:
echo -n redhat | base64 -w 0  | base64 -d

6. 查找替换过滤器

  • replace过滤器
- hosts: test
  gather_facts: no
  tasks:
    - debug:
        msg: "{{ 'marvin, arthur' | replace('ar','**') }}"
  • regex_search 过滤器
- hosts: test
  gather_facts: no
  tasks:
    - debug:
        msg: "{{ 'marvin, arthur' | regex_search('ar\\S*r') }}"

\是特殊字符,需要特殊处理以后再交给正则表达式
  • regex_replace过滤器
- hosts: test
  gather_facts: no
  vars:
      str1: 'marvin, arthur'
  tasks:
    - debug: 
        msg: "{{ str1 | regex_replace('ar(\\S*)r','\\1mb') }}"

正则表达式:

\w  匹配字母数字及下划线
\W  匹配非字母数字及下划线
\s  匹配任意空白字符,等价于 [ \t\n\r\f]。
\S  匹配任意非空字符
\d  匹配任意数字,等价于 [0-9].
\D  匹配任意非数字
\A  匹配字符串开始
\Z  匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。
\z  匹配字符串结束
\G  匹配最后匹配完成的位置。
\b  匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。
\B  匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。
\n, \t, 等.  匹配一个换行符。匹配一个制表符。等
\1...\9  匹配第n个分组的内容。
\10  匹配第n个分组的内容,如果它经匹配。否则指的是八进制字符码的表达式。

7. 应用于字典变量的过滤器

  • combine 将两个字典合成一个
- hosts: servera
  gather_facts: no
  vars:
      user1:
         name: bob
         uid: 1200
      userinfo:
         home: /home/bob
         comment: "user account"
  tasks:
     - name: combine
       debug:
          msg: "{{ user1 | combine(userinfo)}}"
  • dict2items 将字典转换为列表

示例一:

tags:
  Application: payment
  Environment: dev

# 转化后:
{{ dict | dict2items }}

- key: Application
  value: payment
- key: Environment
  value: dev

示例二:

files:
  users: /etc/passwd
  groups: /etc/group

{{ files | dict2items(key_name='file', value_name='path') }}

# 转化后:
- file: users
  path: /etc/passwd
- file: groups
  path: /etc/group
  • items2dict 将列表转换为字典

示例一:

tags:
  - key: Application
    value: payment
  - key: Environment
    value: dev


{{ tags | items2dict }}

# 转化后:

Application: payment
Environment: dev

示例二:

  fruits:
  - fruit: apple
    color: red
  - fruit: pear
    color: yellow
  - fruit: grape
    color: yellow

  {{ fruits | items2dict(key_name='fruit', value_name='color') }}

  # 必须指定key_name和value_name,否则会报KeyError: 'key'或者

  # 转化后:
        "apple": "red", 
        "grapefruit": "yellow", 
        "pear": "yellow"
  • json_query 将变量转换成json,并查询子元素
- hosts: localhost
  gather_facts: no
  vars:
    domain_definition: "{{ lookup('file','json_example.json') | from_json  }}"
  tasks:
    - name: "Display all cluster names"
      debug:
        var:  item
      loop: "{{ domain_definition | json_query('domain.cluster[*].name') }}"
  • from_json/from_yaml
{{ some_variable | from_json }}
{{ some_variable | from_yaml }}

- hosts: localhost
  gather_facts: no
  tasks:
    - name: from_json filter test
      debug:
        msg:  "{{ domain_definition }}"
  vars:
    domain_definition: "{{ lookup('file','json_example.json') | from_json  }}"
  • to_json/to_yaml/to_nice_json/to_nice_yaml
{{ some_variable | to_json }}
{{ some_variable | to_yaml }}

{{ some_variable | to_nice_json }}
{{ some_variable | to_nice_yaml }}
- name: Convert between JSON and YAML format
  vars:
     hosts:
     - name: bastion
       ip:
         - 172.25.250.254
         - 172.25.252.1
  debug:
      msg: '{{ hosts | to_json }}'


# 结果:
'[{"name": "bastion", "ip": ["172.25.250.254", "172.25.252.1"]}]'

示例变量:

# cat json_example.json
{
        "domain": {
            "cluster": [
                {
                    "name": "cluster1"
                },
                {
                    "name": "cluster2"
                }
            ],
            "server": [
                {
                    "name": "server11",
                    "cluster": "cluster1",
                    "port": "8080"
                },
                {
                    "name": "server12",
                    "cluster": "cluster1",
                    "port": "8090"
                },
                {
                    "name": "server21",
                    "cluster": "cluster2",
                    "port": "9080"
                },
                {
                    "name": "server22",
                    "cluster": "cluster2",
                    "port": "9090"
                }
            ],
            "library": [
                {
                    "name": "lib1",
                    "target": "cluster1"
                },
                {
                    "name": "lib2",
                    "target": "cluster2"
                }
            ]
        }
}

8. ipaddr过滤器处理网络地址

ipaddr过滤器主要用于处理网络地址, 其支持的过滤器参数如下:

  • address

  • host

  • prefix

  • size

  • network

  • netmask

  • broadcast

  • subnet

  • ipv4 and ipv6

要想使用ipaddr过滤器,需要在主控端安装python-netaddr包:

yum install -y python-netaddr

示例:

- hosts: localhost
  gather_facts: no
  vars: 
    my_ip_addr: ["192.168.0.1/24"]
  tasks:
  - name: ipaddr 
    debug:
      msg: "{{ my_ip_addr | ipaddr }}"
  - name: ipaddr(network)
    debug:
      msg: "{{ my_ip_addr | ipaddr('network') }}"
  - name: ipaddr(host)
    debug:
      msg: "{{ my_ip_addr | ipaddr('host') }}"
  - name: ipaddr(netmask)
    debug:
      msg: "{{ my_ip_addr | ipaddr('netmask') }}"
  - name: ipaddr(size)
    debug:
      msg: "{{ my_ip_addr | ipaddr('size') }}"
  - name: ipaddr(subnet)
    debug:
      msg: "{{ my_ip_addr | ipaddr('subnet') }}"
  - name: ipaddr(ipv4)
    debug:
      msg: "{{ my_ip_addr | ipaddr('ipv4') }}"
  - name: ipaddr(broadcast)
    debug:
      msg: "{{ my_ip_addr | ipaddr('broadcast') }}"

输出:

TASK [ipaddr] *********************************************************************
ok: [localhost] => {
    "msg": [
        "192.168.0.1/24"
    ]
}

TASK [ipaddr(network)] ************************************************************
ok: [localhost] => {
    "msg": [
        "192.168.0.0"
    ]
}

TASK [ipaddr(host)] ***************************************************************
ok: [localhost] => {
    "msg": [
        "192.168.0.1/24"
    ]
}

TASK [ipaddr(netmask)] ************************************************************
ok: [localhost] => {
    "msg": [
        "255.255.255.0"
    ]
}

TASK [ipaddr(size)] ***************************************************************
ok: [localhost] => {
    "msg": [
        256
    ]
}

TASK [ipaddr(subnet)] *************************************************************
ok: [localhost] => {
    "msg": [
        "192.168.0.0/24"
    ]
}

TASK [ipaddr(ipv4)] ***************************************************************
ok: [localhost] => {
    "msg": [
        "192.168.0.1/24"
    ]
}

TASK [ipaddr(broadcast)] **********************************************************
ok: [localhost] => {
    "msg": [
        "192.168.0.255"
    ]
}

9. url分割过滤器

urlspilt用于分割url,并取出相应字段

- hosts: localhost
  gather_facts: no
  vars:
    url: 'http://user:password@www.example.com:8080/xxx/index.html?query=132'
  tasks:
    - name: "get url hostname"
      debug:
        msg: "{{ url | urlsplit('hostname')  }}"
# ==> "msg": "www.example.com"

    - name: "get url netloc"
      debug:
        msg: "{{ url | urlsplit('netloc')  }}"
# ==> "user:password@www.example.com:8080"

    - name: "get url path"
      debug:
        msg: "{{ url | urlsplit('path')  }}"
# ==> "msg": "/xxx/index.html"

    - name: "get url port"
      debug:
        msg: "{{ url | urlsplit('port')  }}"
# ==> "msg": "8080"

    - name: "get url scheme"
      debug:
        msg: "{{ url | urlsplit('scheme')  }}"
# ==> "msg": "http"

    - name: "get url query"
      debug:
        msg: "{{ url | urlsplit('query') }}"
# ==> "msg": "query=132"

10. 应用于注册变量的过滤器

正常情况下,当某个task执行失败的时候,ansible会中止运行。此时我们可以通过ignore_errors来捕获异常以让task继续往下执行。然后调用debug模块打印出出错时的内容,拿来错误结果后,主动失败。

- name: Run myprog
  command: /opt/myprog
  register: result
  ignore_errors: True

- debug: 
    var: result

- debug: 
    msg: "Stop running the playbook if myprog failed"
  failed_when: result|failed

任务返回值过滤器:

  • failed: 如果注册变量的值是任务failed则返回True

  • changed: 如果注册变量的值是任务changed则返回True

  • success:如果注册变量的值是任务succeeded则返回True

  • skipped:如果注册变量的值是任务skipped则返回True

在ansible2.9中,该方式会被废弃,不推荐使用

11. 应用于文件路径的过滤器

  • basename:返回文件路径中的文件名部分

  • dirname:返回文件路径中的目录部分

  • expanduser:将文件路径中的~替换为用户目录

  • realpath:处理符号链接后的文件实际路径

下面是一个示例:

- name: test basename
  hosts: servera
  vars:
    homepage: ~/index.html
    linkpath: /etc/systemd/system/default.target
  tasks:
    - name: copy homepage
      copy:
        src: index.html
        dest: "{{ homepage }}"
    - debug:
        msg: "{{ homepage | basename }}"
    - debug:
        msg: "{{ homepage | dirname }}"
    - debug:
        msg: "{{ homepage | expanduser }}"
    - debug:
        msg: "{{ linkpath | realpath  }}"

12. 自定义过滤器

举个简单的例子,现在有一个playbook如下:

- name: test filter
  hosts: test
  vars:
    domains: ["www.example.com","example.com"]
  tasks:
    template:
      src: templates/test.conf.j2
      dest: /tmp/test.conf

templates/test.conf.j2如下:

hosts = [{{ domains | join(',') }}]

执行playbook后,在目标机上的test.conf如下:

hosts = [www.example.com,example.com]

现在如果希望目标机上的test.conf文件返回结果如下:

hosts = ["www.example.com","example.com"]

没有现成的过滤器来帮我们做这件事情。我们可以自己简单写一个surround_by_quote.py内容如下:

# 定义过滤器执行的操作
def surround_by_quote(a_list):
  return ['"%s"' % an_element for an_element in a_list]

class FilterModule(object):
  def filters(self):
    return {'surround_by_quote': surround_by_quote}

我们需要开启ansible.cfg的配置项:

filter_plugins     = /usr/share/ansible/plugins/filter

将刚刚编写的代码文件放入/usr/share/ansible/plugins/filter目录下,然后修改templates/test.conf.j2如下:

hosts = [{{ domains | join(',') |surround_by_quote }}]

再次执行playbook,最后返回结果:

hosts = ["www.example.com","example.com"]

关于jinja2更多用法参考:http://docs.jinkan.org/docs/jinja2/

评论