1、Nginx高可用集群模式

再介绍Nginx高可用之前,先介绍一下实现高可用的软件:keepalived,我们需要借助keepalived实现Nginx的高可用。

1.1 高可用keepalived

1.1.1 keepalived简介

Keepalived软件起初是专为LVS负载均衡软件设计的,用来管理并监控LVS集群系统中各个服务节点的状态,后来又加入了可以实现高可用的VRRP功能。因此,Keepalived除了能够管理LVS软件外,还可以作为其他服务(例如:Nginx、Haproxy、MySQL等)的高可用解决方案软件。
Keepalived软件主要是通过**VRRP**协议实现高可用功能的。VRRP是Virtual Router RedundancyProtocol(虚拟路由器冗余协议)的缩写,VRRP出现的目的就是为了解决静态路由单点故障问题的,它能够保证当个别节点宕机时,整个网络可以不间断地运行。
keepalived是如何基于VRRP协议实现高可用的呢?

  • 首先集群通过VRRP协议的竞选机制确定节点的主备身份,工作时,主节点优先获取所有的资源,备用节点处于等待状态;
  • 在keepalived服务正常工作时,主Master节点会不断地向备节点通过多播的方式发送心跳消息,通知备节点自己还活着;
  • 当主Master节点发生故障时就无法发送心跳消息,备节点也就无法继续检测到来自主Master节点的心跳了,此时备节点会调用自身的接管程序,接管主Master节点的资源及服务(切换时间最快小于1s,但切换的1s的时间内服务不可用?);
  • 当主Master节点恢复时,备节点又会释放掉自己接管的主节点的资源及服务,恢复到备用身份。

生产环境使用Keepalived正常运行,共启动3个进程,一个是父进程,负责监控其子进程,一个是VRRP子进程,另外一个是Checkers子进程
两个子进程都被系统Watchlog看管,两个子进程各自负责自己的事,Healthcheck子进程检查各自服务器的健康状况
如果Healthcheck进程检查到Master上服务不可用了,就会通知本机上的VRRP子进程,让他删除通告,并且去掉虚拟IP,转换为BACKUP状态。

1.1.2 keepalived.conf

一个完整的keepalived配置文件可以包含三个部分:全局定义块VRRP实例块(包含健康检查脚本配置)以及虚拟服务器定义块。其中全局定义块和虚拟服务器块是必须的,VRRP实例块是可选的,具体细节如下:

  1. # 全局定义模块
  2. ! Configuration File for keepalived
  3. global_defs {
  4. notification_email {
  5. acassen@firewall.loc
  6. failover@firewall.loc
  7. # 邮件报警,可以不设置,后期nagios统一监控
  8. sysadmin@firewall.loc
  9. }
  10. notification_email_from Alexandre.Cassen@firewall.loc
  11. smtp_server 192.168.200.1
  12. smtp_connect_timeout
  13. # router_id为负载均衡标识,在局域网内应该是唯一的。
  14. router_id LVS_DEVEL
  15. vrrp_skip_check_adv_addr
  16. vrrp_strict # 严格遵守VRRP协议
  17. vrrp_garp_interval
  18. vrrp_gna_interval
  19. }
  20. # 健康检查脚本配置
  21. vrrp_script chk_nginx {
  22. # 心跳检测脚本路径
  23. script "/etc/keepalived/check_nginx.sh"
  24. # 脚本执行间隔,每2s检测一次
  25. interval 2
  26. # 脚本结果导致的优先级变更,检测失败(脚本返回非0)则优先级 -20
  27. weight -20
  28. # 检测连续3次失败才算确定是真失败。会用weight减少优先级(1-255之间)
  29. fall 3
  30. # 检测2次成功就算成功。但不修改优先级
  31. rise 2
  32. }
  33. # VRRP实例定义块
  34. vrrp_instance VI_1 {
  35. # 表示状态是MASTER主机还是备用机BACKUP
  36. state MASTER
  37. # 该实例绑定的网卡名称
  38. interface ens33
  39. # 保证主备节点的这个配置项的值一致即可
  40. virtual_router_id 51
  41. # 权重,master权重一般高于backup,如果有多个,那就是选举,谁的权重高,谁就当选
  42. priority 100
  43. # 主备之间同步检查时间间隔,单位秒
  44. advert_int 2
  45. # 认证权限密码,防止非法节点进入
  46. authentication {
  47. auth_type PASS
  48. auth_pass 1111
  49. }
  50. # 虚拟出来的ip,可以有多个(vip)
  51. virtual_ipaddress {
  52. 192.168.1.108
  53. }
  54. }
  55. #虚拟服务器定义块
  56. #虚拟IP,来源与上面的虚拟IP地址,后面加空格加端口号
  57. virtual_server 192.168.200.100 443 {
  58. # 健康检查间隔,单位为秒
  59. delay_loop 6
  60. # 负载均衡调度算法,一般用wrr、rr、wlc
  61. lb_algo rr
  62. # 负载均衡转发规则,一般包括DR,NAT,TUN 3种
  63. lb_kind NAT
  64. # 会话保持时间,会话保持,就是把用户请求转发给同一个服务器,不然刚在1上提交完帐号密码,就跳转到另一台服务器2上了。
  65. persistence_timeout 50
  66. # 转发协议,有TCP和UDP两种,一般用TCP
  67. protocol TCP
  68. # 真实服务器,包括IP和端口号
  69. real_server {
  70. # 权重,数值越大,权重越高
  71. weight 1
  72. # 连接超时时间
  73. connect_timeout 3
  74. # 重连次数
  75. nb_get_retry 3
  76. delay_before_retry
  77. }
  78. }

上面繁杂的配置项中,有以下几个是我们这次试验要修改适配的:

  • 全局块:
    • router_id LVS_DEVEL:同一个keepalived集群中的节点上该配置相同,且在局域网应是全局唯一的;
  • VRRP实例块:
    • state MASTER:状态只有MASTER和BACKUP两种,表明当前keepalived节点是主节点还是备节点;
    • interface ens33:绑定的网卡名称;
    • virtual_router_id 51:负载均衡标识,局域网中相同virtual_router_id的keepalived节点组成了一套高可用集群,virtual_router_id在局域网内应该是唯一的;
    • priority 100:优先级,Master的priority必须必Backup的priority高;
    • virtual_ipaddress:keepalived集群对外暴露的虚Ip。

      1.2 Nginx高可用试验

      1.2.1 实现效果

      Nginx高可用示意图.png
      如上图所示,一台节点上同时部署keepalived和Nginx,由这样的三台节点组成一个高可用集群,三台keepalived节点向客户端暴露一个共同的虚Ip;每一台Nginx都反向代理到三台Tomcat节点,Tomcat集群模拟后端服务集群。要达到的效果是:

      1.2.2 准备工作

      (1)拉取centos镜像
      docker pull centos:7
      
      (2)创建centos7容器
      docker run -it -d --name centos1 centos:7
      
      (3)进入容器centos1,并安装常用工具 ```bash docker exec -it centos1 bash

yum update yum install -y vim yum install -y wget yum install -y gcc-c++ yum install -y pcre pcre-devel yum install -y zlib zlib-devel yum install -y openssl openssl—devel yum install -y popt-devel yum install -y initscripts yum install -y net-tools

**(4)将容器centos1打包成镜像,以后可以使用该镜像生成比较完善的centos容器**
```bash
docker commit -a 'zj' -m 'centos7 with common tools' 5ed60794e0e4

其中5ed60794e0e4是容器centos7的ID。
(5)将打包好的镜像上传到阿里云镜像仓库中

# 登录阿里云镜像仓库
sudo docker login --username=158****4705 registry.cn-chengdu.aliyuncs.com

# 将生成好的centos镜像打标签
sudo docker tag 42840af0d2a4 registry.cn-chengdu.aliyuncs.com/docker_study_jerry/mycentos7:1.0

# 将打过标签的镜像上传到阿里云镜像仓库
sudo docker push registry.cn-chengdu.aliyuncs.com/docker_study_jerry/mycentos7:1.0

上传好的镜像在阿里云的镜像仓库中如下:
image.png

(6)删除之前创建的容器centos1,以上面创建的新镜像创建容器

docker rm -f centos1

# 容器内需要使用systemctl服务,需要加上/usr/sbin/init
docker run -it --name centos_temp -d --privileged 42840af0d2a4 /usr/sbin/init

docker exec -it centos_temp bash

(7)安装Nginx

# 使用yum安装nginx,需要包括Nginx的库
rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm

# yum install 安装nginx
yum install -y nginx

# 启动nginx
systemctl start nginx.service

# 查看是否启动成功,出现nginx欢迎界面表示安装成功
curl localhost:80

Nginx启动成功如图所示:
image.png
(8)安装keepalived

# 下载keepalived 
wget http://www.keepalived.org/software/keepalived-1.2.18.tar.gz

# 解压安装
tar -zxvf keepalived-1.2.18.tar.gz -C /usr/local/

# 下载插件openssl
yum install -y openssl openssl-devel

# 编译keepalivedcd
cd /usr/local/keepalived-1.2.18/ && ./configure --prefix=/usr/local/keepalived

# make
make && make install

(9)将keepalived安装成系统服务

mkdir /etc/keepalived

cp /usr/local/keepalived/etc/keepalived/keepalived.conf /etc/keepalived/

cp /usr/local/keepalived/etc/rc.d/init.d/keepalived /etc/init.d/

cp /usr/local/keepalived/etc/sysconfig/keepalived /etc/sysconfig/

chkconfig keepalived on

(10)修改keepalived.conf文件

# 备份一下keepalived.conf
cp /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.backup

# 修改keepalived.conf,关键配置如下:
vrrp_script chk_nginx {
  script "/etc/keepalived/nginx_check.sh"
  interval 2
  weight -20
}

vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.200.16
    }
}

我们将虚拟IP设置成192.168.200.16(后来发现需要设置成docker0一个网段的ip:172.17.0.100)
(11)添加keepalived心跳检测脚本

# 新建脚本
touch nginx_check.sh

# 添加以下内容
#!/bin/bash
A=`ps -C nginx –no-header |wc -l`
if [ $A -eq 0 ];then
  /usr/local/nginx/sbin/nginx
  sleep 2
  if [ `ps -C nginx --no-header |wc -l` -eq 0 ];then
    killall keepalived
  fi
fi

# 给nginx_check.sh脚本赋予执行权限
chmod +x nginx_check.sh

(12)将keepalived设置成开机启动

systemctl enable keepalived.service

# 开启keepalived
systemctl start keepalived.service

如果启动失败,执行如下命令:

cd /usr/sbin/
rm -f keepalived 
cp /usr/local/keepalived/sbin/keepalived /usr/sbin/

keepalived服务相关命令:

# 重新加载
systemctl daemon-reload 

# 设置开机自动启动
systemctl enable keepalived.service 

# 取消开机自动启动
systemctl disable keepalived.service 

# 启动
systemctl start keepalived.service

# 停止
systemctl stop keepalived.service

# 查看服务状态
systemctl status keepalived.service

13)验证虚拟IP是否成功,keepalived与nginx是否连通

curl 192.168.200.16

出现如下nginx欢迎页面,说明成功:
image.png
(14)将上面安装了nginx、keepalived以及一些基本工具的centos镜像再次打包成一个新的镜像,之后要用这个镜像生成主备节点的容器

# 生成镜像
docker commit -a 'zj' -m 'centos7 with common tools nginx keepalived' 9e7bc142e2f1

# 打tag
sudo docker tag 4af9e6bf2a5a registry.cn-chengdu.aliyuncs.com/docker_study_jerry/mycentos7:2.0

# push到阿里云镜像仓库
sudo docker push registry.cn-chengdu.aliyuncs.com/docker_study_jerry/mycentos7:2.0

(15)删除之前的容器centos_temp

docker rm -f 9e7bc142e2f1

1.2.3 热备试验修改配置文件

我们准备配置一个主节点centos(nginx + keepalived),2个从节点,试验过程我们验证以下几个点:

  • 访问虚IP 192.168.200.16,请求会先转发给Master节点;
  • 停止Master节点的centos容器,再次访问虚IP 192.168.200.16(后来发现需要设置成docker0一个网段的ip:172.17.0.100),请求会转发给Slave节点;
  • 重启Master节点的centos容器,访问访问虚IP 192.168.200.16,请求会重新转发给Master节点。

(1)用上面创建好的nginx + keepalived镜像创建Master节点

docker run --privileged -tid --name centos_master --restart=always 4af9e6bf2a5a /usr/sbin/init

docker exec -it centos_master bash

(2)修改centos_master节点的nginx的欢迎页,并配置centos_master的keepalived.conf

    vim /usr/share/nginx/html/index.html

image.png

vim /etc/keepalived/keepalived.conf

image.png
(3)重新加载keepalived

systemctl daemon-reload
systemctl restart keepalived.service

(4)创建两个slave节点的centos容器,修改nginx首页和keepalived.conf,并重新加载keepalived
image.png
image.png

1.2.4 验证工作

验证过程中开始时没有成功,排查了下原因,还需要做以下动作:

  • 实验开始时发现主备节点的nginx进程没有起来,需要手动启动nginx进程: ```bash

    查看nginx进程是否启动

    ps -ef | grep nginx

手动启动nginx进程

systemctl start nginx

如果nginx进程在,则输入`ps -ef | grep nginx`命令时结果如下:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1536187/1616169217277-5d9c61bf-7b32-4f9a-8a8e-13b11f9648c2.png#height=120&id=sEa5s&margin=%5Bobject%20Object%5D&name=image.png&originHeight=120&originWidth=1023&originalType=binary&size=14853&status=done&style=none&width=1023)

- 开始时在宿主机curl 192.168.200.16发现网不通,在宿主机输入`ip addr`,发现docker0网桥的ip为:172.17.0.1,如下:

![image.png](https://cdn.nlark.com/yuque/0/2021/png/1536187/1616167654898-0411a7bf-9e09-402b-9744-36c95928719e.png#height=403&id=xXDt3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=403&originWidth=1022&originalType=binary&size=58658&status=done&style=none&width=1022)<br />由于上面设置的虚ip地址为192.168.200.16,与docker0网桥都不在一个网段,自然ping不通,因此我们要将虚IP设置成跟docker0网关的ip是一个网段的,这里我们将虚IP重新改成:172.17.0.100。
> 有关docker网络的基本介绍请参考:[https://www.yuque.com/docs/share/d9d0ff52-2917-45e2-b1c3-46adb6bb083f?#](https://www.yuque.com/docs/share/d9d0ff52-2917-45e2-b1c3-46adb6bb083f?#) 《狂神Docker学习笔记》,第9节。


**(1)验证宿主机curl虚Ip,请求是否能转给Master的centos节点**
```bash
# 在宿主机上curl
curl 172.17.0.100

结果成功请求到Master的centos节点,如下:
image.png
(2)停止Master的centos容器,验证请求是否能转发到从节点的centos容器

# 停止主节点的centos
docker stop centos_master

此时再在宿主机上curl虚ip,看到请求可以发送给备节点的centos上,如下图所示:
image.png
如果没有成功,检查centos容器里的nginx进程是否还在。
(3)重启Master的centos容器,验证请求是否能重新转发到Master节点上

# 重启Master容器
docker start centos_master

# 进入容器
docker exec -it centos_master bash

# 启动nginx进程
systemctl start nginx

结果是请求又重新转发到Master节点上,说明Master节点恢复后,keepalived会将从节点退出之前占有的主节点的资源,让主节点重新获取资源,结果如下图所示:
image.png
如果启动nginx进程出现一直卡着没反应的情况,输入以下命令:

systemctl status nginx.service

2、Nginx原理浅析

2.1 Nginx的进程模型

Nginx启动后,会有一个Master进程和多个Worker进程,Worker进程的数目是在nginx.conf配置中的全局块里的worker process配置项设置的,默认是1,查看Ninx相关进程,如下图所示:
image.png
Nginx的进程模型如下图所示:

10、高可用集群 - 图13

2.1.1 Master进程

Master进程只有一个,Master进程充当整个进程组与用户的交互接口,请求先过Master进程,再到下面的Worker进程。Master进程不负责处理网络事件,而是交给下面的Worker进程来处理,Master进程来管理这些worker进程,Master进程有以下5个功能:

  • 接收来自外界的请求;
  • 向各Worker进程发送请求信号;
  • 监控Worker进程的运行状态;
  • 当Worker进程在异常情况下退出后,Master进程来重启新的Worker进程;
  • 进行Nginx配置的热加载。

    2.1.2 Worker进程

    基本的网络事件(请求)是放在Worker进程中处理的,每个Worker进程之间是平等且独立的,一个请求只能在一个Worker进程中处理,一个Worker进程不能处理其他进程的请求。Worker进程的数目一般设置为服务器CPU核数一致。一个Worker进程可以处理多个请求,一个Worker进程可以处理的请求数目是在nginx.conf中的Event块的worker_connections配置项设置的,默认为512。
    一个请求仅由一个Worker进程处理,采用这种方式的好处是?

  • 节省锁带来的开销。独立的进程意味着不需要加锁,省掉了加锁带来的开销,同时在定位问题看日志也会方便;

  • 高可用。采用独立的进程处理请求,可以让不同的Worker进程间互不影响,一个Worker进程因为异常退出后其他Worker进程还在工作,服务不会终止,且Master进程会很快重启新的Worker进程;
  • 无需进程切换。一个请求仅有一个进程处理,不需要进行进程间的切换,也是节省了开销。

    2.2 Nginx处理请求的流程

    Nginx的IO使用的是epoll,epoll使用了I/O复用模型,与I/O阻塞模型比较,I/O复用模型的优势在于可以同时等待多个(而不只是一个)套接字描述符就绪(这就触碰到知识盲区了,没研究过Socket),Nginx的epoll工作流程如下:
  1. Master进程接收到信号(nginx -s reload)后,读取nginx.conf配置文件,建立需要listen的socket后,根据nginx.conf中的worker process配置fork出多个Worker进程,这样每个Worker进程都可以去accept这个socket;
  2. 当一个client链接到来时,所有accept的Worker进程都会收到通知,但只有一个Worker进程可以accept成功,其他的则会accept失败。Nginx提供了一把进程间的共享锁accept_mutex来保证同一时刻只有一个Worker进程在accept链接,从而解决了惊群问题;
  3. 当一个占有共享锁的Workeraccept这个链接后,就开始读取请求、解析请求、处理请求、获取数据并返回给客户端,最后断开连接,这样一个完整的请求就结束了。

    2.3 IO多路复用 + 异步非阻塞

    一个worker进程可以同时处理多个请求,每个worker进程只有一个主线程,而是采用异步非阻塞的方式来处理并发请求。比如同时有多个http request的时候,worker主线程与第一条request建议连接将其处理转发给下游fast cgi后,并不会挂起等待,而是立马处理下一条,可以理解轮询处理。与多线程相比,这种事件处理方式是有很大的优势的,不需要创建线程,每个请求占用的内存也很少,没有上下文切换,事件处理非常的轻量级。并发数再多也不会导致无谓的资源浪费(上下文切换),更多的并发数,只是会占用更多的内存而已。因此nginx 是非常适合处理高并发请求的。