目录html
对于任何自动管理工具而言,对于文件的管理都是其绕不开的话题。一样,ansible也围绕文件管理提供了众多的模块。同时还提供了Jinja2模板语法来配置文件模板。node
咱们在讲ansible ad-hoc的时候,已经说过file模块,在playbook中的使用也没什么不一样,下面给个简单的示例:python
- name: Touch a file and set permissions file: path: /path/to/file owner: user1 group: group1 mode: 0640 state: touch
synchronize模块示例:linux
- name: synchronize local file to remote files synchronize: src: file dest: /path/to/file
一样的,咱们已经介绍过copy模块,示例以下:nginx
- name: copy a file to managed hosts copy: src: file dest: /path/to/file
fetch模块与copy模块正好相反,copy是把主控端的文件复制到被控端,而fetch则是把被控端的文件复制到主控端。而且在主控端指定的目录下,以被控端主机名的形式来组织目录结构。web
- 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
在主控端文件存储的目录树以下:redis
# 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服务器
lineinfile是一个很是有用的模块,并且相对来讲,也是用法比较复杂的模块,可直接参考《Ansible lineinfile模块》app
stat模块与linux中的stat命令同样,用来显示文件的状态信息。dom
- 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
围绕着被标记的行插入、更新、删除一个文本块。
#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是基于python的模板引擎。那么什么是模板?
假设说如今咱们须要一次性在10台主机上安装redis,这个经过playbook如今已经很容易实现。默认状况下,全部的redis安装完成以后,咱们能够统一为其分发配置文件。这个时候就面临一个问题,这些redis须要监听的地址各不相同,咱们也不可能为每个redis单独写一个配置文件。由于这些配置文件中,绝大部分的配置其实都是相同的。这个时候最好的方式其实就是用一个通用的配置文件来解决全部的问题。将全部须要修改的地方使用变量替换,以下示例中redis.conf.j2文件:
daemonize yes pidfile /var/run/redis.pid port 6379 logfile "/var/log/redis/redis.log" dbfilename dump.rdb dir /data/redis maxmemory 1G 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中如何来使用呢?
playbook使用template模块来实现模板文件的分发,其用法与copy模块基本相同,惟一的区别是,copy模块会将原文件原封不动的复制到被控端,而template会将原文件复制到被控端,而且使用变量的值将文件中的变量替换以生成完整的配置文件。
下面是一个完整的示例:
# cat config_redis.yml - name: Configure Redis hosts: test tasks: - 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模块的更多参数说明:
在上面的示例中,咱们直接取了被控节点的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_eth0.ipv4.address %} bind {{ ansible_eth0.ipv4.address }} 127.0.0.1 {% elif ansible_bond0.ipv4.address %} bind {{ ansible_bond0.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 1G {% if ansible_eth0.ipv4.address %} bind {{ ansible_eth0.ipv4.address }} 127.0.0.1 {% elif ansible_bond0.ipv4.address %} bind {{ ansible_bond0.ipv4.address }} 127.0.0.1 {% else%} bind 0.0.0.0 {% endif %} {% if redis_slave is defined %} slaveof {{ masterip }} {{ masterport|default(6379) }} {% endif %} {% if masterpass is defined %} masterauth {{ masterpass }} {% endif %} {% if requirepass is defined %} requirepass {{ requirepass }} {% 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 stop-writes-on-bgsave-error no
咱们定义一个inventory以下:
[redis] 10.1.61.27 redis_slave=true masterip=10.1.61.187 masterpass=123456 10.1.61.187 requirepass=123456
定义一个inventory示例以下:
[proxy] 10.1.61.195 [webserver] 10.1.61.27 10.1.61.187
如今把proxy主机组中的主机做为代理服务器,安装nginx作反向代理,将请求转发至后面的两台webserver,即webserver组的服务器。
如今咱们编写一个playbook以下:
#cat config_nginx.conf - name: gather facts gather_facts: Fasle hosts: webserver 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['webserver'] %} {% if hostvars[host]['ansible_bond0']['ipv4']['address'] is defined %} server {{ hostvars[host]['ansible_bond0']['ipv4']['address'] }}; {% elif hostvars[host]['ansible_eth0']['ipv4']['address'] is defined %} server {{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}; {% endif %} {% endfor %} } server { listen 80 default_server; server_name _; location / { proxy_pass http://web; } } }
下面再给一个域名解析服务bind的配置文件 named.conf的jinja2模板示例:
options { listen-on port 53 { 127.0.0.1; {% for ip in ansible_all_ipv4_addresses %} {{ ip }}; {% endfor %} }; listen-on-v6 port 53 { ::1; }; directory "/var/named"; dump-file "/var/named/data/cache_dump.db"; statistics-file "/var/named/data/named_stats.txt"; memstatistics-file "/var/named/data/named_mem_stats.txt"; }; zone "." IN { type hint; file "named.ca"; }; include "/etc/named.rfc1912.zones"; include "/etc/named.root.key"; {# Variables for zone config #} {% if 'authorativenames' in group_names %} {% set zone_type = 'master' %} {% set zone_dir = 'data' %} {% else %} {% set zone_type = 'slave' %} {% set zone_dir = 'slaves' %} {% endif %} zone "internal.example.com" IN { type {{ zone_type }}; file "{{ zone_dir }}/internal.example.com"; {% if 'authorativenames' not in group_names %} masters { 192.168.2.2; }; {% endif %} };
简单示例:
"Host": "{{ db_host | default('lcoalhost') }}"
正常状况下,当某个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
任务返回值过滤器:
下面是一个示例:
- name: test basename hosts: test vars: homepage: /usr/share/nginx/html/index.html tasks: - name: copy homepage copy: src: files/index.html dest: {{ homepage }}
能够经过basename改写成以下方式:
- name: test basename hosts: test vars: homepage: /usr/share/nginx/html/index.html tasks: - name: copy homepage copy: src: files/{{ homepage | basename }} dest: {{ homepage }}
举个简单的例子,如今有一个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(',') }}]
再次执行playbook,最后返回结果:
hosts = ["www.example.com","example.com"]
关于jinja2更多用法参考:http://docs.jinkan.org/docs/jinja2/