Nginx的实战规划案例
高业务量的互联网应用服务器通常需要应对每秒几万个到几十万个请求的处理。为实现高并发的处理能力,网站架构师们会使用负载均衡设备对同一个应用的服务器集群进行负载。负载均衡设备由硬件或软件设备构成,负责把客户端的请求按照不同的策略转发给后端的应用服务器,每组应用服务器集群均可根据实际的处理性能进行横向扩展,以提高请求的处理能力。
本章将通过对现有的开源软件 Jenkins、GitLab 和 Ansible 进行组合,快速搭建一套 Web 化的 Nginx 集群配置管理框架的方法。该管理框架通过 Jenkins 的 Web 化管理界面实现了权限管理、前端配置、发布记录等功能。它结合 GitLab 的版本控制功能对每次的变更进行归档,并可随时实现配置回滚。通过对 Ansible 剧本的调用,自动化地实现了 Nginx 集群的配置修改、加载、灰度发布等操作。
1.LVS(Linux虚拟服务器)简介
LVS(Linux Virtual Server)是一个开源的负载均衡项目,是国内最早出现的开源项目之一,目前已被集成到 Linux 内核模块中。该项目在 Linux 内核中实现了基于 TCP 层的 IP 数据负载均衡分发,其工作在内核空间且仅做负载均衡分发处理,所以稳定性相对较好,性能相对较强,对内存及 CPU 资源的消耗也最低。
1.1 LVS术语
LVS 相关术语说明如下。
-
- DS(Director Server):控制器服务器,部署 LVS 软件的服务器;
- RS(Real Server):真实服务器,被负载的后端服务器;
- VIP(Virtual IP):虚拟 IP,对外提供用户访问的 IP 地址;
- DIP(Director Server IP):控制器服务器 IP,控制器服务器的 IP 地址;
- RIP(Real Server IP):真实服务器 IP,真实服务器的 IP 地址;
- CIP(Client IP):客户端 IP,客户端的 IP 地址;
- IPVS(IP Virtual Server):LVS 的核心代码,工作于内核空间,主要有 IP 包处理、负载均衡算法、系统配置管理及网络链表处理等功能;
- ipvsadm:IPVS 的管理器,工作于用户空间,负责 IPVS 运行规则的配置。
1.2 LVS工作原理
IPVS 是基于 Linux 的 Netfilter 框架实现的,其以数据包的网络检测链为挂载点完成数据的负载均衡及转发处理。其工作原理如下图所示。
图:LVS 工作原理
客户访问虚拟 IP(VIP)时,数据包先在主机内核空间被 PREROUTING 链检测,根据数据包的目标地址进行路由判断,若目标地址是本地,则交由 INPUT 链进行处理。
IPVS 工作于 INPUT 链,当数据包到达 INPUT 链时,会先由 IPVS 进行检查,并根据负载均衡算法选出真实服务器 IP。
IPVS 转发模式为 NAT 模式时,将数据包由 FORWARD 链进行处理后由 POST-ROUTING 链发送给真实服务器。
IPVS 转发模式为非 NAT 模式时,则将数据包由 POSTROUTING 链发送给真实服务器。
1.3 LVS转发模式
LVS 支持多种网络部署结构,官方版本提供了 NAT、TUN 及 DR 这 3 种标准转发模式,另阿里巴巴工程师根据自身需求进行扩展,实现了 FullNAT 转发模式。
1) LVS标准转发模式如下:
NAT,该模式需要真实服务器的网关指向 DS,客户端的请求包和返回包都要经过 DS,该模式对 DS 的硬件性能的要求相对较高。
TUN,该模式是将客户端的请求包通过 IPIP 方式封装后分发给真实服务器,客户端的返回包则由真实服务器的本地路由自行处理,源 IP 地址还是 VIP 地址(真实服务器需要在本地回环接口配置 VIP)。因 DS 只负责请求包转发,其处理性能比 NAT 模式要高,但需要真实服务器支持 IPIP 协议。
DR,该模式是将客户端的请求包通过修改 MAC 地址为真实服务器的 MAC 地址后将数据包分发给真实服务器,客户端的返回包则由真实服务器的本地路由自行处理,源 IP 地址还是 VIP 地址(真实服务器需要在本地回环接口配置 VIP)。
因 DS 只负责请求包转发,且与真实服务器间进行基于二层的数据分发,所以处理性能最高,但要求 DS 与真实服务器在同一 MAC 广播域内。
2) 阿里扩展版本转发模式如下:
FullNAT,该模式是客户端的请求包和返回包都要经过 DS,但真实服务器可以在网络中的任意位置,且无须将网关配置为 DS 的 IP 地址,该方式虽然对 DS 的性能要求较高,但始终由 DS 面对客户端,有效保护了真实服务器的安全。
阿里扩展版本还针对 LVS 官方版本在安全方面进行了增强,提供了 SYNPROXY 功能支持,该功能在 LVS 上增加了一层 foold 类型的攻击包防护,实现了 UDP/IP FRAG DDOS 攻击防护。
1.4 LVS负载均衡算法
LVS 实现了 10 种负载均衡算法,负载均衡算法及其功能介绍如下表所示。
算法名称 | 英文名称 | 配置简称 | 功能说明 |
轮询调度 | Round Robin | rr | 将请求依次循环分发给负载的真实服务器 |
加权轮询调度 | Weight Round Robin | wrr | 按照配置的权重比例将请求分发给真实服务器,权重越高,分配的请求越多 |
目标地址散列调度 | Destination Hashing | dh | 该算法将目标地址作为散列键(Hash Key),从散列表中找出对应的真实服务器进行请求分发 |
源地址散列调度 | Source Hashing | sh | 该算法根据源地址作为散列键(Hash Key)从散列表中找出对应的真实服务器进行请求分发 |
最小连接调度 | Least Connections | lc | 将新的请求分发给当前连接数最小的服务器,其通过每个真实服务器当前连接数进行统计判断 |
加权最小连接调度 | Weight Least Connec-tions | wlc | 按照配置的权重,将新请求分发给当前连接数最小的服务器 |
最短延迟调度 | Shortest Expected Delay | sed |
该算法在 WLC 算法的基础上增加了基于活动连接的筛选算法,并把请求分发给算法值最小的真实服务器, 该算法避免了 WLC 算法中权重小的空闲服务器无法被分发到连接的情况 |
最少队列调度 | Never Queue | nq |
若有真实服务器的连接数为空,直接分发请求给该真实服务器,如果所有服务器都处于有连接状态, 则使用 SED 算法进行调度 |
基于局部的最少连接 | Locality-Based Least Connections | lblc |
该算法将目标地址相同的请求尽可能地分发到上次被分发的真实服务器,真实服务器若超载或不可用则使 用最少连接算法进行分发。该方法常用在真实服务器为缓存服务器时,以提高缓存的命中率 |
带复制的基于局部性的最少连接 | Locality-Based Least Connections with Replication | lblcr |
该算法维护一组被分发相同目标地址请求的真实服务器列表,按照最小连接算法创建和添加组成员, 并在一定条件下将组内最繁忙的成员移除。目标地址相同的请求将被分发到该组列表中最少连接的 成员。该方法常用在真实服务器为缓存服务器时,以提高缓存的命中率 |
1.5 IPVS的管理器ipvsadm
ipvsadm 1.2.1 版本命令的常用场景分为虚拟服务管理和真实服务器管理两类。
1) 虚拟服务管理
在 LVS 配置管理中,每个 VIP 与端口组成一个虚拟服务。虚拟服务管理命令参数格式如下:
ipvsadm -A [-t|u|f] [vip_addr:port] [-s:负载算法]
虚拟服务管理命令参数如下表所示。
参数 | 参数选项 | 参数说明 |
-A | 添加虚拟服务,为虚拟服务绑定 VIP 地址及端口 | |
-t | 虚拟服务协议为 TCP 协议 | |
-u | 虚拟服务协议为 UDP 协议 | |
-s | 虚拟服务负载均衡算法 | |
-p |
虚拟服务负载均衡保持连接的超时时间,默认超时时间为 360s。LVS 会把同一个客户端的请求信息记录到 LVS 的 hash 表里, 该参数设置了记录的保存时间,设定时间内的客户端连接会被转发到同一真实服务器 |
|
-D | 删除虚拟服务记录 | |
-E | 修改虚拟服务记录 | |
-С | 清空所有虚拟服务记录 |
命令样例如下:
# 添加虚拟服务,VIP地址为192.168.2.100:80,协议为TCP,负载均衡算法为轮询算法(rr),启用保持 # 连接支持,默认超时时间为300s ipvsadm -A -t 192.168.2.100:80 -s rr -p
2) 真实服务器管理
真实服务器管理命令参数格式如下:
ipvsadm -a [-t|u|f] [vip_addr:port] [-r ip_addr] [-g|i|m] [-w指定权重]
真实服务器管理命令参数如下图所示。
参数 | 参数选项 | 参数说明 |
-a | 添加真实服务器 | |
-t | 与真实服务器用 TCP 协议建立连接 | |
-u | 与真实服务器用 UDP 协议建立连接 | |
-r | 真服务器 IP | |
-g | 与真实服务器的转发模式为 DR 模式 | |
-i | 与真实服务器的转发模式为 TUN 模式 | |
-m | 与真实服务器的转发模式为 NAT 模式 | |
-w | 指定真实服务器的权重 | |
-d | 删除真实服务器记录 | |
-e | 修改真实服务器记录 |
命令样例如下:
# 在虚拟服务192.168.2.100:80中添加真实服务器192.168.10.3:80,转发模式为NAT模式 ipvsadm -a -t 192.168.2.100:80 -r 192.168.10.3:80 -m
3) 其他常用命令参数
其他常用命令参数格式如下:
# 查看IPVS配置 ipvsadm -ln
更多命令参数可以通过 man 命令查看。
man ipvsadm
2.Keepalived配置简述
Keepalived 是一款用C语言编写的开源路由软件,目前仍处于活跃开发的状态,其主要目标是基于 Linux 系统提供一款配置简单且功能强大的负载均衡和高可用的软件应用。负载均衡是基于 LVS(IPVS)实现的,Keepalived 在 LVS 的基础上增加了多种主动健康检测机制,可以根据后端真实服务器的运行状态,自动对虚拟服务器负载的真实服务器进行维护和管理。
高可用性是通过虚拟冗余路由协议(Virtual Reduntant Routing Protocol,VRRP)实现的。VRRP 是工作在网络层的一种路由容错协议,通过组播的通告机制进行网络路由快速转移,以实现网络设备的高可用。
2.1 Keepalived相关术语
Keepalived相关术语如下:
-
- 虚拟 IP(VIP):对外提供用户访问的 IP 地址,与 LVS 的 VIP 概念相同;
- 真实服务器(Real Server):被负载的后端服务器;
- 服务器池(Server Pool):同一虚拟 IP 及端口的一组真实服务器;
- 虚拟服务器(Virtual Server):服务器池的外部访问点,每个虚拟 IP 和端口组成一个虚拟服务器;
- 虚拟服务(Virtual Service):与 VIP 关联的 TCP/UDP 服务;
- VRRP:Keepalived 实现高可用的虚拟路由器冗余协议;
- VRRP 路由器(VRRP Router):运行 VRRP 协议的路由器设备;
- 虚拟路由器(Virtual Router):一个抽象对象,一组具有相同 VRID(虚拟路由器标识符)的多个 VRRP 路由器集合;
- MASTER 状态:主路由状态,是 VIP 地址的拥有者,负责转发到达虚拟路由的三层数据包,负责对虚拟 IP 地址的 ARP 请求进行响应;
- BACKUP 状态:备份路由状态,当主路由状态设备故障时,负责接管数据包转发及ARP请求响应。
2.2 Keepalived的工作模式
Keepalived 为 LVS 提供了文件形式的配置方式,并为真实服务器提供了多种主动健康检测机制,通过 VRRP 协议为 LVS 提供了高可用的负载集群解决方案。Keepalived 的工作模式如下图所示。
-
- 处于 MASTER 状态的 Keepalived 主机是 VIP 的拥有者,负责上层路由 VIP 的 ARP 查询响应和数据包转发;
- 处于 MASTER 状态的 Keepalived 主机通过 VRRP 协议在局域网内组播 VRRP 通告信息;
- 处于 MASTER 状态的 Keepalived 主机通过配置的健康检测机制主动检查服务器池中真实服务器的状态;
- 处于 BACKUP 状态的 Keepalived 主机接收 VRRP 通告信息,并根据通告信息判断本机状态是否变更;
- 当处于 MASTER 状态的路由发生故障时,处于 BACKUP 状态的路由确认主路由状态的 VRRP 通告超时时,则改变自身状态为 MASTER 状态,负责上层路由 IP 地址的 ARP 请求响应,并对外组播 VRRP 通告。
图:Keepalived 的工作模式示意图
2.3 健康检测
Keepalived 设计了多种主动健康检测机制,每个健康检测机制都注册在全局调度框架中,通过检测真实服务器的运行状态,自动对服务池中的真实服务器进行维护和管理。常用的健康检测机制有以下 4 种。
-
- TCP 检测。通过非阻塞式 TCP 连接超时检查机制检查真实服务器的状态,当真实服务器不响应请求或响应超时时,则确认为检测失败,并将该真实服务器从服务池中移除。
- HTTP 检测。通过 HTTP GET 方法访问指定的 URL 并对返回结果进行 MD5 算法求值,如果与配置文件中的预设值不匹配,则确认为检测失败,并将该真实服务器从服务池中移除。该机制支持同一服务器的多 URL 获取检测。
- SSL 检测。对 HTTP 检测增加了 SSL 支持。
- 自定义脚本。允许用户自定义检测脚本进行检测判断,支持脚本外部传递参数,执行的结果必须是 0 或 1。0 表示检测成功,1 表示检测失败。
2.4 配置关键字
Keepalived 配置文件可以分为 3 个部分,分别为全局配置、VRRP 配置和虚拟服务配置。各部分的常用配置关键字及其功能如下。
1) 全局配置
Keepalived 全局配置关键字实现邮件告警的 SMTP 配置及自身 VRRP 路由相关的全局配置,配置关键字如下表所示。
配置关键字 | 功能描述 |
global_defs | 全局配置区域标识 |
notification_email | 设置接收告警邮件的地址列表 |
notification_email_from | 设置发送邮件的地址列表 |
smtp_server | 设置用于发送邮件的 SMTP 服务器地址 |
smtp_connection_timeout | 设置 SMTP 服务器连接超时时间 |
router_id | 设置当前设备的路由 ID,每个设备均不相同 |
vrrp_version | VRRP 协议版本 |
nopreempt | 是否启用非抢占模式,即不参与 MASTER 的选举,默认为抢占模式 |
配置样例如下:
global_defs{ notification_email { monitor@nginxbar.org # 接收邮件的邮箱为monitor@nginxbar.org } smtp_server smtp.nginxbar.org # SMTP服务器地址为smtp.nginxbar.org smtp_connect_timeout 30 # SMTP服务器连接超时时间为30秒 router_id LVS_Nginx1 # 当前设备路由ID为LVS_Nginx1 }
2) VRRP配置
Keepalived 的 VRRP 配置关键字用于创建 VRRP 路由器,并为其配置运行参数。配置文件中可以创建多个不同名称的 VRRP 路由器实例,每个 VRRP 路由器实例都需要通过设定虚拟路由 ID 加入虚拟路由器中。VRRP 路由器接收组播的 VRRP 通告,并根据 VRRP 通告切换自身状态。当切换状态时会触发配置中对应状态的 shell 脚本,并根据配置参数判断是否发送告警邮件。VRRP 配置关键字如下表所示。
配置关键字 | 功能描述 |
vrrp_instance | VRRP 实例配置区域标识 |
state | 设置当前 VRRP 路由的初始状态 |
interface | 设置 VRRP 绑定的设备网络接口 |
virtual_router_id | 设置当前设备所属的虚拟路由 ID |
priority | 设置当前 VRRP 路由的初始优先级,优先级最高的会被选举为 MASTER,优先级取值范围为 1~254 |
advert_int | 发送组播包的间隔时间,默认为 1 秒 |
nopreempt | 是否启用非抢占模式,即不参与 MASTER 的选举,默认为抢占模式 |
preempt_delay | 设置抢占延时,取值范围为 0~1000,默认为 0,单位为秒。即等待多少秒才参与 MASTER 选举 |
authentication | VRRP 通信认证配置区域标识 |
auth_type | 指定 VRRP 通信的认证类型,有 PASS 简单密码认证和 AH:IPSEC 认证两种类型 |
auth_pass | 指定 VRRP 通信密码字符串,最大为 8 位 |
virtual_ipaddress | VIP 地址配置区域标识 |
notify_master | 指定一个转换为 MASTER 状态后执行的 shell 脚本 |
notify_backup | 指定一个转换为 BACKUP 状态后执行的 shell 脚本 |
notify_fault | 指定一个转换为 FAULT 状态后执行的 shell 脚本 |
smtp_alert | 使用 SMTP 的配置发送邮件告警通知 |
配置样例如下:
vrrp_instance VI_1 { state MASTER # 初始路由状态为MASTER interface eth0 # VRRP绑定接口为eth0 virtual_router_id 51 # 虚拟路由器的VRID为51 priority 100 # 当前设备的优先级是100 nopreempt # 不参与MASTER的选举 advert_int 5 # VRRP组播的间隔时间是5秒 authentication { auth_type PASS # 认证类型为PASS auth_pass 2222 # 认证密码为2222 } virtual_ipaddress { 192.168.2.155 # 虚拟服务器的VIP是192.168.2.155 } }
VRRP 本身是通过 VRRP 通告机制实现路由器状态切换判断的,但在实际的应用场景中会存在因网络抖动等原因影响 VRRP 的通告传递的情况,为提高状态切换的准确性,Keepalived 还提供了一种脚本检测机制,可以让用户通过自定义脚本更精准地进行路由状态切换。相关配置关键字如下表所示。
配置关键字 | 功能描述 |
vrrp_script | VRRP 脚本配置区域标识 |
script | 指定要执行的脚本路径 |
weight | 用于调整 VRRP 路由器优先级的权重值,如果脚本执行成功且 weight 为正时,则优先级增力相应值;如果脚本执行失败且 weight 为负,则优先级减少相应值。优先级的取值范围为 1~254 |
interval | 设置检测脚本的执行间隔。单位是 s。默认为 1s |
timeout | 脚本执行返回结果超时时间,超过指定时间则认为检测失败 |
rise | 连续检测成功次数为设定值时才确认为成功状态 |
fall | 连续检测失败次数为设定值时才确认为失败状态 |
init_fail | 设置脚本初始检测状态为失败状态 |
Keepalived 通过 VRRP 通告判断虚拟路由器中其他 VRRP 路由状态并确保路由的转移,对于业务层的高可用,则需要用户单独对应用进程进行同步检测。例如,Nginx 与 Keepalived 部署在同一台设备上,可以通过脚本检测 Nginx 进程的状态,如果 Nginx 检测失败并无法自动恢复,则降低 VRRP 的优先级。要尽量避免在切换为 MASTER 状态时,因自身业务层故障导致业务高可用切换失败。也可用多个脚本组合实现 VRRP 路由优先级的动态调整。配置样例如下:
vrrp_script checknginx { script "/opt/data/scripts/checknginx.sh" interval 3 # 检测脚本执行时间间隔 weight -20 # 当检测失败时,VRRP路由优先级降低20 rise 3 # 连续监测3次成功才确认为成功 fall 3 # 连续监测3次失败才确认为失败 }
检测脚本内容如下:
#!/bin/bash # 检测脚本查询Nginx进程是否存在,若存在则返回0,若检测失败则返回1 check = `ps aux | grep -v grep | grep nginx | wc -l` if [ $check > 0 ]; then exit 0 else systemctl start nginx exit 1 fi
3) 虚拟服务器配置
Keepalived 的虚拟服务器是负载均衡的外部访问点,通过配置关键字实现对 LVS 运行参数的配置,配置文件中可以为 VIP 绑定不同的端口创建多个虚拟服务器。虚拟服务器配置关键字如下表所示。
配置关键字 | 功能描述 |
virtual_server | 虚拟服务器配置区域标识 |
delay_loop | 设置健康检测的间隔时间 |
lb_algo | LVS 调度算法 |
lb_kind | LVS 转模式(NAT、DR、TUN) |
persistence_timeout | 设置保持连接的超时时间,在设定时间内会把同一个客户端的连接全部转发给同一真实服务器 |
persistence_granularity |
对启用保持连接的客户端 IP 进行掩码调整,当 IP 为 255.255.255.255 时,则仅限这个 IP 的客户端; 当 IP 为 255.255.255.0 时,则为这个客户端所在子网网段内 IP 的所有客户端 |
virtualhost | 为 HTTP_GET 或 SSL_GET 设置执行要检测的虚拟主机 |
protocol | 转发协议类型(TCP、UDP、SCTP) |
sorry_server | 设置一个服务池中所有真实服务器都无法访问时的备用服务器 |
真实服务器相关关键字如下表所示。
配置关键字 | 功能描述 |
real_server | 配置真实服务器 IP 及端口 |
weight | 设置真实服务器的权重,默认为 1 |
inhibit_on_failure | 当健康检测失败时,将当前服务器权重设置为 0,而不将其从服务器池中移除 |
notify_up | 当前服务器健康检查成功时执行的脚本 |
notify_down | 当前服务器健康检查失败时执行的脚本 |
uthreshold | 当前服务器的最大连接数 |
lthreshold | 当前服务器的最小连接数 |
TCP_CHECK | TCP 检测设置区域标识 |
MISC_CHECK | 自定义检测脚本设置区域标识 |
HTTP_GET | HTTP 检测设置区域标识 |
SSL_GET | SSL HTTP 检测设置区域标识 |
通过 Keepalived 为真实服务器配置关键字不仅可以实现 LVS 真实服务器的运行参数配置,还可以对自身增加的真实服务器的主动健康检测进行配置。真实服务器健康检测配置关键字如下表所示。
配置关键字 | 功能描述 |
url | HTTP_GET 和 SSL_GET 的 URL 检测标识 |
path | HTTP_GET 和 SSL_GET 的 URL 检测路径 |
digest | HTTP_GET 和 SSL_GET 的返回结果的 MD5 计算值 |
status_code | HTTP_GET 和 SSL_GET 的健康检测返回状态码 |
connect_ip | 检测的 IP 地址,默认为真实服务器的 IP 地址 |
connect_port | 检测的端口,默认为真实服务器的端口 |
bindto | 发起检测连接的接口地址,默认为本地 IP 地址 |
bind_port | 发起检测连接的源端口,默认为随机端口 |
connect_timeout | 检测连接的超时时间,默认为 5s |
fwmark | 使用 fwmark 对所有发出去的检查数据包进行标记 |
warmup | 指定一个随机延迟时间用于防止网络阻塞,如果为 0,则表示关闭该功能 |
nb_get_retry | GET 尝试次数,仅 HTTP_GET 和 SSL_GET 有效 |
retry | 重试次数,默认是 1 次,仅 TCP_CHECK 有效 |
delay_before_retry | 设置在重试之前延迟的秒数 |
配置样例如下:
virtual_server 192.168.2.155 80 { # 虚拟服务器IP及端口 delay_loop 6 # 健康检测间隔时间为6s lb_algo wrr # 负载均衡调度算法为加权轮询 lb_kind DR # 转发模式为DR persistence_timeout 60 # 保持连接的超时时间为60s protocol TCP # 负载均衡转发协议为TCP real_server 192.168.2.109 80 { # 真实服务器IP及端口 weight 100 # 真实服务器权重为100 notify_down /etc/keepalived/scripts/stop.sh # 当真实服务器健康检测失败时执 # 行stop.sh脚本 HTTP_GET { url { path "/healthcheck" # 指定要检查的URL的路径 digest bfaa324fdd71444e43eca3b7a1679a1a # 检测URL返回值的MD5计算值 status_code 200 # 健康检测返回状态码 } connect_timeout 10 # 连接超时时间为10s nb_get_retry 3 # 重试3次确认失败 delay_before_retry 3 # 失败重试的时间间隔为3s } } } # digest值的计算方法 genhash -s 192.168.2.109 -p 80 -u /healthcheck
Keepalived 的其他配置关键字此处并未列出,更多配置关键字可以通过 man 命令获取。
man keepalived.conf
3.Nginx集群负载(基于LVS和Keepalived)搭建
基于 LVS 和 Keepalived 的 Nginx 集群负载是使用 LVS 做传输层的负载均衡设备,将客户端请求从传输层负载到后端的多组 Nginx 集群,并由 Nginx 集群实现应用层负载均衡处理的多层负载均衡网络架构。Keepalived 通过文件配置的方式实现 LVS 的运行管理,并通过 VRRP 机制实现传输层负载的高可用,为 Nginx 集群提供高性能、高可用的负载应用。
Nginx 集群负载部署图如下图所示。
-
- LVS 作为传输层负载均衡与接入路由对接,负责把数据包转发给后端的 Nginx 服务器。
- LVS 选用 DR 转发模式,网络数据包在传输层被分发到 Nginx 服务器,并由 Nginx 经过本地路由返回给客户端。
- LVS 对后端 Nginx 服务器集群选用加权轮询(wrr)的负载均衡调度策略。
- Keepalived 通过 VRRP 协议组播通告状态信息,确保两台 LVS 服务器的高可用。
- 当处于 MASTER 状态的 Keepalived 发生故障时,处于 BACKUP 状态的 Keepalived 切换为 MASTER 状态,负责与接入路由对接,把数据包转发给后端的 Nginx 服务器。
- Keepalived 通过健康检测机制检测 Nginx 集群内每台 Nginx 服务器的健康状态。
- Nginx 负责应用层负载均衡,完成客户端请求的负载、路由分流、过滤等操作。
图:Nginx 集群负载部署图
1) Keepalived安装
Keepalived 在 CentOS 7 系统下使用 yum 安装即可。在 CentOS 7 系统下,LVS 已被集成到内核中,无须单独安装。
yum -y install keepalived
systemctl enable keepalived
2) Keepalived配置
Keepalived 需要分别在两台 LVS 服务器上进行配置,主服务器上的 Keepalived 配置如下:
! Configuration File for keepalived global_defs { notification_email { monitor@nginxbar.org # 发生故障时发送邮件告警通知的邮箱 } notification_email_from admin@nginxbar.org # 使用哪个邮箱发送 smtp_server mail.nginxbar.org # 发件服务器 smtp_connect_timeout 30 router_id LVS_01 # 当前设备路由ID为LVS_01 } vrrp_instance VI_1 { state MASTER # 初始路由状态为MASTER interface eth0 # VRRP绑定的本地网卡接口为eth0 virtual_router_id 51 # 虚拟路由器的VRID为51 priority 100 # 当前设备的优先级是100 advert_int 5 # VRRP组播的间隔时间是5s authentication { auth_type PASS # 认证类型为PASS auth_pass 2222 # 认证密码为2222 } virtual_ipaddress { 192.168.21.155 # 虚拟服务器的VIP是192.168.21.155 } } virtual_server 192.168.21.155 80 { # 虚拟服务器IP及端口 delay_loop 6 # 健康检测间隔时间为6s lb_algo wrr # 负载均衡调度算法为加权轮询 lb_kind DR # 转发模式为DR persistence_timeout 60 # 保持连接的超时时间为60s protocol TCP # 负载均衡转发协议为TCP real_server 192.168.2.108 80 { # 真实服务器IP及端口 weight 100 # 真实服务器权重为100 notify_down /etc/keepalived/scripts/stop.sh # 当真实服务器健康检测失败时执 # 行stop.sh脚本 HTTP_GET { url { path "/healthcheck" # 指定要检查的URL的路径 digest bfaa324fdd71444e43eca3b7a1679a1a # 检测URL返回值的MD5计算值 status_code 200 # 健康检测返回状态码 } connect_timeout 10 # 连接超时时间为10s nb_get_retry 3 # 重试3次确认失败 delay_before_retry 3 # 失败重试的时间间隔为3s } } real_server 192.168.2.109 80 { # 真实服务器IP及端口 weight 100 # 真实服务器权重为100 notify_down /etc/keepalived/scripts/stop.sh # 当真实服务器健康检测失败时执 # 行stop.sh脚本 HTTP_GET { url { path "/healthcheck" # 指定要检查的URL的路径 digest bfaa324fdd71444e43eca3b7a1679a1a # 检测URL返回值的MD5计算值 status_code 200 # 健康检测返回状态码 } connect_timeout 10 # 连接超时时间为10s nb_get_retry 3 # 重试3次确认失败 delay_before_retry 3 # 失败重试的时间间隔为3s } } }
备份服务器上的 Keepalived 配置样例如下:
! Configuration File for keepalived global_defs { notification_email { monitor@nginxbar.org # 发生故障时发送邮件告警通知 # 的邮箱 } notification_email_from admin@nginxbar.org # 使用哪个邮箱发送 smtp_server mail.nginxbar.org # 发件服务器 smtp_connect_timeout 30 router_id LVS_02 # 当前设备路由ID为LVS_02,此 # 处与主服务器配置不同 } vrrp_instance VI_1 { state BACKUP # 初始路由状态为BACKUP,此处 # 与主服务器配置不同 interface eth0 # VRRP绑定的本地网卡接口为eth0 virtual_router_id 51 # 虚拟路由器的VRID为51 priority 99 # 当前设备的优先级是99,此处 # 与主服务器配置不同 advert_int 5 # VRRP组播的间隔时间是5s authentication { auth_type PASS # 认证类型为PASS auth_pass 2222 # 认证密码为2222 } virtual_ipaddress { 192.168.21.155 # 虚拟服务器的VIP是192.168.21.155 } } virtual_server 192.168.21.155 80 { # 虚拟服务器IP及端口 delay_loop 6 # 健康检测间隔时间为6s lb_algo wrr # 负载均衡调度算法为加权轮询 lb_kind DR # 转发模式为DR persistence_timeout 60 # 保持连接的超时时间为60s protocol TCP # 负载均衡转发协议为TCP real_server 192.168.2.108 80 { # 真实服务器IP及端口 weight 100 # 真实服务器权重为100 notify_down /etc/keepalived/scripts/stop.sh # 当真实服务器健康检测失败时执 # 行stop.sh脚本 HTTP_GET { url { path "/healthcheck" # 指定要检查的URL的路径 digest bfaa324fdd71444e43eca3b7a1679a1a # 检测URL返回值的MD5计算值 status_code 200 # 健康检测返回状态码 } connect_timeout 10 # 连接超时时间为10s nb_get_retry 3 # 重试3次确认失败 delay_before_retry 3 # 失败重试的时间间隔为3s } } real_server 192.168.2.109 80 { # 真实服务器IP及端口 weight 100 # 真实服务器权重为100 notify_down /etc/keepalived/scripts/stop.sh # 当真实服务器健康检测失败时执 # 行stop.sh脚本 HTTP_GET { url { path "/healthcheck" # 指定要检查的URL的路径 digest bfaa324fdd71444e43eca3b7a1679a1a # 检测URL返回值的MD5计算值 status_code 200 # 健康检测返回状态码 } connect_timeout 10 # 连接超时时间为10s nb_get_retry 3 # 重试3次确认失败 delay_before_retry 3 # 失败重试的时间间隔为3s } } }
至此,高可用的 LVS 负载均衡就配置完成了。当主 LVS 服务器出现故障时,备份 LVS 服务器可以快速接管传输层网络数据的负载均衡工作,将数据包分发给后端的 Nginx 服务器集群。
4.Nginx集群配置管理规划
Nginx 的配置是以文件形式存在的,配置指令会在启动时一次性加载并生效,采用这种方式除 upstream 的配置可动态变更(商业版本支持 API 变更,开源版本依赖第三方模块动态修改)外,其他配置的修改均需要重启或热加载 Nginx 进程才可生效。为实现便捷的 Nginx 配置变更管理,需要从以下几个方面进行规划。
4.1 配置目录结构
Nginx 默认所有配置文件均存放在其安装目录的 conf 目录下,为防止配置文件不方便阅读和管理,可以按照虚拟主机(具有独立主机名或网络端口)进行拆分,每个虚拟主机一个配置文件,并存放在统一的目录下。对功能固定、全局的配置指令以固定文件的形式存放在配置文件目录的根目录下。所有的配置文件都以 nginx.conf 为统一入口,并使用配置指令 include 按需引入。
Nginx 的目录结构规划样例如下。
conf/
├── conf.d
│ ├── mysql_apps.ream
│ ├── www.nginxbar.com.conf
│ └── www.nginxbar.org.conf
├── fastcgi.conf
├── fastcgi_params
├── fscgi.conf
├── gzip.conf
├── mime.types
├── nginx.conf
├── proxy.conf
├── scgi_params
├── ssl
│ ├── www_nginxbar_org.csr
│ ├── www_nginxbar_org.key
│ └── www_nginxbar_org.pem
└── uwsgi_params
目录说明如下:
-
- conf.d 为自建目录,是存放虚拟主机配置文件的目录。
- mysql_apps.ream 是自定义应用 apps 的 MySQL 集群代理配置文件。
- www.nginxbar.com.conf 是域名 www.nginxbar.com (http://www.nginxbar.com) 对应的虚拟主机配置文件。
- www.nginxbar.org.conf 是域名 www.nginxbar.org (http://www.nginxbar.org) 对应的虚拟主机配置文件。
- fscgi.conf 是自定义 FastCGI 代理配置文件。
- gzip.conf 是自定义响应数据 gzip 压缩配置指令文件。
- proxy.conf 是自定义 HTTP 代理配置指令文件。
- ssl 是自建目录,用于存放虚拟主机的 SSL 证书文件。
nginx.conf 配置样例如下:
# 选择加载动态模块 load_module "modules/ngx_http_geoip_module.so"; load_module "modules/ngx_http_image_filter_module.so"; load_module "modules/ngx_http_xslt_filter_module.so"; # 工作进程及事件配置 worker_processes auto; # 启动与CPU核数一致的工作进程 worker_priority -5; # 工作进程在linux系统中的优先级为-5 events { worker_connections 65535; # 每个工作进程的最大连接数 multi_accept on; # 每个工作进程每次都可以接收多个连接 } # TCP/UDP代理配置 stream { # 配置TCP/UDP代理的日志格式模板,模板名为tcp log_format tcp '$remote_addr - $connection - [$time_local] $server_addr:$server_port - $protocol' '- $status - $upstream_addr - $bytes_received - $bytes_sent - $session_time ' '- $proxy_protocol_addr:$proxy_protocol_port '; # 配置TCP/UDP代理的错误日志输出位置,错误级别为error error_log logs/tcp_error.log error; # 引入conf.d目录下所有后缀名为ream的配置文件 include conf.d/*.ream; } # HTTP配置 http { include mime.types; # 引入MIME类型映射表文件 # 配置HTTP的错误日志输出位置,错误级别为error error_log logs/error.log error; # 配置HTTP的日志格式,模板名为main log_format main '$remote_addr - $connection - $remote_user [$time_local] "$request" - $upstream_addr ' '$status - $body_bytes_sent - $request_time - "$http_referer" ' '"$http_user_agent" - "$http_x_forwarded_for" - '; # 配置全局访问日志输出位置,并以模板main的日志格式输出 access_log logs/access.log main; charset utf-8; # 字符编码为utf-8 variables_hash_max_size 2048; # 变量哈希表最大值为2048B variables_hash_bucket_size 128; # 变量哈希桶最大值为128B server_names_hash_bucket_size 256; # 服务主机名哈希桶大小为256B client_header_buffer_size 32k; # 请求头缓冲区大小为32KB large_client_header_buffers 4 128k; # 最大缓存为4个128KB client_max_body_size 20m; # 允许客户端请求的最大单个文件字节数为20MB sendfile on; # 开启零复制机制 tcp_nopush on; # 启用在零复制时数据包最小传输的限制机制 tcp_nodelay on; # 当处于保持连接状态时以最快的方式发送数据包 keepalive_timeout 60; # 保持连接超时时间为60s client_header_timeout 10; # 读取客户请求头的超时时间是10s client_body_timeout 10; # 请求体接收超时时间为10s server_tokens on; # 不显示Nginx版本信息 include gzip.conf; # HTTP gzip的配置文件 include proxy.conf; # HTTP代理配置 include conf.d/*.conf; # 引入HTTP虚拟主机配置 }
nginx.conf 中编辑在全局区域的配置指令均可按照 Nginx 配置指令规范在 server、location 指令域中被同名的配置指令覆盖。
4.2 配置归档
Nginx 作为负载均衡应用时,是业务应用的入口,Nginx 服务器的可用性决定了其所负责的所有被代理业务的可用性。所以 Nginx 进行配置变更时要及时做好归档和版本控制,因为 Nginx 配置是以文件方式存在的,所以可以将每次修改的文件以 Git 标签的方式在 Git 仓库中进行存档和版本控制。
4.3 配置变更
可以使用对应的配置模板进行 Nginx 配置内容的修改、配置部分的标准化及通用性约定,以便进行自动化操作。开源软件 Ansible 提供了自定义模板的功能,使用户可以按照预期设计更加严谨、规范地配置变更。Ansible 支持批量操作,可以快速完成多台 Nginx 服务器配置文件的同步和加载。
4.4 配置发布
Ansible 虽然提供了命令行的操作能力,但是用户权限、操作日志及快速回滚等操作仍不够便捷。Jenkins 是一款 Web 化的持续集成发布工具,被广泛应用于业务应用的发布,拥有超过 1000 个插件,用户无须额外开发就可快速完成代码从代码仓库到运行部署的整个流程,同时还支持用户权限、操作日志及快速回滚等操作。
根据上述 4 个方面的规划,通过 Jenkins 与 GitLab 及 Ansible 的配合使用,无须复杂编程就可以快速搭建一套 Web 化的 Nginx 配置管理系统。应用架构如下图所示。
图:应用架构图
关于上图,有以下几点需要说明:
-
- Jenkins 通过 GitLab 获取 Git 仓库中的 Nginx 文件。
- Ansible 根据 Jenkins Web 界面输入的参数与对应配置模板生成配置文件,更新本地的 Nginx 配置文件。
- Ansible 将更新后的配置文件同步到 Nginx 集群的所有 Nginx 服务器,并对 Nginx 进程执行 reload 操作,以加载更新后的配置。
- Jenkins 将更新后的 Nginx 配置文件以 Git 标签的方式进行归档。
- 用户可以通过 Jenkins 获取对应 Git 仓库的所有 Git 标签,并根据需求选择对应的 Git 标签代码执行回滚操作。
5.Nginx GitLab(配置归档工具)配置
GitLab 是使用 Ruby 语言编写的 Git 仓库管理工具,以 Git 作为代码管理工具,并提供了 Web 管理、WIKI 及 Issue 等功能。GitLab 是按照 MIT 许可证分发的开源软件,已被很多知名公司使用,目前由 GitLabInc. 开发维护。
GitLab 可以搭建在私有服务器上,被授权的用户可以创建自己的代码仓库,并可授权给多人协作进行维护。GitLab 拥有与 GitHub 类似的功能,可以通过 Web 浏览器浏览代码、管理缺陷和注释。通过 GitLab 管理 Nginx 配置文件可以从 Web 浏览器中非常方便地浏览到提交过的历史变更,也可以利用 Git 相关命令实现 Nginx 配置的快速回滚操作。
GitLab 同样支持以 Docker 方式部署,官方在 Docker Hub 中也提供了可直接使用的镜像,通过编写相应的
docker-compose 脚本,可以快速搭建 GitLab 服务器,部署过程如下。
1) 初始化系统环境
主机的操作系统为 CentOS 7.6,初始化 Docker 环境如下:
yum install -y yum-utils # 安装yum工具 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 安装Docker安装源 yum install -y docker-ce docker-compose # 安装Docker和docker-compose systemctl enable docker # 将Docker注册为自启动服务 systemctl start docker
2) 编写docker-compose脚本
创建 docker-compose 脚本,保存为 gitlab.yaml。
gitlab: image: 'gitlab/gitlab-ce:latest' restart: always hostname: 'gitlab' container_name: gitlab # environment: # GITLAB_OMNIBUS_CONFIG: | #external_url ‘https://gitlab.example.com’ #Add any other gitlab.rb configuration here, each on its own line ports: - '8080:80' - '8443:443'
3) 持久化GitLab数据
GitLab 需要持久化的有 3 个部分的内容,分别是 GitLab 的配置、GitLab 的代码仓库和 GitLab 日志。
# 运行GitLab容器 docker -f gitlab.yaml up -d # 创建挂载目录并复制原容器内的文件 mkdir -p /opt/data/apps/gitlab docker cp gitlab:/etc/gitlab /opt/data/apps/gitlab/config docker cp gitlab:/var/opt/gitlab /opt/data/apps/gitlab/data docker cp gitlab:/var/log/gitlab /opt/data/apps/gitlab/logs chown -R 998:998 /opt/data/apps/gitlab/logs # 添加挂载卷配置 echo " volumes: - '/opt/data/apps/gitlab/config:/etc/gitlab' - '/opt/data/apps/gitlab/logs:/var/log/gitlab' - '/opt/data/apps/gitlab/data:/var/opt/gitlab' " >>gitlab.yaml docker stop gitlab docker rm gitlab docker-compose -f gitlab.yaml up -d
GitLab 运行后可通过 http://IP:8080 访问登录。如果 GitLab 仅作为代码仓库应用,只需默认配置即可使用。
6.Nginx配置修改工具Ansible
Ansible 是一款自动化的运维工具,是基于 Python 开发的。Ansible 提供了一种自动化执行框架,其可以按照用户设计的剧本自动化执行相关操作。Ansible 是基于模块工作的,其可以实现使用各种模块,并按照设计的剧本,批量对多个目标执行相同的操作。Ansible 集合了众多运维工具的优点,配置更加简单方便。
6.1 Ansible剧本(playbook)
Ansible 剧本是 Ansible 可以自动化执行一系列动作的执行方式。其通过 YAML 格式文件描述任务步骤,Ansible 按照指定的步骤有序执行,并支持同步和异步,非常适合完成各种复杂的部署工作。Ansible 剧本的相关术语如下。
-
- 任务(Tasks),由 YAML 描述的一系列操作步骤。
- 变量(Variables),被剧本引用,可以使剧本的设计更加灵活,并根据变量的值执行不同的步骤。
- 模板(Templates),可根据变量的值,动态生成目标文件的预置文件,Ansible 使用 Jinja2 模板语法。
- 处理器(Handlers),当剧本任务条件满足时,触发执行的任务步骤。
- 角色(Roles),描述某一特定任务的集合,其由以上术语的 YAML 描述文件组成。
- 主机(Hosts),被剧本操作的目标主机 IP 或组名称,主机组名称由外部的 hosts 文件定义。
Ansible 中每个剧本只有一个主入口文件,并且只有一个主线任务,主线任务可根据不同条件选用不同的角色,每个角色由任务、变量、模板、处理器的 YAML 描述文件组成。
Ansible 及剧本目录结构如下:
. ├── ansible.cfg # ansible配置文件 ├── hosts # 目标主机资源文件 └── roles # 剧本目录,可自定义 ├── nginx # 剧本角色名称及角色任务文件目录 │ ├── defaults # 角色默认变量目录 │ │ └── main.yaml # 默认变量自动加载的文件 │ ├── files # 文件存放目录 │ ├── handlers # 处理器任务文件目录 │ │ └── main.yaml # 默认处理器任务自动加载的文件 │ ├── tasks # 角色任务文件存放目录 │ │ └── main.yaml # 当前角色默认的任务入口文件 │ └── templates # 任务模板存放目录 └── nginx.yaml # 剧本入口文件
6.2 基础语法
1) 步骤描述
任务由多个步骤组成,每个步骤由步骤命名、任务模块、动作组成,配置样例如下:
- name: reload Nginx Service # 步骤名 systemd: "name=nginx state=reloaded enabled=yes" # 任务模块为systemd,动作是对系统服务 # Nginx执行reload操作
2) 执行顺序
Ansible 剧本中的步骤是自上而下执行的,在默认情况下,每个步骤的执行结果返回值如果不为 0,就会报错,剧本任务也终止,也可以通过忽略错误指令继续运行,配置样例如下:
- name: Create conf.d shell: mkdir -p /etc/nginx/conf.d # 任务模块是shell,动作是执行mkdir命令 ignore_errors: True # 如果当前动作执行出错,忽略错误继续执行
3) 变量赋值
Ansible 剧本中变量赋值有静态和动态两种方式,一种是在 defaults 目录的 main.yaml 文件中静态地直接赋值,这通常被用作默认变量的赋值,配置样例如下:
confdir: "/etc/nginx" # 定义变量confdir的值为/etc/nginx
另一种是在当前执行的过程中动态地进行变量赋值,被赋值的变量可以在当前剧本执行过程中被引用,配置样例如下所示:
# 方法一:将执行结果动态赋值给变量 - name: Test Nginx Config shell: nginx -c {{ confdir }}/nginx.conf -t # 任务模块是shell,动作是Nginx # 执行-t参数命令 register: test_result # 将执行结果赋值给变量test_result # 方法二:根据其他变量的值进行动态的变量赋值 - name: check set_fact output set_fact: output="{{ work }}/output" # 为变量output赋值 when: test_result # 当变量test_result为真时
4) 条件判断
在 Ansible 剧本中可以根据变量的值判断是否执行当前步骤,配置样例如下:
- debug: msg="{{ test_result.stderr_lines }}" # 任务模块debug,输出变量test_result. # stderr_lines的内容 when: not test_result.stderr_lines == "" # 当test_result变量的输出结果不为空, # 执行当前步骤
5) 外部引入
多个步骤可以编写在一个 YAML 文件中,并通过指令 include_tasks 被其他任务引入,结合条件判断,它可以使主线任务因变量不同而存在多个不同分支,配置样例如下:
- name: "check system type" include_tasks: linux.yaml # 引入外部任务步骤 when: ansible_os_family != "Windows" # 当目标操作系统非windows时,执行当前 # 任务
6.3 剧本执行
Ansible 剧本编写结束后,使用 ansible-playbook 命令调用剧本入口文件执行相应的剧本,即可自动完成预设的任务。
ansible-playbook -i /etc/ansible/hosts /etc/ansible/roles/nginx.yaml --extra-vars 'hosts=192.168.2.145'
7.Jenkins安装与配置简述
Jenkins 是基于 Java 开发的一个开源的持续集成项目,其提供了一个可扩展的可对代码持续集成、发布(代码编译、打包、部署)及交付的 Web 化操作平台。Jenkins 拥有超过 1000 个插件,使其支持包括 SVN、Git 等多种版本的管理工具(SCM)的代码管理,也可以快速实现 Java、Node.js、.Net 等语言项目的编译构建,并支持包括 Docker 在内的多种形式的部署交付。通过 Jenkins 的 Web 化管理界面,依赖各种强大的插件功能,可以使 Nginx 的配置变更管理变得更加便捷和安全。
使用 Jenkins 官方提供的 Docker 镜像,可以很方便地搭建 Jenkins 工作环境,搭建过程如下:
1) 初始化系统环境
主机的操作系统为 CentOS 7.6,初始化 Docker 环境如下:
yum install -y yum-utils # 安装yum工具 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 安装Docker安装源 yum install -y docker-ce docker-compose # 安装Docker和docker-compose systemctl enable docker # 将Docker注册为自启动服务 systemctl start docker
2) 编写docker-compose脚本
将脚本保存为jenkinsci.yaml
jenkinsci: image: 'jenkinsci/blueocean' restart: always hostname: 'jenkinsci' container_name: jenkinsci environment: - PATH=/opt/apps/apache-maven-3.5.3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin - JAVA_OPTS="-Duser.timezone=Asia/Shanghai" - JENKINS_SLAVE_AGENT_PORT=50000 ports: - '8086:8080' - '50000:50000'
3) 数据持久化
Jenkins 需要持久化的是 Jenkins 的运行目录,该目录包含其运行的所有配置文件,具体如下:
docker cp jenkinsci:/var/jenkins_home /opt/data/apps/jenkinsci/ chown -R 1000:1000 /opt/data/apps/jenkinsci/jenkins_home echo " volumes: - '/opt/data/apps/jenkinsci/jenkins_home:/var/jenkins_home' - '/opt/data/apps/jenkinsci/apps:/opt/apps' " >>jenkinsci.yaml docker stop jenkinsci docker rm jenkinsci docker-compose -f jenkinsci.yaml up -d
4) 初始化配置及插件
Jenkins 启动后,在浏览器中访问 http://IP:8086 即可进入初始化安装界面,使用初始化密码登录,选择安装推荐插件即可。
通过以下指令获取初始化密码:
docker exec -it jenkinsci2 cat /var/jenkins_home/secrets/initialAdminPassword
Jenkins 是以任务(Job)为管理单元的,常用的任务类型有自由风格、Maven 项目、文件夹和流水线(pipeline)四种,本样例中仅使用自由风格任务类型。自由风格及流水线任务按照工作流程被划分为多个阶段,Jenkins 负责维护和管理任务在每个阶段的执行,并通过工作流的状态,按照任务的设定推动任务工作流的完成。自由风格任务的 6 个阶段配置如下表所示。
阶段配置名称 | 英文名称 | 阶段配置作用 |
全局配置 | General | 用以任务维护相关的配置,如时间戳格式、构建历史管理、构建参数等配置 |
代码仓库配置 | SCM | 用以配置代码仓库类型及代码仓库地址和账号 |
自动触发构建的条件配置 | Build Triggers | 用以设置自动触发当前任务的外部条件 |
构建环境配置 | Build Environment | 设置构建前的环境变量等配置 |
构建操作配置 | Build | 设置用以构建的工具或构建的脚本 |
构建后动作配置 | Post-build Actions | 设置构建后的相关动作,如生成构建报告、构建归档或触发其他构建等 |
8.Nginx集群配置管理实例
根据部署规划,如果对 Nginx 集群配置实现管理,需要在 GitLab、Jenkins 上完成相关的配置及编写 Ansible 剧本。本节将通过对配置文件 nginx.conf 举例 GitLab、Jenkins 及 Ansible 的配置,以实现 Nginx 配置管理的操作。
8.1 GitLab配置
首先为 Nginx 配置创建用户及 Nginx 项目,操作步骤如下:
-
- 创建发布用户 gitlab_nginx:Admin Area → Users → New User 用户名 gitlab_nginx。
- 创建项目组 nginx:GitLab 登录后创建项目组(Group)nginx,可视级别(Visibility Level)为 Private。
- 添加组用户:将用户 gitlab_nginx 添加到项目组 nginx 中,权限为 Developer。
- 创建 Nginx 配置项目 homebox:按照 Nginx 集群名称创建 Gitlab 项目,nginx 组项目 → New project,命名为 home-box。
- 初始化:进入 Nginx 配置文件目录,将配置文件初始化到 GitLab 仓库中。初始化命令如下:
git init git remote add origin http://IP:8080/nginx/homebox.git git add . git commit -m "Initial commit" git push -u origin master
8.2 Ansible剧本
根据 Nginx 配置目录的规划,定义 Ansible 剧本目录结构如下:
.
├── ansible.cfg
├── hosts
└── roles
├── nginx
│ ├── defaults
│ │ └── main.yaml
│ ├── files
│ │ ├── gzip.conf
│ │ ├── fscgi.conf
│ │ └── proxy.conf
│ ├── handlers
│ │ └── main.yaml
│ ├── tasks
│ │ ├── config_nginx.yaml
│ │ ├── config_server.yaml
│ │ ├── config_status.yaml
│ │ ├── deploy.yaml
│ │ ├── install.yaml
│ │ ├── rollback.yaml
│ │ └── main.yaml
│ └── templates
│ ├── nginx.conf
│ ├── server.conf
│ └── status.conf
└── nginx.yaml
关于目录的说明如下所示:
-
- defaults 目录中的 main.yaml 是自定义默认变量值的描述文件,文件内容如下:
self_services: nginx exclude: ".git" rsync_opts: - "--exclude={{ exclude }}" process_events: > worker_processes auto; worker_rlimit_nofile 65535; worker_priority -5; modules: "" server: "" confdir: "/etc/nginx" env_packages: - pcre-devel - zlib-devel - openssl-devel - libxml2-devel - libxslt-devel - gd-devel - GeoIP-devel - jemalloc-devel - libatomic_ops-devel - luajit - luajit-devel - perl-devel - perl-ExtUtils-Embed
-
- gzip.conf、fscgi.conf、proxy.conf 这 3 个文件是全局的配置文件,放在 files 目录中仅作 Nginx 初始化安装时使用。
- handlers 的 main.yaml 是处理器任务描述文件,文件内容如下:
# 重启Nginx服务任务 - name: Restart Nginx services service: name: "{{ self_services }}" state: restarted # 启动Nginx服务任务 - name: Start Nginx services service: name: "{{ self_services }}" state: started
-
- tasks 目录中的 main.yaml 是当前角色的默认入口文件,文件内容如下:
--- # 当变量deploy的值为deploy时执行deploy.yaml的任务步骤 - name: "Starting deploy for nginx" include_tasks: deploy.yaml when: deploy == "deploy" # 当变量deploy的值为rollback时执行rollback.yaml的任务步骤 - name: "Starting rollback for nginx" include_tasks: rollback.yaml when: deploy == "rollback"
-
- tasks 目录中的 deploy.yaml 是修改配置的任务分支描述文件,文件内容如下:
--- # 检查目标服务器是否存在配置文件,并将检查结果赋值给变量has_nginx - name: "check nginx service" stat: path={{ confdir }}/nginx.conf register: has_nginx # 如果目标服务器不存在Nginx服务器则调用分支任务install进行安装 - name: "Starting install nginx " include_tasks: install.yaml when: not has_nginx.stat.exists # 如果当前任务为配置nginx.conf,则调用config_nginx任务配置nginx.conf文件 - name: "Starting config nginx.conf " include_tasks: config_nginx.yaml when: not jobname == "" and jobname == "nginx.conf" # 如果当前任务为配置status.conf,则调用config_status任务配置status.conf文件 - name: "Starting config website status for nginx" include_tasks: config_status.yaml when: not jobname == "" and jobname == "status.conf" # 如果当前任务为配置server.conf,则调用config_server任务配置server.conf文件 - name: "Starting config website server for nginx" include_tasks: config_server.yaml when: not jobname == "" and jobname == "server.conf" # 初始化rsync模块的ssh免登录key - name: add authorized_keys authorized_key: user: "{{ ansible_user_id }}" key: "{{ lookup('file', '/home/jenkins/.ssh/id_rsa.pub') }}" state: present exclusive: no # 使用rsync模块将Nginx配置文件同步到目标机器 - name: check rsync_opts rsync dir synchronize: src: "{{ work }}/" dest: "{{ confdir }}" delete: yes copy_links: yes private_key: "/home/jenkins/.ssh/id_rsa" rsync_opts: "{{ rsync_opts }}" register: rsync_result # 输出rsync的执行详情 - debug: msg="{{ rsync_result.stdout_lines }}" # 使用Nginx的测试参数测试配置文件是否存在语法错误 - name: Test Nginx Config shell: nginx -c {{ confdir }}/nginx.conf -t -q ignore_errors: True register: test_result # 如果执行检测失败,则停止当前任务,并输出检测结果 - fail: msg="{{ test_result.stderr_lines }}" when: test_result.failed # 热加载Nginx进程 - name: reload Nginx Service systemd: "name=nginx state=reloaded enabled=yes"
-
- tasks 目录中的 rollback.yaml 是回滚配置的任务分支描述文件,文件内容如下:
--- - name: check rsync_opts rsync dir synchronize: src: "{{ work }}/" dest: "{{ confdir }}" delete: yes copy_links: yes private_key: "/home/jenkins/.ssh/id_rsa" rsync_opts: "{{ rsync_opts }}" register: rsync_result - debug: msg=" {{ rsync_result.stdout_lines }} " - name: "Test Nginx Config" shell: nginx -c {{ confdir }}/nginx.conf -t -q ignore_errors: True register: test_result - fail: msg="{{ test_result.stderr_lines }}" when: test_result.failed - name: reload Nginx Service systemd: "name=nginx state=reloaded enabled=yes" register: test_result
-
- tasks 目录中的 config_nginx.yaml 是 Nginx 配置文件 nginx.conf 的任务分支描述文件,文件内容如下:
--- # 通过模板文件与外部输入变量生成新的nginx.conf文件,替换Jenkins的工作目录中的 # nginx.conf - name: "Starting init nginx.conf " template: src=nginx.conf dest={{ work }}/nginx.conf delegate_to: localhost # 因外部参数中的单、双引号及变量符号被转义,此处则重新替换回原符号 - name: "Starting format nginx.conf " shell: sed -i 's/%24/$/g' {{ work }}/nginx.conf && sed -i 's/%9c/\"/g' {{ work }}/nginx.conf && sed -i "s/%98/\'/g" {{ work }}/nginx.conf && python /etc/ansible/bin/nginxfmt.py {{ work }}/nginx.conf delegate_to: localhost
-
- tasks 目录中的 config_server.yaml 是 Nginx 配置文件中配置各虚拟主机的任务分支描述文件,文件内容如下:
--- # 通过模板文件与外部输入变量生成新的虚拟主机文件,替换Jenkins的工作目录中虚拟主机 # 文件并在conf.d目录下保存 - name: "Starting init {{ jobname }} " template: src=server.conf dest={{ workdir }}/conf.d/{{ jobname }}.conf delegate_to: localhost # 因外部参数中的单、双引号及变量符号被转义,此处则重新替换回原符号 - name: "Starting format nginx.conf " shell: sed -i 's/%24/$/g' {{ work }}/nginx.conf && sed -i 's/%9c/\"/g' {{ work }}/nginx.conf && sed -i "s/%98/\'/g" {{ work }}/nginx.conf && python /etc/ansible/bin/nginxfmt.py {{ work }}/nginx.conf delegate_to: localhost
-
- tasks 目录中的 config_staus.yaml 是 Nginx 配置文件中统一状态监控的虚拟主机任务描述文件,文件内容如下:
--- # 通过模板文件与外部输入变量生成新的状态监控虚拟主机文件,替换Jenkins的工作目录中 # 的conf.d目录下保存 - name: "Starting init status.conf " template: src=status.conf dest={{ workdir }}/conf.d/status.conf delegate_to: localhost # 因外部参数中的单、双引号及变量符号被转义,此处则重新替换回原符号 - name: "Starting format nginx.conf " shell: sed -i 's/%24/$/g' {{ work }}/nginx.conf && sed -i 's/%9c/\"/g' {{ work }}/nginx.conf && sed -i "s/%98/\'/g" {{ work }}/nginx.conf && python /etc/ansible/bin/nginxfmt.py {{ work }}/nginx.conf delegate_to: localhost
-
- tasks 目录中的 install.yaml 是 Nginx 的部署任务描述文件,文件内容如下:
# 添加Nginx yum安装源 - name: add repo yum_repository: name: nginx description: nginx repo baseurl: http://nginx.org/packages/centos/7/$basearch/ gpgcheck: no enabled: 1 # 安装环境依赖包 - name: install centos packages yum: name: "{{ env_packages }}" disable_gpg_check: yes state: present # yum方式安装Nginx,并触发处理器Start Nginx services任务 - name: install nginx yum: name: nginx state: latest notify: Start Nginx services
-
- templates 目录中的 nginx.conf 为配置文件 nginx.conf 的模板文件,文件内容如下:
{{ modules }} {{ process_events }} stream { {{ stream }} include conf.d/*.ream; } http { {{ http }} {% if gzip != "false" %} include gzip.conf; # HTTP gzip的配置文件 {% endif %} {% if fscgi != "false" %} include fscgi.conf; # FastCGI代理的配置文件 {% endif %} {% if proxy != "false" %} include proxy.conf; # HTTP代理配置 {% endif %} include conf.d/*.conf; }
-
- templates 目录中的 server.conf 为配置文件中虚拟服务器的模板文件,文件内容如下:
{{ global }} upstream { {{ upstream }} } server{ {{ server }} }
-
- templates 目录中的 status.conf 为配置文件中用于状态监控的虚拟主机模板文件,文件内容如下:
{{ global }} server{ {{ server }} }
-
- roles 目录中的 nginx.yaml 为主剧本文件,该剧本文件调用了角色 Nginx,使用外部变量、应用角色 Nginx 中的任务描述文件完成 Nginx 的配置修改、同步及加载动作,文件内容如下:
--- # 变量hosts由外部输入,设定操作的目标主机 - hosts: - "{{ hosts }}" max_fail_percentage: 30 # 当有30%的操作目标任务执行出错时,则终止整个剧本的执行 serial: "{{ serial }}" # 该模块可以设定操作目标数量实现灰度发布的效果,当设定为 # 30%且操作目标为3台时,则表示一次仅操作一个目标 roles: - nginx # 调用Nginx角色
8.3 Jenkins配置
根据 GitLab 及 Ansible 剧本的设置,Jenkins 需要创建具有如下操作内容的任务:
-
- 通过 Web 页面设定 Nginx 的配置内容;
- 使用账号 gitlab_nginx 从 GitLab 中获取 Nginx 的配置文件;
- 调用 Ansible 剧本实现 Nginx 配置文件中的修改、同步及加载;
- 实现修改文件的归档;
- 实现修改内容的快速回滚;
- 对操作者设定访问的权限;
- 对发布的历史可以查看;
- 可以满足多个 Nginx 集群的配置管理。
按照上述需求的设定,可以将不同的 Nginx 集群以文件夹类型任务进行创建,每个 Nginx 集群文件夹中包括 nginx.conf、status.conf 全局配置的自由风格任务,每个虚拟主机则按照虚拟主机名称创建自由风格任务分列在该集群文件夹下。任务层级结构如下:
homebox # Nginx集群名称,任务类型为文件夹 nginx.conf # nginx.conf任务,任务类型为自由风格 status.conf # status.conf任务,任务类型为自由风格 www.nginxbar.org # 虚拟主机任务,任务类型为自由风格
该任务层级设计,可以使操作者清晰地知道所操作的 Nginx 集群,同时还可以结合 Jenkins 的权限功能进行细粒度的权限控制。任务配置 nginx.conf 的创建步骤首先是在全局配置阶段通过参数化构建插件实现 Web 化变量的输入,通过参数化配置,设计部署与回滚操作选项。当选择回滚时,通过 Git 参数插件提供 Git 标签(tag)筛选功能列出可用的 Git 标签,选择后执行回滚操作。同时还要按照之前的规划在此阶段将 nginx.conf 文件内容分割成多个不同的变量,并定义为构建参数,让发布者在点击参数化构建后,可以通过 Web 界面进行选择和修改。
在构建操作配置阶段,编写 shell 脚本对所有输入的变量进行判断、修整后通过 ansible-playbook 命令传递给 Nginx 剧本,完成 Nginx 配置的修改、同步及加载操作。若在构建后动作配置阶段,则通过 Git Publisher 插件将当前的修改标记 Git 标签进行归档。详细配置过程如下。
1) 全局配置
- 定义时间戳变量格式 [Change date pattern for the BUILD_TIMESTAMP(build time-stamp)variable] 为 yyyyMMdd
- 选项参数 deploy,选项(Choices)为 deploy 和 rollback,用以定义构建脚本中的变量,进行控制是更新配置文件还是回滚以前的配置操作。
- Git 参数(Git Parameter)tag,参数类型为 tag,过滤(Tag Filter)配置为 nginx.conf-deploy-*,排序(Sort Mode)选择 DESCENDING,默认值为 Default Value。该参数可以获取当前任务 Git 仓库的分支及 tag 列表,这里获取过滤被标记为部署成功的 Git 标签,用以实现代码回滚。
- 文本参数 modules,加载动态模块,参数值如下:
# 选择加载动态模块 load_module "modules/ngx_http_geoip_module.so"; load_module "modules/ngx_http_image_filter_module.so"; load_module "modules/ngx_http_xslt_filter_module.so";
- 文本参数 process_events,工作进程及事件配置,参数值如下:
# 工作进程及事件配置,定义文本参数process_events worker_processes auto; # 启动与CPU核数一致的工作进程 worker_priority -5; # 工作进程在Linux系统中的优先级为-5 events { worker_connections 65535; # 每个工作进程的最大连接数 multi_accept on; # 每个工作进程每次都可以接受多个连接 }
- 文本参数 stream,加载 TCP/UDP 代理配置,参数值如下:
# 配置TCP/UDP代理的日志格式模板,模板名为tcp log_format tcp '$remote_addr - $connection - [$time_local] $server_addr:$server_port - $protocol' '- $status - $upstream_addr - $bytes_received - $bytes_sent - $session_time ' '- $proxy_protocol_addr:$proxy_protocol_port '; # 配置TCP/UDP代理的错误日志输出位置,错误级别为error error_log /var/log/nginx/tcp_error.log error;
- 文本参数 http,加载 HTTP 配置,参数值如下:
include mime.types; # 引入MIME类型映射表文件 # 配置HTTP的错误日志输出位置,错误级别为error error_log /var/log/nginx/error.log error; # 配置HTTP的日志格式,模板名为main log_format main '$remote_addr - $connection - $remote_user [$time_local] "$request" - $upstream_addr ' '$status - $body_bytes_sent - $request_time - "$http_referer" ' '"$http_user_agent" - "$http_x_forwarded_for" - '; # 配置全局访问日志输出位置,并使用模板main的日志格式输出 access_log /var/log/nginx/access.log main; charset utf-8; # 字符编码为utf-8 variables_hash_max_size 2048; # 变量哈希表最大值为2048字节 variables_hash_bucket_size 128; # 变量哈希桶最大值为128字节 server_names_hash_bucket_size 256; # 服务主机名哈希桶大小为256字节 client_header_buffer_size 32k; # 请求头缓冲区的大小为32KB large_client_header_buffers 4 128k; # 最大缓存为4个128KB client_max_body_size 20m; # 允许客户端请求的最大单个文件字节数为20MB sendfile on; # 开启零复制机制 tcp_nopush on; # 启用在零复制时数据包最小传输的限制机制 tcp_nodelay on; # 当处于保持连接状态时,以最快方式发送数据包 keepalive_timeout 60; # 保持连接超时时间为60s client_header_timeout 10; # 读取客户请求头的超时时间是10s client_body_timeout 10; # 请求体接收超时时间为10s server_tokens on; # 不显示Nginx版本信息
- 布尔值参数 proxy,该设置默认为选中,用以选择是否加载代理相关指令配置。
- 布尔值参数 gzip,该设置默认为不选中,用以选择是否加载 gzip 相关指令配置。
- 布尔值参数 fscgi,该设置默认为不选中,用以选择是否加载 FastCGI 相关指令配置。
2) 代码仓库配置
- 添加 GitLab 的地址、账户及密码。
- 构建分支(Branches to build),填写 ${tag},Git 参数定义的变量。
3) 构建环境配置
- 选择构建前先删除之前的构建目录(Delete workspace before build starts)。
- 选择设置 Jenkins 用户变量(Set jenkins user build variables)。
4) 构建操作配置
编写构建脚本。
#!/bin/bash set -x # 初始化变量 jobname=${JOB_NAME} jobnum=${BUILD_TIMESTAMP}-${BUILD_NUMBER} OLD_IFS="$IFS" ;IFS="/" ;arr=($jobname) ;IFS="$OLD_IFS" cluster=${arr[1]} name=${arr[2]} # 部署时执行的操作 if [ "$deploy" == "deploy" ];then rm -rf *.default # 对变量中的单引号、双引号及变量符号进行转义 stream=${stream//$/%24} stream=${stream//\'/%98} stream=${stream//\"/%9c} http=${http//$/%24} http=${http//\'/%98} http=${http//\"/%9c} # 生成当前配置变量 jobvars="process_events='$process_events' modules='$modules' stream= '$stream' http='$http' proxy='$proxy' gzip='$gzip' fscgi='$fscgi'" fi # 回滚时执行的操作 if [ "$deploy" == "rollback" ];then OLD_IFS="$IFS" ;IFS="-" ;arr=($tag) ;IFS="$OLD_IFS" jobnum=${arr[${#arr[@]}-2]}-${arr[${#arr[@]}-1]} jobvars="" fi # 生成版本信息 echo "#$cluster-$name-$jobnum $deploy by ${BUILD_USER}" >version.txt # 生成任务变量 vars="hosts=$cluster jobname=$name work=${WORKSPACE} serial=30% deploy= '$deploy' $jobvars " # 执行Ansible剧本 ansible-playbook -i /etc/ansible/hosts /etc/ansible/roles/nginx.yaml --extra-vars "$vars " if [ $? -ne 0 ];then exit 1; fi # 执行部署操作成功时,对变更的配置文件进行归档 if [ "$deploy" == "deploy" ];then git add . git commit -m "#$cluster-$name-$jobnum deploy by ${BUILD_USER}" fi
添加修改构建名(Update build name),选择从文件名中读取(Read from file),文件名填写为 version.txt。
5) 构建后动作配置
- 使用 Git Publisher 插件,将修改成功的代码提交到 Gitlab 中,并打标签(tag)为当前构建的时间戳和编号;
- 选择构建成功后,再打标签(Push Only If Build Succeeds);
- 选择合并结果(Merge Results);
- 标签名(Tag to push),填写$ {JOB_NAME}-${deploy}-${BUILD_TIMESTAMP}-${BUILD_NUMBER};
- 选择创建新标签(Create new tag)。
配置文件 staus.conf 及 server.conf 的 Jenkins 任务创建过程仅与 nginx.conf 在构建的参数配置和 shell 脚本上略有变化,此处就不一一详细举例了。
Jenkins 拥有诸多功能强大的插件,使其可以完成各种部署及发布的操作需求。例如,可以通过 jQuery 插件对 Jenkins 的操作界面进行自定义修改,增加根据选择项动态实现参数选项的显示和隐藏,或者增加自定义按钮实现配置预览等功能,此处就不再进行深入探讨了。结合 GitLab、Ansible 及 Jenkins 等开源软件,用户可以根据实际需求,不断优化并打造符合自身需求的 Nginx 配置管理工具。
smtp_server