目錄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/