Skip to content
On this page

Ansible Playbook 基础

基础

ansible-playbook 常用选项

--step: 按步骤执行,每一步都需要操作员确认
--check: 检查playbook正确性,并运行干跑模式
--syntax-check: 仅检查语法错误
--list-tasks
--list-tags
--list-hosts
--step: 按步骤执行,每一步都需要操作员确认
--check: 检查playbook正确性,并运行干跑模式
--syntax-check: 仅检查语法错误
--list-tasks
--list-tags
--list-hosts

Host 和 Users

- hosts: webservers
  remote_user: yourname			# 切换用户
  become: yes
  become_user: postgres
---
- hosts: webservers
  remote_user: yourname			# 全局默认的用户
  tasks:
    - service:
        name: nginx
        state: started
      become: yes			# 对某一个task开启用户切换
      become_method: sudo		# 切换方法为sudo,此外还可以使用su
- hosts: webservers
  remote_user: yourname			# 切换用户
  become: yes
  become_user: postgres
---
- hosts: webservers
  remote_user: yourname			# 全局默认的用户
  tasks:
    - service:
        name: nginx
        state: started
      become: yes			# 对某一个task开启用户切换
      become_method: sudo		# 切换方法为sudo,此外还可以使用su

tasks

tasks:
  - name: 运行一些命令,并忽略其输出内容
    shell: /usr/bin/somecommand || /bin/true      # 或者使用`ignore_errors: true`
  - name: Copy ansible inventory file to client
    copy: src=/etc/ansible/hosts dest=/etc/ansible/hosts    # 使用缩进进行长句换行
            owner=root group=root mode=0644
tasks:
  - name: 运行一些命令,并忽略其输出内容
    shell: /usr/bin/somecommand || /bin/true      # 或者使用`ignore_errors: true`
  - name: Copy ansible inventory file to client
    copy: src=/etc/ansible/hosts dest=/etc/ansible/hosts    # 使用缩进进行长句换行
            owner=root group=root mode=0644

handlers

当配置文件改变时,调用handlers触发重启

tasks:
  - name: template configuration file
    template:
      src: template.j2
      dest: /etc/foo.conf
    notify:
      - restart memcached
      - restart apache
handlers:
  - name: restart memcached
    service:
      name: memcached
      state: restarted
  - name: restart apache
    service:
      name: apache
      state: restarted
tasks:
  - name: template configuration file
    template:
      src: template.j2
      dest: /etc/foo.conf
    notify:
      - restart memcached
      - restart apache
handlers:
  - name: restart memcached
    service:
      name: memcached
      state: restarted
  - name: restart apache
    service:
      name: apache
      state: restarted

在ansible2.2之后,可以使用listen创建"主题",tasks会通知这些"主题"

handlers:
    - name: restart memcached
      service:
        name: memcached
        state: restarted
      listen: "restart web services"
    - name: restart apache
      service:
        name: apache
        state: restarted
      listen: "restart web services"

tasks:
    - name: restart everything
      command: echo "this task will restart the web services"
      notify: "restart web services"
handlers:
    - name: restart memcached
      service:
        name: memcached
        state: restarted
      listen: "restart web services"
    - name: restart apache
      service:
        name: apache
        state: restarted
      listen: "restart web services"

tasks:
    - name: restart everything
      command: echo "this task will restart the web services"
      notify: "restart web services"

运行一个 Playbook

ansible-playbook [-C] playbook.yml [-f 10] [--list-hosts] [--verbose]
# -C 表示干跑测试模式,实际运行时应当取消, -f 为批次
ansible-playbook [-C] playbook.yml [-f 10] [--list-hosts] [--verbose]
# -C 表示干跑测试模式,实际运行时应当取消, -f 为批次

ansible-Pull

客户端主动拉取

ansible-lint(语法检查)

playbook语法及规范检查工具, 默认安装时并为安装, 使用yum install -y ansible-lint进行安装即可

使用方法:

ansible-lint playbook.yaml
ansible-lint playbook.yaml

当然, 你也可以使用ansible-playbook自带的ansible-playbook --syntax-check来检查

ansible-galaxy

ansible-galaxy客户端允许您从Ansible Galaxy下载角色,并且还提供了一个出色的默认框架来创建您自己的角色.

进阶

import_tasks & include_tasks

二者区别:

  • import*: 预处理运行
  • include*: 中途运行

如果任务就一个的话,两个用哪个都无所谓.

tasks:
- import_tasks: common_tasks.yml
  vars:   # 可以向指定playbook传递参数
    foot: bar
# or
- include_tasks: common_tasks.yml

# handlers中也可以使用
handlers:
- include_tasks: more_handlers.yml
# or
- import_tasks: more_handlers.yml
tasks:
- import_tasks: common_tasks.yml
  vars:   # 可以向指定playbook传递参数
    foot: bar
# or
- include_tasks: common_tasks.yml

# handlers中也可以使用
handlers:
- include_tasks: more_handlers.yml
# or
- import_tasks: more_handlers.yml

Role

Role 目录结构

roles/
├── dbsrvs
│   ├── defaults		# 默认变量存放位置
│   ├── files			# 所要传输或者使用的文件位置
│   ├── handlers
│   ├── meta			# 元数据存放位置,可存放角色依赖,参考https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html#role-dependencies
│   ├── tasks
│   ├── templates
│   └── vars			# 变量存放位置
└── websrvs
    ├── files
    ├── handlers
    ├── tasks
    ├── templates
    └── vars
roles/
├── dbsrvs
│   ├── defaults		# 默认变量存放位置
│   ├── files			# 所要传输或者使用的文件位置
│   ├── handlers
│   ├── meta			# 元数据存放位置,可存放角色依赖,参考https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html#role-dependencies
│   ├── tasks
│   ├── templates
│   └── vars			# 变量存放位置
└── websrvs
    ├── files
    ├── handlers
    ├── tasks
    ├── templates
    └── vars

示例及使用

# roles/example/tasks/main.yml
- name: added in 2.4, previously you used 'include'
  import_tasks: redhat.yml
  when: ansible_facts['os_family']|lower == 'redhat'		# when相当于条件语句
- import_tasks: debian.yml
  when: ansible_facts['os_family']|lower == 'debian'

# roles/example/tasks/redhat.yml
- yum:
    name: "httpd"
    state: present

# roles/example/tasks/debian.yml
- apt:
    name: "apache2"
    state: present
# roles/example/tasks/main.yml
- name: added in 2.4, previously you used 'include'
  import_tasks: redhat.yml
  when: ansible_facts['os_family']|lower == 'redhat'		# when相当于条件语句
- import_tasks: debian.yml
  when: ansible_facts['os_family']|lower == 'debian'

# roles/example/tasks/redhat.yml
- yum:
    name: "httpd"
    state: present

# roles/example/tasks/debian.yml
- apt:
    name: "apache2"
    state: present
# 调用角色
- hosts: webservers
  roles:
    - example
    - webservers
# 调用角色
- hosts: webservers
  roles:
    - example
    - webservers

在ansible2.4之后,可以使用import_roleinclude_role:

- hosts: webservers
  tasks:
    - debug:
        msg: "before we run our role"
    - import_role:
        name: example
    - include_role:
        name: example
    - debug:
        msg: "after we ran our role"
- hosts: webservers
  tasks:
    - debug:
        msg: "before we run our role"
    - import_role:
        name: example
    - include_role:
        name: example
    - debug:
        msg: "after we ran our role"

自定义role文件路径, 传递参数 ,tags

- hosts: webservers
  tasks:
    - include_role:
        name: foo_app_instance
      vars:
        dir: '/path/to/my/roles/common'
        app_port: 5000
      when: "ansible_facts['os_family'] == 'RedHat'"
      tags:
      	- web
        - redhat
- hosts: webservers
  tasks:
    - include_role:
        name: foo_app_instance
      vars:
        dir: '/path/to/my/roles/common'
        app_port: 5000
      when: "ansible_facts['os_family'] == 'RedHat'"
      tags:
      	- web
        - redhat

variables

playbook中直接定义:

- hosts: webservers
  vars:
    http_port: 80
- hosts: webservers
  vars:
    http_port: 80

在文件中定义:

- hosts: all
  remote_user: root
  vars:
    favcolor: blue
  vars_files:
    - /vars/external_vars.yml
- hosts: all
  remote_user: root
  vars:
    favcolor: blue
  vars_files:
    - /vars/external_vars.yml
# /vars/external_vars.yml
somevar: somevalue
password: magic
# /vars/external_vars.yml
somevar: somevalue
password: magic

在命令行中传递:

ansible-playbook websrvs.yml --extra-vars "http_ver=2.4.6"
ansible-playbook release.yml --extra-vars "@some_file.json"
ansible-playbook websrvs.yml --extra-vars "http_ver=2.4.6"
ansible-playbook release.yml --extra-vars "@some_file.json"

传递json或者json file 参考:https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#passing-variables-on-the-command-line

调用Facts中的变量:

# 查看
ansible websrvs -m setup
# 调用
【【 ansible_facts['devices']['xvda']['model'] 】】		# 层级结构

【【 ansible_facts['nodename'] 】】 
# 或者
【【 ansible_nodename 】】
# 查看
ansible websrvs -m setup
# 调用
【【 ansible_facts['devices']['xvda']['model'] 】】		# 层级结构

【【 ansible_facts['nodename'] 】】 
# 或者
【【 ansible_nodename 】】

ℹ️注意:默认情况下执行命令或者playbook会先收集facts,如果你知道你不需要任何来自facts的数据,那么可以使用以下参数来进行关闭:

- hosts: whatever
  gather_facts: no
- hosts: whatever
  gather_facts: no

传递本地facts:

/etc/ansible/facts.d目录下创建一个*.fact的文件, 即可将本地facts传递给主机setupansible_local``facts中去.

示例:

# /etc/ansible/facts.d/preferences.fact
[general]
asdf=1
bar=2
# 查看本地facts
ansible local -m setup -a "filter=ansible_local"
# 调用
【【 ansible_local['preferences']['general']['asdf'] 】】
# 或者
【【 ansible_local.prefetences.general.asdf 】】
# /etc/ansible/facts.d/preferences.fact
[general]
asdf=1
bar=2
# 查看本地facts
ansible local -m setup -a "filter=ansible_local"
# 调用
【【 ansible_local['preferences']['general']['asdf'] 】】
# 或者
【【 ansible_local.prefetences.general.asdf 】】

详情参考:https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#local-facts-facts-d

https://www.cnblogs.com/f-ck-need-u/p/7571974.html

注册与自定义变量:

    - name: register vars
      shell: echo bar
      register: foo
    - set_fact: var1="【【 foo.stdout 】】"
    - set_fact: var2=" foo ///  "
    - debug: msg="【【 var2 】】 【【var1】】"
    - name: register vars
      shell: echo bar
      register: foo
    - set_fact: var1="【【 foo.stdout 】】"
    - set_fact: var2=" foo ///  "
    - debug: msg="【【 var2 】】 【【var1】】"

with_item迭代变量

    - name: with_items
      shell: echo "【【 item 】】"
      with_items:
        - hello
        - ansible
    - name: with_items
      shell: echo "【【 item 】】"
      with_items:
        - hello
        - ansible

inventroy主机组变量和主机变量

# /etc/ansible/hosts
[websvrs]
node01	var1="var1 node01"
node02

[websrvs:vars]
var2="var2 websrvs"
# /etc/ansible/hosts
[websvrs]
node01	var1="var1 node01"
node02

[websrvs:vars]
var2="var2 websrvs"

📓综合vars示例:

---
- hosts: localhost
  gather_facts: yes
  vars:
    - http_port: 80
    - https_port: 443
  vars_files:
    - /root/playbooks/vars/external_vars.yml
    - /root/playbooks/vars/external_vars2.yml
  tasks:
    - name: show playbook vars
      debug: msg=" the http_port is  【【 http_port 】】 /// https_port is  【【 https_port 】】 "
      vars:
        http_port: 8080
    - name: import vars form localfile
      debug: msg=" vars from localfile 【【 hello 】】 /// 【【 ansible 】】"
    - name: import vars form local facts
      debug: msg=" vars form local facts --- 【【 ansible_local.preferences.general.bar 】】 "
    - name: import vars form commandline
      debug: msg=" vars form commandline -- 【【 command 】】  "
      ignore_errors: yes
    - name: import vars form json file
      debug: msg="ipv6 address is  --- 【【 ipv6[0].address 】】"

    - name: register vars
      shell: echo bar
      register: foo
    - set_fact: var1="【【 foo.stdout 】】"
    - set_fact: var2=" foo ///  "
    - debug: msg="【【 var2 】】 【【var1】】"

    - name: with_items
      shell: echo "【【 item 】】"
      with_items:
        - hello
        - ansible
      register: items
      tags: items
    - debug: var=items.results[0].stdout
      tags: items
    - debug: msg=" {% for i in items.results %} 【【 i.stdout 】】 {% endfor %} "
      tags: items

    - name: inventory var
      debug: msg=" 【【 inventory_var1 】】 /// 【【 inventory_var2  】】 "
      tags: inventory
---
- hosts: localhost
  gather_facts: yes
  vars:
    - http_port: 80
    - https_port: 443
  vars_files:
    - /root/playbooks/vars/external_vars.yml
    - /root/playbooks/vars/external_vars2.yml
  tasks:
    - name: show playbook vars
      debug: msg=" the http_port is  【【 http_port 】】 /// https_port is  【【 https_port 】】 "
      vars:
        http_port: 8080
    - name: import vars form localfile
      debug: msg=" vars from localfile 【【 hello 】】 /// 【【 ansible 】】"
    - name: import vars form local facts
      debug: msg=" vars form local facts --- 【【 ansible_local.preferences.general.bar 】】 "
    - name: import vars form commandline
      debug: msg=" vars form commandline -- 【【 command 】】  "
      ignore_errors: yes
    - name: import vars form json file
      debug: msg="ipv6 address is  --- 【【 ipv6[0].address 】】"

    - name: register vars
      shell: echo bar
      register: foo
    - set_fact: var1="【【 foo.stdout 】】"
    - set_fact: var2=" foo ///  "
    - debug: msg="【【 var2 】】 【【var1】】"

    - name: with_items
      shell: echo "【【 item 】】"
      with_items:
        - hello
        - ansible
      register: items
      tags: items
    - debug: var=items.results[0].stdout
      tags: items
    - debug: msg=" {% for i in items.results %} 【【 i.stdout 】】 {% endfor %} "
      tags: items

    - name: inventory var
      debug: msg=" 【【 inventory_var1 】】 /// 【【 inventory_var2  】】 "
      tags: inventory

🛩️扩展阅读:

lineinfile

该模块确保文件中包含特定行,或使用向后引用的正则表达式替换现有行。

只适用于一行内容.

# NOTE: Before 2.3, option 'dest', 'destfile' or 'name' was used instead of 'path'
- name: 确保SElinux为enforce窗台
  lineinfile:
    path: /etc/selinux/config
    regexp: '^SELINUX='
    line: SELINUX=enforcing

- name: 确保wheel组不在sudoers配置文件中
  lineinfile:
    path: /etc/sudoers
    state: absent
    regexp: '^%wheel'

- name: 确保127.0.0.1为本地localhost
  lineinfile:
    path: /etc/hosts
    regexp: '^127\.0\.0\.1'
    line: 127.0.0.1 localhost
    owner: root
    group: root
    mode: '0644'

- name: 确保httpd的监听端口为8080
  lineinfile:
    path: /etc/httpd/conf/httpd.conf
    regexp: '^Listen '
    insertafter: '^#Listen '
    line: Listen 8080

- name: 确保注释在匹配文本上面
  lineinfile:
    path: /etc/services
    regexp: '^# port for http'
    insertbefore: '^www.*80/tcp'
    line: '# port for http by default'

- name: 为空文件添加一行内容
  lineinfile:
    path: /tmp/testfile
    line: 192.168.1.99 foo.lab.net foo
    create: yes

# NOTE: Yaml requires escaping backslashes in double quotes but not in single quotes
- name: Ensure the JBoss memory settings are exactly as needed
  lineinfile:
    path: /opt/jboss-as/bin/standalone.conf
    regexp: '^(.*)Xms(\\d+)m(.*)$'
    line: '\1Xms${xms}m\3'
    backrefs: yes

# NOTE: Fully quoted because of the ': ' on the line. See the Gotchas in the YAML docs.
- name: Validate the sudoers file before saving
  lineinfile:
    path: /etc/sudoers
    state: present
    regexp: '^%ADMIN ALL='
    line: '%ADMIN ALL=(ALL) NOPASSWD: ALL'
    validate: /usr/sbin/visudo -cf %s
# NOTE: Before 2.3, option 'dest', 'destfile' or 'name' was used instead of 'path'
- name: 确保SElinux为enforce窗台
  lineinfile:
    path: /etc/selinux/config
    regexp: '^SELINUX='
    line: SELINUX=enforcing

- name: 确保wheel组不在sudoers配置文件中
  lineinfile:
    path: /etc/sudoers
    state: absent
    regexp: '^%wheel'

- name: 确保127.0.0.1为本地localhost
  lineinfile:
    path: /etc/hosts
    regexp: '^127\.0\.0\.1'
    line: 127.0.0.1 localhost
    owner: root
    group: root
    mode: '0644'

- name: 确保httpd的监听端口为8080
  lineinfile:
    path: /etc/httpd/conf/httpd.conf
    regexp: '^Listen '
    insertafter: '^#Listen '
    line: Listen 8080

- name: 确保注释在匹配文本上面
  lineinfile:
    path: /etc/services
    regexp: '^# port for http'
    insertbefore: '^www.*80/tcp'
    line: '# port for http by default'

- name: 为空文件添加一行内容
  lineinfile:
    path: /tmp/testfile
    line: 192.168.1.99 foo.lab.net foo
    create: yes

# NOTE: Yaml requires escaping backslashes in double quotes but not in single quotes
- name: Ensure the JBoss memory settings are exactly as needed
  lineinfile:
    path: /opt/jboss-as/bin/standalone.conf
    regexp: '^(.*)Xms(\\d+)m(.*)$'
    line: '\1Xms${xms}m\3'
    backrefs: yes

# NOTE: Fully quoted because of the ': ' on the line. See the Gotchas in the YAML docs.
- name: Validate the sudoers file before saving
  lineinfile:
    path: /etc/sudoers
    state: present
    regexp: '^%ADMIN ALL='
    line: '%ADMIN ALL=(ALL) NOPASSWD: ALL'
    validate: /usr/sbin/visudo -cf %s

replace

替换所有, 与lineinfile不同

- name: Before Ansible 2.3, option 'dest', 'destfile' or 'name' was used instead of 'path'
  replace:
    path: /etc/hosts
    regexp: '(\s+)old\.host\.name(\s+.*)?$'
    replace: '\1new.host.name\2'

- name: Replace after the expression till the end of the file (requires Ansible >= 2.4)
  replace:
    path: /etc/apache2/sites-available/default.conf
    after: 'NameVirtualHost [*]'
    regexp: '^(.+)$'
    replace: '# \1'

- name: Replace before the expression till the begin of the file (requires Ansible >= 2.4)
  replace:
    path: /etc/apache2/sites-available/default.conf
    before: '# live site config'
    regexp: '^(.+)$'
    replace: '# \1'

# Prior to Ansible 2.7.10, using before and after in combination did the opposite of what was intended.
# see https://github.com/ansible/ansible/issues/31354 for details.
- name: Replace between the expressions (requires Ansible >= 2.4)
  replace:
    path: /etc/hosts
    after: '<VirtualHost [*]>'
    before: '</VirtualHost>'
    regexp: '^(.+)$'
    replace: '# \1'

- name: Supports common file attributes
  replace:
    path: /home/jdoe/.ssh/known_hosts
    regexp: '^old\.host\.name[^\n]*\n'
    owner: jdoe
    group: jdoe
    mode: '0644'

- name: Supports a validate command
  replace:
    path: /etc/apache/ports
    regexp: '^(NameVirtualHost|Listen)\s+80\s*$'
    replace: '\1 127.0.0.1:8080'
    validate: '/usr/sbin/apache2ctl -f %s -t'

- name: Short form task (in ansible 2+) necessitates backslash-escaped sequences
  replace: path=/etc/hosts regexp='\\b(localhost)(\\d*)\\b' replace='\\1\\2.localdomain\\2 \\1\\2'

- name: Long form task does not
  replace:
    path: /etc/hosts
    regexp: '\b(localhost)(\d*)\b'
    replace: '\1\2.localdomain\2 \1\2'

- name: Explicitly specifying positional matched groups in replacement
  replace:
    path: /etc/ssh/sshd_config
    regexp: '^(ListenAddress[ ]+)[^\n]+$'
    replace: '\g<1>0.0.0.0'

- name: Explicitly specifying named matched groups
  replace:
    path: /etc/ssh/sshd_config
    regexp: '^(?P<dctv>ListenAddress[ ]+)(?P<host>[^\n]+)$'
    replace: '#\g<dctv>\g<host>\n\g<dctv>0.0.0.0'
- name: Before Ansible 2.3, option 'dest', 'destfile' or 'name' was used instead of 'path'
  replace:
    path: /etc/hosts
    regexp: '(\s+)old\.host\.name(\s+.*)?$'
    replace: '\1new.host.name\2'

- name: Replace after the expression till the end of the file (requires Ansible >= 2.4)
  replace:
    path: /etc/apache2/sites-available/default.conf
    after: 'NameVirtualHost [*]'
    regexp: '^(.+)$'
    replace: '# \1'

- name: Replace before the expression till the begin of the file (requires Ansible >= 2.4)
  replace:
    path: /etc/apache2/sites-available/default.conf
    before: '# live site config'
    regexp: '^(.+)$'
    replace: '# \1'

# Prior to Ansible 2.7.10, using before and after in combination did the opposite of what was intended.
# see https://github.com/ansible/ansible/issues/31354 for details.
- name: Replace between the expressions (requires Ansible >= 2.4)
  replace:
    path: /etc/hosts
    after: '<VirtualHost [*]>'
    before: '</VirtualHost>'
    regexp: '^(.+)$'
    replace: '# \1'

- name: Supports common file attributes
  replace:
    path: /home/jdoe/.ssh/known_hosts
    regexp: '^old\.host\.name[^\n]*\n'
    owner: jdoe
    group: jdoe
    mode: '0644'

- name: Supports a validate command
  replace:
    path: /etc/apache/ports
    regexp: '^(NameVirtualHost|Listen)\s+80\s*$'
    replace: '\1 127.0.0.1:8080'
    validate: '/usr/sbin/apache2ctl -f %s -t'

- name: Short form task (in ansible 2+) necessitates backslash-escaped sequences
  replace: path=/etc/hosts regexp='\\b(localhost)(\\d*)\\b' replace='\\1\\2.localdomain\\2 \\1\\2'

- name: Long form task does not
  replace:
    path: /etc/hosts
    regexp: '\b(localhost)(\d*)\b'
    replace: '\1\2.localdomain\2 \1\2'

- name: Explicitly specifying positional matched groups in replacement
  replace:
    path: /etc/ssh/sshd_config
    regexp: '^(ListenAddress[ ]+)[^\n]+$'
    replace: '\g<1>0.0.0.0'

- name: Explicitly specifying named matched groups
  replace:
    path: /etc/ssh/sshd_config
    regexp: '^(?P<dctv>ListenAddress[ ]+)(?P<host>[^\n]+)$'
    replace: '#\g<dctv>\g<host>\n\g<dctv>0.0.0.0'

参考链接