13-Ansible-playbook剧本进阶

devops运维开发高度自动化。

运维,python首要语言。

Ansible-playbook 有许多高级特性,这些特性可以帮助用户编写更复杂、灵活和高效的自动化脚本。以下为你详细介绍一些常见的高级特性:

1. 变量和事实(Variables and Facts)

变量定义与使用

在 Playbook 中可以定义多种类型的变量,如全局变量、主机变量、组变量等,并且可以在task中引用这些变量。

---
- name: Use variables in playbook
  hosts: webservers
  vars:
    app_port: 8080
  tasks:
    - name: Start application
      command: ./app start --port {{ app_port }}

事实(Facts)

Ansible 会自动收集目标主机的系统信息,即事实(Facts),这些信息可以作为变量在 Playbook 中使用。例如,ansible_os_family 可以用于判断目标主机的操作系统家族。

---
- name: Install package based on OS family
  hosts: all
  tasks:
    - name: Install appropriate package
      package:
      #jinja2 变量替换,条件判断语句
        name: "{{ 'apache2' if ansible_os_family == 'Debian' else 'httpd' }}"
        state: present

2. 条件判断(When Statements)

使用 when 语句可以根据条件决定是否执行某个任务。

---
- name: Conditional task execution
  hosts: servers
  tasks:
    - name: Restart service on specific hosts
      service:
        name: myservice
        state: restarted
      when: ansible_hostname in ['server1', 'server2']

3. 循环(Loops)

简单循环

可以使用 loop 关键字对列表或字典进行循环操作。

---
- name: Install multiple packages
  hosts: all
  tasks:
    - name: Install packages
      apt:
        name: "{{ item }}"
        state: present
      loop:
        - nginx
        - mysql-server
        - php

嵌套循环

支持嵌套循环,处理更复杂的数据结构。

---
- name: Create multiple users with different groups
  hosts: all
  tasks:
    - name: Create users
      user:
        name: "{{ item[0] }}"
        groups: "{{ item[1] }}"
        state: present
      loop:
        - ['user1', 'group1']
        - ['user2', 'group2']

4. 错误处理(Error Handling)

忽略错误

使用 ignore_errors 关键字可以忽略某个任务执行过程中产生的错误,继续执行后续任务。

---
- name: Ignore errors in a task
  hosts: web
  tasks:
    - name: Try to run a command that may fail
      command: /path/to/command
      #ignore_errors: true
    - name: Continue with the next task
      debug:
        msg: "This task will run even if the previous one failed."

失败时重试

使用 retriesdelay 关键字可以在任务失败时进行重试。

---
- name: Example playbook with retries and delay
  hosts: web  # 替换为实际的目标主机或主机组
  gather_facts: false

  tasks:
    - name: Run a command with retries
      command: /usr/bin/some_command  # 替换为实际要执行的命令
      retries: 3  # 重试次数
      delay: 5    # 每次重试间隔的秒数
      register: result  # 注册任务结果,用于后续判断
      until: result.rc == 0  # 直到命令返回状态码为0(表示成功)才停止重试

    - name: Print result
      debug:
        var: result

代码解释

  1. command 模块:用于在远程主机上执行指定的命令。
  2. retries:设置任务失败后重试的次数为 3 次。
  3. delay:设置每次重试之间的间隔时间为 5 秒。
  4. register:将任务的执行结果存储在变量 result 中,方便后续使用。
  5. until:指定一个条件,当该条件满足时停止重试。在这个例子中,当命令的返回状态码 rc 为 0 时,表示命令执行成功,停止重试。

运行剧本

5. 角色(Roles)

角色是一种组织 Playbook 代码的方式,将相关的任务、变量、模板等组织在一起,提高代码的复用性和可维护性。

角色目录结构

roles/
└── webserver/ LNMP(nginx,mysql,php,配置文件变量,启动脚本)
    ├── defaults/
    │   └── main.yml
    ├── files/nginx.conf *.conf
    ├── handlers/
    │   └── main.yml
    ├── meta/
    │   └── main.yml
    ├── tasks/
    │   └── main.yml
    ├── templates/
    └── vars/
        └── main.yml

在 Playbook 中使用角色

---
- name: Use roles in playbook
  hosts: webservers
  roles:
    - webserver

6. 块(Blocks)

块可以将多个任务组合在一起,方便进行统一的错误处理、条件判断等操作。

---
- name: Use blocks in playbook
  hosts: all
  tasks:
    - block:
        - name: Task 1
          command: /path/to/command1
        - name: Task 2
          command: /path/to/command2
      when: ansible_os_family == "Debian"
      rescue:
        - name: Handle errors
          debug:
            msg: "An error occurred in the block."

7. 标签(Tags)

使用标签可以选择性地执行 Playbook 中的部分任务。

---
- name: Use tags in playbook
  hosts: all
  tasks:
    - name: Task 1
      command: /path/to/command1
      tags: tag1
    - name: Task 2
      command: /path/to/command2
      tags: tag2

# 只执行带有 tag1 标签的任务
ansible-playbook playbook.yml --tags tag1

8. 异步执行(Asynchronous Execution)

对于一些耗时较长的任务,可以使用异步执行来避免长时间等待。

---
- name: Asynchronous task execution
  hosts: all
  tasks:
    - name: Run a long-running command asynchronously
      command: /path/to/long-running-command
      async: 3600  # 最大执行时间(秒)
      poll: 0      # 不轮询结果

剧本高级特性篇

循环

在写 playbook 的时候发现了很多 task 都要重复引用某个相同的模块,比如一次启动10个服务,或者一次拷贝10个文件,如果按照传统的写法最少要写10次,这样会显得 playbook 很臃肿。

如果使用循环的方式来编写 playbook ,这样可以减少重复编写 task 带来的臃肿。

https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html?highlight=loop
关于循环的标准用法

早期ansible教程中,关于循环关键字是with_item


ansible自2.5版本后,通过loop关键字提供循环功能

root@ansible-01:~/ansible_shell# ansible --version
ansible 2.10.8
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3/dist-packages/ansible
  executable location = /usr/bin/ansible
  python version = 3.10.12 (main, Jan 17 2025, 14:35:34) [GCC 11.4.0]

在 Ansible Playbook 里,loop 是一个强大的特性,它能够对列表、字典等数据结构进行迭代,从而让单个任务可以多次执行,下面为你详细介绍 loop 的语法和不同使用场景。

基本语法

loop 的基本使用形式是在任务中添加 loop 关键字,后面紧跟要迭代的数据结构。示例如下:

- name: 任务名称
  模块名:
    参数: "{{ item }}"
  loop:
    - 元素1
    - 元素2
    - 元素3

在这个结构中,item 是一个特殊的变量,它在每次迭代时会依次代表列表里的各个元素。

下面详细介绍一下 Ansible Playbook 中使用 loop 遍历列表的各种用法和技巧。


1. 基本用法

对于一个简单的列表(例如字符串列表),你可以直接用 loop 来迭代每个元素:

- name: 安装常用软件包
  hosts: h_nfs
  become: yes
  tasks:
    - name: 安装软件包
      apt:
        name: "{{ item }}"
        state: present
        update_cache: yes
      loop:
        - vim
        - git
        - curl

说明:

  • 每次迭代时,item 会依次取列表中的 "vim""git""curl"

2. 循环字典列表

有时列表中的每个元素都是一个字典,这样可以为每个项定义多个属性。例如:

- name: 创建用户
  hosts: nfs
  become: yes
  vars:
    users:
      - name: alice
        uid: 1001
        shell: /bin/bash
        addr: Beijing
        phone: 10086
      - name: bob
        uid: 1002
        shell: /bin/zsh
        addr: JiangSu
        phone: 10010
  tasks:
    - name: 添加用户
      user:
        name: "{{ item.name }}"
        uid: "{{ item.uid }}"
        shell: "{{ item.shell }}"
        state: present
      loop: "{{ users }}"

说明:

  • 列表 users 中的每个元素都是一个字典,通过 item.nameitem.uid 等方式可以访问字典中的属性。

3. 使用loop_control 自定义循环变量

如果你需要在循环中访问当前项的索引或修改 item 的名称,可以使用 loop_control

- name: 输出列表元素及其索引
  hosts: localhost
  gather_facts: no
  tasks:
    - name: 打印元素和索引
      debug:
        msg: "索引 {{ index }} 的值是 {{ item }}"
      loop:
        - apple
        - banana
        - cherry
      loop_control:
        index_var: yu_index

说明:

  • loop_control.index_var 允许你定义一个变量(本例中为 index),来存储当前项的索引(从 0 开始)。
  • 除了 index_var,还可以使用 label(如果你想显示其他信息)等选项。

进阶loop_control

loop_control 在 Ansible 中的使用教程

在 Ansible 中,loop_control 用于增强 loop 循环的控制能力,提供更细粒度的配置,如修改 index_var 变量名、跳过空值、调整循环的输出格式等。


1. 基本使用

loop_control 主要用于:

  • 控制 index_var 变量名(循环索引)
  • 设置 label(日志输出时显示更友好的信息)
  • 跳过空值
  • extended 选项(用于控制如何解析 loop

示例:

- hosts: localhost
  gather_facts: no
  tasks:
    - name: 使用 loop_control 进行循环控制
      debug:
        msg: "当前用户:{{ item }},索引:{{ my_index }}"
      loop:
        - user1
        - user2
        - user3
      loop_control:
        index_var: my_index

输出示例

TASK [使用 loop_control 进行循环控制] ***
ok: [localhost] => (item=user1) => {
    "msg": "当前用户:user1,索引:0"
}
ok: [localhost] => (item=user2) => {
    "msg": "当前用户:user2,索引:1"
}
ok: [localhost] => (item=user3) => {
    "msg": "当前用户:user3,索引:2"
}

2. 使用 label让日志更易读

默认情况下,日志输出 item 可能会很长或不易读。可以使用 label 让日志更加直观。

示例:

- hosts: localhost
  gather_facts: no
  tasks:
    - name: 任务执行
      debug:
        msg: "正在处理 {{ item.name }},ID:{{ item.id }}"
      loop:
        - { name: "Alice", id: 101 }
        - { name: "Bob", id: 102 }
      loop_control:
        label: "{{ item.name }}"

输出示例

TASK [任务执行] ***
ok: [localhost] => (item=Alice) => {
    "msg": "正在处理 Alice,ID:101"
}
ok: [localhost] => (item=Bob) => {
    "msg": "正在处理 Bob,ID:102"
}


4. 使用 pause 结合 loop_control

在循环中添加 pause 任务,并在日志中显示更友好的信息:

- hosts: localhost
  gather_facts: no
  tasks:
    - name: 每个用户执行任务前暂停 2 秒
      pause:
        seconds: 8
      loop:
        - Alice
        - Bob
      loop_control:
        label: "暂停2s前处理 {{ item }}"

输出

TASK [每个用户执行任务前暂停 5 秒] ***
Pausing for 5 seconds (暂停前处理 Alice)
(ctrl+C 可跳过)

Pausing for 5 seconds (暂停前处理 Bob)
(ctrl+C 可跳过)

结合 with_items 和 loop

虽然较早版本的 Ansible 使用 with_items 进行循环,但推荐使用 loop。下面是对比示例:

  • with_items 示例:

    - hosts: localhost
      gather_facts: no
      tasks:
      - name: 使用 with_items 安装软件包
        apt:
          name: ""
          state: present
        with_items:
          - vim
          - git
    
  • loop 示例:

    - name: 使用 loop 安装软件包
      apt:
        name: ""
        state: present
      loop:
        - vim
        - git
    

建议: 新项目中推荐使用 loop,因为它语法统一、扩展性好。


5. 循环嵌套列表或使用 subelements

在 Ansible 的 loop 中,subelements 主要用于遍历嵌套列表(字典),即当一个列表中的每个元素本身,包含另一个列表时,subelements 可以展开子元素并进行迭代。

当你有一个包含子项的字典列表,需要对每个字典的子项进行迭代时。

常见用于用户管理、权限分配、磁盘挂载等场景

如果列表元素中嵌套了其它列表,可以使用 subelements 来处理。例如:

- name: 遍历用户及其所属组
  hosts: localhost
  gather_facts: no
  vars:
    users:
      - name: alice
        groups:
          - admin
          - developers
      - name: bob
        groups:
          - testers
          - developers
  tasks:
    - name: 输出用户及其组
      debug:
        msg: "用户 {{ item.0.name }} 属于组 {{ item.1 }}"
      loop: "{{ users | subelements('groups') }}"

7. 小结

  • 基本用法: 使用 loop 遍历列表,item 表示当前迭代的元素。
  • 字典列表: 对于列表中的字典,可以直接通过 item.属性 访问各属性。
  • 自定义变量: 利用 loop_control 可以增加索引变量或自定义显示信息。
  • 嵌套数据结构: 使用 subelements 处理嵌套列表。
  • 字典遍历: 推荐使用 dict2items 过滤器,将字典转换为易于遍历的列表。

这些用法可以帮助你更灵活地处理各类数据结构,并在 Ansible Playbook 中实现复杂的循环逻辑。

创建多个系统用户

需求,在nfs机器组中创建5个用户test1~5,且均设置密码yuchao666

比较low的写法,看着都头疼,但是没办法,语法就是这样

(添加、或是删除用户,区别在于state=absent)

---
- name: create user test1~5
  hosts: nfs
  vars:
  tasks:
    - name: create test1
      user: 
        name: test1
        state: absent
    - name: create test2
      user: 
        name: test2
        state: absent
    - name: create test3
      user: 
        name: test3
        state: absent
    - name: create tes4
      user: 
        name: test4
        state: absent
    - name: create test5
      user: 
        name: test5 
        state: absent

循环创建用户

在 Ansible Playbook 中使用 loop 循环可以很方便地创建多个系统用户。以下是详细的实现步骤和示例代码。

实现思路

  1. 定义用户列表:在 Playbook 中定义一个包含多个用户信息的列表,每个用户信息可以包含用户名、密码、家目录等。
  2. 使用 loop 循环:在创建用户的任务中使用 loop 关键字,对用户列表进行迭代,为每个用户执行创建操作。
  3. 使用 user 模块user 模块是 Ansible 中用于管理系统用户的模块,可以通过该模块指定用户名、密码、家目录等参数来创建用户。

通过vars变量定义循环变量

上面会发现已然有重复的变量,还可以再简化

  • 通过vars关键字定义用户列表,变量一般定义在任务开始之前
  • 通过item关键字提取loop中每次循环的数据

在 Ansible 中,可以使用 loop 来批量创建用户,并使用 password 进行加密设置密码。以下是一个适用于 Ubuntu 的 ansible-playbook,用于创建 10 个用户并设置密码:

1. 生成密码

Ansible 需要加密密码才能正确设置用户密码。可以使用 mkpasswd 生成加密密码:

apt install -y whois  # 安装 mkpasswd
mkpasswd -m sha-512 "你的密码"

复制生成的加密密码,在 playbook.yml 中使用。


2. 编写 playbook.yml

chaoge666

bob666

Tom666

- name: Create users and set passwords
  hosts: h_nfs
  become: yes
  tasks:
    - name: Create users
      user:
        name: "{{ item.name }}"
        password: "{{ item.password }}"
        shell: /bin/bash
        state: present
      loop:
        - { name: "jack01", password: "$6$.BkTLJlokQZIvb9d$53YcUedHNAycMkN8NfqC5vxKzcOqIgE82vnsV2QzWEYqWZ7yMondl88WiPBA.jpuFg2Up/NowP/qdr1TopWd7/" }
        - { name: "bob01", password: "$6$tRGM8qdcXjjiBetl$cq561R/RYlxdhG1n8j.ebWwKrIkoHpb/Su5PHEf8jCZr6oW8IlccClrtTLO/VX/Dp0clTaajdN5QtqNHGbYD6." }
        - { name: "tom01", password: "$6$htz7bcNnngDsvRK/$dji.yoCQ5gT/67lJVaLlKWiqRmvZuUD5cCTrXxZ6LgAhwqDjHdc7fU7BYw6eM8.FnMP1zI1nd3VoeqNYNwB3J." }


    - name: Ensure users can log in
      file:
        path: "/home/{{ item.name }}"
        state: directory
        owner: "{{ item.name }}"
        group: "{{ item.name }}"
        mode: "0755"
      loop:
        - { name: "jack01" }
        - { name: "bob01" }
        - { name: "tom01" }

3. 运行 Playbook

ansible-playbook -i inventory.ini playbook.yml

inventory.ini 示例:

[servers]
your_server_ip ansible_user=your_ssh_user ansible_password=your_ssh_password ansible_become_pass=your_sudo_password

这样就会创建 10 个用户,并设置他们的密码。

循环处理字典数据

创建用户以及用户id号

low办法超哥就不再写了,太墨迹

循环字典数据如下,字典就是key:value这样的数据

字典用法,主要是根据key、获取value

---
- name: create user
  hosts: h_nfs
  tasks: 
  - name: create user and uid
    user:
        name: "{{ item.user }}"
        uid: "{{ item.uid }}"


    loop:
      - {user: 'test1', uid: '2000'}
      - {user: 'test2', uid: '2001'}
      - {user: 'test3', uid: '2002'}
      - {user: 'test4', uid: '2003'}

写法2:通过vars定义字典数据

  • vars定义字典数据
  • loop提供循环功能,通过item变量提取循环数据
---
- name: create user
  hosts: nfs
  vars:
    users_list:
      - {user: 'test1', uid: '2000'}
      - {user: 'test2', uid: '2001'}
      - {user: 'test3', uid: '2002'}
      - {user: 'test4', uid: '2003'}
  tasks:
  - name: create user and uid
    user:
      name: "{{ item.user}}"
      uid: "{{ item.uid }}"
    loop: "{{ users_list }}"

案例

在 Ansible Playbook 里,你可以借助 loop 对字典数据进行循环处理。下面为你详细介绍不同场景下循环处理字典数据的方法和示例。

场景一:遍历字典的键值对

当你需要对字典中的每一个键值对进行操作时,可以采用以下方式。

示例代码

---
- name: Loop through dictionary key - value pairs
  hosts: localhost
  gather_facts: no
  vars:
    user_info:
      name: John
      age: 30
      occupation: Engineer
  tasks:
    - name: Print key - value pairs
      debug:
        msg: "Key: {{ item.0 }}, Value: {{ item.1 }}"
      loop: "{{ user_info.items() }}"

场景二:根据字典数据创建多个文件

假设你有一个字典,其中键表示文件名,值表示文件内容,你可以利用循环来创建这些文件。

示例代码

---
- name: Create files based on dictionary data
  hosts: localhost
  gather_facts: no
  become: true
  vars:
    file_content:
      file1.txt: "This is the content of file1."
      file2.txt: "This is the content of file2."
      file3.txt: "This is the content of file3."
  tasks:
    - name: Create files
      copy:
        content: "{{ item.1 }}"
        dest: "/opt/{{ item.0 }}"
      loop: "{{ file_content.items() }}"

场景三:根据字典数据配置多个服务

假设你有一个字典,包含多个服务的配置信息,你可以使用循环来配置这些服务。

示例代码

---
- name: Configure services based on dictionary data
  hosts: localhost
  gather_facts: no
  become: true
  vars:
    service_config:
      httpd:
        state: started
        enabled: yes
      nginx:
        state: stopped
        enabled: no
  tasks:
    - name: Configure services
      service:
        name: "{{ item.key }}"
        state: "{{ item.value.state }}"
        enabled: "{{ item.value.enabled }}"
      loop: "{{ service_config.items() }}"

items方法将字典元素,转换为 列表 [key,value]

循环安装多个软件(yum基础环境安装)

在咱们期中综合架构开篇时,需要大家系统初始化,这个初始化步骤也是需要你做成ansible脚本的。
比如如下大量的基础软件,如何安装?

yum install -y tree wget bash-completion bash-completion-extras lrzsz net-tools sysstat iotop iftop htop unzip telnet ntpdate lsof

# apt yum软件包名不一样

必然不能挨个的执行yum模块去安装,那得累死,循环写法如下

- name: yuchaoit.cn
  hosts: nfs
  remote_user: root
  tasks:
    - name: install basic packages
      yum:
        name: "{{ item }}"
        state: installed
      loop:
        - tree
        - wget 
        - bash-completion 
        - bash-completion-extras 
        - lrzsz 
        - net-tools 
        - sysstat 
        - iotop 
        - iftop
        - htop
        - unzip
        - telnet 
        - ntpdate
        - lsof

写法2:通过vars定义变量

- name: yuchaoit.cn
  hosts: nfs
  remote_user: root
  vars:
    basic_packages:
        - tree
        - wget 
        - bash-completion 
        - bash-completion-extras 
        - lrzsz 
        - net-tools 
        - sysstat 
        - iotop 
        - iftop
        - htop
        - unzip
        - telnet 
        - ntpdate
        - lsof
  tasks:
    - name: install basic packages
      yum:
        name: "{{ item }}"
        state: installed
      loop: "{{ basic_packages }}"

案例

在 Ansible Playbook 中,你可以使用 loop 来循环安装多个软件。以下分别针对不同操作系统家族(Debian 和 Red Hat)给出具体示例。

针对 Debian 系系统(如 Ubuntu)

示例 Playbook

---
- name: Install multiple packages on Debian systems
  hosts: debian_servers
  become: true
  tasks:
    - name: Install packages
      apt:
        name: "{{ item }}"
        state: present
        update_cache: yes
      loop:
        - nginx
        - mysql-server
        - php-fpm

代码解释

  1. hosts:指定该 Playbook 要在哪些主机或主机组上执行,这里假设存在名为 debian_servers 的主机组。
  2. become: true:表示使用 sudo 权限执行任务,因为安装软件通常需要管理员权限。
  3. tasks
    • name:任务的描述性名称,方便在执行时识别。
    • apt:Ansible 中用于在 Debian 系系统上管理软件包的模块。
    • name: ""itemloop 迭代时的变量,每次迭代会依次取 loop 列表中的元素作为软件包名称。
    • state: present:确保软件包已安装。
    • update_cache: yes:在安装软件包之前更新软件包缓存。
    • loop:包含要安装的软件包名称的列表。

针对 Red Hat 系系统(如 CentOS)

示例 Playbook

---
- name: Install multiple packages on Red Hat systems
  hosts: redhat_servers
  become: true
  tasks:
    - name: Install packages
      yum:
        name: "{{ item }}"
        state: present
      loop:
        - httpd
        - mariadb-server
        - php

代码解释

  1. hosts:指定目标主机或主机组,这里假设存在名为 redhat_servers 的主机组。
  2. become: true:同样是为了获取管理员权限。
  3. tasks
    • name:任务名称。
    • yum:Ansible 中用于在 Red Hat 系系统上管理软件包的模块。
    • name: "":通过 item 变量迭代获取软件包名称。
    • state: present:保证软件包已安装。
    • loop:列出要安装的软件包。

通用示例(兼容不同系统)

示例 Playbook

---
- name: Install multiple packages on different systems
  hosts: all
  become: true
  tasks:
    - name: Install packages on Debian systems
      apt:
        name: "{{ item }}"
        state: present
        update_cache: yes
      loop:
        - nginx
        - mysql-server
        - php-fpm
      when: ansible_os_family == "Debian"

    - name: Install packages on Red Hat systems
      yum:
        name: "{{ item }}"
        state: present
      loop:
        - httpd
        - mariadb-server
        - php
      when: ansible_os_family == "RedHat"

代码解释

  1. hosts: all:表示该 Playbook 会在所有主机上执行。
  2. when 条件:根据目标主机的操作系统家族(通过 ansible_os_family 变量判断)来决定执行哪个安装任务。如果是 Debian 系系统,则执行 apt 模块的安装任务;如果是 Red Hat 系系统,则执行 yum 模块的安装任务。

你可以将上述 Playbook 保存为一个 .yml 文件,然后使用 ansible-playbook 命令来执行,例如:

ansible-playbook install_packages.yml

这样就能通过循环的方式在目标主机上安装多个软件包了。

rsync文件夹场景

比如部署nfs、rsync、nginx的综合剧本;
1.要安装多个软件
2.创建多个目录
3.复制多个目录
4.每个文件的权限都不一样

循环风格1:单行模式

比如rsync创建备份目录,有多个目录需要创建,普通的写法出现了诸多重复语句

- name: create data dir
  file: path=/data state=directory owner=www group=www

- name: create backup dir
  file: path=/backup state=directory owner=www group=www

循环风格2:缩进模式

上述创建备份目录的剧本语法,也可以用如下的缩进模式写,但是依然很多重复语句

- name: create data dir
  file:
    path: /data
    state: directory
    owner: www
    group: www

- name: create backup dir
  file:
    path: /backup
    state: directory
    owner: www
    group: www

改造为循环语句,使用yaml的缩进语法

- name: create data,backup dir
  file:
    path: "{{ item }}"
    state: directory
    owner: www
    group: www
  loop:
    - /data
    - /backup

循环风格3:混合语法

  • 等号赋值语法
  • 缩进语法
- name: create data backup 
  file: path="{{ item }}" state=directory owner=www group=www
  loop:
    - /data
    - /backup

循环风格4:循环结合字典取值

比如rsync服务部署,需要创建多个文件夹,以及对应的权限设置

- name: yuchaoit.cn
  hosts: backup
  tasks:
  - name: create_data
    file:
      path: "{{ item.file_path }}"
      state: directory
      owner: www
      group: www
      mode: "{{ item.mode }}"
    loop:
      - { file_path:'/data' ,mode:'755' }
      - { file_path:'/backup' ,mode:'755' }

变量定义

官网文档
https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html

在ansible中使用变量,能让我们的工作变得更加灵活,在ansible中,变量的使用方式有很多种,我们慢慢聊。

先说说怎样定义变量,变量名应该由字母、数字、下划线组成,变量名需要以字母开头,ansible内置的关键字不能作为变量名。

变量我们已经在循环知识中使用过了,主要通过vars关键字定义变量名、以及对应的变量值。

在 Ansible Playbook 中,变量是非常重要的特性,它可以让 Playbook 更加灵活和可复用。以下从变量的定义、作用域、优先级以及使用方法等方面详细介绍 Ansible Playbook 变量。

变量定义方式

1. 在 Playbook 中直接定义

可以在 Playbook 的 vars 部分直接定义变量。

---
- name: Use variables in playbook
  hosts: all
  vars:
    app_port: 8080
    app_name: "my_app"
  tasks:
    - name: Print variables
      debug:
        msg: "The application {{ app_name }} is running on port {{ app_port }}."

在这个例子中,app_portapp_name 是在 vars 部分定义的变量,然后在 debug 模块的 msg 参数中引用。

2. 在 Inventory 文件中定义

可以在 Ansible 的 Inventory 文件(如 /etc/ansible/hosts)中为特定的主机或主机组定义变量。

[webservers]
webserver1.example.com app_port=80
webserver2.example.com app_port=8080

[webservers:vars]
app_name="web_app"

这里为 webservers 主机组中的每个主机定义了 app_port 变量,同时为整个 webservers 主机组定义了 app_name 变量。在 Playbook 中可以直接使用这些变量。

3. 在单独的变量文件中定义

可以将变量定义在单独的 YAML 文件中,然后在 Playbook 中引用。 变量文件 vars.yml

app_port: 8080
app_name: "my_app"

Playbook

---
- name: Use variables from file
  hosts: all
  vars_files:
    - vars.yml
  tasks:
    - name: Print variables
      debug:
        msg: "The application {{ app_name }} is running on port {{ app_port }}."

通过 vars_files 关键字引用变量文件。

4. 通过命令行传递变量

在执行 ansible-playbook 命令时,可以使用 -e--extra-vars 选项传递变量。

ansible-playbook playbook.yml -e "app_port=8080 app_name=my_app"

在 Playbook 中可以直接使用这些传递的变量。

变量作用域

  • 全局变量:在 group_vars/allhost_vars/all 中定义的变量对所有主机和所有 Playbook 都有效。
  • 组变量:在 group_vars 目录下为特定主机组定义的变量,只对该主机组的主机有效。
  • 主机变量:在 host_vars 目录下为特定主机定义的变量,只对该主机有效。
  • Play 变量:在 Playbook 的 vars 部分定义的变量,只对当前 Play 有效。
  • 任务变量:在任务中使用 vars 关键字定义的变量,只对当前任务有效。

变量优先级

当同一个变量在不同的地方定义时,Ansible 会根据以下优先级来决定使用哪个值(从高到低):

  1. 命令行传递的变量(-e--extra-vars
  2. 任务变量
  3. Play 变量
  4. 主机变量
  5. 组变量
  6. 全局变量

变量使用方法

1. 在任务中引用变量

在任务的参数中可以使用双花括号 {{ }} 来引用变量。

---
- name: Use variable in task
  hosts: localhost
  vars:
    package_name: "nginx"
  tasks:
    - name: Install package
      apt:
        name: "{{ package_name }}"
        state: present
    - name: 打印变量看看,其他task也能访问这个全局变量
      debug:
        msg: "获取变量值:{{package_name}}"

2. 在条件判断中使用变量

可以在 when 语句中使用变量进行条件判断。

---
- name: Conditional task based on variable
  hosts: localhost
  vars:
    is_production: true
  tasks:
    - name: Restart service in production
      debug:
        msg: "我被执行啦,因为{{is_production}}"
      when: is_production

3. 变量的运算和过滤

可以对变量进行运算和使用过滤器来处理变量的值。

模板语言进阶知识点了

---
- name: Variable operation and filtering
  hosts: all
  vars:
    num1: 10
    num2: 20
  tasks:
    - name: Print sum of variables
      debug:
        msg: "The sum of {{ num1 }} and {{ num2 }} is {{ num1 + num2 }},乘积{{num1*num2}}"

    - name: Print variable in uppercase
      debug:
        msg: "The uppercase of 'hello' is {{ 'hello' | upper }}"

在这个例子中,进行了变量的加法运算和字符串的大小写转换。

应用场景

1.通过自定义变量,可以在多个tasks中,通过变量名调用,取值
2.ansible在启动时,默认收集了客户端机器大量的静态属性(变量),可以提取该客户端的机器信息。

vars自定义变量

定义多个文件夹变量,创建rsync的数据目录,配置文件

- hosts: backup
  vars:
    data_path: /data
    dest_path: /etc
    config_path: /etc/rsync.passwd
  tasks:
    - name: 01 mkdir data dir
      file:
        path: "{{ data_path }}" 
        state: directory

    - name: 02 copy  config file
      copy:
        src: "{{ config_path }}"
        dest: "{{ dest_path }}"

获取主机静态属性(ip地址)

Facts

内置获取ip的变量
ansible_all_ipv4_addresses #适用于多ip
ansible_default_ipv4.address #适用单ip

剧本


- hosts: web
  tasks:
  - name: 01 get ip address
    debug: msg="该web组机器,ip是  {{ ansible_all_ipv4_addresses }}" 
  - name: 02 get hostname
    debug: msg="该web组,主机名是 {{ ansible_hostname }}"
  - name: 03 单ip
    debug: msg="{{ansible_default_ipv4.address }}"
  - name: 04 eth0 ip地址是
    debug: msg="{{ansible_facts.enp0s5.ipv4.address}}"
  - name: 05 eth1 ip地址是
    debug: msg="{{ansible_facts.enp0s5.ipv4.address}}"

完整的ansible内置变量手册

https://docs.ansible.com/ansible/latest/user_guide/playbooks_vars_facts.html

通过setup模块可以看到所有的内置变量,提取变量的值需要遵循python的数据类型语法,如列表还是字典取值

[root@master-61 ~]#ansible web -m ansible.builtin.setup |wc -l

主机清单文件中也用到了变量

1.主机清单文件中定义变量

[root@master-61 ~]#tail -20  /etc/ansible/hosts 

[all:vars]
ansible_port=22999
#ansible_user=root
#ansible_password=123123
my_name='yuchao'


[web:vars]
nginx_version='1.19'

[web]
172.16.1.7 port=22999 
172.16.1.8 port=22999
172.16.1.9 port=22999

[nfs]
172.16.1.31

[backup]
172.16.1.41

2.只要操作该主机组,即可使用该变量

- hosts: web
  tasks:
  - name: 获取自定义hosts中的变量,每个组都能拿到
    debug: msg="{{my_name}}"

执行

[root@master-61 ~]#ansible-playbook get_nginx.yaml

loop循环中引用变量

- hosts: localhost
  vars:
    rsyncd_config: /script/rsyncd.conf
    rsyncd_pwd: /script/rsync.passwd
  tasks:
    - name: 01 copy config
      debug:
        msg: "{{ item.t_src }}---{{ item.t_dest }}---{{ item.t_mode  }}  "
      loop:
        - {t_src: "{{ rsyncd_config }}",t_dest: "/etc/",t_mode: '0644'}
        - {t_src: "{{ rsyncd_pwd }}",t_dest: "/etc/",t_mode: '0600'}

注册变量

在 Ansible Playbook 里,注册变量(Registered Variables)是一项非常实用的功能。

借助它,你能够捕获某个任务的执行结果,并将这些结果存储到一个变量中,方便在后续的任务里使用。以下为你详细介绍注册变量的用法、应用场景和示例。

基本语法

要注册一个变量,需要在任务中使用 register 关键字,后面紧跟变量的名称。示例如下:

- name: 执行命令并注册结果
  command: ls -l /tmp
  register: command_output

在这个例子中,command 模块执行 ls -l /tmp 命令,执行结果会被存储到 command_output 变量里。

注册变量的结构

注册变量通常是一个字典,包含多个键值对,常见的键有:

  • changed:一个布尔值,表明任务是否对目标主机的状态产生了改变。
  • stdout:命令执行后的标准输出内容,以字符串形式呈现。
  • stderr:命令执行后的错误输出内容,以字符串形式呈现。
  • rc:命令执行后的返回码,0 通常表示命令成功执行,非 0 则表示有错误发生。

示例

1. 简单示例

---
- name: 使用注册变量示例
  hosts: localhost
  tasks:
    - name: 执行命令并注册结果
      command: ls -l /opt
      register: command_output

    - name: 输出命令执行结果
      debug:
        var: command_output

    - name: 输出标准输出内容
      debug:
        msg: "命令的标准输出是: {{ command_output.stdout }} 更简单的信息: {{command_output.stdout_lines}}"

在这个示例中,第一个任务执行 ls -l /tmp 命令,并把结果注册到 command_output 变量中。第二个任务使用 debug 模块输出整个 command_output 变量的内容,第三个任务则只输出 stdout 键的值。

2. 基于注册变量进行条件判断

---
- name: 基于注册变量进行条件判断
  hosts: all
  tasks:
    - name: 检查文件是否存在
      stat:
        path: /etc/passwd
      register: file_status

    - name: 若文件存在则输出信息
      debug:
        msg: "/etc/passwd 文件存在"
      when: file_status.stat.exists

在这个示例中,stat 模块用于检查 /etc/passwd 文件是否存在,结果被注册到 file_status 变量中。后续任务根据 file_status.stat.exists 的值进行条件判断,若文件存在则输出相应信息。

3. 循环结合注册变量

---
- name: 循环结合注册变量
  hosts: localhost
  tasks:
    - name: 循环执行命令并注册结果
      command: echo "{{ item }}"
      loop:
        - "Hello"
        - "World"
      register: loop_output

    - name: 输出每次循环的结果
      debug:
        msg: "循环的输出是: {{ item.stdout_lines}}"
      loop: "{{ loop_output.results }}"
      loop_control:
        label: "{{item.cmd}}"

在这个示例中,command 模块在循环中执行 echo 命令,每次循环的结果都会被注册到 loop_output 变量中。loop_output.results 是一个包含每次循环结果的列表,后续任务对这个列表进行循环,输出每次循环的标准输出内容。

注意事项

  • 数据类型和结构:要清楚注册变量的数据类型和结构,这样才能正确地访问其中的值。
  • 错误处理:在使用注册变量进行条件判断或后续操作时,要考虑可能出现的错误情况,例如命令执行失败时返回码和输出内容的处理。

使用场景

调试,回显命令的执行结果
把状态保存为变量,其他task再继续调用

用内置变量获取IP地址写入文件,并且显示文件内容

- hosts: nfs
  tasks:
  - name: echo ip address
    shell: "echo {{ ansible_default_ipv4.address }} >> /tmp/ip.log"

  - name: cat ip.log
    shell: "cat /tmp/ip.log"
    register: about_ip_log

  - name: debug about_ip_log
    debug: 
      msg: "{{ about_ip_log.stdout_lines }}"

注册多个变量

同时记录且显示客户端的ip信息、主机名信息

结合循环知识,打印多个命令的结果

- name: yuchaoit.cn
  hosts: localhost
  tasks:
  - name: 01 get ip
    shell: "echo {{ ansible_default_ipv4.address }} > /tmp/ip.log"

  - name: 02 get hostname
    shell: "echo {{ ansible_hostname }} > /tmp/hostname.log"

  - name: 03 echo hostname
    shell: "cat /tmp/hostname.log"
    register: hostname_log

  - name: 04 echo ip
    shell: "cat /tmp/ip.log"
    register: ip_log

  - debug:
      msg: "{{item}}"
    loop:
      - "{{ ip_log.stdout_lines}}"
      - "{{ hostname_log.stdout_lines}}"

判断当配置文件变化后,就重启服务

我们重启配置服务的标准是,修改了配置文件,否则无须重启

例如,判断rsyncd.conf文件状态发生变化后,就重启服务。

- name: 重启nginx的判断
  hosts: localhost
  tasks:
  - name: 修改配置文件nginx.conf
    copy: 
        src: ./nginx.conf 
        dest: /etc/nginx/sites-enabled/default    
    register: conf_status
  - name: 打印注册变量
    debug:
      msg: "{{conf_status}}"

  - name: 重启nginx,当它nginx.conf被修改
    systemd:
      name: nginx
      state: restarted
    when: conf_status.changed

查看rsync进程端口是否更新,即为rsyncd服务是否重启

[root@master-61 ~]#ansible backup -m shell -a "netstat -tunlp|grep rsync"
172.16.1.41 | CHANGED | rc=0 >>
tcp        0      0 0.0.0.0:873             0.0.0.0:*               LISTEN      635/rsync           
tcp6       0      0 :::873                  :::*                    LISTEN      635/rsync

修改rsyncd.conf,再次执行剧本

[root@master-61 ~]#ansible backup -m shell -a "netstat -tunlp|grep rsync"
172.16.1.41 | CHANGED | rc=0 >>
tcp        0      0 0.0.0.0:873             0.0.0.0:*               LISTEN      8274/rsync          
tcp6       0      0 :::873                  :::*                    LISTEN      8274/rsync

when条件判断语句

文档
https://docs.ansible.com/ansible/latest/user_guide/playbooks_conditionals.html

在 Ansible Playbook 中,when 语句是一个非常重要的特性,它允许你根据特定条件来决定是否执行某个任务。

以下将从基本语法、常见使用场景、示例代码等方面详细介绍 when 语句。

基本语法

when 语句通常放在任务的定义中,其基本形式如下:

- name: 任务名称
  模块名:
    参数: 值
  when: 条件表达式

其中,条件表达式 是一个布尔表达式,当该表达式的值为 true 时,任务会被执行;当值为 false 时,任务会被跳过。

常见使用场景及示例

1. 根据目标主机的操作系统类型执行任务

---
- name: Install package based on OS family
  hosts: localhost
  become: true
  tasks:
    - name: Install Apache on Debian systems
      apt:
        name: apache2
        state: present
      when: ansible_os_family == "Debian"

    - name: Install Apache on Red Hat systems
      yum:
        name: httpd
        state: present
      when: ansible_os_family == "RedHat"

在这个示例中,ansible_os_family 是 Ansible 自动收集的目标主机操作系统家族信息。根据这个信息,使用 when 语句判断目标主机是 Debian 系还是 Red Hat 系,然后执行相应的软件包安装任务。

2. 根据变量的值执行任务

prod线上

stage预生产

dev 开发测试环境

test 测试

---
- name: Conditional task based on variable
  hosts: localhost
  vars:
    is_prod: true
  tasks:
    - name: Restart service in production
      service:
        name: nginx
        state: restarted
      when: is_prod

这里定义了一个变量 is_production,并在任务的 when 语句中使用该变量进行判断。如果 is_production 的值为 true,则执行重启服务的任务;否则,跳过该任务。

3. 根据注册变量的值执行任务

---
- name: Conditional task based on registered variable
  hosts: localhost
  tasks:
    - name: Check if file exists
      stat:
        path: /etc/passwdd
      register: file_status

    - name: Print message if file exists
      debug:
        #msg: "/etc/passwd file exists"
        msg: "{{file_status}}"
      when: file_status.stat.exists

在这个示例中,首先使用 stat 模块检查 /etc/passwd 文件是否存在,并将结果注册到 file_status 变量中。然后,在后续任务的 when 语句中,根据 file_status.stat.exists 的值判断文件是否存在,如果存在则输出相应的消息。

4. 多条件判断

when 语句支持使用逻辑运算符(如 andornot)进行多条件判断。

---
- name: Multi - condition task
  hosts: localhost
  vars:
    is_production: true
    has_sufficient_memory: true
  tasks:
    - name: Perform action if conditions are met
      debug:
        msg: "任务执行了。。"
      when: is_production or has_sufficient_memory

这里使用 and 运算符将两个条件组合起来,只有当 is_productionhas_sufficient_memory 的值都为 true 时,才会执行任务。

注意事项

  • 变量引用:在 when 语句中引用变量时,不需要使用双花括号 ,因为 when 语句本身就是一个表达式上下文。
  • 语法正确性:确保 when 语句中的条件表达式语法正确,否则可能会导致任务执行逻辑出错。

通过使用 when 语句,你可以根据不同的条件灵活控制 Ansible Playbook 中任务的执行,从而实现更复杂的自动化场景。

使用场景

判断nfs配置文件是否存在

1.存在,则显示其文件内容
2.不存在,则输出 /etc/exports is not exists。

答案

- name: yuchaoit.cn
  hosts: localhost
  vars:
    nfs_file: /etc/exports
  tasks:
  - name: 01 check nfs config
    shell: "cat {{nfs_file}}"
    register: nfs_result
    ignore_errors: true
  - name: 打印注册变量
    debug:
      msg: "{{nfs_result}}"
  - name: 02 debug nfs config
    debug:
      msg: "{{ansible_hostname}} has {{nfs_file}},file content is : {{nfs_result.stdout}}"
    when: nfs_result is success

  - name: 03 debug nfs not exists
    debug: msg="{{nfs_file}} is not exists."
    when: nfs_result is failed

高级特性handler

官网文档
https://docs.ansible.com/ansible/latest/user_guide/playbooks_handlers.html


Handlers: running operations on change
处理程序:对更改运行操作

在 Ansible Playbook 里,handler(处理程序)是一项关键特性,它能够让你在特定任务的执行结果满足条件时;

触发相应的操作,最常见的应用场景是在配置文件发生变更后重启服务。

下面为你详细介绍 handler 的概念、使用方法、工作原理以及示例。

概念

handler 本质上是一种特殊的任务,只有在其他任务通过 notify 关键字对其进行通知时才会被执行。

而且,handler 会在当前 Play 里所有任务都执行完毕之后统一执行,就算有多个任务都通知了同一个 handler,该 handler 也仅会执行一次。

使用方法

1.定义 handler

handler 通常在 Playbookhandlers 部分进行定义,其语法格式和普通任务类似。示例如下:

handlers:
  - name: Restart Nginx service
    service:
      name: nginx
      state: restarted

在这个例子中,定义了一个名为 Restart Nginx servicehandler,其作用是重启 nginx 服务。

2.通知 handler

在普通任务中,使用 notify 关键字来通知 handler。示例如下:

tasks:
  - name: Copy Nginx configuration file
    copy:
      src: nginx.conf
      dest: /etc/nginx/nginx.conf
      mode: '0644'
    notify:
      - Restart Nginx service

这里的 copy 任务负责复制 nginx 配置文件,当该任务的执行结果为 changed(即配置文件被修改)时,会通知名为 Restart Nginx servicehandler

工作原理

  1. 任务执行:Ansible 按照 Playbook 中定义的顺序依次执行各个任务。
  2. 状态检测:每个任务执行完毕后,Ansible 会检查其执行结果是否导致目标主机的状态发生改变。如果发生改变,任务状态会被标记为 changed
  3. 通知 handler:当任务的状态为 changed 且使用了 notify 关键字时,会记录要通知的 handler
  4. handler 执行:在当前 Play 里所有任务都执行完毕后,Ansible 会统一执行被通知的 handler

示例

以下是一个完整的 Playbook 示例,展示了如何使用 handler 在 Nginx 配置文件变更后重启 Nginx 服务。

---
- name: Manage Nginx service and configuration
  hosts: localhost
  become: true  # 使用 sudo 权限执行任务
  tasks:
    - name: Copy Nginx configuration file
      copy:
        src: nginx.conf
        dest: /etc/nginx/nginx.conf
        mode: '0644'
      notify:
        - restart_nginx_handler
    - name: 测试任务2
      debug:
        msg: "echo 任务2执行啦"
    - name: 测试任务3
      debug:
        msg: "echo 任务23执行啦"   


  handlers:
    - name: restart_nginx_handler
      service:
        name: nginx
        state: restarted

注意事项

  • handler 名称notify 中指定的 handler 名称必须和 handlers 部分定义的名称完全一致,否则 handler 不会被触发。
  • 执行时机handler 会在当前 Play 中所有任务执行完毕后执行,而不是在通知它的任务执行后立即执行。
  • 权限问题:由于重启服务等操作通常需要管理员权限,所以在 Playbook 中可能需要使用 become: true 来获取相应权限。

通过合理运用 handler,可以有效地管理系统服务,确保在配置文件变更等情况下及时更新服务状态。

handler这个机制一般用于服务状态管理,如

  • 你在tasks中修改了nginx的配置文件,你就必须得重启nginx服务,才能生效
handler解决什么问题
1.现状、配置文件修改了,程序不可能自己重启,得手动restart


利用handler添加在剧本中,实现效果
1.配置文件没变化,就不执行restart重启
2.配置文件发生了变化,就执行restart重启

在 Ansible Playbook 里,要实现当配置文件发生变化后就重启服务,可以借助 Ansible 的 handlers(处理程序)和 notify(通知)机制。下面为你详细介绍实现步骤和示例代码。

实现思路

  1. 复制或修改配置文件:使用 copy 或者 template 模块来复制或修改目标主机上的配置文件。
  2. 检测文件变化:当配置文件被修改时,Ansible 会将该任务标记为 changed
  3. 使用 notify 通知处理程序:在复制或修改配置文件的任务中,使用 notify 关键字指定一个处理程序。
  4. 定义处理程序:在 handlers 部分定义一个处理程序,用于重启服务。当 notify 被触发时,这个处理程序会被执行。

示例代码

以下是一个具体的示例,假设要管理 Nginx 服务,当 Nginx 配置文件发生变化时,就重启 Nginx 服务。

iNotify,rsync

---
- name: Manage Nginx service and configuration
  hosts: webservers
  become: true  # 使用 sudo 权限执行任务
  tasks:
    - name: Copy Nginx configuration file
      copy:
        src: nginx.conf
        dest: /etc/nginx/nginx.conf
        mode: '0644'
      notify:
        - Restart Nginx service

  handlers:
    - name: Restart Nginx service
      service:
        name: nginx
        state: restarted

代码解释

  1. tasks 部分

    • name: Copy Nginx configuration file:任务的名称,用于描述该任务的作用。
    • copy 模块:用于将本地的 nginx.conf 文件复制到目标主机的 /etc/nginx/nginx.conf 路径下,并设置文件权限为 0644
    • notify: - Restart Nginx service:当这个任务的执行结果为 changed(即配置文件被修改)时,会通知名为 Restart Nginx service 的处理程序。
  2. handlers 部分

    • name: Restart Nginx service:处理程序的名称,要和 notify 中指定的名称一致。
    • service 模块:用于管理系统服务,这里将 nginx 服务的状态设置为 restarted,即重启服务。

注意事项

  • 处理程序的执行时机:处理程序会在当前 Play 中所有任务执行完毕后统一执行。也就是说,如果多个任务都通知了同一个处理程序,该处理程序只会执行一次。
  • 权限问题:由于重启服务通常需要管理员权限,所以在 Playbook 中使用了 become: true。确保 Ansible 控制节点在目标主机上有足够的权限来执行这些操作。
  • 服务名称:要根据目标主机的实际情况确保服务名称(如 nginx)的正确性。不同的操作系统可能对服务名称的命名有所不同。

通过上述方法,你可以在 Ansible Playbook 中实现当配置文件变化后自动重启服务的功能。

low办法实现

- name: yuchaoit.cn
  hosts: localhost
  remote_user: root
  tasks:
  - name: nfs文件变更
    copy:
      src=./exports
      dest=/etc/exports    
    register: nfs_file_change
  - name: xxx
    debug:
      msg: "{{nfs_file_change}}"
  - name: 重启nfs服务
    systemd:
      name: nfs-server
      state: restarted
    when: nfs_file_change.changed

你会发现,这个用法,无论你配置文化改没改,都必然会执行重启的task,这个写法就不太合理。

改造为handler

1.handlers中的任务会被tasks调用
2.只有tasks的确执行了,发生了change状态,handler才会执行
3.在tasks任务列表中,定义notify属性,用于触发handler的执行

剧本

- name: yuchaoit.cn
  hosts: localhost

  tasks:
  - name: 01 copy rsyncd.conf
    copy:
      src=exports    
      dest=/etc/exports    
    notify:
      - restart_nfs

  handlers:
  - name: restart_nfs
    systemd:
      name: nfs
      state: restarted

细节注意

1.handlers必须写在结尾
2.handlers定义的任务名字,必须和notify一致

给task打上tag标签

在 Ansible Playbook 中,tag(标签)是一个非常实用的特性,它允许你选择性地执行 Playbook 中的部分任务,而不是执行整个 Playbook。以下详细介绍 tag 的使用方法、应用场景及示例。

初始化剧本
初始化apt源
主机名
dns
hosts文件
swap关闭
磁盘内存同步,清理
软件源更新
等等

基本语法

在任务定义中,可以使用 tags 关键字为任务添加一个或多个标签。示例如下:

- name: Task with tag
  apt:
    name: nginx
    state: present
  tags:
    - install
    - webserver

在这个任务中,为其添加了 installwebserver 两个标签。

使用 tag 执行部分任务

当执行 ansible-playbook 命令时,可以使用 --tags 选项指定要执行的标签,使用 --skip-tags 选项指定要跳过的标签。

执行指定标签的任务

ansible-playbook playbook.yml --tags "install"

这会执行 playbook.yml 中所有带有 install 标签的任务。

跳过指定标签的任务

ansible-playbook playbook.yml --skip-tags "webserver"

这会执行 playbook.yml 中除了带有 webserver 标签之外的所有任务。

应用场景

1. 分阶段执行任务

假设一个 Playbook 包含安装、配置和启动服务三个阶段的任务,你可以为每个阶段的任务添加不同的标签,以便根据需要分阶段执行。

---
- name: Manage web server
  hosts: localhost
  become: true
  tasks:
    - name: Install web server
      apt:
        name: nginx
        state: present
      tags:
        - t_install
    - name: remove nginx
      apt:
        name: nginx-common
        state: absent
        purge: yes
      tags:
        - t_remove
        - t_absent

    - name: Configure web server
      copy:
        src: nginx.conf
        dest: /etc/nginx/sites-enabled/default
      tags:
        - t_configure

    - name: Start web server
      service:
        name: nginx
        state: started
      tags:
        - t_start
    - name: stop nginx
      service:
        name: nginx
        state: stopped
      tags:
        - t_stop
    - name: 重启nginx
      systemd:
        name: nginx
        state: restarted
      tags:
        - t_restart

如果你只想安装服务,可以执行:

ansible-playbook playbook.yml --tags "install"

如果你已经完成了安装和配置,只想启动服务,可以执行:

选择多个tags

Ansible Playbook 支持通过命令行传入 多个 Tags,并且这是其核心功能之一。以下是具体用法、示例及注意事项:


1. 基本语法

同时指定多个 Tags

  • 逗号分隔:直接在一个 --tags 参数后列出多个标签,用逗号分隔:
    ansible-playbook playbook.yml --tags "tag1,tag2,tag3"
    
  • 多次指定:使用多个 --tags 参数:
    ansible-playbook playbook.yml --tags tag1 --tags tag2
    

排除多个 Tags

使用 --skip-tags 排除特定标签:

  ansible-playbook playbook.yml --skip-tags "skip_tag1,skip_tag2"

2. 示例场景

Playbook 示例

假设 Playbook 中包含以下任务:

- name: 安装 Nginx
  apt:
    name: nginx
    state: present
  tags:
    - install
    - web

- name: 配置防火墙
  ufw:
    rule: allow
    port: "80"
  tags:
    - firewall

- name: 部署静态文件
  copy:
    src: files/
    dest: /var/www/html/
  tags:
    - deploy
    - web

执行命令

  • 仅运行 installfirewall 标签的任务
    ansible-playbook playbook.yml --tags "install,firewall"
    
  • 运行所有标记为 webdeploy 的任务
    ansible-playbook playbook.yml --tags web,deploy
    
  • 排除 installfirewall 任务
    ansible-playbook playbook.yml --skip-tags "install,firewall"
    

3. 逻辑关系

  • 包含标签(--tags:任务只需匹配任意一个指定标签即会执行(逻辑 OR)。
  • 排除标签(--skip-tags:任务若匹配任意一个排除标签则跳过。

4. 高级用法

标签通配符

使用 * 匹配部分标签名(Ansible 2.5+ 支持):

ansible-playbook playbook.yml --tags "web*"

特殊标签

  • always:无论是否指定,默认执行(除非显式跳过)。
  • never:除非显式指定,否则不执行。
  • tagged:仅运行有标签的任务。
  • untagged:仅运行无标签的任务。

示例:

ansible-playbook playbook.yml --tags "tagged"   # 只运行有标签的任务
ansible-playbook playbook.yml --tags "untagged" # 只运行无标签的任务

5. 注意事项

  1. 标签命名规范:建议使用清晰的命名(如 install, config, test),避免歧义。
  2. 性能优化:合理使用标签可以显著缩短 Playbook 执行时间(跳过无关任务)。
  3. 依赖关系:确保标签之间的任务没有隐式依赖,或者通过 meta: flush_handlers 强制触发依赖。
  4. 版本兼容性:通配符 (*) 和特殊标签需要 Ansible 2.5+。

总结

通过灵活使用多标签功能,可以精准控制 Playbook 的执行范围,非常适合以下场景:

  • 选择性部署:仅更新部分服务(如 --tags "deploy_web,deploy_db")。
  • 环境隔离:为开发、测试、生产环境定义不同标签。
  • 快速调试:跳过耗时任务(如 --skip-tags "migration,backup")。
ansible-playbook playbook.yml --tags "start"

2. 调试和测试

在开发和调试 Playbook 时,你可能只想执行部分任务来验证其功能。通过为这些任务添加特定的标签,可以方便地进行测试。

---
- name: Test task
  hosts: all
  tasks:
    - name: Task for testing
      debug:
        msg: "This is a test task."
      tags:
        - test

执行以下命令只运行测试任务:

ansible-playbook playbook.yml --tags "test"

3. 维护和更新

在对系统进行维护或更新时,可能只需要执行部分相关任务。例如,只更新配置文件而不重新安装软件。

---
- name: Update web server configuration
  hosts: webservers
  become: true
  tasks:
    - name: Install web server
      apt:
        name: nginx
        state: present
      tags:
        - install

    - name: Update web server configuration
      copy:
        src: new_nginx.conf
        dest: /etc/nginx/nginx.conf
      tags:
        - update_config

    - name: Restart web server
      service:
        name: nginx
        state: restarted
      tags:
        - restart

若要只更新配置文件并重启服务,可以执行:

ansible-playbook playbook.yml --tags "update_config,restart"

注意事项

  • 标签的命名:标签名应具有描述性,方便识别和管理。
  • 标签的继承:如果一个 role 中的任务有标签,在 Playbook 中使用该 role 时,这些标签也会被继承。

通过使用 tag,可以提高 Playbook 的灵活性和可维护性,根据不同的需求灵活执行部分任务。

你写了一个很长的playbook,其中有很多的任务,这并没有什么问题,不过在实际使用这个剧本时,你可能只是想要执行其中的一部分任务而已

或者,你只想要执行其中一类任务而已,而并非想要执行整个剧本中的全部任务

这个时候我们该怎么办呢?我们可以借助tags实现这个需求。

见名知义,tags可以帮助我们对任务进行'打标签'

当任务存在标签以后,我们就可以在执行playbook时,借助标签,指定执行哪些任务,或者指定不执行哪些任务了。

tag作用
调试,选择性的执行某个task

1.部署nfs-server剧本参考

- name: yuchaoit.cn
  hosts: nfs
  tasks:
  - name: 01 安装nfs-utils 服务
    apt: name=nfs-utils state=installed
    tags: 01_install_nfs_service

  - name: 02 安装rpcbind 服务
    yum: name=rpcbind state=installed
    tags: 02_install_rpcbind_service

  - name: 03 创建组
    group: name=www gid=666
    tags: 03_add_group

  - name: 04 创建用户
    user: name=www uid=666 group=www create_home=no shell=/sbin/nologin
    tags: 04_add_user

  - name: 05 创建共享目录
    file: path=/data owner=www group=www state=directory
    tags: 05_create_data_dir

  - name: 06 拷贝配置文件
    copy: src=/script/exports dest=/etc/exports
    tags: 06_copy_nfs_exports

  - name: 07 创建关于rsync密码文件
    copy: content='yuchao666' dest=/etc/rsync.passwd mode=600
    tags: 07_create_rsync_passwd

  - name: 08 启动rpcbind
    service: name=rpcbind state=started enabled=yes
    tags: 08_start_rpcbind

  - name:  09 启动nfs
    systemd: name=nfs state=started enabled=yes
    tags: 09_start_nfs

2.打印剧本中可用的标签

也就是你可以直接选择执行哪些任务

[root@master-61 ~]#ansible-playbook --list-tags tag_nfs.yaml 

playbook: tag_nfs.yaml

  play #1 (nfs): yuchaoit.cn    TAGS: []
      TASK TAGS: [01_install_nfs_service, 02_install_rpcbind_service, 03_add_group, 04_add_user, 05_create_data_dir, 06_copy_nfs_exports, 07_create_rsync_passwd, 08_start_rpcbind, 09_start_nfs]

3.指定运行某个标签

[root@master-61 ~]#ansible-playbook -t 01_install_nfs_service tag_nfs.yaml 
[root@master-61 ~]#ansible-playbook -t 04_add_user tag_nfs.yaml

4.指定运行多个标签

[root@master-61 ~]#ansible-playbook -t "01_install_nfs_service,05_create_data_dir,04_add_user" tag_nfs.yaml

5.指定不运行一个/多个标签

[root@master-61 ~]#ansible-playbook --skip-tags  01_install_nfs_service,05_create_data_dir,04_add_user tag_nfs.yaml

选择tasks执行

使用场景

1.调试剧本时,task数量太多,不想从头执行,可以指定执行位置

查看task列表

[root@master-61 ~]#ansible-playbook --list-tasks tag_nfs.yaml 

playbook: tag_nfs.yaml

  play #1 (nfs): yuchaoit.cn    TAGS: []
    tasks:
      01 安装nfs-utils 服务    TAGS: [01_install_nfs_service]
      02 安装rpcbind 服务    TAGS: [02_install_rpcbind_service]
      03 创建组    TAGS: [03_add_group]
      04 创建用户    TAGS: [04_add_user]
      05 创建共享目录    TAGS: [05_create_data_dir]
      06 拷贝配置文件    TAGS: [06_copy_nfs_exports]
      07 创建关于rsync密码文件    TAGS: [07_create_rsync_passwd]
      08 启动rpcbind    TAGS: [08_start_rpcbind]
      09 启动nfs    TAGS: [09_start_nfs]

选择执行的task位置

从第五步骤开始

[root@master-61 ~]#ansible-playbook --start-at-task '05 创建共享目录' tag_nfs.yaml

总结(playbook规范流程)

通过这个流程,去编写、检验、阅读所有的playbook都是一个靠谱的办法

无论是你自己写好剧本,进行校验

还是工作后,先看公司里现成的剧本,进行维护,都可以基于这个思路

1.检查剧本语法

如果语法不对,ansible会具体告诉你错误的位置

[root@master-61 ~]#ansible-playbook --syntax-check tag_nfs.yaml 

playbook: tag_nfs.yaml

2.检查该剧本操作的主机有哪些

搞清楚这个剧本会影响到哪些主机,关乎于这些机器的作用

[root@master-61 ~]#ansible-playbook --list-hosts get_ip.yaml 

playbook: get_ip.yaml

  play #1 (web): web    TAGS: []
    pattern: [u'web']
    hosts (3):
      172.16.1.7
      172.16.1.8
      172.16.1.9

3.查看剧本有哪些任务

轻松的搞清楚这个剧本有什么作用,整体的工作流程

[root@master-61 ~]#ansible-playbook --list-tasks tag_nfs.yaml 

playbook: tag_nfs.yaml

  play #1 (nfs): yuchaoit.cn    TAGS: []
    tasks:
      01 安装nfs-utils 服务    TAGS: [01_install_nfs_service]
      02 安装rpcbind 服务    TAGS: [02_install_rpcbind_service]
      03 创建组    TAGS: [03_add_group]
      04 创建用户    TAGS: [04_add_user]
      05 创建共享目录    TAGS: [05_create_data_dir]
      06 拷贝配置文件    TAGS: [06_copy_nfs_exports]
      07 创建关于rsync密码文件    TAGS: [07_create_rsync_passwd]
      08 启动rpcbind    TAGS: [08_start_rpcbind]
      09 启动nfs    TAGS: [09_start_nfs]

4.模拟剧本执行

模拟执行,查看执行流程是否存在错误,以及执行的状态

[root@master-61 ~]#ansible-playbook -C tag_nfs.yaml 

# 任务2,创建文件, 任务3,报错。

5.真正执行剧本

对目标机器发生实质性的改变、修改操作

[root@master-61 ~]#ansible-playbook  tag_nfs.yaml

ansible剧本大作业(吐血提醒)

等你坐在访客室、开始1v1和面试官交流面试

你就会回想起,当初于超老师让我好好学ansible,结合备份同步访问写剧本,以及让我锻炼口述能力,叙述架构流程的重要性,悔不当初啊!!

以及面试官问你

ansible熟悉吗?写下你用过的哪些模块?

用过ansible高级特性吗?说一说有哪些

你心中一万个羊驼。。。超哥当时怎么教来着,我为什么没好好做作业,害!

使用学过的模块

如
shell
copy
file
group
user
script
yum
apt
systemd
等

尽量结合剧本高级特性

使用高级特性,是为了简化脚本写法,提升可维护性,同时也会提升阅读难度,需要额外学习。

循环 loop
变量 vars
注册变量 register
条件语句 when
触发任务 handler
标签tag

综合练习

继续改造 ,完全改造为ansible-playbook 一键部署

rsync
nfs
lsyncd
nginx

Nginx综合案例

以下是一个基于 Ubuntu 系统,使用 Ansible 完成 Nginx 部署的 Playbook,综合运用了你所提到的模块(shellcopyfilegroupuserscriptyum 这里替换为 apt 因为是 Ubuntu 系统、systemd)以及高级特性(loopvarsregisterwhenhandler)。

需求概述

在 Ubuntu 目标主机上部署并配置 Nginx,涵盖创建 Nginx 用户和组、安装 Nginx、复制配置文件、启动并管理 Nginx 服务等步骤。

root@ansible-01:/opt# ls /etc/nginx/ -l
total 64
drwxr-xr-x 2 root root 4096 Feb 15 02:40 conf.d # 子配置文件目录 *.conf
-rw-r--r-- 1 root root 1125 May 31  2023 fastcgi.conf
-rw-r--r-- 1 root root 1055 May 31  2023 fastcgi_params
-rw-r--r-- 1 root root 2837 May 31  2023 koi-utf
-rw-r--r-- 1 root root 2223 May 31  2023 koi-win
-rw-r--r-- 1 root root 3957 May 31  2023 mime.types
drwxr-xr-x 2 root root 4096 Feb 15 02:40 modules-available # nginx可用模块
drwxr-xr-x 2 root root 4096 Feb 26 19:07 modules-enabled  # nginx已开启的模块
-rw-r--r-- 1 root root 1447 May 31  2023 nginx.conf # 这是个入口文件,include加载
-rw-r--r-- 1 root root  180 May 31  2023 proxy_params
-rw-r--r-- 1 root root  636 May 31  2023 scgi_params
drwxr-xr-x 2 root root 4096 Feb 26 19:07 sites-available # nginx站点,多个站点,可用
drwxr-xr-x 2 root root 4096 Feb 26 19:07 sites-enabled #已开启,导入
drwxr-xr-x 2 root root 4096 Feb 26 19:07 snippets
-rw-r--r-- 1 root root  664 May 31  2023 uwsgi_params
-rw-r--r-- 1 root root 3071 May 31  2023 win-utf

nginx.conf

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
        worker_connections 768;
        # multi_accept on;
}

http {
        sendfile on;
        tcp_nopush on;
        types_hash_max_size 2048;
        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        ##
        # Logging Settings
        ##

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        ##
        # Gzip Settings
        ##

        gzip on;

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
#       include /etc/nginx/sites-available/*;
}

defualt

server {
        listen 34444 default_server;
        root /var/www/html;

        # Add index.php to the list if you are using PHP
        index index.html index.htm index.nginx-debian.html;

        server_name _;

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                try_files $uri $uri/ =404;
        }
}

Playbook 代码

---
- name: Deploy and Configure Nginx on Ubuntu
  hosts: web
  become: true
  vars:
    nginx_user: www-data
    nginx_group: www-data
    nginx_config_dir: /etc/nginx
    nginx_log_dir: /var/log/nginx
    nginx_conf_file: nginx.conf
    nginx_sites_available: /etc/nginx/sites-available
    nginx_sites_enabled: /etc/nginx/sites-enabled
    nginx_site_name: default
    nginx_service_name: nginx

  tasks:
    - name: 创建nginx运行,组信息
      group:
        name: "{{nginx_group}}"
        state: present

    - name: 创建nginx运行,用户信息
      user:
        name: "{{nginx_user}}"
        group: "{{nginx_group}}"
        state: present

    - name: 更新缓存
      apt:
        update_cache: yes
        cache_valid_time: 3600

    - name: 安装nginx
      apt:
        name: nginx
        state: present
      tags:
        - t_install
    - name: 创建nginx日志目录
      file:
        path: "{{ nginx_log_dir }}"
        state: directory
        owner: "{{ nginx_user }}"
        group: "{{ nginx_group }}"
        mode: '0755'

    - name: 创建sites-available 和 sites-enabled ,其他站点目录
      file:
        path: "{{ item }}"
        state: directory
        owner: "{{ nginx_user }}"
        group: "{{ nginx_group }}"
        mode: '0755'
      loop:
        - "{{ nginx_sites_available }}"
        - "{{ nginx_sites_enabled }}"

    - name: 拷贝nginx主配置文件
      copy:
        src: "{{ nginx_conf_file }}"
        dest: "{{ nginx_config_dir }}/{{ nginx_conf_file }}"
        owner: "{{ nginx_user }}"
        group: "{{ nginx_group }}"
        mode: '0644'
      notify:
        - Reload Nginx configuration

    - name: 拷贝站点端口,域名,网页目录,代理规则,配置文件
      copy:
        src: "{{ nginx_site_name }}"
        dest: "{{ nginx_sites_enabled }}/{{ nginx_site_name }}"
        owner: "{{ nginx_user }}"
        group: "{{ nginx_group }}"
        mode: '0644'
      notify:
        - Reload Nginx configuration

    - name: 判断nginx是否运行
      shell: systemctl is-active {{ nginx_service_name }}
      register: nginx_service_status
      ignore_errors: true
    - name: 打印nginx运行结果查看
      debug:
        msg: "{{nginx_service_status}}"

    - name: Start and enable Nginx service
      systemd:
        name: "{{ nginx_service_name }}"
        state: started
        enabled: yes
      when: nginx_service_status.rc != 0

  handlers:
    - name: Reload Nginx configuration
      systemd:
        name: "{{ nginx_service_name }}"
        state: reloaded
      listen: "Reload Nginx configuration"

详细解释

1. vars 部分

  • 定义了一系列变量,用于在后续任务中引用。
    • nginx_usernginx_group:通常 Ubuntu 上 Nginx 默认使用 www-data 用户和组。
    • nginx_config_dir:Nginx 配置文件所在目录。
    • nginx_log_dir:Nginx 日志文件所在目录。
    • nginx_conf_file:Nginx 主配置文件名称。
    • nginx_sites_availablenginx_sites_enabled:Nginx 虚拟主机配置文件目录。
    • nginx_site_name:虚拟主机配置文件名称。
    • nginx_service_name:Nginx 服务名称。

2. tasks 部分

  • 用户和组管理
    • 使用 group 模块确保 nginx_group 存在。
    • 使用 user 模块确保 nginx_user 存在,并将其归属到 nginx_group
  • 软件安装
    • 使用 apt 模块更新软件包缓存,cache_valid_time 设定缓存有效期为 3600 秒(1 小时)。
    • 继续使用 apt 模块安装 Nginx。
  • 目录创建
    • 使用 file 模块创建 Nginx 日志目录,并设置正确的用户、组和权限。
    • 利用 loop 特性,使用 file 模块创建 sites-availablesites-enabled 目录。
  • 配置文件复制
    • 使用 copy 模块复制 Nginx 主配置文件和虚拟主机配置文件到目标位置,同时使用 notify 关键字通知 handler 在配置文件有变化时重新加载 Nginx 配置。
  • 符号链接创建:使用 file 模块为虚拟主机配置文件创建符号链接,并通知 handler
  • 服务状态检查与启动
    • 使用 shell 模块检查 Nginx 服务是否正在运行,并将结果注册到 nginx_service_status 变量中。
    • 使用 systemd 模块,结合 when 条件判断,如果服务未运行则启动并设置为开机自启。

3. handlers 部分

  • 重新加载 Nginx 配置:当配置文件发生变化时,handler 会接收到 notify 通知,使用 systemd 模块重新加载 Nginx 服务配置。

使用说明

  1. 准备工作
    • 确保 ubuntu_web_servers 主机组在 Ansible 清单文件中正确定义。
    • 在本地准备好 nginx.confdefault 配置文件。
  2. 执行 Playbook
    ansible-playbook playbook.yml
    

通过这个案例,你可以综合运用 Ansible 的模块和高级特性完成 Ubuntu 系统上 Nginx 的部署和配置。

Copyright © www.yuchaoit.cn 2025 all right reserved,powered by Gitbook作者:于超 2025-02-27 11:56:24

results matching ""

    No results matching ""