使用Nginx1.9.9+Keepalived1.2.x搭建高可用负载均衡集群

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
应用型负载均衡 ALB,每月750个小时 15LCU
简介:

一 简介以及原理介绍

(1)Nginx概念介绍:

Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行。由俄罗斯的程序设计师Igor Sysoev所开发。其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等

(2)Keepalived概念介绍:

Keepalived的作用是检测服务器的状态,如果有一台web服务器死机,或工作出现故障,Keepalived将检测到,并将有故障的服务器从系统中剔除,同时使用其他服务器代替该服务器的工作,当服务器工作正常后Keepalived自动将服务器加入到服务器群中,这些工作全部自动完成,不需要人工干涉,需要人工做的只是修复故障的服务器

(3)下面实例的简单网络拓扑图:

wKiom1gEL7yBrD3FAACSP2q2wtw165.png

几个重要概念:

  • 反向代理:指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器

  • 负载均衡:将来自互联网的大量请求通过某种算法分配到多台应用服务器上执行,以此增加应用吞吐量、加强网络数据处理能力以及提高网络的灵活性和可用性

(4)原理介绍:

使用Nginx搭建高可用负载均衡集群,顾名思义就是使用Nginx充当反向代理服务器,将来自互联网的大量请求通过某种hash策略均匀地分配到内网中的多个应用节点。当某个应用节点因为某种原因变得不可用时,Nginx将会自动将该节点从这个服务的节点列表中剔除,直到该节点恢复正常

从上面的我画的这个网络拓扑图中可以看出,这里的具体业务就是一个H5应用了。至于这个应用是用Java来写的还是用PHP或者其他语言来写的都不是我们这里关心的重点,重点是我们知道这个H5应用分别运行在192.168.100.104和192.168.100.105的9080端口、192.168.100.106和192.168.100.107的9280端口就足够了

那么,从用户点击一条链接到返回对应视图到用户的浏览器,这个整个流程是如何通过Nginx来实现的呢?

这里,我就以用户点击H5应用的“首页”来简单叙述整个过程:

  1. 访问“首页”,如:http://h5.zifangsky.cn/index.html

  2. 通过DNS解析,h5.zifangsky.cn个域名被解析到整个应用集群的外网IP:xx.xx.xx.xx,也就是说在这一步请求的是:http://xx.xx.xx.xx/index.html

  3. 当然,这里的外网IP并没有解析到某一个具体的服务器,而是连接的路由器,因此接下来通过路由器的端口映射,这个请求就被指向192.168.100.100这个Keepalived生成的虚拟IP。因此这时相当于请求的是:http://192.168.100.100/index.html

  4. 这个虚拟IP对应于某个真实的Nginx服务器的IP(PS:关于Keepalived生成一个虚拟IP的作用以及如何对应到一台具体的服务器我会在下面再说),如:192.168.100.104,因此这时相当于请求的是:http://192.168.100.104/index.html

  5. 到了这一步,nginx将通过请求的域名、端口号以及URI地址等因素决定将该请求分配给哪个upstream(某个应用的节点列表),当然这里的节点列表包括104-107四个节点。此时,来自互联网的这个请求才通过nginx的某种hash策略被分配到一个具体的应用服务器,如:http://192.168.100.107:9280/h5/index.html

  6. 应用服务器执行完请求之后将结果原路返回给用户

  7. 用户浏览器根据返回数据包展示页面。到此,此次请求流程结束

看到这里,很多童鞋可能会有疑问:为什么在上面的网络拓扑图中安装了2个nginx,一个nginx不是也可以达到我们所需要的效果吗?为什么要用keepalived产生一个虚拟IP?为什么还要多此一举通过那个虚拟IP转一次?

其实原因很简单,那就是如果只用一台服务器装nginx来做反向代理服务器,那么这个集群的所有请求都将会通过这个nginx来转发,如果某一天这台装nginx的服务器突然宕机了,那将导致所有服务都没法使用了,直到nginx恢复正常工作

因此,为了避免这个问题的产生,一般需要安装两个nginx来做反向代理服务器。同时这两个nginx之间是一种主-备份策略的模式,也就是说当某一个nginx挂掉的时候,外网的所有请求可以自动转发到另一台nginx上去。那么怎么来实现这个目的呢?

这时,keepalived就派上用场了,keepalived的作用是:可以将多个IP绑定在一起,生成一个虚拟IP。其中,当有多个IP节点存在时,可以设置一个主节点以及多个备份节点。当keepalived启动时,将会在主节点上生成我们在配置文件中配置的虚拟IP,如果某一时刻keepalived检测到主节点不可用(PS:通过检测IP是否可以ping通、某个预先配置的端口是否打开等手段来检测)时,将会自动把该虚拟IP切换到其他备份节点上,至于切换到哪个备份节点是根据每个备份节点的权重来决定的(PS:可以在每个节点的keepalived配置文件中配置权重等参数)。因为来自外网的请求一直请求的是虚拟IP,因此只要不是所有的nginx节点同时挂掉,整个应用服务都是可用的,这样就保证了应用的高可用性

 

关于原理的介绍我就暂时说到这里了,下面我将以具体的实例来简单介绍具体应该如何安装和搭建

注:以上概念部分参考至百度百科

二 nginx安装和配置

1 关闭SELinux:

查看SELinux的状态:

1
[root@nmp01 src] # getenforce

如果是开启状态,则

1
[root@nmp01 src] # vim /etc/selinux/config

修改如下:

1
2
3
#SELINUX=enforcing #注释掉
#SELINUXTYPE=targeted #注释掉
SELINUX=disabled #增加

重启系统:

1
[root@nmp01 src] # reboot

2 安装编译工具:

1
2
3
4
[root@nmp01 src] # yum update
[root@nmp01 src] # yum install wget make gcc gcc-c++ zlib-devel openssl openssl-devel pcre-devel gd kernel keyutils patch perl mhash
 
[root@nmp01 src] # yum install popt-devel -y

3 系统约定:

软件源代码包存放位置:/usr/local/src
源码包编译安装位置:/usr/local/软件名字

默认系统就会加载/dev/shm ,它就是所谓的tmpfs,有人说跟ramdisk(虚拟磁盘),但不一样。象虚拟磁盘一样,tmpfs 可以使用您的
RAM,但它也可以使用您的交换分区来存储。而且传统的虚拟磁盘是个块设备,并需要一个 mkfs 之类的命令才能真正地使用它,tmpfs
是一个文件系统,而不是块设备;您只是安装它,它就可以使用了。
tmpfs有以下优势:
1)动态文件系统的大小,
2)tmpfs 的另一个主要的好处是它闪电般的速度。因为典型的 tmpfs 文件系统会完全驻留在 RAM 中,读写几乎可以是瞬间的。
3)tmpfs 数据在重新启动之后不会保留,因为虚拟内存本质上就是易失的。所以有必要做一些脚本做诸如加载,绑定的操作。
首先在/dev/shm建个tmp文件夹,然后与实际/tmp绑定

1
2
3
4
[root@nmp01 src] # mkdir /dev/shm/tmp
 
[root@nmp01 src] # chmod 777 /dev/shm/tmp
[root@nmp01 src] # mount --bind /dev/shm/tmp /tmp

4 下载软件:

1
[root@nmp01 src] # cd /usr/local/src

(1)下载nginx:

1
[root@nmp01 src] # wget http://nginx.org/download/nginx-1.9.9.tar.gz

(2)下载pcre (支持nginx伪静态):

1
[root@nmp01 src] # wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.36.tar.gz

(3)下载ngx_cache_purge(清除指定URL缓存):

1
[root@nmp01 src] # wget http://labs.frickle.com/files/ngx_cache_purge-2.0.tar.gz

(4)下载keepalived:

1
[root@nmp01 src] # wget http://www.keepalived.org/software/keepalived-1.2.2.tar.gz

注:由于这些软件包是我当初下载安装的,因此其他童鞋现在安装时可以考虑下载新版安装包

5 添加www用户以及www用户组:

1
2
[root@nmp01 src] # groupadd www #添加www组
[root@nmp01 src] # useradd -g www www -s /bin/false

6 安装pcre:

1
2
3
4
5
[root@nmp01 src] # tar zxvf pcre-8.36.tar.gz
[root@nmp01 src] # cd /usr/local/src/pcre-8.38
[root@nmp01 pcre-8.38] # ./configure
[root@nmp01 pcre-8.38] # make
[root@nmp01 pcre-8.38] # make install

7 安装nginx:

(1)解压缩:

1
2
3
[root@nmp01 src] # tar -zxvf ngx_cache_purge-2.0.tar.gz
[root@nmp01 src] # tar -zxvf nginx-1.9.9.tar.gz
[root@nmp01 src] # cd nginx-1.9.9

(2)编译安装:

1
2
3
4
[root@nmp01 nginx-1.9.9] # ./configure --prefix=/usr/local/nginx --user=www --group=www --with-http_stub_status_module --with-http_ssl_module --with-openssl=/usr/local/ssl --with-pcre=/usr/local/src/pcre-8.38 --addmodule=/usr/local/src/ngx_cache_purge-2.3 --with-http_gzip_static_module
 
[root@nmp01 nginx-1.9.9] # make && make install
[root@nmp01 nginx-1.9.9] # chown www.www -R /usr/local/nginx/html #设置目录所有者

注:如果不需要https证书的话,可以把“–with-http_ssl_module –with-openssl=/usr/local/ssl”删掉,否则需要安装openssl


附:openssl安装:

i)下载地址:

https://www.openssl.org/source/openssl-1.0.2h.tar.gz

ii)安装:

1
2
3
4
[root@nmp01 src] # tar zxvf ./openssl-1.0.2h.tar.gz
[root@nmp01 src] # cd ./openssl-1.0.2h
[root@nmp01 src] # ./config enable-tlsext
[root@nmp01 src] # make && make install


注:

  • 编译时添加的“enable-tlsext”是为了提供对多个https证书的支持

  • 如果nginx在添加openssl之后编译出错的话,可以参考下我以前写过的这篇文章:https://www.zifangsky.cn/519.html

(3)添加nginx启动脚本:

i)如果是redhat7.x或centos7.x的话,使用以下脚本:

1
[root@prx03 init.d] # vim /usr/lib/systemd/system/nginx.service

内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Unit]
Description=nginx - high performance web server
Documentation=http: //nginx .org /en/docs/
After=network.target remote-fs.target nss-lookup.target
[Service]
Type=forking
PIDFile= /usr/local/nginx/logs/nginx .pid
ExecStartPre= /usr/local/nginx/sbin/nginx  -t -c  /usr/local/nginx/conf/nginx .conf
ExecStart= /usr/local/nginx/sbin/nginx  -c  /usr/local/nginx/conf/nginx .conf
ExecReload= /bin/kill  -s HUP $MAINPID
ExecStop= /bin/kill  -s QUIT $MAINPID
PrivateTmp= true
[Install]
WantedBy=multi-user.target

给脚本添加执行权限:

1
[root@prx03 init.d] # chmod a+x /usr/lib/systemd/system/nginx.service

ii)如果是CentOS6.x或者redhat6.x的话使用下面这个脚本:

1
[root@app01 ~] # vim /etc/init.d/nginx

内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#!/bin/sh
#
# nginx - this script starts and stops the nginx daemon
#
# chkconfig:   - 85 15
# description: Nginx is an HTTP(S) server, HTTP(S) reverse \
#               proxy and IMAP/POP3 proxy server
# processname: nginx
# config:      /etc/nginx/nginx.conf
# config:      /etc/sysconfig/nginx
# pidfile:     /var/run/nginx.pid
# Source function library.
/etc/rc .d /init .d /functions
# Source networking configuration.
/etc/sysconfig/network
# Check that networking is up.
"$NETWORKING"  "no"  ] &&  exit  0
nginx= "/usr/local/nginx/sbin/nginx"
prog=$( basename  $nginx)
NGINX_CONF_FILE= "/usr/local/nginx/conf/nginx.conf"
[ -f  /etc/sysconfig/nginx  ] && .  /etc/sysconfig/nginx
lockfile= /var/lock/subsys/nginx
start() {
     [ -x $nginx ] ||  exit  5
     [ -f $NGINX_CONF_FILE ] ||  exit  6
     echo  -n $ "Starting $prog: "
     daemon $nginx -c $NGINX_CONF_FILE
     retval=$?
     echo
     [ $retval - eq  0 ] &&  touch  $lockfile
     return  $retval
}
stop() {
     echo  -n $ "Stopping $prog: "
     killproc $prog -QUIT
     retval=$?
     echo
     [ $retval - eq  0 ] &&  rm  -f $lockfile
     return  $retval
killall -9 nginx
}
restart() {
     configtest ||  return  $?
     stop
     sleep  1
     start
}
reload() {
     configtest ||  return  $?
     echo  -n $ "Reloading $prog: "
     killproc $nginx -HUP
RETVAL=$?
     echo
}
force_reload() {
     restart
}
configtest() {
$nginx -t -c $NGINX_CONF_FILE
}
rh_status() {
     status $prog
}
rh_status_q() {
     rh_status > /dev/null  2>&1
}
case  "$1"  in
     start)
         rh_status_q &&  exit  0
     $1
         ;;
     stop)
         rh_status_q ||  exit  0
         $1
         ;;
     restart|configtest)
         $1
         ;;
     reload)
         rh_status_q ||  exit  7
         $1
         ;;
     force-reload)
         force_reload
         ;;
     status)
         rh_status
         ;;
     condrestart|try-restart)
         rh_status_q ||  exit  0
             ;;
     *)    
       echo  $ "Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload|configtest}"  
         exit  2
esac

同样给脚本添加可执行权限:

1
[root@app01 ~] # chmod a+x /etc/init.d/nginx

关闭80端口进程:

1
2
[root@app01 init.d] # fuser -k 80/tcp
[root@app01 init.d] # systemctl daemon-reload

将nginx添加到开机自启:

1
[root@app01 ~] # chkconfig nginx on

(4)启动nginx:

1
[root@app01 ~] # service nginx start

如果nginx能够顺利启动的话,那么nginx的安装就完成了,剩下就是nginx的配置了

8 配置和优化nginx:

(1)关闭nginx:

1
[root@app01 ~] # service nginx stop

(2)新建目录vhost:

1
2
[root@app01 ~] # cd /usr/local/nginx/conf
[root@app01 conf] # mkdir vhost

注:nginx可以监听来自多个域名或者同一域名不同端口的多种请求,对这些请求进行反向代理都将在这个目录中配置

(3)配置nginx:

添加以下4个文件(在/usr/local/nginx/conf目录下),如果原文件存在则覆盖

i)gzip.conf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#网页GZIP压缩设置
#2012.4.2
  #可通过http://tool.chinaz.com/Gzips/检测压缩情况
#
  #启动预压缩功能,对所有类型的文件都有效
gzip_static on;    #开启nginx_static后,对于任何文件都会先查找是否有对应的gz文件
 
#找不到预压缩文件,进行动态压缩
gzip on;
  gzip_min_length   1k;  #设置最小的压缩值,单位为bytes.超过设置的min_length的值会进行压缩,小于的不压缩.
gzip_comp_level   3;   #压缩等级设置,1-9,1是最小压缩,速度也是最快的;9刚好相反,最大的压缩,速度是最慢的,消耗的CPU资源也多
gzip_buffers      16 64k;   #设置系统的缓存大小,以存储GZIP压缩结果的数据流,它可以避免nginx频烦向系统申请压缩空间大小
gzip_types text/plain application/x-javascript text/css text/javascript;
 
  #关于gzip_types,如果你想让图片也开启gzip压缩,那么用以下这段吧:
#gzip_types text/plain application/x-javascript text/css text/javascript application/x-httpd-php image/jpeg image/gif image/png;
 
  #gzip公共配置
gzip_http_version 1.1;      #识别http的协议版本(1.0/1.1)
  gzip_proxied      any;      #设置使用代理时是否进行压缩,默认是off的
gzip_vary         on;       #和http头有关系,加个vary头,代理判断是否需要压缩
gzip_disable "MSIE [1-6]."; #禁用IE6的gzip压缩

这个文件是配置gzip压缩的

ii)proxy.conf:

1
2
3
4
5
6
7
8
9
10
11
12
proxy_temp_path   /tmp/proxy_temp;
  proxy_cache_path  /tmp/proxy_cache levels=1:2 keys_zone=cache_one:500m inactive=1d max_size=3g;
  client_body_buffer_size  512k;     #原为512k
  proxy_connect_timeout    50;       #代理连接超时
proxy_read_timeout       600;      #代理发送超时
proxy_send_timeout       600;      #代理接收超时
proxy_buffer_size        128k;     #代理缓冲大小,原为32k
  proxy_buffers           16 256k;   #代理缓冲,原为4 64k
  proxy_busy_buffers_size 512k;      #高负荷下缓冲大小,原为128k
  proxy_temp_file_write_size 1024m;  #proxy缓存临时文件的大小原为128k
  #proxy_ignore_client_abort  on;    #不允许代理端主动关闭连接
proxy_next_upstream error timeout invalid_header http_500 http_503 http_404 http_502 http_504;

这个文件是关于代理的一些配置

iii)nginx.conf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
user  www www;
  worker_processes  32;   # 工作进程数,为CPU的核心数或者两倍
  error_log   logs/error.log  crit; # debug|info|notice|warn|error|crit
  pid        logs/nginx.pid;
 
  events {
     use epoll;                            #Linux最常用支持大并发的事件触发机制
     worker_connections  65535;
  }
 
  http {
      include       mime.types;             #设定mime类型,类型由mime.type文件定义
      default_type  application/octet-stream;
 
     log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                       '$status $body_bytes_sent "$http_referer" '
                       '"$http_user_agent" "$http_x_forwarded_for"';
     log_format zifangsky_log '$remote_addr – $remote_user [$time_local] "$request" '
                       '$status $body_bytes_sent "$http_referer" '
                       '"$http_user_agent" "$http_x_forwarded_for" '
                       '"$upstream_addr" "$upstream_cache_status" "$upstream_status" "$upstream_response_time" "$cookie_jsessionid"';
 
     log_format h5_log '$remote_addr – $remote_user [$time_local] "$request" '
                       '$status $body_bytes_sent "$http_referer" '
                       '"$http_user_agent" "$http_x_forwarded_for" "$proxy_add_x_forwarded_for"';
 
      access_log  logs/access.log  main;