当前位置: 首页 > ds >正文

【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" % namemachine.vm.network :public_network,bridge: "en1", ip: ipmachine.vm.provider "virtualbox" do |v|v.name = namev.customize ["modifyvm", :id, "--memory", 1024]endendend
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

输出应如下所示:

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

输出应类似于:

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 版本

ansible -i hosts -m shell -a 'grep DISTRIB_RELEASE /etc/lsb-release' all

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

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

回复包含大量信息:

host1 | SUCCESS => {"ansible_facts": {"ansible_all_ipv4_addresses": ["10.0.2.15","192.168.0.221"],    .......................................#还有很多"module_setup": true},"changed": false
}

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

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

ansible -i hosts -m setup -a 'filter=ansible_memtotal_mb' all

输出如下:

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模块获取所有事实变量。例如:

    #显示所有主机的事实,并将其按“主机名”索引存储在“/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 facteransible.builtin.setup:gather_subset:- '!all'- '!<any valid subset>'- facter
    
  • 使用filter参数来查看指定的信息,例如:

    - name: Filter and return only selected factsansible.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_serverstasks:- name: Gather facts from db serversansible.builtin.setup:delegate_to: "{{ item }}"delegate_facts: trueloop: "{{ 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

[web]
host2 ansible_host=192.168.0.222 ansible_user=root

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

- hosts: webtasks:- name: Installs apache web serverapt:pkg: apache2state: presentupdate_cache: true
  • -name 为此任务添加了一个名称。虽然这不是必需的,但它可以在 playbook 运行时提供信息
  • apt 模块,它可以 安装 Debian 软件包

运行 playbook :apache.yml

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

运行后可看到以下内容:

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)表示无失败、无跳过、无重试等。

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

ansible-playbook -i step-04/hosts -l host2 step-04/apache.ymlPLAY [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

<VirtualHost *:80>DocumentRoot /var/www/awesome-appOptions -IndexesErrorLog /var/log/apache2/error.logTransferLog /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

[web]
host1 ansible_host=192.168.0.221 ansible_user=root

更新 apache playbook

---
- hosts: web  #指定该 Play 针对清单文件(inventory)中定义的 web 主机组执行。tasks:- name: Installs apache web server
#模块:apt(适用于 Debian/Ubuntu 系统)。apt:
#安装 Apache2 软件包。pkg: apache2
#确保 Apache2 已安装。state: presentupdate_cache: true
#等同于执行 apt update,更新软件包缓存(确保安装最新版本)。- name: Push default virtual host configurationcopy:src: files/awesome-appdest: /etc/apache2/sites-available/awesome-app.conf
#将本地 files/awesome-app 文件复制到目标主机的 /etc/apache2/sites-available/awesome-app.conf。mode: 0640
#设置文件权限为 -rw-r-----(所有者可读写,所属组可读,其他用户无权限)。- name: Activates our virtualhostfile:src: /etc/apache2/sites-available/awesome-app.confdest: /etc/apache2/sites-enabled/awesome-app.confstate: 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 virtualhostfile:dest: /etc/apache2/sites-enabled/000-default.confstate: absentnotify:- restart apache- name: Disable the default ssl virtualhost
#这两步禁用默认虚拟主机,防止默认虚拟主机与新配置冲突。    file:dest: /etc/apache2/sites-enabled/default-ssl.confstate: absent
#state: absent:删除 sites-enabled 目录中的默认配置文件符号链接(等同于 a2dissite 000-default.conf 和 a2dissite default-ssl.conf)。notify:- restart apachehandlers:- name: restart apache
#处理器(Handlers),当任务触发 notify: restart apache 时,重启 Apache 服务(仅在相关配置文件发生变更时触发)。service:name: apache2state: restarted

执行输出:

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

<VirtualHost *:80>RocumentDoot /var/www/awesome-appOptions -IndexesErrorLog /var/log/apache2/error.logTransferLog /var/log/apache2/access.log
</VirtualHost>

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

---
- hosts: web  # 🌐 指定目标主机组为 `web`(需在 Ansible 清单中定义)tasks:# 🔧 任务1:安装 Apache- name: Installs apache web serverapt:  # 📦 使用 apt 模块(Debian/Ubuntu 系统)pkg: apache2  # 软件包名称state: present  # 确保 apache2 已安装update_cache: true  # 先执行 `apt update` 更新软件源缓存# 📄 任务2:推送虚拟主机配置文件- name: Push future default virtual host configurationcopy:  # 📂 使用 copy 模块复制文件src: files/awesome-app  # 本地文件路径(相对于 Playbook)dest: /etc/apache2/sites-available/awesome-app.conf  # 目标路径mode: 0640  # 设置文件权限为 -rw-r-----# 🔗 任务3:启用自定义虚拟主机- name: Activates our virtualhostcommand: a2ensite awesome-app  # 🖥️ 直接调用 Apache 命令启用站点# 等同于创建符号链接:/etc/apache2/sites-enabled/awesome-app.conf -> ../sites-available/awesome-app.conf# ✅ 任务4:验证 Apache 配置语法- name: Check that our config is validcommand: apache2ctl configtest  # 🔍 检查配置是否有语法错误# 如果配置错误,任务会失败并终止 Playbook 执行# 🚫 任务5:禁用默认虚拟主机(非 SSL)- name: Deactivates the default virtualhostcommand: a2dissite 000-default  # ❌ 禁用默认站点# 🚫 任务6:禁用默认 SSL 虚拟主机- name: Deactivates the default ssl virtualhostcommand: a2dissite default-ssl  # ❌ 禁用默认 SSL 站点notify:  # 🔔 触发处理器- restart apache  # 仅当此任务状态为 changed 时重启 Apachehandlers:  # ⚙️ 处理器(异步任务)# 🔄 处理器:重启 Apache 服务- name: restart apacheservice:  # 🛠️ 使用 service 模块name: apache2  # 服务名称state: restarted  # 重启服务

执行

ansible-playbook -i step-06/hosts -l host1 step-06/apache.ymlPLAY [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 执行失败

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,推送了虚拟主机并重启了服务器。 但如果出现问题,我们希望将一切恢复到稳定状态。

出现问题时回退

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

- hosts: web  # 仅在清单中定义的 'web' 主机组执行tasks:# 安装 Apache- name: Installs apache web serverapt:pkg: apache2        # 安装 Apache2 软件包state: present      # 确保软件包存在update_cache: true  # 先执行 `apt update` 更新缓存# 推送配置文件(潜在风险点)- name: Push future default virtual host configurationcopy:src: files/awesome-app  # 本地配置文件名dest: /etc/apache2/sites-available/awesome-app.conf  # 目标路径mode: 0640  # 权限设置(可能需确保 Apache 用户可读)# 启用新虚拟主机(非幂等操作)- name: Activates our virtualhostcommand: 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 validcommand: apache2ctl configtestregister: result        # 存储命令执行结果ignore_errors: true     # 即使验证失败也继续执行(触发回滚)# 回滚操作1:重新启用默认站点(需确保默认配置存在)- name: Rolling back - Restoring old default virtualhostcommand: a2ensite 000-defaultwhen: result is failed  # 仅在配置验证失败时执行# 回滚操作2:禁用新站点- name: Rolling back - Removing our virtualhostcommand: a2dissite awesome-appwhen: result is failed# 强制终止 Playbook 并提示错误- name: Rolling back - Ending playbookfail:msg: "Configuration file is not valid. Please check that before re-running the playbook."when: result is failed  # 终止后续任务# 禁用默认站点(仅在配置验证成功后执行)- name: Deactivates the default virtualhostcommand: a2dissite 000-defaultnotify: restart apache  # 统一服务重启触发,任何修改站点配置的操作都需要重启 Apache,否则变更不会生效。# 禁用默认 SSL 站点- name: Deactivates the default ssl virtualhostcommand: a2dissite default-sslnotify:- restart apache  # 触发服务重启(但其他任务也需要类似设置)handlers:# 服务重启处理器- name: restart apacheservice:name: apache2state: restarted

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

ansible-playbook -i step-07/hosts -l host1 step-07/apache.ymlPLAY [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."]}
...ignoringTASK [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 看看是否真的成功了:

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 可以遍历一系列项目,并像这样在操作中使用每个项目:

...
- name: Installs necessary packagesapt:pkg: "{{ item }}"state: latestupdate_cache: truewith_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

最终方案:

- hosts: webtasks:- name: ;**安装必要软件包**;(包括Apache, PHP模块, Git)apt:pkg: ["apache2", "libapache2-mod-php", "git"]state: latestupdate_cache: true- name: ;**推送默认的虚拟主机配置文件**;copy:src: files/awesome-appdest: /etc/apache2/sites-available/awesome-app.confmode: 0640- name: ;**激活我们的虚拟主机**;command: a2ensite awesome-app- name: ;**检查配置是否有效**;command: apache2ctl configtestregister: resultignore_errors: true- name: ;**回滚 - 恢复旧的默认虚拟主机**;(如果配置测试失败)command: a2ensite 000-defaultwhen: result is failed- name: ;**回滚 - 移除我们的虚拟主机**;(如果配置测试失败)command: a2dissite awesome-appwhen: 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-appforce: yes       # 强制覆盖本地修改update: yes  # 确保拉取最新代码version: master  # 明确指定分支tags: deploy  # 可通过`ansible-playbook ... --tags deploy`单独执行此任务- name: ;**禁用默认的虚拟主机**;(应在部署后禁用,确保不会冲突)command: a2dissite 000-defaultnotify: restart apache  # 统一服务重启触发,任何修改站点配置的操作都需要重启 Apache,否则变更不会生效。- name: ;**禁用默认的SSL虚拟主机**;(如果不需要SSL,可考虑移除或注释此行)command: a2dissite default-sslnotify:- restart apachehandlers:- name: restart apacheservice:name: apache2state: restarted

启动

ansible-playbook -i step-08/hosts -l host1 step-08/apache.ymlPLAY [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”只是一个字符串,它没有任何特定的含义,可以是任何内容。让我们看看如何使用它:

ansible-playbook -i step-08/hosts -l host1 step-08/apache.yml -t deployPLAY [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-appstate: absentignore_errors: yes  # 防止目录不存在时报错tags: deploy- name: ;**部署我们的应用程序**;(使用Git)git:repo: 'https://github.com/leucos/ansible-tuto-demosite.git'dest: /var/www/awesome-appforce: 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

[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服务器

cp step-08/apache.yml step-09/apache.yml
ansible-playbook -i step-09/hosts step-09/apache.ymlPLAY [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

globaldaemon              # 以守护进程模式运行maxconn 256         # 设置最大并发连接数为256defaultsmode http           # 默认使用HTTP模式timeout connect 5000ms   # 连接超时时间为5秒timeout client 50000ms   # 客户端超时时间为50秒timeout server 50000ms   # 服务器端超时时间为50秒listen clusterbind {{ 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中改为正确的路径

#我这里的事实中['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

- hosts: webgather_facts: true  # 收集目标服务器的系统信息(如IP、OS版本)- hosts: haproxy      # 针对名为 "haproxy" 的主机组执行以下任务tasks:# 任务1:安装 HAProxy- name: Installs haproxy load balancerapt:pkg: haproxy        # 包名称state: present     # 确保已安装update_cache: yes   # 更新 apt 缓存(相当于 apt update)# 任务2:推送配置文件- name: Pushes configurationtemplate: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 1lineinfile:dest: /etc/default/haproxy  # 修改系统服务配置文件regexp: "^ENABLED"          # 查找以 ENABLED 开头的行line: "ENABLED=1"           # 强制设置为 1(启用服务)notify:- restart haproxyhandlers:# 定义重启 HAProxy 的处理程序- name: restart haproxyservice:name: haproxystate: restarted

执行:

 ansible-playbook -i step-10/hosts step-10/apache.yml step-10/haproxy.ymlPLAY [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=0PLAY [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配置文件)
cat /etc/haproxy/haproxy.cfg #检查类似以下部分server host1 192.168.0.221:80 checkserver 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: 3000stats_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 中定义的变量。

更新模板

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

globaldaemonmaxconn 256
{% if haproxy_stats_socket %}stats socket {{ haproxy_stats_socket }}
{% endif %}defaultsmode httptimeout connect 5000mstimeout client 50000mstimeout server 50000mslisten clusterbind {{ ansible_host }}:80mode httpstats enablebalance 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:

---
- name: 配置 Web 服务器组hosts: webgather_facts: true  # 收集 web 组的服务器信息(如 IP、OS 版本)# 用于后续模板生成后端服务器列表- name: 配置 HAProxy 负载均衡器hosts: haproxy      # 目标为 haproxy 主机组tasks:# 任务1:安装 HAProxy- name: 安装 HAProxyapt:pkg: haproxystate: 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: rootgroup: rootnotify: 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: 重启 HAProxyservice:name: haproxystate: restartedwhen: - 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: yeswhen: - haproxy_validate.rc != 0  # 仅当验证失败时触发listen: haproxy_config_flow    # 绑定到同一事件链notify: 重启 HAProxy           # 恢复配置后触发重启

执行:

ansible-playbook -i step-11/hosts step-11/haproxy.ymlPLAY [配置 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

root@host0:/home/ansible-tuto# ansible -i hosts -m shell -a "cat /etc/haproxy/haproxy.cfg" host3
host3 | CHANGED | rc=0 >>
globaldaemonmaxconn 256stats socket /tmp/sockdefaultsmode httptimeout connect 5000mstimeout client 50000mstimeout server 50000mslisten clusterbind 192.168.0.223:80mode httpstats enablebalance roundrobinserver host1 192.168.0.221:80 check port 80 weight 100server host2 192.168.0.222:80 check port 80 weight 150option 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 的对比

场景传统 notifylisten 机制
触发单个处理程序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 statsbind *:8404stats enablestats uri /haproxy?statsstats auth admin:SecurePassword123!  # 强制认证stats refresh 10s  # 自动刷新间隔
  • 功能
    • 查看实时统计信息(如服务器状态、请求速率)。
    • 通过浏览器或 curl 手动禁用服务器(需配合前端工具)。
  • 限制
    • 无法直接执行复杂命令(如动态调整权重)。
    • 依赖 HTTP 接口,需额外安全防护(如 HTTPS、IP 白名单)。

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

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

globalruntime-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.j2dest: /etc/haproxy/haproxy.cfgnotify: 安全重载 HAProxyhandlers:- name: 安全重载 HAProxycommand: 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)的目录结构如下:

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 显式引入。

    • 例如:

      # 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: webtasks:- name: Installs necessary packagesapt:*- name: Push future default virtual host configurationcopy:
        #现tasks/main.yml
        ---
        - name: Installs necessary packages
        apt:
        *
        - name: Push future default virtual host configuration
        copy:
        
  • 移除对 files/templates/ 的路径引用(角色会自动查找对应目录)。

    • #源文件中路径表示
      - name: Push future default virtual host configurationcopy:src: files/awesome-app # 显式路径
      # 角色任务文件 (roles/apache/tasks/main.yml)
      #迁移到角色后写法
      - name: Push future default virtual host configurationcopy: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.ymlhandlers:- name: restart apacheservice:name: apache2state: restarted
      #现handlers/main.yml
      ---
      - name: restart apacheservice:name: apache2state: restarted
      
  2. 移动静态文件

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

    # site.yml
    - hosts: webroles:- apache  # 自动加载 roles/apache 下的任务
    - hosts: haproxyroles:- 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: apache2state: latesttags:- web- install- critical

行内数组式

- name: 配置防火墙规则ufw:rule: allowport: "{{ 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 -Vregister: ssh_versiontags: security_audit- name: 验证防火墙状态command: ufw status verbosetags: - security_audit- compliance

场景3:多环境配置

- name: 加载生产环境配置template:src: prod.env.j2dest: /etc/app.envtags: prod_only- name: 开发环境调试设置lineinfile:path: /etc/app.confline: "debug_mode=true"tags: dev_only

4、高级技巧揭秘
  1. 标签继承模式
# meta/main.yml
dependencies:- role: commontags: [base_setup]  # 自动继承base_setup标签
  1. 动态标签生成
- name: 按条件动态标记debug:msg: "数据库主节点专属配置"tags: "{{ 'db_master' if is_master else 'db_slave' }}"
  1. Handler标签联动
handlers:- name: 滚动重启服务systemd:name: appstate: restartedtags: 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 headtags:- 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_onlywhen: ansible_distribution == "CentOS"

7、性能优化指标
场景无标签执行时间标签优化时间提升幅度
全量部署12m 34s12m 34s0%
配置更新11m 45s1m 12s89%
紧急回滚9m 23s45s92%
安全补丁8m 56s38s93%

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
    
http://www.xdnf.cn/news/3776.html

相关文章:

  • 【算法基础】插入排序算法 - JAVA
  • 怎样增加AI对话的拟人化和增加同理心
  • WEB前端小练习——记事本
  • 先知AIGC超级工场,撬动运营效率新杠杆
  • 在 Trae CN IDE 中配置 Python 3.11的指南
  • Nat. Hum. Behav:大脑“变形记”,注意力错误下的空间认知奇遇
  • 如何解决 403 错误:请求被拒绝,无法连接到服务器
  • 【KWDB 创作者计划】Docker单机环境下KWDB集群快速搭建指南
  • with的用法
  • 家用服务器 Ubuntu 服务器配置与 Cloudflare Tunnel 部署指南
  • 【中间件】brpc_基础_用户态线程上下文
  • 小程序与快应用:中国移动互联网的渐进式革命——卓伊凡的技术演进观
  • JavaScript性能优化实战之调试与性能检测工具
  • KeyPresser 一款自动化按键工具
  • 【c语言】数据在内存中的存储
  • Servlet(二)
  • 怎样提升社交机器人闲聊能力
  • 【Linux】进程优先级与进程切换理解
  • 第38课 常用快捷操作——双击“鼠标左键”进入Properties Panel
  • Linux运维——Vim技巧一
  • LeetCode —— 102. 二叉树的层序遍历
  • 设计模式简述(十七)备忘录模式
  • yolov5 train笔记4 roboflow
  • Android Compose 中 Side Effects 和 State 相关的 API 使用
  • 数据仓库方法论书籍及其阅读建议
  • Linux 库文件详解
  • 自动化测试项目1 --- 唠嗑星球 [软件测试实战 Java 篇]
  • 旧版本NotionNext图片失效最小改动解决思路
  • 解决跨域问题
  • 【质量管理】现代TRIZ问题识别中的功能分析——相互接触分析