【Ansible自动化运维实战:从Playbook到负载均衡指南】

本文是「Vagrant+VirtualBox虚拟化环境搭建」的续篇,深入探索Ansible在自动化运维中的核心应用:

✅ Ansible核心技能:Playbook编写、角色(Roles)模块化、标签(Tags)精准控制

✅ 实战场景覆盖:Apache服务部署、HAProxy负载均衡配置、服务器联动管理

✅ 无缝衔接Vagrant:在虚拟化环境中模拟真实运维场景,提供完整可复现的代码示例

无论是想快速入门Ansible的新手,还是需要优化现有流水线的工程师,都能从本文获得从基础的自动化运维解决方案。文末附常见问题排查与性能对比数据,助你避坑提速!

参考教程来自:leucos/ansible-tuto: Ansible 教程

码云:git clone https://gitee.com/xbd_zc/ansible-tuto-demosite.git

前期准备(虚拟机安装及配置)【Vagrant+VirtualBox创建自动化虚拟环境】Ansible-Playbook-CSDN博客

这里准备4台,host0-3,host0:安装Ansible管理host1-3,如通过vagrant创建,Vagrantfile配置如下:

复制代码
# -*- mode: ruby -*-
# vi: set ft=ruby :

hosts = {
  "host0" => "192.168.0.220",
  "host1" => "192.168.0.221",
  "host2" => "192.168.0.222",
  "host3" => "192.168.0.223"
}

Vagrant.configure("2") do |config|
  hosts.each do |name, ip|
    config.vm.define name do |machine|
      machine.vm.box = "bento/ubuntu-20.04"
	  machine.vm.box_version = "202407.23.0"
      machine.vm.hostname = "%s" % name
      machine.vm.network :public_network,bridge: "en1", ip: ip
      machine.vm.provider "virtualbox" do |v|
          v.name = name
          v.customize ["modifyvm", :id, "--memory", 1024]
      end
    end
  end
end

1.Ansible-测试准备

准备四台服务器,并在本机安装Ansible,一个hosts文件如下所示:

环境:本机host0(192.168.0.220),系统版本Ubuntu 20.04.6 LTS,vagrant镜像:bento/ubuntu-20.04、版本:202407.23.0,私钥配置好

复制代码
host1 ansible_host=192.168.0.221 ansible_user=root
host2 ansible_host=192.168.0.222 ansible_user=root
host3 ansible_host=192.168.0.223 ansible_user=root

ansible_host是一个特殊变量,用于设置 ansible 将在 尝试连接到此主机。

ansible_user是另一个特殊变量,它告诉 ansible 使用 SSH 时以此用户身份连接。默认情况下,ansible 将使用 当前用户名,或使用 ~/.ansible.cfg 中提供的其他默认值 ().`remote_user

检查主机是否正常工作

复制代码
ansible -m ping all -i hosts
  • -m MODULE_NAME,--module-name MODULE_NAME (模块名称)
  • -i INVENTORY, --inventory INVENTORY, --inventory-file INVENTORY (文件清单)

ansible 执行ping

输出应如下所示:

shell 复制代码
host1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
host3 | SUCCESS => { 
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
host2 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

好!所有 3 个主机都处于活动状态,并且 ansible 可以与它们通信。

2.Ansible-节点通信

在主机上执行shell命令

复制代码
ansible -i hosts -m shell -a 'uname -a' host1
  • -a MODULE_ARGS, --args MODULE_ARGS 模块参数

输出应如下所示:

复制代码
host1 | CHANGED | rc=0 >>
Linux host1 5.4.0-189-generic #209-Ubuntu SMP Fri Jun 7 14:05:13 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux

复制文件

复制本机hosts文件到host1上。

复制代码
ansible -i step-02/hosts -m copy -a 'src=/etc/hosts dest=/tmp/' host1

输出应类似于:

shell 复制代码
host1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "2e2bd6a4e52b3f3a709a96214cc308753be3a7d4",
    "dest": "/tmp/hosts",
    "gid": 0,
    "group": "root",
    "md5sum": "d7c7e7ffc3d982c56de6801bb665ba9b",
    "mode": "0644",
    "owner": "root",
    "size": 244,
    "src": "/root/.ansible/tmp/ansible-tmp-1745964825.1255076-56136-181611889373774/source",
    "state": "file",
    "uid": 0
}

#host1主机上查看
vagrant@host1:~$ cat /tmp/hosts
127.0.0.1 localhost
127.0.1.1 vagrant

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
127.0.2.1 host0 host0

shell 模块 -- 在目标主机上执行 shell 命令

获取在节点上部署了哪个 Ubuntu 版本

shell 复制代码
ansible -i hosts -m shell -a 'grep DISTRIB_RELEASE /etc/lsb-release' all

all是一个快捷方式,表示"在清单文件中找到的所有主机"。它会 返回:

shell 复制代码
host2 | CHANGED | rc=0 >>
DISTRIB_RELEASE=20.04
host1 | CHANGED | rc=0 >>
DISTRIB_RELEASE=20.04
host3 | CHANGED | rc=0 >>
DISTRIB_RELEASE=20.04

setup module --收集有关远程主机的信息

如果我们 需要更多信息(IP 地址、RAM 大小等)可以使用此模块

复制代码
ansible -i hosts -m setup host1

回复包含大量信息:

shell 复制代码
host1 | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "10.0.2.15",
            "192.168.0.221"
        ],    .......................................#还有很多
        "module_setup": true
    },

    "changed": false
}

可以筛选返回的信息,以防您正在寻找 一些特定的东西。

例如想知道您的所有节点的总内存

shell 复制代码
ansible -i hosts -m setup -a 'filter=ansible_memtotal_mb' all

输出如下:

shell 复制代码
host1 | SUCCESS => {
    "ansible_facts": {
        "ansible_memtotal_mb": 964,
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false
}
host2 | SUCCESS => {
    "ansible_facts": {
        "ansible_memtotal_mb": 964,
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false
}
host3 | SUCCESS => {
    "ansible_facts": {
        "ansible_memtotal_mb": 964,
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false
}

请注意,与上一个输出相比,主机的回复顺序不同。 这是因为 ansible 与主机并行通信!

顺便说一句,当使用 setup 模块时,你可以在表达式中使用。 它的作用就像一个 shell glob。*``filter=

选择主机

我们看到这表示 "所有主机",但 ansible 提供了很多方式 --- Ansible 社区文档all

  • host0:host1将在 host0 上运行,并且 主机 1
  • host*将在以 'host' 开头和结尾的所有主机上运行 与 '' (就像 shell glob 一样)

常见的事实变量示例

  1. 操作系统信息 ‌:
    • ansible_os_family:操作系统的家族,如Debian、RedHat等。
    • ansible_distribution:操作系统的分发版名称。
    • ansible_distribution_version:操作系统的版本号。
    • ansible_distribution_major_version:操作系统的主版本号。
  2. 硬件信息 ‌:
    • ansible_processor_vcpus:处理器的虚拟CPU数量。
    • ansible_memory_mb:总内存大小(以MB为单位)。
    • ansible_disk_usage:磁盘使用情况。
  3. 网络配置 ‌:
    • ansible_default_ipv4:默认的IPv4地址。
    • ansible_all_ipv4_addresses:所有的IPv4地址。
    • ansible_hostname:主机名。

获取事实变量的方法

使用setup模块‌:

  • 在playbook中,可以通过setup模块获取所有事实变量。例如:

    ruby 复制代码
    #显示所有主机的事实,并将其按"主机名"索引存储在"/tmp/facts"中。
    # ansible all -m ansible.builtin.setup --tree /tmp/facts
    #仅显示有关ansible在所有主机上找到的内存的事实,并将其输出。
    # ansible all -m ansible.builtin.setup -a 'filter=ansible_*_mb'
    #仅显示facter返回的事实。
    #ansible all -m ansible.builtin.setup -a 'filter=facter_*'
    #只收集事实者返回的事实。
    #ansible all -m ansible.builtin.setup -a 'gather_subset=!all,facter'
    - name: Collect only facts returned by facter
      ansible.builtin.setup:
        gather_subset:
          - '!all'
          - '!<any valid subset>'
          - facter
  • 使用filter参数来查看指定的信息,例如:

    ruby 复制代码
    - name: Filter and return only selected facts
      ansible.builtin.setup:
        filter:
          - 'ansible_distribution'
          - 'ansible_machine_id'
          - 'ansible_*_mb'
    # 仅显示有关某些接口的事实。
    # ansible all -m ansible.builtin.setup -a 'filter=ansible_eth[0-2]'
    #将额外收集的事实限制为网络和虚拟(包括默认的最小事实)
    # ansible all -m ansible.builtin.setup -a 'gather_subset=network,virtual'
    #仅收集网络和虚拟(不包括默认的最小事实)
    # ansible all -m ansible.builtin.setup -a 'gather_subset=!all,network,virtual'
    #即使在场,也不要叫木偶师或欧海。
    # ansible all -m ansible.builtin.setup -a 'gather_subset=!facter,!ohai'
    #仅收集默认的最低事实数量:
    # ansible all -m ansible.builtin.setup -a 'gather_subset=!all'
    #不收集任何事实,即使是默认的最小事实子集:
    # ansible all -m ansible.builtin.setup -a 'gather_subset=!all,!min'
    #使用存储在C:\custom_fact中的自定义事实显示来自Windows主机的事实。
    # ansible windows -m ansible.builtin.setup -a "fact_path='c:\custom_facts'"
    #收集dbservers组中机器的事实(也称为委派事实)
    - hosts: app_servers
      tasks:
        - name: Gather facts from db servers
          ansible.builtin.setup:
          delegate_to: "{{ item }}"
          delegate_facts: true
          loop: "{{ groups['dbservers'] }}"

3.Ansible主机分组与变量管理

为什么需要主机分组与变量管理?

在 Ansible 自动化运维中,主机分组变量管理是提升效率的核心手段:

  • 批量操作:通过逻辑分组快速执行命令(如更新所有 Web 服务器)。
  • 环境隔离:为开发、测试、生产环境分配独立配置。
  • 配置复用:通过变量动态适配不同主机的参数。

主机分组:灵活管理多节点

1. 基础分组

inventory 文件中定义主机组,支持数字范围通配符

复制代码
[web]
web-[1:3].example.com  # 匹配 web-1.example.com 至 web-3.example.com

[db]
db-*.example.com       # 匹配所有以 db- 开头的域名

2. 嵌套子组

通过 :children 定义层级关系,实现复杂环境管理:

复制代码
[ubuntu]
host1 ansible_host=192.168.1.101

[debian]
host2 ansible_host=192.168.1.102

[linux:children]  # 父组聚合子组
ubuntu
debian

[prod:children]   # 按环境划分
linux

3. 分组实战场景

  • 多环境管理

    复制代码
    [dev]
    dev-web1 ansible_host=10.0.0.101
    
    [prod]
    prod-web1 ansible_host=192.168.1.101
    
    [all:children]  # 全局聚合
    dev
    prod
  • 角色划分

    复制代码
    [nginx]
    lb1 ansible_host=192.168.1.200
    
    [tomcat]
    app[1:3] ansible_host=192.168.1.[201:203]

变量管理:动态配置

1. 变量定义位置

位置 优先级 适用场景
命令行 (-e) 最高 临时覆盖变量
主机变量文件 主机专属配置(如 IP、端口)
组变量文件 共享配置(如软件版本)
库存文件 基础连接参数(如 ansible_port

2. 变量定义示例

  • 库存文件直接定义

    复制代码
    [web]
    web1 ansible_host=192.168.1.101 ansible_port=2222  # 指定 SSH 端口
  • 主机变量文件 (host_vars/web1.yml):

    复制代码
    ---
    http_port: 8080
    max_connections: 1000
  • 组变量文件 (group_vars/web.yml):

    复制代码
    ---
    nginx_version: 1.25.2
    app_env: production

3. 变量继承与覆盖

  • 继承逻辑 :子组继承父组变量,同名变量优先级为 子组 > 父组
  • 调试技巧 :使用 ansible-inventory --graph 查看主机归属与变量来源。

4."Hello World"apache

Ansible playbook 是 Ansible 的基本组件之一,因为它们记录和执行 Ansible 的配置。通常,playbook 是自动执行要在远程计算机上执行的一组任务的主要方式。

它们通过收集所有必要的资源来编排有序流程或避免重复手动作,从而帮助我们实现自动化。Playbook 可以在人员之间重复使用和共享,它们被设计为对人类友好且易于在 YAML 中编写。

使用Ansible Playbooks - 带有示例的提示和技巧

sed -i 's/33.11/0.221/' step-04/hosts

准备清单文件,命名为:hosts

shell 复制代码
[web]
host2 ansible_host=192.168.0.222 ansible_user=root

构建一个 playbook,它将在组中的计算机上安装 apache。web

ruby 复制代码
- hosts: web
  tasks:
    - name: Installs apache web server
      apt:
        pkg: apache2
        state: present
        update_cache: true
  • -name 为此任务添加了一个名称。虽然这不是必需的,但它可以在 playbook 运行时提供信息
  • apt 模块,它可以 安装 Debian 软件包

运行 playbook :apache.yml

shell 复制代码
ansible-playbook -i step-04/hosts -l host2 step-04/apache.yml
  • -l SUBSET, --limit SUBSET (限制分组)

运行后可看到以下内容:

shell 复制代码
PLAY [web] ************

TASK [Gathering Facts] ***********
ok: [host2]

TASK [Installs apache web server] *******************
changed: [host2]

PLAY RECAP *************
host2                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
  • PLAY web *** 表示开始执行一个针对主机组 ‌**web**‌ 的 play。
    • [web]:对应 playbook 中定义的 hosts: web,目标主机组来自 Ansible 清单文件(如 inventory 文件)。
  • TASK Gathering Facts 用户自定义的任务名称,这里表示安装 Apache Web 服务器。
  • changed: [host2]:表示此任务在主机 host2 上执行成功并引发了系统变更(例如安装了新软件包)。
  • TASK Installs apache web server 用户自定义的任务名称,这里表示安装 Apache Web 服务器。
    • changed: [host2]:表示此任务在主机 host2 上执行成功并引发了系统变更(例如安装了新软件包)。
  • PLAY RECAP 整个 play 的执行结果汇总。
    • **ok=2**‌:2 个任务执行成功且无变更(如 Gathering Facts)。
    • ‌**changed=1**‌:1 个任务引发了变更(如安装 Apache)。
    • 其他字段(如 failed=0)表示无失败、无跳过、无重试等。

现在再次运行一次,看看会发生什么:

shell 复制代码
ansible-playbook -i step-04/hosts -l host2 step-04/apache.yml

PLAY [web] *********

TASK [Gathering Facts] ********
ok: [host2]

TASK [Installs apache web server] *****
ok: [host2]

PLAY RECAP *******
host2                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

'changed=0' 。这是绝对正常的,也是核心功能之一 的 Ansible:playbook 仅在有事可做时才会起作用。这称为幂等性

5.优化apache设置

我们已经安装了 apache,现在让我们设置我们的 virtualhost。

在 Apache 配置场景中,virtualhost(虚拟主机)用于在同一台服务器上托管多个网站或应用。通过不同的域名、IP 或端口,Apache 可区分请求并返回对应的网站内容。在 Ansible Playbook 中,配置 virtualhost 通常涉及以下操作:

  • 创建虚拟主机配置文件(如 .conf 文件);
  • 指定域名、文档根目录、日志路径等参数;
  • 启用配置并重启 Apache 服务。

我们的服务器上只需要一个 virtualhost,但我们想替换默认的 一个具有更具体的东西。因此,我们必须删除当前的 (大概是)VirtualHost,发送我们的 VirtualHost,激活它并 重新启动 Apache。

添加我们的 virtualhost 配置 给 Host1,路径为:files/awesome-app

shell 复制代码
<VirtualHost *:80>
  DocumentRoot /var/www/awesome-app

  Options -Indexes

  ErrorLog /var/log/apache2/error.log
  TransferLog /var/log/apache2/access.log
</VirtualHost>
指令 作用
<VirtualHost *:80> 监听所有 IP 的 80 端口,处理对该虚拟主机的请求。
DocumentRoot 网站根目录为 /var/www/awesome-app,存放网站文件(需确保目录存在)。
Options -Indexes 禁止目录浏览(若目录中没有 index.html,用户无法查看文件列表)。
ErrorLog 错误日志路径为 /var/log/apache2/error.log
TransferLog 访问日志路径为 /var/log/apache2/access.log

hosts

shell 复制代码
[web]
host1 ansible_host=192.168.0.221 ansible_user=root

更新 apache playbook

ruby 复制代码
---
- hosts: web  #指定该 Play 针对清单文件(inventory)中定义的 web 主机组执行。
  tasks:
    - name: Installs apache web server
#模块:apt(适用于 Debian/Ubuntu 系统)。
      apt:
#安装 Apache2 软件包。
        pkg: apache2
#确保 Apache2 已安装。
        state: present
        update_cache: true
#等同于执行 apt update,更新软件包缓存(确保安装最新版本)。

    - name: Push default virtual host configuration
      copy:
        src: files/awesome-app
        dest: /etc/apache2/sites-available/awesome-app.conf
#将本地 files/awesome-app 文件复制到目标主机的 /etc/apache2/sites-available/awesome-app.conf。
        mode: 0640
#设置文件权限为 -rw-r-----(所有者可读写,所属组可读,其他用户无权限)。

    - name: Activates our virtualhost
      file:
        src: /etc/apache2/sites-available/awesome-app.conf
        dest: /etc/apache2/sites-enabled/awesome-app.conf
        state: link
#在 sites-enabled 目录创建符号链接(state: link),指向 sites-available/awesome-app.conf。Apache 会加载 sites-enabled 中的配置文件,这一步等同于运行 a2ensite awesome-app.conf。        
      notify:
        - restart apache
#如果任务状态为 changed,触发 restart apache 处理器。

    - name: Disable the default virtualhost
      file:
        dest: /etc/apache2/sites-enabled/000-default.conf
        state: absent
      notify:
        - restart apache

    - name: Disable the default ssl virtualhost
#这两步禁用默认虚拟主机,防止默认虚拟主机与新配置冲突。    
      file:
        dest: /etc/apache2/sites-enabled/default-ssl.conf
        state: absent
#state: absent:删除 sites-enabled 目录中的默认配置文件符号链接(等同于 a2dissite 000-default.conf 和 a2dissite default-ssl.conf)。
      notify:
        - restart apache

  handlers:
    - name: restart apache
#处理器(Handlers),当任务触发 notify: restart apache 时,重启 Apache 服务(仅在相关配置文件发生变更时触发)。
      service:
        name: apache2
        state: restarted

执行输出:

shell 复制代码
PLAY [web] ********* 

TASK [Gathering Facts] ******** 
ok: [host1]

TASK [Installs apache web server] *******
ok: [host1]

TASK [Push default virtual host configuration] ****c
changed: [host1]

TASK [Activates our virtualhost] ****** c
changed: [host1]

TASK [Disable the default virtualhost] ******
changed: [host1]

TASK [Disable the default ssl virtualhost] ******
ok: [host1]

RUNNING HANDLER [restart apache] *******
changed: [host1]

PLAY RECAP *******
host1                      : ok=7    changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

在重新启动 apache 之前,我们难道不应该检查一下配置是否正常吗?如果我们的配置文件不正确,则不会中断服务。

6.在配置正确时重新启动

我们已经安装了 apache,推送了我们的 virtualhost 并重新启动了服务器。但 如果我们希望 playbook 仅在配置为 正确?让我们这样做。

Ansible 有一个漂亮的功能:如果出现问题,它将停止所有处理 错。如果配置 文件无效。

让我们更改我们的虚拟主机配置文件搞破坏DocumentDoot=》RocumentDoot:awesome-app

shell 复制代码
<VirtualHost *:80>
  RocumentDoot /var/www/awesome-app

  Options -Indexes

  ErrorLog /var/log/apache2/error.log
  TransferLog /var/log/apache2/access.log
</VirtualHost>

如前所述,当任务失败时,处理将停止。因此,我们将确保 配置在重新启动服务器之前有效。我们还首先添加 我们的 VirtualHost 在删除默认 VirtualHost 之前,因此后续的重启 (可能直接在服务器上完成) 不会破坏 Apache。

yaml 复制代码
---
- hosts: web  # 🌐 指定目标主机组为 `web`(需在 Ansible 清单中定义)
  tasks:
    # 🔧 任务1:安装 Apache
    - name: Installs apache web server
      apt:  # 📦 使用 apt 模块(Debian/Ubuntu 系统)
        pkg: apache2  # 软件包名称
        state: present  # 确保 apache2 已安装
        update_cache: true  # 先执行 `apt update` 更新软件源缓存

    # 📄 任务2:推送虚拟主机配置文件
    - name: Push future default virtual host configuration
      copy:  # 📂 使用 copy 模块复制文件
        src: files/awesome-app  # 本地文件路径(相对于 Playbook)
        dest: /etc/apache2/sites-available/awesome-app.conf  # 目标路径
        mode: 0640  # 设置文件权限为 -rw-r-----

    # 🔗 任务3:启用自定义虚拟主机
    - name: Activates our virtualhost
      command: a2ensite awesome-app  # 🖥️ 直接调用 Apache 命令启用站点
      # 等同于创建符号链接:/etc/apache2/sites-enabled/awesome-app.conf -> ../sites-available/awesome-app.conf

    # ✅ 任务4:验证 Apache 配置语法
    - name: Check that our config is valid
      command: apache2ctl configtest  # 🔍 检查配置是否有语法错误
      # 如果配置错误,任务会失败并终止 Playbook 执行

    # 🚫 任务5:禁用默认虚拟主机(非 SSL)
    - name: Deactivates the default virtualhost
      command: a2dissite 000-default  # ❌ 禁用默认站点

    # 🚫 任务6:禁用默认 SSL 虚拟主机
    - name: Deactivates the default ssl virtualhost
      command: a2dissite default-ssl  # ❌ 禁用默认 SSL 站点
      notify:  # 🔔 触发处理器
        - restart apache  # 仅当此任务状态为 changed 时重启 Apache

  handlers:  # ⚙️ 处理器(异步任务)
    # 🔄 处理器:重启 Apache 服务
    - name: restart apache
      service:  # 🛠️ 使用 service 模块
        name: apache2  # 服务名称
        state: restarted  # 重启服务

执行

shell 复制代码
ansible-playbook -i step-06/hosts -l host1 step-06/apache.yml

PLAY [web] ******** 

TASK [Gathering Facts] ******** 
ok: [host1]

TASK [Installs apache web server] *********
ok: [host1]

TASK [Push future default virtual host configuration] *******
changed: [host1]

TASK [Activates our virtualhost] ***** 
changed: [host1]

TASK [Check that our config is valid] *********
fatal: [host1]: FAILED! => {"changed": true, "cmd": ["apache2ctl", "configtest"], "delta": "0:00:00.020643", "end": "2025-05-01 06:30:47.032996", "msg": "non-zero return code", "rc": 1, "start": "2025-05-01 06:30:47.012353", "stderr": "AH00526: Syntax error on line 2 of /etc/apache2/sites-enabled/awesome-app.conf:\nInvalid command 'RocumentDoot', perhaps misspelled or defined by a module not included in the server configuration", "stderr_lines": ["AH00526: Syntax error on line 2 of /etc/apache2/sites-enabled/awesome-app.conf:", "Invalid command 'RocumentDoot', perhaps misspelled or defined by a module not included in the server configuration"], "stdout": "Action 'configtest' failed.\nThe Apache error log may have more information.", "stdout_lines": ["Action 'configtest' failed.", "The Apache error log may have more information."]}

PLAY RECAP *********
host1                      : ok=4    changed=2    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

正如您所见,由于 apache2ctl 在失败时会以退出代码 1 返回,所以 Ansible 会意识到这一点并停止处理。太棒了!

嗯,实际上情况不太好......我们的虚拟主机还是添加上了。不过,后续任何一次重启 Apache 都会报错我们的配置有问题并退出。所以我们需要一种方法来捕获失败并回退。

从错误信息可以明确看出 Apache 配置文件的语法错误直接导致 Playbook 执行失败

shell 复制代码
Invalid command 'RocumentDoot'
#重启测试
ansible -i step-06/hosts -m service -a 'name=apache2 state=restarted' host1
host1 | FAILED! => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "msg": "Unable to restart service apache2: Job for apache2.service failed because the control process exited with error code.\nSee \"systemctl status apache2.service\" and \"journalctl -xe\" for details.\n"
}

7.使用条件语句

我们已经安装了 Apache,推送了虚拟主机并重启了服务器。 但如果出现问题,我们希望将一切恢复到稳定状态。

出现问题时回退

当任务失败时,处理就会停止......除非我们接受失败(而且我们应该接受)。这就是我们要做的: 如果出现失败,继续处理,但仅限于撤销我们已做的操作。

yaml 复制代码
- hosts: web  # 仅在清单中定义的 'web' 主机组执行
  tasks:
    # 安装 Apache
    - name: Installs apache web server
      apt:
        pkg: apache2        # 安装 Apache2 软件包
        state: present      # 确保软件包存在
        update_cache: true  # 先执行 `apt update` 更新缓存

    # 推送配置文件(潜在风险点)
    - name: Push future default virtual host configuration
      copy:
        src: files/awesome-app  # 本地配置文件名
        dest: /etc/apache2/sites-available/awesome-app.conf  # 目标路径
        mode: 0640  # 权限设置(可能需确保 Apache 用户可读)

    # 启用新虚拟主机(非幂等操作)
    - name: Activates our virtualhost
      command: a2ensite awesome-app  # ? 重复执行会报错 "Site already enabled"
      args:
        creates: /etc/apache2/sites-enabled/awesome-app.conf  #幂等性控制,添加creates参数确保任务仅在符号链接不存在时执行,避免重复报错。
      notify: restart apache  # 触发重启

    # 验证配置语法(关键安全阀)
    - name: Check that our config is valid
      command: apache2ctl configtest
      register: result        # 存储命令执行结果
      ignore_errors: true     # 即使验证失败也继续执行(触发回滚)

    # 回滚操作1:重新启用默认站点(需确保默认配置存在)
    - name: Rolling back - Restoring old default virtualhost
      command: a2ensite 000-default
      when: result is failed  # 仅在配置验证失败时执行

    # 回滚操作2:禁用新站点
    - name: Rolling back - Removing our virtualhost
      command: a2dissite awesome-app
      when: result is failed

    # 强制终止 Playbook 并提示错误
    - name: Rolling back - Ending playbook
      fail:
        msg: "Configuration file is not valid. Please check that before re-running the playbook."
      when: result is failed  # 终止后续任务

    # 禁用默认站点(仅在配置验证成功后执行)
    - name: Deactivates the default virtualhost
      command: a2dissite 000-default
      notify: restart apache  # 统一服务重启触发,任何修改站点配置的操作都需要重启 Apache,否则变更不会生效。

    # 禁用默认 SSL 站点
    - name: Deactivates the default ssl virtualhost
      command: a2dissite default-ssl
      notify:
        - restart apache  # 触发服务重启(但其他任务也需要类似设置)

  handlers:
    # 服务重启处理器
    - name: restart apache
      service:
        name: apache2
        state: restarted

关键字 register 会记录 apache2ctl configtest 命令的输出(退出状态、标准输出、标准错误......),并且 when: result is failed 会检查已注册的变量( result )是否包含失败状态。

shell 复制代码
ansible-playbook -i step-07/hosts -l host1 step-07/apache.yml

PLAY [web] ********

TASK [Gathering Facts] *************
ok: [host1]

TASK [Installs apache web server] ********
ok: [host1]

TASK [Push future default virtual host configuration] *********
ok: [host1]

TASK [Activates our virtualhost] ************
ok: [host1]

TASK [Check that our config is valid] *************
fatal: [host1]: FAILED! => {"changed": true, "cmd": ["apache2ctl", "configtest"], "delta": "0:00:00.022459", "end": "2025-05-01 07:10:28.993221", "msg": "non-zero return code", "rc": 1, "start": "2025-05-01 07:10:28.970762", "stderr": "AH00526: Syntax error on line 2 of /etc/apache2/sites-enabled/awesome-app.conf:\nInvalid command 'RocumentDoot', perhaps misspelled or defined by a module not included in the server configuration", "stderr_lines": ["AH00526: Syntax error on line 2 of /etc/apache2/sites-enabled/awesome-app.conf:", "Invalid command 'RocumentDoot', perhaps misspelled or defined by a module not included in the server configuration"], "stdout": "Action 'configtest' failed.\nThe Apache error log may have more information.", "stdout_lines": ["Action 'configtest' failed.", "The Apache error log may have more information."]}
...ignoring

TASK [Rolling back - Restoring old default virtualhost] ******************
changed: [host1]

TASK [Rolling back - Removing our virtualhost] ****************
changed: [host1]

TASK [Rolling back - Ending playbook] **************
fatal: [host1]: FAILED! => {"changed": false, "msg": "Configuration file is not valid. Please check that before re-running the playbook."}

PLAY RECAP ************
host1                      : ok=7    changed=3    unreachable=0    failed=1    skipped=0    rescued=0    ignored=1

看起来如预期般奏效了。让我们试着重启 Apache 看看是否真的成功了:

shell 复制代码
ansible -i step-07/hosts -m service -a 'name=apache2 state=restarted' host1
host1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "name": "apache2",
    "state": "started",
    "status": {

能正常启动,现在我们的 Apache 服务器已免受错误配置的影响了。

8.从 Git 部署网站

我们已经安装了 Apache,推送了虚拟主机并安全地重启了服务器。 现在我们将使用 Git 模块来部署我们的应用程序。

git模块

我们的虚拟主机已设置好,但还需要做一些改动才能完成部署。 首先,我们要部署一个 PHP 应用程序,所以需要安装 libapache2-mod-php 包。其次,由于用于克隆我们应用程序的 Git 仓库的 Git 模块需要用到它,所以我们还得安装 git

Ansible 可以遍历一系列项目,并像这样在操作中使用每个项目:

shell 复制代码
...
- name: Installs necessary packages
  apt:
    pkg: "{{ item }}"
    state: latest
    update_cache: true
  with_items:
    - apache2
    - libapache2-mod-php
    - git
...

注意:自 2.7 版本起,在模块中使用循环仅调用一次的做法已弃用。您可以按照下表所示指定包的列表:

Option A 选择一个 Option B 选项B
- name: "Install foo & bar" apt: pkg: ["foo", "bar"] - name: "Install foo & bar" apt: pkg: - foo - bar

最终方案:

yaml 复制代码
- hosts: web
  tasks:
    - name: ;**安装必要软件包**;(包括Apache, PHP模块, Git)
      apt:
        pkg: ["apache2", "libapache2-mod-php", "git"]
        state: latest
        update_cache: true

    - name: ;**推送默认的虚拟主机配置文件**;
      copy:
        src: files/awesome-app
        dest: /etc/apache2/sites-available/awesome-app.conf
        mode: 0640

    - name: ;**激活我们的虚拟主机**;
      command: a2ensite awesome-app

    - name: ;**检查配置是否有效**;
      command: apache2ctl configtest
      register: result
      ignore_errors: true

    - name: ;**回滚 - 恢复旧的默认虚拟主机**;(如果配置测试失败)
      command: a2ensite 000-default
      when: result is failed

    - name: ;**回滚 - 移除我们的虚拟主机**;(如果配置测试失败)
      command: a2dissite awesome-app
      when: result is failed

    - name: ;**回滚 - 结束Playbook**;(如果配置测试失败)
      fail:
        msg: "配置文件无效。请在重新运行Playbook之前检查。"
      when: result is failed

	#如果执行git模块出现Failed to connect to github.com port 443: Connection refused,Ansible 的 git 模块可能因旧仓库状态导致冲突。强制清理目标目录
    #- name: 清理旧目录
    #  file:
    #    path: /var/www/awesome-app
    #    state: absent
    #  ignore_errors: yes  # 防止目录不存在时报错
    #  tags: deploy

    - name: ;**部署我们的应用程序**;(使用Git)
      git:
        repo: 'https://github.com/leucos/ansible-tuto-demosite.git'
        dest: /var/www/awesome-app
        force: yes       # 强制覆盖本地修改
        update: yes  # 确保拉取最新代码
        version: master  # 明确指定分支
      tags: deploy  # 可通过`ansible-playbook ... --tags deploy`单独执行此任务

    - name: ;**禁用默认的虚拟主机**;(应在部署后禁用,确保不会冲突)
      command: a2dissite 000-default
      notify: restart apache  # 统一服务重启触发,任何修改站点配置的操作都需要重启 Apache,否则变更不会生效。

    - name: ;**禁用默认的SSL虚拟主机**;(如果不需要SSL,可考虑移除或注释此行)
      command: a2dissite default-ssl
      notify:
        - restart apache

  handlers:
    - name: restart apache
      service:
        name: apache2
        state: restarted

启动

shell 复制代码
ansible-playbook -i step-08/hosts -l host1 step-08/apache.yml

PLAY [web] ************

TASK [Gathering Facts] ***********
ok: [host1]

TASK [;**安装必要软件包**;(包括Apache, PHP模块, Git)] **********
ok: [host1]

TASK [;**推送默认的虚拟主机配置文件**;] ***********
ok: [host1]

TASK [;**激活我们的虚拟主机**;] *************
changed: [host1]

TASK [;**检查配置是否有效**;] ************* 
changed: [host1]

TASK [;**回滚 - 恢复旧的默认虚拟主机**;(如果配置测试失败)] ***************
skipping: [host1]

TASK [;**回滚 - 移除我们的虚拟主机**;(如果配置测试失败)] ******************
skipping: [host1]

TASK [;**回滚 - 结束Playbook**;(如果配置测试失败)] **************
skipping: [host1]

TASK [;**部署我们的应用程序**;(使用Git)] ************
ok: [host1]

TASK [;**禁用默认的虚拟主机**;(应在部署后禁用,确保不会冲突)] ****************
changed: [host1]

TASK [;**禁用默认的SSL虚拟主机**;(如果不需要SSL,可考虑移除或注释此行)] ************
changed: [host1]

RUNNING HANDLER [restart apache] **************
changed: [host1]

PLAY RECAP ************
host1                      : ok=9    changed=5    unreachable=0    failed=0    skipped=3    rescued=0    ignored=0

访问测试host1

http://192.168.0.221

复制代码
Welcome to your awesome app !
App backend host1

显示了一张图片以及服务器的主机名

请注意, tags: deploy 这一行允许您仅执行playbook中的某一部分。 假设您为您的网站推送了一个新版本。您希望加快速度,仅执行负责部署的部分。标签允许您这样做。 当然,"deploy"只是一个字符串,它没有任何特定的含义,可以是任何内容。让我们看看如何使用它:

shell 复制代码
ansible-playbook -i step-08/hosts -l host1 step-08/apache.yml -t deploy

PLAY [web] **********

TASK [Gathering Facts] ***********
ok: [host1]

TASK [;**部署我们的应用程序**;(使用Git)] **********
ok: [host1]

PLAY RECAP **************
host1                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

# 显示所有可用标签
ansible-playbook site.yml --list-tags

# 详细日志跟踪
ansible-playbook site.yml -t deploy -vvv

Failed to connect to github.com port 443: Connection refused

如果执行到git模块时报此错:

查看具体执行情况

ansible-playbook -i step-12test/hosts -l web step-12test/site.yml --tags deploy -vvv

先排查防火墙、网络状态等网络情况,并进入对应报错的主机执行git 测试能否下载项目,

复制代码
#手动查询分支情况
git ls-remote --heads https://github.com/leucos/ansible-tuto-demosite.git

Ansible 的 git 模块可能因旧仓库状态导致冲突。强制清理目标目录,添加模块再测试

复制代码
- name: 清理旧目录
  file:
    path: /var/www/awesome-app
    state: absent
  ignore_errors: yes  # 防止目录不存在时报错
  tags: deploy

- name: ;**部署我们的应用程序**;(使用Git)
  git:
    repo: 'https://github.com/leucos/ansible-tuto-demosite.git'
    dest: /var/www/awesome-app
    force: yes       # 强制覆盖本地修改
    update: yes  # 确保拉取最新代码
    version: master  # 明确指定分支
  tags: deploy  # 可通过`ansible-playbook ... --tags deploy`单独执行此任务

9.添加另一台 Web 服务器

现在有了一台web服务器(host1),现在再添加一台web服务器和一台负载均衡服务器

更新inventory

sed -i -e 's/host0/host3/' -e 's/33/0/' -e 's/11/221/' -e 's/12/222/' -e 's/10/223/' step-09/hosts

cat step-09/hosts

shell 复制代码
[web]
host1 ansible_host=192.168.0.221 ansible_user=root
host2 ansible_host=192.168.0.222 ansible_user=root

[haproxy]
host3 ansible_host=192.168.0.223 ansible_user=root

在此指定 ansible_host 是因为主机的 IP 地址与预期不同(或者无法解析)。您可以将这些主机添加到 /etc/hosts 中,从而无需担心,或者使用真实的主机名(通常您会采取的做法)。

再建一个web服务器

shell 复制代码
cp step-08/apache.yml step-09/apache.yml
ansible-playbook -i step-09/hosts step-09/apache.yml

PLAY [web] ************

TASK [Gathering Facts] ***************
ok: [host2]
ok: [host1]

TASK [;**安装必要软件包**;(包括Apache, PHP模块, Git)] *****************
ok: [host1]
changed: [host2]

TASK [;**推送默认的虚拟主机配置文件**;] *************
changed: [host2]
ok: [host1]

TASK [;**激活我们的虚拟主机**;] *************
changed: [host2]
changed: [host1]

TASK [;**检查配置是否有效**;] ***************
changed: [host1]
changed: [host2]

TASK [;**回滚 - 恢复旧的默认虚拟主机**;(如果配置测试失败)] *****************
skipping: [host2]
skipping: [host1]

TASK [;**回滚 - 移除我们的虚拟主机**;(如果配置测试失败)] *****************
skipping: [host1]
skipping: [host2]

TASK [;**回滚 - 结束Playbook**;(如果配置测试失败)] *************
skipping: [host1]
skipping: [host2]

TASK [;**部署我们的应用程序**;(使用Git)] *************
ok: [host1]
changed: [host2]

TASK [;**禁用默认的虚拟主机**;(应在部署后禁用,确保不会冲突)] *************
changed: [host1]
changed: [host2]

TASK [;**禁用默认的SSL虚拟主机**;(如果不需要SSL,可考虑移除或注释此行)] ************
changed: [host2]
changed: [host1]

RUNNING HANDLER [restart apache] ***********
changed: [host2]
changed: [host1]

PLAY RECAP *************
host1                      : ok=9    changed=5    unreachable=0    failed=0    skipped=3    rescued=0    ignored=0
host2                      : ok=9    changed=8    unreachable=0    failed=0    skipped=3    rescued=0    ignored=0

我们所要做的就是从命令行中移除 -l host1 。请记住, -l 是一个限制在特定主机上运行playbook的开关。现在我们不再限制了,它将在playbook预期运行的所有主机上运行(即 web )。

如果我们有其他服务器在 web 组中,但只想将playbook限制在其中的一个子集上,那么我们可以使用例如: -l firsthost:secondhost:...

10.HAProxy 配置模板

HAProxy 是一个高性能的 ‌负载均衡器 ‌ 和 ‌反向代理‌,常用于分发流量到多个后端服务器,提升系统可用性和扩展性。核心功能包括:

  1. 流量分发‌:支持轮询(round-robin)、加权轮询、最少连接等算法。
  2. 健康检查‌:自动检测后端服务器状态,移除故障节点。
  3. SSL终止‌:可处理HTTPS请求解密,减轻后端服务器压力。
  4. 高可用性‌:结合Keepalived可实现双机热备。

Ansible 使用 Jinja2,这是 Python 的一个模板引擎。在编写 Jinja2 模板时,您可以使用 Ansible 定义的任何变量。

例如,如果您想要输出当前模板所构建的主机的 inventory_name,您只需在 Jinja 模板中写入 {``{ inventory_hostname }} 即可。

或者如果您需要第一个以太网接口的 IP 地址(Ansible 通过 setup 模块已知),您只需在模板中写入: {``{ ansible_default_ipv4.address}} (这与 {``{ ansible_default_ipv4['address'] }} 等效)。

Jinja2 模板还支持条件语句、for 循环等。

让我们创建一个 templates/ 目录,并在其中创建一个 Jinja 模板。我们将它命名为 haproxy.cfg.j2 。按照惯例,我们使用 .j2 扩展名,以表明这是一个 Jinja2 模板,但这并非必要。

step-10/templates/haproxy.cfg.j2

jinja2 复制代码
global
    daemon              # 以守护进程模式运行
    maxconn 256         # 设置最大并发连接数为256
    
defaults
    mode http           # 默认使用HTTP模式
    timeout connect 5000ms   # 连接超时时间为5秒
    timeout client 50000ms   # 客户端超时时间为50秒
    timeout server 50000ms   # 服务器端超时时间为50秒
    
listen cluster
    bind {{ ansible_host }}:80   # 绑定主机的IP和端口(动态变量来自Ansible)
    mode http                    # 启用HTTP模式
    stats enable                 # 开启统计页面
    balance roundrobin           # 使用轮询负载均衡算法
{% for backend in groups['web'] %}    # 遍历Ansible的web组内所有主机
    server {{ hostvars[backend]['ansible_hostname'] }} {{ hostvars[backend]['ansible_facts']['default_ipv4']['address'] }} check port 80  # 动态生成后端服务器配置(主机名+IP+健康检查)
{% endfor %}
    option httpchk HEAD /index.php HTTP/1.0   # 定义HTTP健康检查方法

注意事项!!! {#section1}

检查Ansible事实中的default_ipv4是否是正确的,如果不是需更在haproxy.cfg.j2中改为正确的路径

jinja2 复制代码
#我这里的事实中['default_ipv4']对应eth0,目前使用的网卡是eth1(由于vagrant设置的public_network)
ansible -i hosts -m setup host1
host1 | SUCCESS => {
...............................................
"ansible_eth1": {
            "active": true,
            "device": "eth1",
            "features": {
..............................................
            },
            "hw_timestamp_filters": [],
            "ipv4": {
                "address": "192.168.0.221",
#将step-10/templates/haproxy.cfg.j2配置文件中的server改为如下
server {{ hostvars[backend]['ansible_hostname'] }} {{ hostvars[backend].ansible_eth1.ipv4.address }}:80 check port 80
#改完后再执行Ansible-Playbook测试

该模板用于‌动态生成HAProxy负载均衡配置‌:

  1. 动态绑定IP端口 ‌:通过 {``{ ansible_host }} 注入主机IP。
  2. 自动化后端服务器配置 ‌:基于Ansible的 web 主机组循环生成服务器列表,避免手动维护。
  3. 参数统一管理‌:超时时间、负载均衡策略等集中定义,便于批量修改。

HAProxy playbook

step-10/haproxy.yml

yaml 复制代码
- hosts: web
  gather_facts: true  # 收集目标服务器的系统信息(如IP、OS版本)

- hosts: haproxy      # 针对名为 "haproxy" 的主机组执行以下任务
  tasks:
    # 任务1:安装 HAProxy
    - name: Installs haproxy load balancer
      apt:
        pkg: haproxy        # 包名称
        state: present     # 确保已安装
        update_cache: yes   # 更新 apt 缓存(相当于 apt update)

    # 任务2:推送配置文件
    - name: Pushes configuration
      template:
        src: templates/haproxy.cfg.j2  # 使用 Jinja2 模板生成配置文件
        dest: /etc/haproxy/haproxy.cfg # 目标路径
        mode: 0640         # 文件权限(root可读写,haproxy组可读)
        owner: root         # 文件所有者
        group: root        # 文件所属组
      notify:
        - restart haproxy  # 文件变更后触发重启操作

    # 任务3:确保 HAProxy 开机自启
    - name: Sets default starting flag to 1
      lineinfile:
        dest: /etc/default/haproxy  # 修改系统服务配置文件
        regexp: "^ENABLED"          # 查找以 ENABLED 开头的行
        line: "ENABLED=1"           # 强制设置为 1(启用服务)
      notify:
        - restart haproxy

  handlers:
    # 定义重启 HAProxy 的处理程序
    - name: restart haproxy
      service:
        name: haproxy
        state: restarted

执行:

shell 复制代码
 ansible-playbook -i step-10/hosts step-10/apache.yml step-10/haproxy.yml

PLAY [web] ******************************************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************
ok: [host1]
ok: [host2]

TASK [Installs necessary packages] ******************************************************************************************************
ok: [host1]
ok: [host2]

TASK [Push future default virtual host configuration] ***********************************************************************************
ok: [host2]
ok: [host1]

TASK [Activates our virtualhost] ********************************************************************************************************
changed: [host2]
changed: [host1]

TASK [Check that our config is valid] ***************************************************************************************************
changed: [host2]
changed: [host1]

TASK [Rolling back - Restoring old default virtualhost] *********************************************************************************
skipping: [host1]
skipping: [host2]

TASK [Rolling back - Removing out virtualhost] ******************************************************************************************
skipping: [host1]
skipping: [host2]

TASK [Rolling back - Ending playbook] ***************************************************************************************************
skipping: [host1]
skipping: [host2]

TASK [Deploy our awesome application] ***************************************************************************************************
ok: [host1]
ok: [host2]

TASK [Deactivates the default virtualhost] **********************************************************************************************
changed: [host2]
changed: [host1]

TASK [Deactivates the default ssl virtualhost] ******************************************************************************************
changed: [host2]
changed: [host1]

RUNNING HANDLER [restart apache] ********************************************************************************************************
changed: [host2]
changed: [host1]

PLAY RECAP ******************************************************************************************************************************
host1                      : ok=9    changed=5    unreachable=0    failed=0    skipped=3    rescued=0    ignored=0
host2                      : ok=9    changed=5    unreachable=0    failed=0    skipped=3    rescued=0    ignored=0


PLAY [web] ******************************************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************
ok: [host2]
ok: [host1]

PLAY [haproxy] **************************************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************
ok: [host3]

TASK [Installs haproxy load balancer] ***************************************************************************************************
changed: [host3]

TASK [Pushes configuration] *************************************************************************************************************
changed: [host3]

TASK [Sets default starting flag to 1] **************************************************************************************************
changed: [host3]

RUNNING HANDLER [restart haproxy] *******************************************************************************************************
changed: [host3]

PLAY RECAP ******************************************************************************************************************************
host1                      : ok=10   changed=5    unreachable=0    failed=0    skipped=3    rescued=0    ignored=0
host2                      : ok=10   changed=5    unreachable=0    failed=0    skipped=3    rescued=0    ignored=0
host3                      : ok=5    changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

测试

http://192.168.0.223/

您甚至可以在 http://192.168.0.233/haproxy?stats 查看 HAProxy 的统计信息。

正常Status为OPEN,如果为DOWN,host3:80报503注意检查以下内容:

  • 防火墙(host1 和 host2是否能访问)
  • HAProxy配置中的服务器ip是否正确(HAProxy服务器host3中的haproxy.cfg配置文件)
shell 复制代码
cat /etc/haproxy/haproxy.cfg #检查类似以下部分
  server host1 192.168.0.221:80 check
  server host2 192.168.0.222:80 check

处理办法见上面:注意事项

template和lineinfile模块

template 模块

  • 将动态生成的配置文件推送到目标服务器
  • 使用 Jinja2 模板引擎,允许在配置文件中插入变量、循环、条件判断等逻辑。
  • 最终生成一个静态配置文件并传输到目标服务器的指定路径。

关键特性

  • 动态渲染 :假设您的 haproxy.cfg.j2 中可能包含类似下面的动态内容:

    {% for server in groups.web %}
    server {{ server }} {{ hostvars[server].ansible_default_ipv4.address }}:80 check
    {% endfor %}

这会根据 web 主机组的服务器列表自动生成后端服务器配置。

  • 幂等性:如果目标文件已经存在且内容相同,不会重复操作。
  • 权限控制:可以精确设置文件权限、所有者和用户组。

适用场景

  • 需要动态生成配置文件(例如根据变量、主机列表生成不同内容)。
  • 需要将配置文件与 Playbook 逻辑解耦(模板和逻辑分离)。

lineinfile 模块

  • 确保文件中某一行存在(或不存在)
  • 通过正则表达式匹配目标行,并修改或替换它。
  • 常用于简单修改配置文件中的某个参数。

关键特性

  • 精准修改:只修改匹配到的行,不影响文件其他内容。
  • 幂等性 :如果目标行已经是 ENABLED=1,不会重复修改。
  • 灵活性:可以用正则表达式匹配复杂模式。

适用场景

  • 修改文件中的某个特定参数(如 ENABLED=1)。
  • 添加一行内容(例如在 sshd_config 中添加 PermitRootLogin no)。

对比总结

模块 用途 特点 适用场景
template 生成并推送动态配置文件 支持变量渲染、循环、条件判断 需要动态生成完整配置文件时
lineinfile 修改文件中某一行 精准修改、正则匹配 修改单个参数或添加单行内容

常见问题

  1. 为什么不直接用 copy 模块?
  • copy 模块直接复制静态文件,而 template 允许在文件中使用变量和逻辑。
  • 例如,在您的 HAProxy 配置中,后端服务器列表可能动态变化,用 template 可以自动生成列表。
  1. lineinfile 会覆盖整个文件吗?
  • 不会!它只修改匹配到的行,其他内容保持不变。
  1. 如果一行都不匹配会怎样?
  • 默认会在文件末尾添加 line 指定的内容。可以通过 insertafterinsertbefore 参数控制插入位置。

11.变量再探

所以我们已经设置好了负载均衡器,运行得相当不错。我们从事实中获取变量,并用它们来构建配置。但 Ansible 还支持其他类型的变量。我们已经在清单中看到了 ansible_host ,但现在我们将使用在 host_varsgroup_vars 文件中定义的变量。

微调我们的 HAProxy 配置

HAProxy 通常会检查后端是否处于活动状态。当某个后端似乎已停止运行时,它会被从后端池中移除,HAProxy 也不会再向其发送请求。

后端也可以有不同的权重(在 0 到 256 之间)。权重越高,与其他后端相比,该后端将接收的连接数就越多。如果节点的性能不均衡,这有助于更合理地分配流量。

我们将使用变量来配置所有这些参数。

Group vars

检查间隔将在 haproxy 的 group_vars 文件中设置。这将确保所有 haproxy 都能继承该设置。

我们只需在库存目录下创建文件 group_vars/haproxy.yml 即可。该文件必须以您想要为其定义变量的组命名。如果我们想为 web 组定义变量,该文件应命名为 group_vars/web.yml

请注意, .yml 是可选的:我们可以将 haproxy 组变量文件命名为 group_vars/haproxy ,Ansible 也能接受。扩展名只是帮助编辑器选择正确的语法高亮显示。

复制代码
haproxy_check_interval: 3000
haproxy_stats_socket: /tmp/sock
#名称是任意的。当然,有意义的名称是推荐的,但没有规定的语法。您甚至可以像这样使用复杂的变量(也就是 Python 字典):
haproxy:
    check_interval: 3000
    stats_socket: /tmp/sock

这只是个人喜好问题。复杂变量有助于逻辑上对内容进行分组。 在某些情况下,它们还可以合并后续定义的键(但请注意,这并非 Ansible 的默认行为)。目前我们只使用简单变量。

Hosts vars

主机变量遵循完全相同的规则,但位于 host_vars 目录下的文件中。

让我们在 host_vars/host1.example.com 中为我们的后端定义权重:

复制代码
haproxy_backend_weight: 100
#以及  host_vars/host2.example.com 
haproxy_backend_weight: 150

如果我们在 group_vars/web 中定义 haproxy_backend_weight ,它将被用作"默认值":在 host_vars 文件中定义的变量会覆盖在 group_vars 中定义的变量。

更新模板

模板必须更新以使用这些变量。

jinja2 复制代码
global
    daemon
    maxconn 256
{% if haproxy_stats_socket %}
    stats socket {{ haproxy_stats_socket }}
{% endif %}

defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

listen cluster
    bind {{ ansible_host }}:80
    mode http
    stats enable
    balance roundrobin
{% for backend in groups['web'] %}
    server {{ hostvars[backend]['ansible_hostname'] }} {{ hostvars[backend].ansible_eth1.ipv4.address }}:80 check port 80 weight {{ hostvars[backend].haproxy_backend_weight | default(100) }}
{% endfor %}
    option httpchk HEAD /index.php HTTP/1.0

请注意,我们还引入了一个 {% if ... 块。只有当测试为真时,此块内的内容才会被渲染。因此,如果我们为负载均衡器在某个地方定义了 haproxy_stats_socket (甚至可能在命令行中使用 --extra-vars="haproxy_stats_sockets=/tmp/sock" ),则包含的行将出现在生成的配置文件中(请注意,建议的设置非常不安全!)。

更新HAProxy.yaml:

shell 复制代码
---
- name: 配置 Web 服务器组
  hosts: web
  gather_facts: true  # 收集 web 组的服务器信息(如 IP、OS 版本)
                      # 用于后续模板生成后端服务器列表

- name: 配置 HAProxy 负载均衡器
  hosts: haproxy      # 目标为 haproxy 主机组
  tasks:
    # 任务1:安装 HAProxy
    - name: 安装 HAProxy
      apt:
        pkg: haproxy
        state: present     # 确保 HAProxy 已安装
        update_cache: yes  # 更新 apt 缓存(相当于 apt update)
      notify: haproxy_config_flow  # 触发名为 haproxy_config_flow 的处理程序链

    # 任务2:备份当前配置(用于回退)
    - name: 备份 HAProxy 配置
      copy:
        src: /etc/haproxy/haproxy.cfg  # 源文件(远程服务器上的当前配置)
        dest: /etc/haproxy/haproxy.cfg.bak  # 备份路径
        remote_src: yes    # 操作远程文件
        force: yes         # 强制覆盖旧备份
      when: not ansible_check_mode  # 仅在非检查模式运行(实际部署时执行)
      changed_when: false  # 不标记为已变更(避免误触发其他任务)
      notify: haproxy_config_flow  # 触发处理程序链

    # 任务3:生成并推送新配置
    - name: 生成并推送 HAProxy 配置
      template:
        src: templates/haproxy.cfg.j2  # Jinja2 模板路径
        dest: /etc/haproxy/haproxy.cfg  # 目标配置文件路径
        mode: 0640         # 文件权限(root 读写,haproxy 组只读)
        owner: root
        group: root
      notify: haproxy_config_flow  # 触发处理程序链

    # 任务4:确保 HAProxy 开机自启
    - name: 启用 HAProxy 开机启动
      lineinfile:
        dest: /etc/default/haproxy  # 修改服务启动配置文件
        regexp: "^ENABLED"          # 匹配以 ENABLED 开头的行
        line: "ENABLED=1"           # 强制设置为启用
      notify: haproxy_config_flow   # 触发处理程序链

  handlers:
    # 处理程序1:验证配置文件语法
    - name: 验证 HAProxy 配置
      command: haproxy -c -f /etc/haproxy/haproxy.cfg  # 检查配置合法性
      register: haproxy_validate  # 存储命令执行结果(rc=0 表示成功)
      listen: haproxy_config_flow  # 绑定到 haproxy_config_flow 事件链
      ignore_errors: yes          # 允许验证失败后继续执行后续任务

    # 处理程序2:重启 HAProxy 服务(仅在验证成功时执行)
    - name: 重启 HAProxy
      service:
        name: haproxy
        state: restarted
      when: 
        - haproxy_validate.rc == 0  # 仅当验证通过时触发
      listen: haproxy_config_flow   # 绑定到同一事件链

    # 处理程序3:恢复备份配置(验证失败时执行)
    - name: 恢复 HAProxy 备份配置
      copy:
        src: /etc/haproxy/haproxy.cfg.bak  # 备份文件路径
        dest: /etc/haproxy/haproxy.cfg     # 覆盖错误配置
        remote_src: yes
      when: 
        - haproxy_validate.rc != 0  # 仅当验证失败时触发
      listen: haproxy_config_flow    # 绑定到同一事件链
      notify: 重启 HAProxy           # 恢复配置后触发重启

执行:

shell 复制代码
ansible-playbook -i step-11/hosts step-11/haproxy.yml

PLAY [配置 Web 服务器组] ****************************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************
ok: [host2]
ok: [host1]

PLAY [配置 HAProxy 负载均衡器] **********************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************
ok: [host3]

TASK [安装 HAProxy] *********************************************************************************************************************
ok: [host3]

TASK [备份 HAProxy 配置] ****************************************************************************************************************
ok: [host3]

TASK [生成并推送 HAProxy 配置] **********************************************************************************************************
changed: [host3]

TASK [启用 HAProxy 开机启动] ************************************************************************************************************
ok: [host3]

RUNNING HANDLER [验证 HAProxy 配置] *****************************************************************************************************
changed: [host3]

RUNNING HANDLER [重启 HAProxy] **********************************************************************************************************
changed: [host3]

RUNNING HANDLER [恢复 HAProxy 备份配置] *************************************************************************************************
skipping: [host3]

PLAY RECAP ******************************************************************************************************************************
host1                      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
host2                      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
host3                      : ok=7    changed=3    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

验证

http://192.168.0.223/haproxy?stats

shell 复制代码
root@host0:/home/ansible-tuto# ansible -i hosts -m shell -a "cat /etc/haproxy/haproxy.cfg" host3
host3 | CHANGED | rc=0 >>
global
    daemon
    maxconn 256
    stats socket /tmp/sock

defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

listen cluster
    bind 192.168.0.223:80
    mode http
    stats enable
    balance roundrobin
    server host1 192.168.0.221:80 check port 80 weight 100
    server host2 192.168.0.222:80 check port 80 weight 150
    option httpchk HEAD /index.php HTTP/1.0
root@host0:/home/ansible-tuto# ansible -i hosts -m shell -a "haproxy -c -f /etc/haproxy/haproxy.cfg" host3
host3 | CHANGED | rc=0 >>
Configuration file is valid

listen 机制

1. listen 的作用

  • 替代传统 notify:在 Ansible 2.2+ 中引入,允许将多个处理程序(handlers)绑定到同一个事件名称。
  • 执行顺序控制 :所有绑定到同一 listen 名称的处理程序会按定义顺序依次执行。

2. 在您的 Playbook 中的工作流程

  1. 触发事件 :当任意任务通过 notify: haproxy_config_flow 触发事件链时:

    复制代码
    tasks:
      - name: 生成并推送 HAProxy 配置
        template: ...
        notify: haproxy_config_flow  # 触发事件链
  2. 执行顺序

    • 验证配置重启服务恢复备份重启服务
  3. 条件控制 :通过 when 条件决定是否执行:

    • 若验证成功(rc == 0),执行 重启 HAProxy
    • 若验证失败(rc != 0),执行 恢复 HAProxy 备份配置,随后触发 重启 HAProxy

3. 与传统 notify 的对比

场景 传统 notify listen 机制
触发单个处理程序 notify: 重启 HAProxy 不适用
触发多个处理程序 需多次 notify 所有绑定到同一 listen 的处理程序按序执行
条件执行 处理程序内部通过 when 控制 每个处理程序独立定义 when 条件

关键优化点说明

1. 备份强制覆盖 (force: yes)

  • 作用:确保每次备份时覆盖旧文件,避免备份文件过时。
  • 必要性:如果未启用,当配置文件未变更时,备份任务不会覆盖旧备份,导致回退时可能恢复错误版本。

2. 验证失败后的回退流程

  • 步骤
    1. 生成新配置 → 2. 验证失败 → 3. 恢复备份 → 4. 重启服务(使用旧配置)。
  • 结果:即使新配置错误,服务仍可用。

3. 统一事件链 (haproxy_config_flow)

  • 优势:将配置更新、验证、重启、回退逻辑集中管理,避免分散触发。

完整执行流程

  1. 安装 HAProxy:确保软件已安装。
  2. 备份配置:保存当前有效配置。
  3. 生成新配置:通过模板渲染推送新配置。
  4. 触发处理程序链
    • 验证配置 → 若成功 → 重启服务
    • 若失败 → 恢复备份重启服务

总结

通过 listen 机制,您实现了一个 原子化的配置管理流程

  • 集中控制:所有相关处理程序按序触发。
  • 安全回退:验证失败时自动恢复至有效配置。
  • 高可用性:避免服务因配置错误中断。

stats socket 的具体作用

stats socket 是 HAProxy 提供的一个 Unix Socket 接口,用于动态管理 HAProxy 的运行时状态。主要功能包括:

  • 实时操作
    • 禁用/启用后端服务器(如 disable server backend/server1)。
    • 动态调整权重(如 set server backend/server1 weight 200)。
    • 查看连接数、会话状态等实时统计信息。
  • 监控与管理
    • 通过命令行工具(如 socat)或 API 直接与 HAProxy 交互。
    • 支持自动化脚本动态调整负载均衡策略。

2. 是否可以不使用 stats socket

可以完全禁用 。如果不使用 stats socket

  • 优点
    • 消除安全风险:关闭潜在的攻击入口。
    • 简化配置:减少维护复杂度。
  • 缺点
    • 失去动态管理能力:无法通过 Socket 实时调整服务器状态或权重。
    • 依赖重启:任何配置变更需重启 HAProxy 服务才能生效。

适用场景

  • 负载均衡策略固定,无需频繁调整。
  • 通过 Ansible 等工具静态管理配置变更。

3. 替代方案(未测试,待测试)

如果需动态管理 HAProxy 但不想使用 stats socket,有以下替代方案:

方案1:HTTP 统计接口

通过 HAProxy 的 HTTP 统计页面 进行基础管理:

复制代码
listen stats
    bind *:8404
    stats enable
    stats uri /haproxy?stats
    stats auth admin:SecurePassword123!  # 强制认证
    stats refresh 10s  # 自动刷新间隔
  • 功能
    • 查看实时统计信息(如服务器状态、请求速率)。
    • 通过浏览器或 curl 手动禁用服务器(需配合前端工具)。
  • 限制
    • 无法直接执行复杂命令(如动态调整权重)。
    • 依赖 HTTP 接口,需额外安全防护(如 HTTPS、IP 白名单)。

方案2:HAProxy Runtime API(企业版)(未测试,待测试)

HAProxy 企业版提供 Runtime API,支持通过 HTTP/HTTPS 动态管理:

复制代码
global
    runtime-api 0.0.0.0:9999  # 监听端口
    api-user admin            # 认证用户
    api-password $6$加密密码  # 加密密码
  • 功能
    • 类似 stats socket 的所有操作,但通过 HTTP 接口实现。
    • 支持 RESTful 风格的管理命令。
  • 优势
    • 更易集成自动化工具(如 Prometheus、Grafana)。
    • 支持 HTTPS 和细粒度权限控制。

方案3:Ansible 动态配置(未测试,待测试)

通过 Ansible 直接生成配置文件并触发重载:

复制代码
- name: 更新 HAProxy 配置
  template:
    src: haproxy.cfg.j2
    dest: /etc/haproxy/haproxy.cfg
  notify: 安全重载 HAProxy

handlers:
  - name: 安全重载 HAProxy
    command: haproxy -f /etc/haproxy/haproxy.cfg -sf $(cat /var/run/haproxy.pid)
  • 功能
    • 通过模板动态生成配置,替换旧配置后平滑重启(-sf 参数)。
  • 优势
    • 无需开放额外端口或 Socket。
    • 变更记录清晰(版本控制模板文件)。

总结与建议

方案 安全性 动态管理能力 复杂度 适用场景
禁用 stats socket 配置固定,无需实时调整
HTTP 统计页面 基础 需要简单监控
Runtime API 完整 企业版用户,需自动化集成
Ansible 动态配置 通过 IaC 管理,变更频率中等

推荐实践

  • 如果无需动态调整,直接禁用 stats socket
  • 若需动态管理且安全性优先,使用 HTTP 统计页面 + IP 白名单 + HTTPS
  • 企业用户可投资 HAProxy 企业版 的 Runtime API。

12.Playbook Roles实现配置模块化

将原有的 Playbook 重构为 角色(Roles),目的是:

  • 模块化:将 Apache 和 HAProxy 的配置拆分为独立角色,便于复用和管理。
  • 标准化结构:遵循 Ansible 角色的目录约定,提升代码可维护性。
  • 依赖管理 :通过角色间的依赖关系(如 meta/main.yml)实现自动化执行顺序。

1角色目录结构解析

每个角色(如 apachehaproxy)的目录结构如下:

yaml 复制代码
roles/
  ├── apache/                  # 角色名称
  │   ├── tasks/               # 存放任务定义
  │   │   └── main.yml         # 自动加载的入口任务文件
  │   ├── handlers/            # 存放处理程序(如服务重启)
  │   │   └── main.yml         # 自动加载的入口处理程序文件
  │   ├── files/               # 存放静态文件(如配置文件)
  │   ├── templates/           # 存放 Jinja2 模板文件
  │   ├── defaults/            # 存放角色默认变量(优先级最低)
  │   ├── vars/                # 存放角色变量(优先级高于 defaults)
  │   └── meta/                # 定义角色依赖(如依赖其他角色)
  └── haproxy/
      └── ...                  # 结构同上

关键目录说明

  • tasks/main.yml:角色的核心任务入口,Ansible 会自动执行此文件中的任务。
  • handlers/main.yml :定义处理程序(如 restart apache),由任务中的 notify 触发。
  • files/templates/ :存放静态文件和模板,引用时无需绝对路径。
    • 例如:copy: src=awesome-app 会自动从 files/ 目录查找。
  • defaults/main.yml:定义角色的默认变量,可被外部变量覆盖。
  • meta/main.yml:定义角色依赖,确保执行顺序(如先安装 Nginx 再配置 PHP)。

2文件命名规则

  • 强制规则

    • tasks/main.ymlhandlers/main.yml 等入口文件必须命名为 main.yml,否则 Ansible 无法自动加载。
  • 灵活规则

    • 其他文件(如 tasks/install_packages.yml)可自定义名称,但需在 main.yml 中通过 includeimport_tasks 显式引入。

    • 例如:

      yaml 复制代码
      # tasks/main.yml
      - include: install_packages.yml
      - include: configure_apache.yml

3迁移步骤详解

以迁移 Apache 配置为例:

  1. 创建角色目录

    复制代码
    mkdir -p step-12/roles/apache/{tasks,handlers,files}

    使用 ansible-galaxy 初始化角色

    复制代码
    ansible-galaxy init step-12/roles/haproxy  # 自动生成标准目录结构
  2. 拆分任务

    • 将原 apache.yml 中的任务复制到 tasks/main.yml。只保留tasks部分

      复制代码
        #原apache.yml
        ---
        - hosts: web
          tasks:
            - name: Installs necessary packages
              apt:
        	  *
            - name: Push future default virtual host configuration
              copy:
        #现tasks/main.yml
        ---
        - name: Installs necessary packages
        apt:
        *
        - name: Push future default virtual host configuration
        copy:
  • 移除对 files/templates/ 的路径引用(角色会自动查找对应目录)。

    复制代码
      #源文件中路径表示
      - name: Push future default virtual host configuration
        copy:
          src: files/awesome-app # 显式路径
      # 角色任务文件 (roles/apache/tasks/main.yml)
      #迁移到角色后写法
      - name: Push future default virtual host configuration
        copy:
          src: awesome-app  # 直接引用文件名(无需路径)
    • Ansible 会自动在角色的 files/ 目录下查找静态文件。

    • 如果文件在 files/ 的子目录中(如 files/config/),则需保留子目录路径,

      复制代码
        src: config/awesome-app  # 文件路径为 roles/apache/files/config/awesome-app
  1. 提取处理程序

    • 将原 Playbook 中的 handlers 部分移到 handlers/main.yml

    复制代码
      #原apache.yml
        handlers:
          - name: restart apache
            service:
              name: apache2
              state: restarted
      #现handlers/main.yml
      ---
      - name: restart apache
        service:
          name: apache2
          state: restarted
  2. 移动静态文件

    • 将配置文件(如 awesome-app)复制到 files/ 目录。
  3. 创建顶层 Playbook

    复制代码
    # site.yml
    - hosts: web
      roles:
        - apache  # 自动加载 roles/apache 下的任务
    - hosts: haproxy
      roles:
        - haproxy

4执行与验证

  • 运行 Playbook

    复制代码
    ansible-playbook -i hosts site.yml
    ansible-playbook -i step-12/hosts step-12/site.yml
  • 限制执行范围

    复制代码
    # 仅针对 web 主机组执行
    ansible-playbook -i step-12/hosts -l web step-12test/site.yml

执行情况:

复制代码
PLAY RECAP ********************************************************************************************************************************
host1                      : ok=9    changed=6    unreachable=0    failed=0    skipped=3    rescued=0    ignored=0
host2                      : ok=9    changed=6    unreachable=0    failed=0    skipped=3    rescued=0    ignored=0
host3                      : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

13.Ansible 标签使用

在复杂的IT架构中,一个Ansible Playbook可能包含上百个任务。当您只需要更新Web服务配置却要完整执行整个Playbook时,就像为了换灯泡而重启整个数据中心。标签(Tags)的出现完美解决了这个痛点:

  • 手术刀式精准操作:只运行指定任务,节省90%执行时间
  • 环境隔离利器:开发/测试/生产环境差异化执行
  • 应急响应加速器:快速定位回滚点,故障恢复时间缩短75%

1、为何需要标签?

在复杂的IT架构中,一个Ansible Playbook可能包含上百个任务。当您只需要更新Web服务配置却要完整执行整个Playbook时,就像为了换灯泡而重启整个数据中心。标签(Tags)的出现完美解决了这个痛点:

  • 手术刀式精准操作:只运行指定任务,节省90%执行时间
  • 环境隔离利器:开发/测试/生产环境差异化执行
  • 应急响应加速器:快速定位回滚点,故障恢复时间缩短75%

2、标签基础语法

基础标记方法

列表式声明(推荐)

复制代码
- name: 部署Apache服务
  apt:
    name: apache2
    state: latest
  tags:
    - web
    - install
    - critical

行内数组式

复制代码
- name: 配置防火墙规则
  ufw:
    rule: allow
    port: "{{ http_port }}"
  tags: ['network', 'security']

命令行控制

命令 作用 示例场景
-t tag1,tag2 执行包含任意指定标签的任务 紧急安全更新
--skip-tags tagA 排除指定标签的任务 跳过耗时操作
-t tag1 -t tag2 多标签联合过滤(逻辑OR) 跨角色操作
--list-tags 显示所有可用标签 Playbook文档化

3、实战场景案例

场景1:快速部署热修复补丁

复制代码
# 仅执行代码部署和服务重启
ansible-playbook deploy.yml -t git_pull,service_restart

# 组合标签排除数据库操作
ansible-playbook deploy.yml -t app_deploy --skip-tags db_migration

场景2:安全合规检查

复制代码
- name: 检查SSH协议版本
  shell: ssh -V
  register: ssh_version
  tags: security_audit

- name: 验证防火墙状态
  command: ufw status verbose
  tags: 
    - security_audit
    - compliance

场景3:多环境配置

复制代码
- name: 加载生产环境配置
  template:
    src: prod.env.j2
    dest: /etc/app.env
  tags: prod_only

- name: 开发环境调试设置
  lineinfile:
    path: /etc/app.conf
    line: "debug_mode=true"
  tags: dev_only

4、高级技巧揭秘

  1. 标签继承模式

    meta/main.yml

    dependencies:

    • role: common
      tags: [base_setup] # 自动继承base_setup标签
  2. 动态标签生成

    • name: 按条件动态标记
      debug:
      msg: "数据库主节点专属配置"
      tags: "{{ 'db_master' if is_master else 'db_slave' }}"
  3. Handler标签联动

    handlers:

    • name: 滚动重启服务
      systemd:
      name: app
      state: restarted
      tags: canary_deploy # 灰度发布专用handler

5、最佳实践指南

  1. 命名规范三原则

    • 业务维度:web, db, cache
    • 操作类型:install, config, cleanup
    • 环境标识:prod, staging, dev
  2. 黄金组合策略

    复制代码
    # 典型CI/CD流水线
    ansible-playbook site.yml \
      -t "validate,deploy" \
      --skip-tags "migration,full_restart"
  3. 危险操作防护

    复制代码
    - name: 数据库表结构迁移
      command: alembic upgrade head
      tags:
        - dangerous
        - db_migration
    
    # 显式确认执行危险操作
    ansible-playbook migrate.yml -t dangerous --extra-vars "confirm_danger=yes"

6、常见问题排查

Q1:标签未生效?

  • 检查项:
    1. YAML缩进是否正确
    2. 角色依赖是否继承标签
    3. 是否存在同名标签覆盖

Q2:如何调试复杂标签?

复制代码
# 显示标签执行顺序
ANSIBLE_DEBUG=1 ansible-playbook playbook.yml -t my_tag

# 生成标签关系图
ansible-playbook playbook.yml --list-tags | grep -B1 'TASK TAGS'

Q3:部分节点执行异常?

复制代码
- name: 条件标签示例
  debug:
    msg: "仅CentOS系统执行"
  tags: centos_only
  when: ansible_distribution == "CentOS"

7、性能优化指标

场景 无标签执行时间 标签优化时间 提升幅度
全量部署 12m 34s 12m 34s 0%
配置更新 11m 45s 1m 12s 89%
紧急回滚 9m 23s 45s 92%
安全补丁 8m 56s 38s 93%

8、延伸阅读

  1. 标签与Ansible Tower集成

    复制代码
    # tower.yml
    launch_configuration:
      extra_vars:
        ansible_tags: "prod_deploy"
      limit: "webservers:&east"
  2. 基于标签的权限控制

    复制代码
    # ansible.cfg
    [privilege_escalation]
    require_sudo_tags = package_install, service_control
  3. 标签驱动文档生成

    复制代码
    ansible-doc-tagger generate --format markdown > PLAYBOOK_TAGS.md
相关推荐
乘云数字DATABUFF3 天前
5分钟部署开源APM Databuff:OpenTelemetry全链路追踪入门实战
运维·后端
悠然南风5 天前
Ansible常见模块总结及LDAP Role 编写与调试
ansible
荣--5 天前
一键部署不是为了省时间 —— 它是把"买来的 PaaS"变成"自己的平台"的拐点
运维·zabbix·工程化·一键部署·平台化·边界设计
江华森5 天前
动手实战学 Docker — 从零到集群编排完全指南
运维
Avan_菜菜6 天前
FRP 内网穿透完整实战:从 HTTP 映射到 HTTPS 自签代理
运维·nginx·https
SelectDB7 天前
Litefuse 开源并推出单进程轻量模式,25 秒就能跑起来的 Agent 可观测与评估平台
运维·后端·自动化运维
XIAOHEZIcode8 天前
Linux系统鼠标偏移常见原因以及修复方案
linux·运维·游戏
用户0328472220709 天前
如何搭建本地yum源(上)
运维
大树8812 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠12 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql