CentOS 7.2 编译安装 Nginx1.12.2

环境说明

1、系统:CentOS最小化安装;升级软件补丁,内核和系统版本不升级;关闭SELinux和防火墙。系统安装可参考CentOS 7系统部署

2、软件:安装包统一放置在/usr/src目录下,安装位置统一在/usr/local目录下。

开始安装

1、安装Nginx所需pcre库,官方网站(www.pcre.org)。安装pcre库是为了使Nginx支持具备URL重写功能的Rewite模块,如不安装则Nginx无法使用此模块功能(典型功能为伪静态)。

[root@localhost ~]# yum -y install pcre-devel
Loaded plugins: fastestmirror
base                                                                                                                                                                                    | 3.6 kB  00:00:00     
extras                                                                                                                                                                                  | 3.4 kB  00:00:00     
updates                                                                                                                                                                                 | 3.4 kB  00:00:00     
Loading mirror speeds from cached hostfile
 * base: mirrors.aliyun.com
 * extras: mirrors.aliyun.com
 * updates: mirrors.aliyun.com
Resolving Dependencies
--> Running transaction check
---> Package pcre-devel.x86_64 0:8.32-17.el7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

===============================================================================================================================================================================================================
 Package                                             Arch                                            Version                                               Repository                                     Size
===============================================================================================================================================================================================================
Installing:
 pcre-devel                                          x86_64                                          8.32-17.el7                                           base                                          480 k

Transaction Summary
===============================================================================================================================================================================================================
Install  1 Package

Total download size: 480 k
Installed size: 1.4 M
Downloading packages:
pcre-devel-8.32-17.el7.x86_64.rpm                                                                                                                                                       | 480 kB  00:00:00     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : pcre-devel-8.32-17.el7.x86_64                                                                                                                                                               1/1 
  Verifying  : pcre-devel-8.32-17.el7.x86_64                                                                                                                                                               1/1 

Installed:
  pcre-devel.x86_64 0:8.32-17.el7                                                                                                                                                                              

Complete!

2、安装编译需要的组件

[root@localhost ~]# yum -y install zlib-devel openssl-devel
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirrors.aliyun.com
 * extras: mirrors.aliyun.com
 * updates: mirrors.aliyun.com
Resolving Dependencies
--> Running transaction check
---> Package openssl-devel.x86_64 1:1.0.2k-8.el7 will be installed
--> Processing Dependency: krb5-devel(x86-64) for package: 1:openssl-devel-1.0.2k-8.el7.x86_64
---> Package zlib-devel.x86_64 0:1.2.7-17.el7 will be installed
--> Running transaction check
---> Package krb5-devel.x86_64 0:1.15.1-8.el7 will be installed
--> Processing Dependency: libkadm5(x86-64) = 1.15.1-8.el7 for package: krb5-devel-1.15.1-8.el7.x86_64
--> Processing Dependency: libverto-devel for package: krb5-devel-1.15.1-8.el7.x86_64
--> Processing Dependency: libselinux-devel for package: krb5-devel-1.15.1-8.el7.x86_64
--> Processing Dependency: libcom_err-devel for package: krb5-devel-1.15.1-8.el7.x86_64
--> Processing Dependency: keyutils-libs-devel for package: krb5-devel-1.15.1-8.el7.x86_64
--> Running transaction check
---> Package keyutils-libs-devel.x86_64 0:1.5.8-3.el7 will be installed
---> Package libcom_err-devel.x86_64 0:1.42.9-10.el7 will be installed
---> Package libkadm5.x86_64 0:1.15.1-8.el7 will be installed
---> Package libselinux-devel.x86_64 0:2.5-11.el7 will be installed
--> Processing Dependency: libsepol-devel(x86-64) >= 2.5-6 for package: libselinux-devel-2.5-11.el7.x86_64
--> Processing Dependency: pkgconfig(libsepol) for package: libselinux-devel-2.5-11.el7.x86_64
---> Package libverto-devel.x86_64 0:0.2.5-4.el7 will be installed
--> Running transaction check
---> Package libsepol-devel.x86_64 0:2.5-6.el7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

===============================================================================================================================================================================================================
 Package                                                   Arch                                         Version                                               Repository                                  Size
===============================================================================================================================================================================================================
Installing:
 openssl-devel                                             x86_64                                       1:1.0.2k-8.el7                                        base                                       1.5 M
 zlib-devel                                                x86_64                                       1.2.7-17.el7                                          base                                        50 k
Installing for dependencies:
 keyutils-libs-devel                                       x86_64                                       1.5.8-3.el7                                           base                                        37 k
 krb5-devel                                                x86_64                                       1.15.1-8.el7                                          base                                       266 k
 libcom_err-devel                                          x86_64                                       1.42.9-10.el7                                         base                                        31 k
 libkadm5                                                  x86_64                                       1.15.1-8.el7                                          base                                       174 k
 libselinux-devel                                          x86_64                                       2.5-11.el7                                            base                                       186 k
 libsepol-devel                                            x86_64                                       2.5-6.el7                                             base                                        74 k
 libverto-devel                                            x86_64                                       0.2.5-4.el7                                           base                                        12 k

Transaction Summary
===============================================================================================================================================================================================================
Install  2 Packages (+7 Dependent packages)

Total download size: 2.3 M
Installed size: 4.6 M
Downloading packages:
(1/9): keyutils-libs-devel-1.5.8-3.el7.x86_64.rpm                                                                                                                                       |  37 kB  00:00:00     
(2/9): libcom_err-devel-1.42.9-10.el7.x86_64.rpm                                                                                                                                        |  31 kB  00:00:00     
(3/9): libkadm5-1.15.1-8.el7.x86_64.rpm                                                                                                                                                 | 174 kB  00:00:00     
(4/9): krb5-devel-1.15.1-8.el7.x86_64.rpm                                                                                                                                               | 266 kB  00:00:00     
(5/9): libselinux-devel-2.5-11.el7.x86_64.rpm                                                                                                                                           | 186 kB  00:00:00     
(6/9): libsepol-devel-2.5-6.el7.x86_64.rpm                                                                                                                                              |  74 kB  00:00:00     
(7/9): libverto-devel-0.2.5-4.el7.x86_64.rpm                                                                                                                                            |  12 kB  00:00:00     
(8/9): zlib-devel-1.2.7-17.el7.x86_64.rpm                                                                                                                                               |  50 kB  00:00:00     
(9/9): openssl-devel-1.0.2k-8.el7.x86_64.rpm                                                                                                                                            | 1.5 MB  00:00:00     
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Total                                                                                                                                                                          4.2 MB/s | 2.3 MB  00:00:00     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : libkadm5-1.15.1-8.el7.x86_64                                                                                                                                                                1/9 
  Installing : libsepol-devel-2.5-6.el7.x86_64                                                                                                                                                             2/9 
  Installing : libselinux-devel-2.5-11.el7.x86_64                                                                                                                                                          3/9 
  Installing : libcom_err-devel-1.42.9-10.el7.x86_64                                                                                                                                                       4/9 
  Installing : libverto-devel-0.2.5-4.el7.x86_64                                                                                                                                                           5/9 
  Installing : keyutils-libs-devel-1.5.8-3.el7.x86_64                                                                                                                                                      6/9 
  Installing : krb5-devel-1.15.1-8.el7.x86_64                                                                                                                                                              7/9 
  Installing : zlib-devel-1.2.7-17.el7.x86_64                                                                                                                                                              8/9 
  Installing : 1:openssl-devel-1.0.2k-8.el7.x86_64                                                                                                                                                         9/9 
  Verifying  : zlib-devel-1.2.7-17.el7.x86_64                                                                                                                                                              1/9 
  Verifying  : keyutils-libs-devel-1.5.8-3.el7.x86_64                                                                                                                                                      2/9 
  Verifying  : libverto-devel-0.2.5-4.el7.x86_64                                                                                                                                                           3/9 
  Verifying  : libselinux-devel-2.5-11.el7.x86_64                                                                                                                                                          4/9 
  Verifying  : krb5-devel-1.15.1-8.el7.x86_64                                                                                                                                                              5/9 
  Verifying  : libcom_err-devel-1.42.9-10.el7.x86_64                                                                                                                                                       6/9 
  Verifying  : libsepol-devel-2.5-6.el7.x86_64                                                                                                                                                             7/9 
  Verifying  : 1:openssl-devel-1.0.2k-8.el7.x86_64                                                                                                                                                         8/9 
  Verifying  : libkadm5-1.15.1-8.el7.x86_64                                                                                                                                                                9/9 

Installed:
  openssl-devel.x86_64 1:1.0.2k-8.el7                                                                     zlib-devel.x86_64 0:1.2.7-17.el7                                                                    

Dependency Installed:
  keyutils-libs-devel.x86_64 0:1.5.8-3.el7     krb5-devel.x86_64 0:1.15.1-8.el7        libcom_err-devel.x86_64 0:1.42.9-10.el7     libkadm5.x86_64 0:1.15.1-8.el7     libselinux-devel.x86_64 0:2.5-11.el7    
  libsepol-devel.x86_64 0:2.5-6.el7            libverto-devel.x86_64 0:0.2.5-4.el7    

Complete!

3、创建软件配套专用的用户(禁止登录及不创建家目录)

[root@localhost ~]# useradd www -s /sbin/nologin  -M

4、进入/usr/src目录下载并解压软件包

[root@localhost src]# cd /usr/src/
[root@localhost src]# wget http://nginx.org/download/nginx-1.12.2.tar.gz && tar -zxf nginx-1.12.2.tar.gz
--2018-03-02 13:05:56--  http://nginx.org/download/nginx-1.12.2.tar.gz
Resolving nginx.org (nginx.org)... 206.251.255.63, 95.211.80.227, 2606:7100:1:69::3f, ...
Connecting to nginx.org (nginx.org)|206.251.255.63|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 981687 (959K) [application/octet-stream]
Saving to: ‘nginx-1.12.2.tar.gz’

100%[=====================================================================================================================================================================>] 981,687     8.26KB/s   in 2m 10s 

2018-03-02 13:08:07 (7.36 KB/s) - ‘nginx-1.12.2.tar.gz’ saved [981687/981687]

5、进入解压的软件目录并编译配置文件 ./configure –user=www –group=www –prefix=/usr/local/nginx-1.12.2 –with-mail_ssl_module –with-http_stub_status_module –with-http_ssl_module –with-http_v2_module –with-http_gzip_static_module –with-http_sub_module

[root@localhost src]# cd nginx-1.12.2
[root@localhost nginx-1.12.2]# ./configure --user=www --group=www --prefix=/usr/local/nginx-1.12.2 --with-mail_ssl_module --with-http_stub_status_module --with-http_ssl_module --with-http_v2_module --with-http_gzip_static_module --with-http_sub_module
checking for OS
 + Linux 3.10.0-327.el7.x86_64 x86_64
checking for C compiler ... found
 + using GNU C compiler
 + gcc version: 4.8.5 20150623 (Red Hat 4.8.5-16) (GCC) 
checking for gcc -pipe switch ... found
checking for -Wl,-E switch ... found
checking for gcc builtin atomic operations ... found
checking for C99 variadic macros ... found
checking for gcc variadic macros ... found
......此处省略......
checking for PCRE library ... found
checking for PCRE JIT support ... found
checking for OpenSSL library ... found
checking for zlib library ... found
creating objs/Makefile

Configuration summary
  + using system PCRE library
  + using system OpenSSL library
  + using system zlib library

  nginx path prefix: "/usr/local/nginx-1.12.2"
  nginx binary file: "/usr/local/nginx-1.12.2/sbin/nginx"
  nginx modules path: "/usr/local/nginx-1.12.2/modules"
  nginx configuration prefix: "/usr/local/nginx-1.12.2/conf"
  nginx configuration file: "/usr/local/nginx-1.12.2/conf/nginx.conf"
  nginx pid file: "/usr/local/nginx-1.12.2/logs/nginx.pid"
  nginx error log file: "/usr/local/nginx-1.12.2/logs/error.log"
  nginx http access log file: "/usr/local/nginx-1.12.2/logs/access.log"
  nginx http client request body temporary files: "client_body_temp"
  nginx http proxy temporary files: "proxy_temp"
  nginx http fastcgi temporary files: "fastcgi_temp"
  nginx http uwsgi temporary files: "uwsgi_temp"
  nginx http scgi temporary files: "scgi_temp

6、编译及安装软件

[root@localhost nginx-1.12.2]# make && make install
make -f objs/Makefile
make[1]: Entering directory `/usr/src/nginx-1.12.2'
cc -c -pipe  -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g  -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs 
        -o objs/src/core/nginx.o 
        src/core/nginx.c
cc -c -pipe  -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g  -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs 
        -o objs/src/core/ngx_log.o 
        src/core/ngx_log.c
cc -c -pipe  -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g  -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs 
        -o objs/src/core/ngx_palloc.o 
        src/core/ngx_palloc.c
cc -c -pipe  -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g  -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs 
        -o objs/src/core/ngx_array.o 
        src/core/ngx_array.c
cc -c -pipe  -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g  -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs 
        -o objs/src/core/ngx_list.o 
        src/core/ngx_list.c
......此处省略......
cc -c -pipe  -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g  -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs -I src/http -I src/http/modules -I src/http/v2 
        -o objs/src/http/modules/ngx_http_upstream_least_conn_module.o 
        src/http/modules/ngx_http_upstream_least_conn_module.c
cc -c -pipe  -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g  -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs -I src/http -I src/http/modules -I src/http/v2 
        -o objs/src/http/modules/ngx_http_upstream_keepalive_module.o 
        src/http/modules/ngx_http_upstream_keepalive_module.c
cc -c -pipe  -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g  -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs -I src/http -I src/http/modules -I src/http/v2 
        -o objs/src/http/modules/ngx_http_upstream_zone_module.o 
        src/http/modules/ngx_http_upstream_zone_module.c
cc -c -pipe  -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g  -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs -I src/http -I src/http/modules -I src/http/v2 
        -o objs/src/http/modules/ngx_http_stub_status_module.o 
        src/http/modules/ngx_http_stub_status_module.c
cc -c -pipe  -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g  -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs 
        -o objs/ngx_modules.o 
        objs/ngx_modules.c
cc -o objs/nginx 
objs/src/core/nginx.o 
objs/src/core/ngx_log.o 
objs/src/core/ngx_palloc.o 
objs/src/core/ngx_array.o 
objs/src/core/ngx_list.o 
......此处省略......
objs/src/http/modules/ngx_http_upstream_least_conn_module.o 
objs/src/http/modules/ngx_http_upstream_keepalive_module.o 
objs/src/http/modules/ngx_http_upstream_zone_module.o 
objs/src/http/modules/ngx_http_stub_status_module.o 
objs/ngx_modules.o 
-ldl -lpthread -lcrypt -lpcre -lssl -lcrypto -ldl -lz 
-Wl,-E
sed -e "s|%%PREFIX%%|/usr/local/nginx-1.12.2|" 
        -e "s|%%PID_PATH%%|/usr/local/nginx-1.12.2/logs/nginx.pid|" 
        -e "s|%%CONF_PATH%%|/usr/local/nginx-1.12.2/conf/nginx.conf|" 
        -e "s|%%ERROR_LOG_PATH%%|/usr/local/nginx-1.12.2/logs/error.log|" 
        < man/nginx.8 > objs/nginx.8
make[1]: Leaving directory `/usr/src/nginx-1.12.2'
make -f objs/Makefile install
make[1]: Entering directory `/usr/src/nginx-1.12.2'
test -d '/usr/local/nginx-1.12.2' || mkdir -p '/usr/local/nginx-1.12.2'
test -d '/usr/local/nginx-1.12.2/sbin' 
        || mkdir -p '/usr/local/nginx-1.12.2/sbin'
test ! -f '/usr/local/nginx-1.12.2/sbin/nginx' 
        || mv '/usr/local/nginx-1.12.2/sbin/nginx' 
                '/usr/local/nginx-1.12.2/sbin/nginx.old'
cp objs/nginx '/usr/local/nginx-1.12.2/sbin/nginx'
test -d '/usr/local/nginx-1.12.2/conf' 
        || mkdir -p '/usr/local/nginx-1.12.2/conf'
cp conf/koi-win '/usr/local/nginx-1.12.2/conf'
cp conf/koi-utf '/usr/local/nginx-1.12.2/conf'
cp conf/win-utf '/usr/local/nginx-1.12.2/conf'
test -f '/usr/local/nginx-1.12.2/conf/mime.types' 
        || cp conf/mime.types '/usr/local/nginx-1.12.2/conf'
cp conf/mime.types '/usr/local/nginx-1.12.2/conf/mime.types.default'
test -f '/usr/local/nginx-1.12.2/conf/fastcgi_params' 
        || cp conf/fastcgi_params '/usr/local/nginx-1.12.2/conf'
cp conf/fastcgi_params 
        '/usr/local/nginx-1.12.2/conf/fastcgi_params.default'
test -f '/usr/local/nginx-1.12.2/conf/fastcgi.conf' 
        || cp conf/fastcgi.conf '/usr/local/nginx-1.12.2/conf'
cp conf/fastcgi.conf '/usr/local/nginx-1.12.2/conf/fastcgi.conf.default'
test -f '/usr/local/nginx-1.12.2/conf/uwsgi_params' 
        || cp conf/uwsgi_params '/usr/local/nginx-1.12.2/conf'
cp conf/uwsgi_params 
        '/usr/local/nginx-1.12.2/conf/uwsgi_params.default'
test -f '/usr/local/nginx-1.12.2/conf/scgi_params' 
        || cp conf/scgi_params '/usr/local/nginx-1.12.2/conf'
cp conf/scgi_params 
        '/usr/local/nginx-1.12.2/conf/scgi_params.default'
test -f '/usr/local/nginx-1.12.2/conf/nginx.conf' 
        || cp conf/nginx.conf '/usr/local/nginx-1.12.2/conf/nginx.conf'
cp conf/nginx.conf '/usr/local/nginx-1.12.2/conf/nginx.conf.default'
test -d '/usr/local/nginx-1.12.2/logs' 
        || mkdir -p '/usr/local/nginx-1.12.2/logs'
test -d '/usr/local/nginx-1.12.2/logs' 
        || mkdir -p '/usr/local/nginx-1.12.2/logs'
test -d '/usr/local/nginx-1.12.2/html' 
        || cp -R html '/usr/local/nginx-1.12.2'
test -d '/usr/local/nginx-1.12.2/logs' 
        || mkdir -p '/usr/local/nginx-1.12.2/logs'
make[1]: Leaving directory `/usr/src/nginx-1.12.2'

7、创建软链接

[root@localhost nginx-1.12.2]# ln -s /usr/local/nginx-1.12.2/ /usr/local/nginx

8、检查Nginx配置文件语法

[root@localhost nginx-1.12.2]# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx-1.12.2/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx-1.12.2/conf/nginx.conf test is successful

9、把Nginx服务添加进启动自定义文件内并启动Nginx软件

echo "/usr/local/nginx/sbin/nginx" >>/etc/rc.local && /usr/local/nginx/sbin/nginx

10、检查端口情况判断Nginx软件是否运行正常

[root@localhost nginx-1.12.2]# ss -lnat|grep 80
LISTEN     0      128          *:80                       *:*

Web访问验证

未分类

调整参数

1、当我们安装完Nginx程序后首先有几个参数需要调整下,需要调整的参数为user、worker_processes、worker_connections;Nginx主配置文件位于/usr/local/nginx/conf目录下的nginx.conf

[root@localhost conf]# vim nginx.conf

user  www www;    #指定运行Worker进程的用户和组,根据编译安装时指定的用户及组修改,默认为nobody;例如:user www www;
worker_processes  auto;    #常规调优选项 Worker线程的个数;通常应该为物理CPU核心个数减1;自动为auto

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  51200;    #常规调优选项 设定单个Worker进程所能够处理响应的最大并发请求数(worker_connections * worker_processes);默认为1024,建议为51200,最大为65535。
}

2、调整参数后验证配置文件是否有误

[root@localhost conf]# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx-1.12.2/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx-1.12.2/conf/nginx.conf test is successful

3、重新加载配置文件或重启系统均可

[root@localhost conf]# /usr/local/nginx/sbin/nginx -s reload

Docker Compose实例之nginx反向代理GitLab

在上一篇文章(Docker快速搭建GitLab私有仓库)中探索了如何用docker实现最简单的GitLab服务。但是现实场景中往往会遇到复杂的情况和需求,光用docker指令可能就比较繁琐了。

举个例子???? 如下图所示,在一个服务器上要部署一个GitLab,N个其它服务(那N个服务或许还要与GitLab进行隔离),如果用docker 指令一个一个的run起来,管理起来可就麻烦了。 网络示意图

未分类

当然docker作为流行工具,不会让我们那么累,我们借助Docker Compose的文件来描述这个多容器的配置,并通过一条命令就能启动这些容器。

Docker Compose 的配置文件 docker-compose.yml 如下:

version: '3.6'
services:
  gitlab: # gitlab服务名
    container_name: gitlab-site # gitlab 容器名
    image: gitlab/gitlab-ce:latest
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'https://gitlab.example.com'  # git域名
        unicorn['worker_timeout'] = 60
        unicorn['worker_processes'] = 2
        nginx['enable'] = true
        nginx['client_max_body_size'] = '250m'
        nginx['redirect_http_to_https'] = true
        nginx['ssl_certificate'] = "/etc/ssl/gitlab.example.com.crt" # 加密证书文件
        nginx['ssl_certificate_key'] = "/etc/ssl/gitlab.example.com.key"
        nginx['ssl_ciphers'] = "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"
        nginx['ssl_prefer_server_ciphers'] = "on"
        nginx['ssl_protocols'] = "TLSv1.1 TLSv1.2"
        nginx['ssl_session_cache'] = "builtin:1000  shared:SSL:10m"
        nginx['listen_addresses'] = ["0.0.0.0"]
        nginx['http2_enabled'] = true
    volumes:
      - "/srv/gitlab/config:/etc/gitlab"         # 此处使用的是绝对路径
      - "/srv/gitlab/logs:/var/log/gitlab"
      - "/srv/gitlab/data:/var/opt/gitlab"
      - "/srv/ssl:/etc/ssl"                         # 加密证书文件路径映射
    networks:
      - git-network  # 使用 git-network 网络,与 other-network 相隔离
other-app: # 用 nginx 模拟的其它服务
    container_name: other-app-nginx
    image: nginx:stable-alpine
    volumes:
      - "./nginx-conf-site:/etc/nginx/conf.d:ro"     # 此处使用的是相对路径
      - "./contents:/usr/share/nginx/html/sites:ro" # 相对的是 docker-compose.yml 的位置
    networks:
      - other-network   # 使用 other-network 网络,与 git-network 相隔离
  nginx-reverse:  # nginx 反向代理服务
    container_name: nginx-reverse
    depends_on:    # 反向代理依赖的服务名
      - gitlab  
      - other-app
    image: nginx:stable-alpine
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - "./nginx-conf-reverse:/etc/nginx/conf.d:ro"  # 此处使用的是相对路径
      - "/srv/ssl:/etc/ssl:ro"   # 此处使用的是绝对炉具
    networks:  # nginx反向代理使用的网络
      - git-network     # gitlab使用的网络
      - other-network  # 其它app使用的网络
networks:  # 声明网络
  git-network:  
  other-network:
nginx反向代理的配置文件1: git-reverse.conf ,放在与docker-compose.yml 所在目录相对的 nginx-conf-reverse 目录下,作用是将对 https://gitlab.example.com 的访问进行转发

server{
    listen      443 ssl http2;    # 监听 443 端口
    listen [::]:443 ssl http2;
    server_name gitlab.example.com;

    ssl_certificate        /etc/ssl/gitlab.example.com.crt;
    ssl_certificate_key    /etc/ssl/gitlab.example.com.key;
    ssl_protocols TLSv1.2 TLSv1.1 TLSv1;

    location / {
      proxy_pass https://gitlab-site;   # 转发给名为 "gitlab-site" 的 容器
    }
}

nginx反向代理的配置文件2: other-reverse.conf ,放在与docker-compose.yml 所在目录相对的 nginx-conf-reverse 目录下, 作用是将对 http://other.example.com 的访问进行转发

server{
    listen      80;   # 监听 80 端口
    server_name other.example.com;  # 其它服务的域名
    location / {
      proxy_pass http://other-app-nginx;  # 转发到"其它"服务
    }
}
本例中的“其它服务”由一个nginx静态网站模拟,其配置文件other-site.conf放在与docker-compose.yml 所在目录相对的 nginx-conf-site 目录下:

server {
    listen 80;
    server_name other.example.com;
    location / {
        root   /usr/share/nginx/html/sites/other-site;
        index  index.html;
    }
}

当然,上述配置中用 nginx 模拟的“其它服务” 可以是任意的网络服务,你不需要的话,把这部分删掉也没问题。

最后,执行一个指令

sudo docker-compose up -d

GitLab服务、其它服务、Nginx反向代理 就会依次启动起来。

搭建 Keepalived + Nginx + Tomcat 的高可用负载均衡架构

1、概述

初期的互联网企业由于业务量较小,所以一般单机部署,实现单点访问即可满足业务的需求,这也是最简单的部署方式,但是随着业务的不断扩大,系统的访问量逐渐的上升,单机部署的模式已无法承载现有的业务量,需要进行服务集群化部署,本文主要介绍服务端 Tomcat 多实例部署,以及如何保证 web 服务的高可用方案。

  • Nginx 是一个高性能的 HTTP 反向代理服务器
  • Keepalived 是一个基于 VRRP 协议来实现的 LVS 服务高可用方案,可以利用其来避免服务的单点故障
  • Tomcat 是一个免费的开放源代码的 Web 应用服务器,属于轻量级应用服务器。

2、Nginx 的高可用负载均衡架构

如下图:为典型的 Tomcat 服务多实例部署的架构图

未分类

  1. 用户通过域名请求到 DNS,由 DNS 解析域名后返回对应的 IP 地址,该 IP 及为 Keepalived 映射服务器的虚拟 IP

  2. 通过该虚拟 IP 访问到对应的负载均衡器(Nginx),这里 Nginx 部署两个,然后通过 Keepalived 来保证 NG 的高可用,正常情况下由 Keepalived-M 将虚拟 IP 映射转发至 Nginx-M,如果 Nginx-M 出现故障,此时 Keepalived 会切换至 Keepalived-S 开始工作,从而保证了 NG 的单点故障问题。

  3. 通过 Nginx 负载均衡器,将请求路由到对应的 Tomcat 服务。

3、搭建 Keepalived + Nginx + Tomcat 的高可用负载均衡架构

3.1 需要准备的软件

(1)apache-tomcat-8.5.16.tar.gz

(2)nginx-1.12.2.tar.gz

(3)keepalived-1.3.9.tar.gz

3.2 服务器准备

两台服务器如:192.168.10.11,192.168.10.12

3.3 安装需要的依赖包

yum -y install gcc gcc-c++ automake pcre pcre-devel zlib zlib-devel open openssl-devel

3.4 安装

3.4.1 安装 Tomcat

(1)分别在两台服务器中安装 Tomcat,解压 apache-tomcat-8.5.16.tar.gz 及可完成安装。

3.4.2 安装 Nginx

(1)解压安装包:tar -zxvf nginx-1.12.2.tar.gz

(2)进入到 nginx-1.12.2 目录:cd nginx-1.12.2

(3)编译:

./configure --with-http_stub_status_module --with-http_ssl_module  --prefix=/usr/local/nginx
make && sudo make install

3.4.3 安装 Keepalived

(1)解压安装包:tar -zxvf keepalived-1.3.9.tar.gz

(2)进入到 keepalived-1.3.9 目录:cd keepalived-1.3.9

(3)执行编译:

./configure --prefix=/usr/local/keepalived --sysconf=/etc
make && sudo make install

3.5 配置

3.5.1 分别配置两台服务器的 Nginx

(1)分别修改两台服务器 nginx 配置文件,vi /usr/local/nginx/conf/nginx.conf

(2)内容如下:

#nginx进程数
worker_processes  1;

#单个进程最大连接数
events {
    worker_connections  1024;
}

#http服务器配置
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    #长连接超时时间,单位是秒
    keepalive_timeout  65;
    #upstream负载均衡配置,配置路由到tomcat的服务地址以及权重
    upstream localhost{
       server 192.168.10.11:8080 weight=2;
       server 192.168.10.12:8080 weight=2;
    }

    #虚拟主机的配置
    server {
        #监听端口
        listen       80;
         #域名可以有多个,用空格隔开
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
            #nginx跟后端服务器连接超时时间(代理连接超时)
            proxy_connect_timeout 3;
            #后端服务器数据回传时间(代理发送超时)
            proxy_send_timeout 30;
            #连接成功后,后端服务器响应时间(代理接收超时)
            proxy_read_timeout 30;
            proxy_pass http://localhost;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

3.5.2 主 Keepalived 配置

(1)修改 11 服务器的 keepalived 配置文件,vi /etc/keepalived/keepalived.conf

(2)内容如下:

! Configuration File for keepalived
#全局配置
global_defs {
   #keepalived切换的时候,发消息到指定的email,可配置多个email
   notification_email {
     [email protected]
     [email protected]
   }
   #通知邮件从哪个地址发出
   notification_email_from [email protected]
   #通知邮件的smtp地址
   smtp_server smtp.exmail.qq.com
   #连接smtp服务器的超时时间,单位秒
   smtp_connect_timeout 30
   #Keepalived的机器标识,一个网络内保持唯一
   router_id nginx-master
}

#执行脚本配置
vrrp_script chk_nginx {
    #脚本所在路径
    script "/home/project/keepalived/check_nginx.sh"
    #脚本执行间隔时间,秒
    interval 2
    #优先级
    weight 2
}
#keepalived实例配置
vrrp_instance VI_1 {
    #指定实例的初始状态,MASTER或BACKUP两种状态,并且需要大写
    state MASTER
    #实例绑定的网卡
    interface ens33
    #虚拟路由标识,是一个数字,整个VRRP内唯一,如果keepalived配置了主备,需要相同
    virtual_router_id 51
    #优先级,数值愈大,优先级越高
    priority 100
    #MASTER与BACKUP之间同步检查的时间间隔,单位为秒
    advert_int 1
    #通信验证
    authentication {
        auth_type PASS
        auth_pass feinik
    }
    #追踪外围脚本
    track_script {
        #这里配置vrrp_script的名称
        chk_nginx
    }
    #虚拟ip配置,可配置多个
    virtual_ipaddress {
        192.168.10.200
    }
}

3.5.3 备 Keepalived 配置

(1)修改 12 服务器的 keepalived 配置文件,vi /etc/keepalived/keepalived.conf

(2)内容如下:

! Configuration File for keepalived
#全局配置
global_defs {
   #keepalived切换的时候,发消息到指定的email,可配置多个email
   notification_email {
     [email protected]
     [email protected]
   }
   #通知邮件从哪个地址发出
   notification_email_from [email protected]
   #通知邮件的smtp地址
   smtp_server smtp.exmail.qq.com
   #连接smtp服务器的超时时间,单位秒
   smtp_connect_timeout 30
   #Keepalived的机器标识,一个网络内保持唯一
   router_id nginx-master
}

#执行脚本配置
vrrp_script chk_nginx {
    #脚本所在路径
    script "/home/project/keepalived/check_nginx.sh"
    #脚本执行间隔时间,秒
    interval 2
    #优先级
    weight 2
}
#keepalived实例配置
vrrp_instance VI_1 {
    #指定实例的初始状态,MASTER或BACKUP两种状态,并且需要大写
    state BACKUP
    #实例绑定的网卡
    interface ens33
    #虚拟路由标识,是一个数字,整个VRRP内唯一,如果keepalived配置了主备,需要相同
    virtual_router_id 51
    #优先级,数值愈大,优先级越高
    priority 99
    #MASTER与BACKUP之间同步检查的时间间隔,单位为秒
    advert_int 1
    #通信验证
    authentication {
        auth_type PASS
        auth_pass feinik
    }
    #追踪外围脚本
    track_script {
        #这里配置vrrp_script的名称
        chk_nginx
    }
    #虚拟ip配置,可配置多个
    virtual_ipaddress {
        192.168.10.200
    }
}

3.5.4 Nginx 状态检查脚本创建

(1)新建 Nginx 的状态检查脚本:check_nginx.sh

(2)内容如下:

#!/bin/sh
NGINX=/usr/common/nginx/sbin/nginx
PORT=80
nmap localhost -p $PORT | grep "$PORT/tcp open"
#echo $?
if [ $? -ne 0 ];then
    $NGINX -s stop
    #这里再次尝试启动NG
    $NGINX
    sleep 5
    nmap localhost -p $PORT | grep "$PORT/tcp open"
    [ $? -ne 0 ] && cd /usr/common/keepalived/sbin && pkill keepalived
    echo "stoped"
fi

4、运行测试

(1)为了更直观的查看到 keepalived 切换的效果,将 11 服务器中的 nginx 的 upstream 服务只配置 11 的 tomcat 服务地址,12 服务器中的 upstream 服务只配置 12 的 tomcat 服务地址,这样只需要观察将 11 服务器中的 nginx 关闭看使用虚拟 ip 是否可以访问到 12 服务器的 tomcat。

(2)分别启动两个服务器中的 tomcat、nginx、keepalived,访问虚拟 ip:192.168.10.200,可以查看到访问的是主 keepalived 服务器的 tomcat

未分类

3)关闭 11 服务器的 nginx,nginx -s stop,再次访问虚拟 ip,如下:说明主 keepalived 通过配置的脚本检测到了本服务的 nginx 服务挂掉了,所以立马切换至了备的 keepalived,这时 12 服务器的 keepalived 升为了主,所以就访问到了 12 服务器的 tomcat。

未分类

Linux 惊群效应之 Nginx 解决方案

前言

因为项目涉及到 Nginx 一些公共模块的使用,而且也想对惊群效应有个深入的了解,在整理了网上资料以及实践后,记录成文章以便大家复习巩固。

结论

  • 不管还是多进程还是多线程,都存在惊群效应,本篇文章使用多进程分析。
  • 在 Linux2.6 版本之后,已经解决了系统调用 accept 的惊群效应(前提是没有使用 select、poll、epoll 等事件机制)。
  • 目前 Linux 已经部分解决了 epoll 的惊群效应(epoll 在 fork 之前),Linux2.6 是没有解决的。
  • Epoll 在 fork 之后创建仍然存在惊群效应,Nginx 使用自己实现的互斥锁解决惊群效应。

惊群效应是什么

惊群效应(thundering herd)是指多进程(多线程)在同时阻塞等待同一个事件的时候(休眠状态),如果等待的这个事件发生,那么他就会唤醒等待的所有进程(或者线程),但是最终却只能有一个进程(线程)获得这个时间的“控制权”,对该事件进行处理,而其他进程(线程)获取“控制权”失败,只能重新进入休眠状态,这种现象和性能浪费就叫做惊群效应。

惊群效应消耗了什么

  • Linux 内核对用户进程(线程)频繁地做无效的调度、上下文切换等使系统性能大打折扣。上下文切换(context switch)过高会导致 CPU 像个搬运工,频繁地在寄存器和运行队列之间奔波,更多的时间花在了进程(线程)切换,而不是在真正工作的进程(线程)上面。直接的消耗包括 CPU 寄存器要保存和加载(例如程序计数器)、系统调度器的代码需要执行。间接的消耗在于多核 cache 之间的共享数据。
  • 为了确保只有一个进程(线程)得到资源,需要对资源操作进行加锁保护,加大了系统的开销。目前一些常见的服务器软件有的是通过锁机制解决的,比如 Nginx(它的锁机制是默认开启的,可以关闭);还有些认为惊群对系统性能影响不大,没有去处理,比如 Lighttpd。

Linux 解决方案之 Accept

Linux 2.6 版本之前,监听同一个 socket 的进程会挂在同一个等待队列上,当请求到来时,会唤醒所有等待的进程。

Linux 2.6 版本之后,通过引入一个标记位 WQ_FLAG_EXCLUSIVE,解决掉了 accept 惊群效应。

具体分析会在代码注释里面,accept代码实现片段如下:

// 当accept的时候,如果没有连接则会一直阻塞(没有设置非阻塞)
// 其阻塞函数就是:inet_csk_accept(accept的原型函数)  
struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
{
    ...  
    // 等待连接 
    error = inet_csk_wait_for_connect(sk, timeo); 
    ...  
}

static int inet_csk_wait_for_connect(struct sock *sk, long timeo)
{
    ...
    for (;;) {  
        // 只有一个进程会被唤醒。
        // 非exclusive的元素会加在等待队列前头,exclusive的元素会加在所有非exclusive元素的后头。
        prepare_to_wait_exclusive(sk_sleep(sk), &wait,TASK_INTERRUPTIBLE);  
    }  
    ...
}

void prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)  
{  
    unsigned long flags;  
    // 设置等待队列的flag为EXCLUSIVE,设置这个就是表示一次只会有一个进程被唤醒,我们等会就会看到这个标记的作用。  
    // 注意这个标志,唤醒的阶段会使用这个标志。
    wait->flags |= WQ_FLAG_EXCLUSIVE;  
    spin_lock_irqsave(&q->lock, flags);  
    if (list_empty(&wait->task_list))  
        // 加入等待队列  
        __add_wait_queue_tail(q, wait);  
    set_current_state(state);  
    spin_unlock_irqrestore(&q->lock, flags);  
}

唤醒阻塞的 accept 代码片段如下:

// 当有tcp连接完成,就会从半连接队列拷贝socket到连接队列,这个时候我们就可以唤醒阻塞的accept了。
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
    ...
    // 关注此函数
    if (tcp_child_process(sk, nsk, skb)) { 
        rsk = nsk;  
        goto reset;  
    }
    ...
}

int tcp_child_process(struct sock *parent, struct sock *child, struct sk_buff *skb)
{
    ...
    // Wakeup parent, send SIGIO 唤醒父进程
    if (state == TCP_SYN_RECV && child->sk_state != state)  
        // 调用sk_data_ready通知父进程
        // 查阅资料我们知道tcp中这个函数对应是sock_def_readable
        // 而sock_def_readable会调用wake_up_interruptible_sync_poll来唤醒队列
        parent->sk_data_ready(parent, 0);  
    }
    ...
}

void __wake_up_sync_key(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, void *key)  
{  
    ...  
    // 关注此函数
    __wake_up_common(q, mode, nr_exclusive, wake_flags, key);  
    spin_unlock_irqrestore(&q->lock, flags);  
    ...  
} 

static void __wake_up_common(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, int wake_flags, void *key)
{
    ...
    // 传进来的nr_exclusive是1
    // 所以flags & WQ_FLAG_EXCLUSIVE为真的时候,执行一次,就会跳出循环
    // 我们记得accept的时候,加到等待队列的元素就是WQ_FLAG_EXCLUSIVE的
    list_for_each_entry_safe(curr, next, &q->task_list, task_list) {  
        unsigned flags = curr->flags;  
        if (curr->func(curr, mode, wake_flags, key) 
        && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
        break; 
    }
    ...
}

Linux 解决方案之 Epoll

在使用 select、poll、epoll、kqueue 等 IO 复用时,多进程(线程)处理链接更加复杂。
在讨论 epoll 的惊群效应时候,需要分为两种情况:

  • epoll_create 在 fork 之前创建
  • epoll_create 在 fork 之后创建

epoll_create 在 fork 之前创建

与 accept 惊群的原因类似,当有事件发生时,等待同一个文件描述符的所有进程(线程)都将被唤醒,而且解决思路和 accept 一致。

为什么需要全部唤醒?因为内核不知道,你是否在等待文件描述符来调用 accept() 函数,还是做其他事情(信号处理,定时事件)。

此种情况惊群效应已经被解决。

epoll_create 在 fork 之后创建

epoll_create 在 fork 之前创建的话,所有进程共享一个 epoll 红黑数。
如果我们只需要处理 accept 事件的话,貌似世界一片美好了。但是 epoll 并不是只处理 accept 事件,accept 后续的读写事件都需要处理,还有定时或者信号事件。

当连接到来时,我们需要选择一个进程来 accept,这个时候,任何一个 accept 都是可以的。当连接建立以后,后续的读写事件,却与进程有了关联。一个请求与 a 进程建立连接后,后续的读写也应该由 a 进程来做。

当读写事件发生时,应该通知哪个进程呢?Epoll 并不知道,因此,事件有可能错误通知另一个进程,这是不对的。所以一般在每个进程(线程)里面会再次创建一个 epoll 事件循环机制,每个进程的读写事件只注册在自己进程的 epoll 种。

我们知道 epoll 对惊群效应的修复,是建立在共享在同一个 epoll 结构上的。epoll_create 在 fork 之后执行,每个进程有单独的 epoll 红黑树,等待队列,ready 事件列表。因此,惊群效应再次出现了。有时候唤醒所有进程,有时候唤醒部分进程,可能是因为事件已经被某些进程处理掉了,因此不用在通知另外还未通知到的进程了。

Nginx 解决方案之锁的设计

首先我们要知道在用户空间进程间锁实现的原理,起始原理很简单,就是能弄一个让所有进程共享的东西,比如 mmap 的内存,比如文件,然后通过这个东西来控制进程的互斥。

Nginx 中使用的锁是自己来实现的,这里锁的实现分为两种情况,一种是支持原子操作的情况,也就是由 NGX_HAVE_ATOMIC_OPS 这个宏来进行控制的,一种是不支持原子操作,这是是使用文件锁来实现。

锁结构体

如果支持原子操作,则我们可以直接使用 mmap,然后 lock 就保存 mmap 的内存区域的地址
如果不支持原子操作,则我们使用文件锁来实现,这里 fd 表示进程间共享的文件句柄,name 表示文件名

typedef struct {  
#if (NGX_HAVE_ATOMIC_OPS)  
    ngx_atomic_t  *lock;  
#else  
    ngx_fd_t       fd;  
    u_char        *name;  
#endif  
} ngx_shmtx_t;

原子锁创建

// 如果支持原子操作的话,非常简单,就是将共享内存的地址付给loc这个域
ngx_int_t ngx_shmtx_create(ngx_shmtx_t *mtx, void *addr, u_char *name)  
{  
    mtx->lock = addr;  


   return NGX_OK;  
} 

原子锁获取

TryLock,它是非阻塞的,也就是说它会尝试的获得锁,如果没有获得的话,它会直接返回错误。
Lock,它也会尝试获得锁,而当没有获得他不会立即返回,而是开始进入循环然后不停的去获得锁,知道获得。不过 Nginx 这里还有用到一个技巧,就是每次都会让当前的进程放到 CPU 的运行队列的最后一位,也就是自动放弃 CPU。

原子锁实现
如果系统库支持的情况,此时直接调用OSAtomicCompareAndSwap32Barrier,即 CAS。

#define ngx_atomic_cmp_set(lock, old, new)                                   
    OSAtomicCompareAndSwap32Barrier(old, new, (int32_t *) lock) 

如果系统库不支持这个指令的话,Nginx 自己还用汇编实现了一个。

static ngx_inline ngx_atomic_uint_t ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,  
    ngx_atomic_uint_t set)  
{  
    u_char  res;  

    __asm__ volatile (  

         NGX_SMP_LOCK  
    "    cmpxchgl  %3, %1;   "  
    "    sete      %0;       "  

    : "=a" (res) : "m" (*lock), "a" (old), "r" (set) : "cc", "memory");  

    return res;  
}

原子锁释放

Unlock 比较简单,和当前进程 id 比较,如果相等,就把 lock 改为 0,说明放弃这个锁。

#define ngx_shmtx_unlock(mtx) (void) ngx_atomic_cmp_set((mtx)->lock, ngx_pid, 0)  

Nginx 解决方案之惊群效应

变量分析

 // 如果使用了 master worker,并且 worker 个数大于 1,并且配置文件里面有设置使用 accept_mutex. 的话,设置
 ngx_use_accept_mutex  
 if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) 
 {  
        ngx_use_accept_mutex = 1;  
        // 下面这两个变量后面会解释。  
        ngx_accept_mutex_held = 0;  
        ngx_accept_mutex_delay = ecf->accept_mutex_delay;  
 } else {  
        ngx_use_accept_mutex = 0;  
 }

ngx_use_accept_mutex 这个变量,如果有这个变量,说明 Nginx 有必要使用 accept 互斥体,这个变量的初始化在 ngx_event_process_init 中。
ngx_accept_mutex_held 表示当前是否已经持有锁。
ngx_accept_mutex_delay 表示当获得锁失败后,再次去请求锁的间隔时间,这个时间可以在配置文件中设置的。

ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;

ngx_accept_disabled,这个变量是一个阈值,如果大于 0,说明当前的进程处理的连接过多。

是否使用锁

// 如果有使用mutex,则才会进行处理。  
if (ngx_use_accept_mutex) 
{  
    // 如果大于0,则跳过下面的锁的处理,并减一。  
    if (ngx_accept_disabled > 0) {  
        ngx_accept_disabled--; 
    } else {  
        // 试着获得锁,如果出错则返回。  
        if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {  
            return;  
        }  
        // 如果ngx_accept_mutex_held为1,则说明已经获得锁,此时设置flag,这个flag后面会解释。
        if (ngx_accept_mutex_held) {  
            flags |= NGX_POST_EVENTS;  
        } else {  
            // 否则,设置timer,也就是定时器。接下来会解释这段。  
            if (timer == NGX_TIMER_INFINITE  
                 || timer > ngx_accept_mutex_delay) {  
                timer = ngx_accept_mutex_delay;  
            }  
        }  
    }  
}

NGX_POST_EVENTS 标记,设置了这个标记就说明当 socket 有数据被唤醒时,我们并不会马上 accept 或者说读取,而是将这个事件保存起来,然后当我们释放锁之后,才会进行 accept 或者读取这个句柄。

// 如果ngx_posted_accept_events不为NULL,则说明有accept event需要nginx处理。  
if (ngx_posted_accept_events) {  
        ngx_event_process_posted(cycle, &ngx_posted_accept_events);  
}

如果没有设置 NGX_POST_EVENTS 标记的话,Nginx 会立即 Accept 或者读取句柄

定时器,这里如果 Nginx 没有获得锁,并不会马上再去获得锁,而是设置定时器,然后在 epoll 休眠(如果没有其他的东西唤醒)。此时如果有连接到达,当前休眠进程会被提前唤醒,然后立即 accept。否则,休眠 ngx_accept_mutex_delay时间,然后继续 tryLock。

获取锁来解决惊群

ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle)  
{  
    // 尝试获得锁  
    if (ngx_shmtx_trylock(&ngx_accept_mutex)) {  
        // 如果本来已经获得锁,则直接返回Ok  
        if (ngx_accept_mutex_held  
            && ngx_accept_events == 0  
            && !(ngx_event_flags & NGX_USE_RTSIG_EVENT))  
        {  
            return NGX_OK;  
        }  

        // 到达这里,说明重新获得锁成功,因此需要打开被关闭的listening句柄。  
        if (ngx_enable_accept_events(cycle) == NGX_ERROR) {  
            ngx_shmtx_unlock(&ngx_accept_mutex);  
            return NGX_ERROR;  
        }  

        ngx_accept_events = 0;  
        // 设置获得锁的标记。  
        ngx_accept_mutex_held = 1;  

        return NGX_OK;  
    }  

    // 如果我们前面已经获得了锁,然后这次获得锁失败
    // 则说明当前的listen句柄已经被其他的进程锁监听
    // 因此此时需要从epoll中移出调已经注册的listen句柄
    // 这样就很好的控制了子进程的负载均衡  
    if (ngx_accept_mutex_held) {  
        if (ngx_disable_accept_events(cycle) == NGX_ERROR) {  
            return NGX_ERROR;  
        }  
        // 设置锁的持有为0.  
        ngx_accept_mutex_held = 0;  
    }  

    return NGX_OK;  
} 

如上代码,当一个连接来的时候,此时每个进程的 epoll 事件列表里面都是有该 fd 的。抢到该连接的进程先释放锁,在 accept。没有抢到的进程把该 fd 从事件列表里面移除,不必再调用 accept,造成资源浪费。

同时由于锁的控制(以及获得锁的定时器),每个进程都能相对公平的 accept 句柄,也就是比较好的解决了子进程负载均衡。

部署Nginx网站服务实现访问状态统计以及访问控制功能

Nginx专为性能优化而开发,最知名的优点是它的稳定性和低系统资源消耗,以及对HTTP并发连接的高处理能力,单个物理服务器可支持30000-50000个并发请求。

Nginx的安装文件可以从官方网站http://www.nginx.org/下载,下面以Nginx1.12版本为例,基于CentOS7,部署Nginx网站服务。

安装Nginx

第一步源码编译安装

1. 安装支持软件

Nginx的配置及运行需要gcc 、 gcc-c++ 、 make 、 pcre、pcre-devel、zlib-devel软件包的支持,以便提供相应的库和头文件,确保Nginx安装顺利。

创建yum仓库的步骤详细步骤请参考 https://www.linuxidc.com/Linux/2018-11/155508.htm

yum install gcc gcc-c++ make pcre pcre-devel zlib-devel -y

如果是在有网络的情况下,CentOS7无需创建yum仓库,直接执行yum list命令更新一下yum源,稍微等待一会儿。

yum list    //更新yum源
yum install gcc gcc-c++ make pcre pcre-devel zlib-devel -y

2. 创建运行用户、组

Nginx服务程序默认以nobody身份运行,建议为其创建专门的用户账号,以便更准确的控制其访问权限,增加灵活性,降低安全风险。

useradd -M -s /sbin/nologin nginx    //创建一个名为nginx用户,不建立宿主文件夹,禁止登录到shell环境

3. 编译安装

tar xzvf nginx-1.12.0.tar.gz -C /opt  //解压Nginx软件至opt目录下
cd /opt/nginx-1.12.0/  //切换到Nginx目录下

根据实际需要配置Nginx的具体选项,配置前可参考“./configure –help”给出的说明。

./configure  
--prefix=/usr/local/nginx  
--user=nginx 
--group=nginx 
--with-http_stub_status_module
  • --prefix:指定Nginx的安装目录
  • --user:指定Nginx的运行用户
  • --group:指定Nginx的运行组
  • --with-http_stub_status_module:启用http_stub_status_module模块以支持状态统计,便于查看服务器的连接信息
make                //生成二进制文件
make install        //编译安装

4. 为主程序Nginx创建链接文件

创建Nginx主程序的链接文件是为了方便管理员直接“nginx”命令就可以调用Nginx的主程序。

ln -s /usr/local/nginx/sbin/nginx /usr/local/sbin/

第二步检查配置文件并启动Nginx服务

1.检查配置文件

Nginx的主程序提供了“-t”选项来对配置文件进行检查,以便找出不当或错误的配置。

[root@centos7-1 nginx-1.12.0]# nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful

2.启动Nginx

直接运行Nginx即可启动Nginx服务器

[root@centos7-1 nginx-1.12.0]# nginx 
[root@centos7-1 nginx-1.12.0]# killall -1 nginx       //重启nginx服务
[root@centos7-1 nginx-1.12.0]# killall -3 nginx      //停止nginx服务

3.使用Nginx服务脚本

为了使nginx服务的启动、停止、重载等操作更加方便,可以编写nginx服务脚本,并使用chkconfig和systemctl工具来进行管理,这更加符合系统的管理习惯。

[root@centos7-1 nginx-1.12.0]# vim /etc/init.d/nginx

#!/bin/bash
# chkconfig: - 99 20
# description: Nginx Service Control Script
PROG="/usr/local/nginx/sbin/nginx"               //主程序路径
PIDF="/usr/local/nginx/logs/nginx.pid"           //PID存放路径
case "$1" in
  start)
    $PROG
    ;;
  stop)
    kill -s QUIT $(cat $PIDF)              //根据PID中止nginx进程
    ;;
  restart)
    $0 stop
    $0 start
    ;;
  reload)
    kill -s HUP $(cat $PIDF)              //根据进程号重载配置
    ;;
  *)
        echo "Usage: $0 {start|stop|restart|reload}"
        exit 1
esac
exit 0
[root@centos7-1 nginx-1.12.0]# chmod +x /etc/init.d/nginx
[root@centos7-1 nginx-1.12.0]# chkconfig --add nginx                 //添加为系统服务
[root@centos7-1 nginx-1.12.0]# systemctl start nginx.service

第三步确认Nginx服务是否正常运行

通过检查Nginx程序的监听状态,或者在浏览器中访问此Web服务,默认页面将显示“Welcome to nginx!”

[root@centos7-1 nginx-1.12.0]# netstat -antp | grep nginx
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      54386/nginx: master 
[root@centos7-1 nginx-1.12.0]# yum install elinks -y
[root@centos7-1 nginx-1.12.0]# elinks http://localhost //使用elinks浏览器

未分类

配置访问状态统计页面

Nginx内置了HTTP_STUB_STATUS状态统计模块,用来反馈当前的Web访问情况。要使用Nginx的状态统计功能,除了启用内建模块以外,还需要修改nginx.conf配置文件,指定访问位置并添加stub_status配置代码。

[root@centos7-1 nginx-1.12.0]# cd /usr/local/nginx/conf
[root@centos7-1 conf]# mv nginx.conf nginx.conf.back
[root@centos7-1 conf]# grep -v "#" nginx.conf.back > nginx.conf     //过滤配置文件#号注释的信息
[root@centos7-1 conf]# vim nginx.conf

server {
        listen       80;
        server_name  localhost;
    charset utf-8;

        location / {
            root   html;
            index  index.html index.htm;
        }

      //在"server"这里插入的这4行的信息
        location ~ /status {                      //访问位置为/status
        stub_status   on;                        //打开状态统计功能
        access_log off;                          //关闭此位置的日志记录
        }                    

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

       }
    }

新的配置生效后,在浏览器中访问nginx服务器的/status网站位置,可以看到当前的状态统计信息。

systemctl reload nginx.service                  //重新加载nginx服务
systemctl stop firewalld.service               //关闭防火墙
systemctl disable firewalld.service         //禁用防火墙

未分类

其中,“Active connections”表示当前的活动连接数;而“server accepts handled requests”表示已经处理的连接信息。三个数字依次表示已处理的连接数、成功的TCP握手次数、已处理的请求数。

配置Nginx的访问控制

1.基于用户授权的访问控制

(1).使用htpasswd生成用户认证文件,如果没有该命令,可使用yum安装httpd-tools软件包,用法与Apache认证时方式一样,在/usr/local/nginx/目录生成passwd.db文件,用户名是test,密码输入2次。

yum install httpd-tools -y    //安装httpd-tools软件包
[root@centos7-1 ~]# htpasswd -c /usr/local/nginx/passwd.db test
New password:                      //设置test用户密码
Re-type new password: 
Adding password for user test
[root@centos7-1 ~]# cat /usr/local/nginx/passwd.db         //查看生成的用户认证文件
test:$apr1$WfkC0IdB$sMyjqJzg2tcqcIe1mJ8LI/

(2).修改密码文件的权限为400,将所有者改为nginx,设置nginx的运行用户能够读取。

[root@centos7-1 ~]# chmod 400 /usr/local/nginx/passwd.db 
[root@centos7-1 ~]# chown nginx /usr/local/nginx/passwd.db 
[root@centos7-1 ~]# ll -d /usr/local/nginx/passwd.db 
-r--------. 1 nginx root 43 6月  20 14:45 /usr/local/nginx/passwd.db

(3).修改主配置文件nginx.conf,添加相应认证配置项。

[root@centos7-1 ~]# vim /usr/local/nginx/conf/nginx.conf

location / {
            auth_basic "secret";       //添加认证配置
            auth_basic_user_file /usr/local/nginx/passwd.db;
            root   html;
            index  index.html index.htm;
        }

(4).检测语法、重启服务

[root@centos7-1 ~]# nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
[root@centos7-1 ~]# systemctl restart nginx.service

(5).用浏览器访问网址,检验控制效果。

未分类

需要输入用户名和密码进行访问,验证通过才能进行访问。

未分类

2.基于客户端的访问控制

Nginx基于客户端的访问控制要比Apache的简单,规则如下:

  • deny IP/IP段:拒绝某个IP或IP段的客户端访问
  • allow IP/IP段:允许某个IP或IP段的客户端访问。
  • 规则从上往下执行,如匹配规则停止,不在往下匹配。

(1).修改主配置文件nginx.conf,添加相应认证配置项。

[root@centos7-1 ~]# vim /usr/local/nginx/conf/nginx.conf

  location / {
            deny 192.168.113.132;          //客户端IP
            allow all;
            root   html;
            index  index.html index.htm;
        }

deny 192.168.113.132表示这个ip地址访问会被拒绝,其他IP客户端正常访问。

(2).重启服务器访问网址,页面已经访问不到。

[root@centos7-1 ~]# systemctl restart nginx.service 

未分类

未分类

要注意的是如果是用域名访问网页,需要配置DNS域名解析服务器,详细步骤参考https://www.linuxidc.com/Linux/2018-11/155523.htm。

Nginx 服务器日志配置 – 解决使用CDN记录真实用户IP(包括宝塔)

关于nginx的日志配置,之前写过一:http://www.vuln.cn/2989 ,今天又用到发现其中还有很多点没有提及到,所以再写一篇记录全面一点。

修改的配置文件位置

该怎么改网上的教程已经很多,但是到底在哪个文件的什么地方修改,如何生效这才是根本问题。

我们需要修改是的nginx.conf 根配置文件

使用lnmp的服务器一般配置文件在:/usr/local/nginx/conf/nginx.conf,宝塔的配置文件我发现有两个地方有nginx.conf配置文件:/www/server/nginx/conf/nginx.conf/www/server/nginx/src/conf/nginx.conf。有效的配置文件是:/www/server/nginx/conf/nginx.conf

在以下位置添加修改的配置:

未分类

其中需要注意的是log_format main,这里的main,需要在vhost中各站点的日志配置的地方要带上,表示使用这个配置,如图:

未分类

修改以上两个位置即可让配置生效。

参数配置

通过抓包可以看到,cdn节点在溯源请求服务器地址的时候会将用户客户端的ip带上,以X-Forwarded-ForCF-Connecting-IP两个参数的形式,如图:

未分类

如果客户端加一个x-forwarded-for参数来尝试篡改,实际上该参数会有两个ip,后面的一个ip永远是真实ip,前面是伪造的:“8.8.8.8,163.160.73.253”

所以在配置日志参数时,可以将X-Forwarded-For参数的值放到第一位,nginx模块中的变量为$http_x_forwarded_for,所以最终的格式为以下,其中保留了原始的$remote_addr,否则真实ip请求网站无法获取ip。

 log_format  main  '"$http_x_forwarded_for"-$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"'; 

综合以上的配置,最终日志格式完美记录真实ip与cdn节点的ip:

未分类

网站(Nginx)配置 HTTPS 完整过程

配置站点使用 https,并且将 http 重定向至 https。

1. nginx 的 ssl 模块安装

查看 nginx 是否安装 http_ssl_module 模块。

$ /usr/local/nginx/sbin/nginx -V

如果出现 configure arguments: --with-http_ssl_module, 则已安装(下面的步骤可以跳过,进入 nginx.conf 配置)。

下载 nginx 安装包 http://nginx.org/download/nginx-1.14.1.tar.gz

# 下载安装包到 src 目录
$ cd /usr/local/src
$ wget http://nginx.org/download/nginx-1.14.1.tar.gz

解压安装包。

$ tar -zxvf nginx-1.14.1.tar.gz

配置 ssl 模块。

$ cd nginx-1.14.1
$ ./configure --prefix=/usr/local/nginx --with-http_ssl_module

使用 make 命令编译(使用make install会重新安装nginx),此时当前目录会出现 objs 文件夹。

用新的 nginx 文件覆盖当前的 nginx 文件。

$ cp ./objs/nginx /usr/local/nginx/sbin/

再次查看安装的模块(configure arguments: --with-http_ssl_module说明ssl模块已安装)。

$ /usr/local/nginx/sbin/nginx -V

nginx version: nginx/1.14.1
...
configure arguments: --with-http_ssl_module

2. ssl 证书部署

这里使用的是阿里云的免费证书,期限为1年,申请地址https://common-buy.aliyun.com/?spm=5176.2020520154.0.0.45d356a7FlPIts&commodityCode=cas#/buy(如果需要更长时间的或者其他证书可能需要购买,这里提供下阿里的优惠活动购物车优惠卷(https://promotion.aliyun.com/ntms/act/shoppingcart.html?userCode=znftgj11)~)。

  • 下载申请好的 ssl 证书文件压缩包到本地并解压(这里是用的 pem 与 key 文件,文件名可以更改)。
  • 在 nginx 目录新建 cert 文件夹存放证书文件。
$ cd /usr/local/nginx
$ mkdir cert

将这两个文件上传至服务器的 cert 目录里。

这里使用 mac 终端上传至服务器的 scp 命令(这里需要新开一个终端,不要使用连接服务器的窗口):

$ scp /Users/yourname/Downloads/ssl.pem [email protected]:/usr/local/nginx/cert/
$ scp /Users/yourname/Downloads/ssl.key [email protected]:/usr/local/nginx/cert/
scp [本地文件路径,可以直接拖文件至终端里面] [<服务器登录名>@<服务器IP地址>:<服务器上的路径>]

3. nginx.conf 配置

编辑 /usr/local/nginx/conf/nginx.conf 配置文件:

配置 https server。

注释掉之前的 http server 配置,新增 https server:

server {
    # 服务器端口使用443,开启ssl, 这里ssl就是上面安装的ssl模块
    listen       443 ssl;
    # 域名,多个以空格分开
    server_name  baidu.com www.baidu.com;

    # ssl证书地址
    ssl_certificate     /usr/local/nginx/cert/ssl.pem;  # pem文件的路径
    ssl_certificate_key  /usr/local/nginx/cert/ssl.key; # key文件的路径

    # ssl验证相关配置
    ssl_session_timeout  5m;    #缓存有效期
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;    #加密算法
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;    #安全链接可选的加密协议
    ssl_prefer_server_ciphers on;   #使用服务器端的首选算法

    location / {
        root   html;
        index  index.html index.htm;
    }
}

将 http 重定向 https

server {
    listen       80;
    server_name  baidu.com www.baidu.com;
    return 301 https://$server_name$request_uri;
}

4. 重启 nginx

$ /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf

如果 80 端口被占用,用kill [id]来结束进程:

# 查看端口使用
$ netstat -lntp

Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      21307/nginx: master 
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      3072/sshd           
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      21307/nginx: master 

# 结束 80 端口进程
$ kill 21307

再次重启 nginx :

$ /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf

无信息提示就成功啦~

Nginx请求处理流程你了解吗?

本文主要介绍了nginx的11个处理阶段和lua的8个处理阶段,并说明了nginx和lua运行阶段的对应关系。

一、nginx 11 个处理阶段

nginx实际把http请求处理流程划分为了11个阶段,这样划分的原因是将请求的执行逻辑细分,以模块为单位进行处理,各个阶段可以包含任意多个HTTP模块并以流水线的方式处理请求。这样做的好处是使处理过程更加灵活、降低耦合度。这11个HTTP阶段如下所示:

1)NGX_HTTP_POST_READ_PHASE:

接收到完整的HTTP头部后处理的阶段,它位于uri重写之前,实际上很少有模块会注册在该阶段,默认的情况下,该阶段被跳过。

2)NGX_HTTP_SERVER_REWRITE_PHASE:

URI与location匹配前,修改URI的阶段,用于重定向,也就是该阶段执行处于server块内,location块外的重写指令,在读取请求头的过程中nginx会根据host及端口找到对应的虚拟主机配置。

3)NGX_HTTP_FIND_CONFIG_PHASE:

根据URI寻找匹配的location块配置项阶段,该阶段使用重写之后的uri来查找对应的location,值得注意的是该阶段可能会被执行多次,因为也可能有location级别的重写指令。

4)NGX_HTTP_REWRITE_PHASE:

上一阶段找到location块后再修改URI,location级别的uri重写阶段,该阶段执行location基本的重写指令,也可能会被执行多次。

5)NGX_HTTP_POST_REWRITE_PHASE:

防止重写URL后导致的死循环,location级别重写的后一阶段,用来检查上阶段是否有uri重写,并根据结果跳转到合适的阶段。

6)NGX_HTTP_PREACCESS_PHASE:

下一阶段之前的准备,访问权限控制的前一阶段,该阶段在权限控制阶段之前,一般也用于访问控制,比如限制访问频率,链接数等。

7)NGX_HTTP_ACCESS_PHASE:

让HTTP模块判断是否允许这个请求进入Nginx服务器,访问权限控制阶段,比如基于ip黑白名单的权限控制,基于用户名密码的权限控制等。

8)NGX_HTTP_POST_ACCESS_PHASE:

访问权限控制的后一阶段,该阶段根据权限控制阶段的执行结果进行相应处理,向用户发送拒绝服务的错误码,用来响应上一阶段的拒绝。

9)NGX_HTTP_TRY_FILES_PHASE:

为访问静态文件资源而设置,try_files指令的处理阶段,如果没有配置try_files指令,则该阶段被跳过。

10)NGX_HTTP_CONTENT_PHASE:

处理HTTP请求内容的阶段,大部分HTTP模块介入这个阶段,内容生成阶段,该阶段产生响应,并发送到客户端。

11)NGX_HTTP_LOG_PHASE:

处理完请求后的日志记录阶段,该阶段记录访问日志。

以上11个阶段中,HTTP无法介入的阶段有4个:

3)NGX_HTTP_FIND_CONFIG_PHASE

5)NGX_HTTP_POST_REWRITE_PHASE

8)NGX_HTTP_POST_ACCESS_PHASE

9)NGX_HTTP_TRY_FILES_PHASE

剩余的7个阶段,HTTP模块均能介入,每个阶段可介入模块的个数也是没有限制的,多个HTTP模块可同时介入同一阶段并作用于同一请求。

HTTP阶段的定义,包括checker检查方法和handler处理方法,如下所示

typedef structngx_http_phase_handler_s ngx_http_phase_handler_t;/*一个HTTP处理阶段中的checker检查方法,仅可以由HTTP框架实现,以此控制HTTP请求的处理流程*/typedef ngx_int_t(*ngx_http_phase_handler_pt)(ngx_http_request_t *r, ngx_http_phase_handler_t*ph);/*由HTTP模块实现的handler处理方法*/typedef ngx_int_t(*ngx_http_handler_pt)(ngx_http_request_t *r);

struct ngx_http_phase_handler_s {    /*在处理到某一个HTTP阶段时,HTTP框架将会在checker方法已实现的前提下首先调用checker方法来处理请求,
    而不会直接调用任何阶段中的hanlder方法,只有在checker方法中才会去调用handler方法,因此,事实上所有
    的checker方法都是由框架中的ngx_http_core_module模块实现的,且普通模块无法重定义checker方法*/
    ngx_http_phase_handler_pt  checker;    /*除ngx_http_core_module模块以外的HTTP模块,只能通过定义handler方法才能介入某一个HTTP处理阶段以处理请求*/
    ngx_http_handler_pt        handler;    /*将要处理的下一个HTTP处理阶段的序号
    next的设计使得处理阶段不必按顺序依次执行,既可以向后跳跃数个阶段继续执行,也可以跳跃到之前的某个阶段重新
    执行,通常,next表示下一个处理阶段中的第1个ngx_http_phase_handler_t处理方法*/
    ngx_uint_t                 next;
};

一个http{}块解析完毕后,将会根据nginx.conf中的配置产生由ngx_http_phase_handler_t组成的数组,在处理HTTP请求时,一般情况下这些阶段是顺序向后执行的,但ngx_http_phase_handler_t中的next成员使得它们也可以非顺序地执行,ngx_http_phase_engine_t结构体就是所有ngx_http_phase_handler_t组成的数组,如下所示:

typedef struct {    /*handlers是由ngx_http_phase_handler_t构成的数组首地址,它表示一个请求可能经历的所有ngx_http_handler_pt处理方法*/
    ngx_http_phase_handler_t  *handlers;    /*表示NGX_HTTP_SERVER_REWRITE_PHASE阶段第1个ngx_http_phase_handler_t处理方法在handlers数组中的序号,用于在执行
    HTTP请求的任何阶段中快速跳转到HTTP_SERVER_REWRITE_PHASE阶段处理请求*/
    ngx_uint_t                 server_rewrite_index;    /*表示NGX_HTTP_PREACCESS_PHASE阶段第1个ngx_http_phase_handler_t处理方法在handlers数组中的序号,用于在执行
    HTTP请求的任何阶段中快速跳转到NGX_HTTP_PREACCESS_PHASE阶段处理请求*/
    ngx_uint_t                 location_rewrite_index;
} ngx_http_phase_engine_t;

可以看到,ngx_http_phase_engine_t中保存了在当前nginx.conf配置下,一个用户请求可能经历的所有ngx_http_handler_pt处理方法,这是所有HTTP模块可以合作处理用户请求的关键,这个ngx_http_phase_engine_t结构体保存在全局的ngx_http_core_main_conf_t结构体中,如下:

typedef struct {
    ngx_array_t                servers;         /* ngx_http_core_srv_conf_t */
    /*由下面各阶段处理方法构成的phases数组构建的阶段引擎才是流水式处理HTTP请求的实际数据结构*/
    ngx_http_phase_engine_t    phase_engine;
    ngx_hash_t                 headers_in_hash;
    ngx_hash_t                 variables_hash;
    ngx_array_t                variables;       /* ngx_http_variable_t */
    ngx_uint_t                 ncaptures;
    ngx_uint_t                 server_names_hash_max_size;
    ngx_uint_t                 server_names_hash_bucket_size;
    ngx_uint_t                 variables_hash_max_size;
    ngx_uint_t                 variables_hash_bucket_size;
    ngx_hash_keys_arrays_t    *variables_keys;
    ngx_array_t               *ports;
    ngx_uint_t                 try_files;       /* unsigned  try_files:1 */
    /*用于在HTTP框架初始化时帮助各个HTTP模块在任意阶段中添加HTTP处理方法,它是一个有11个成员的ngx_http_phase_t数组,
    其中每一个ngx_http_phase_t结构体对应一个HTTP阶段,在HTTP框架初始化完毕后,运行过程中的phases数组是无用的*/
    ngx_http_phase_t           phases[NGX_HTTP_LOG_PHASE + 1];
} ngx_http_core_main_conf_t;

在ngx_http_phase_t中关于HTTP阶段有两个成员:phase_engine和phases,其中phase_engine控制运行过程中的一个HTTP请求所要经过的HTTP处理阶段,它将配合ngx_http_request_t结构体中的phase_handler成员使用(phase_handler制定了当前请求应当执行哪一个HTTP阶段);而phases数组更像一个临时变量,它实际上仅会在Nginx启动过程中用到,它的唯一使命是按照11个阶段的概率初始化phase_engine中的handlers数组。

typedef struct {    /*handlers动态数组保存着每一个HTTP模块初始化时添加到当前阶段的处理方法*/
    ngx_array_t                handlers;
} ngx_http_phase_t;

在HTTP框架的初始化过程中,任何HTTP模块都可以在ngx_http_module_t接口的postconfiguration方法中将自定义的方法添加到handler动态数组中,这样,这个方法就会最终添加到phase_engine动态数组中。

二、nginx lua 8个阶段

init_by_lua                         http
set_by_lua                         server, server if, location, location if
rewrite_by_lua                   http, server, location, location if
access_by_lua                    http, server, location, location if
content_by_lua                  location, location if
header_filter_by_lua          http, server, location, location if
body_filter_by_lua             http, server, location, location if
log_by_lua                         http, server, location, location if

1)init_by_lua:

在nginx重新加载配置文件时,运行里面lua脚本,常用于全局变量的申请。(例如:lua_shared_dict共享内存的申请,只有当nginx重起后,共享内存数据才清空,这常用于统计。)

2)set_by_lua:

流程分支处理判断变量初始化(设置一个变量,常用与计算一个逻辑,然后返回结果,该阶段不能运行Output API、Control API、Subrequest API、Cosocket API)

3)rewrite_by_lua:

转发、重定向、缓存等功能 (例如特定请求代理到外网,在access阶段前运行,主要用于rewrite)

4)access_by_lua:

IP准入、接口权限等情况集中处理(例如配合iptable完成简单防火墙,主要用于访问控制,能收集到大部分变量,类似status需要在log阶段才有。这条指令运行于nginx access阶段的末尾,因此总是在 allow 和 deny 这样的指令之后运行,虽然它们同属 access 阶段。)

5)content_by_lua:

内容生成,阶段是所有请求处理阶段中最为重要的一个,运行在这个阶段的配置指令一般都肩负着生成内容(content)并输出HTTP响应。

6)header_filter_by_lua:

应答HTTP过滤处理,一般只用于设置Cookie和Headers等,该阶段不能运行Output API、Control API、Subrequest API、Cosocket API(例如添加头部信息)。

7)body_filter_by_lua:

应答BODY过滤处理(例如完成应答内容统一成大写)(一般会在一次请求中被调用多次, 因为这是实现基于 HTTP 1.1 chunked 编码的所谓“流式输出”的,该阶段不能运行Output API、Control API、Subrequest API、Cosocket API)

8)log_by_lua:

会话完成后本地异步完成日志记录(日志可以记录在本地,还可以同步到其他机器)(该阶段总是运行在请求结束的时候,用于请求的后续操作,如在共享内存中进行统计数据,如果要高精确的数据统计,应该使用body_filter_by_lua,该阶段不能运行Output API、Control API、Subrequest API、Cosocket API)

三、nginx和lua运行阶段的对应关系

1)init_by_lua,运行在initialization Phase;

2)set_by_lua,运行在rewrite 阶段;

set 指令来自 ngx_rewrite 模块,运行于 rewrite 阶段;

3)rewrite_by_lua 指令来自 ngx_lua 模块,运行于 rewrite 阶段的末尾

4)access_by_lua 指令同样来自 ngx_lua 模块,运行于 access 阶段的末尾;

deny 指令来自 ngx_access 模块,运行于 access 阶段;

5)content_by_lua 指令来自 ngx_lua 模块,运行于 content 阶段;不要将它和其它的内容处理指令在同一个location内使用如proxy_pass;

echo 指令则来自 ngx_echo 模块,运行在 content 阶段;

6)header_filter_by_lua 运行于 content 阶段,output-header-filter 一般用来设置cookie和headers;

7)body_filter_by_lua,运行于 content 阶段;

8)log_by_lua,运行在Log Phase 阶段;

如图:

未分类

Nginx 反向代理实现线上测试环境(微信开发类项目)

环境说明

  • Ubuntu 16.04 LTS
  • Nginx version: nginx/1.10.3 (Ubuntu)
  • PHP 7.1.18
  • Laravel 5.5

需求说明

微信开发类项目,需要要调试微信接口,本地开发上可以采用微信开发者工具和微信测试公众号模拟运行环境和接口。但有部分微信商户号的接口例如微信支付,目前需要用沙盒模拟的方式开发,不够方便。
因此,我们想要构建一个能用于微信开发项目的线上测试环境,能调取到真实微信公众号的接口方便线上测试。

思路

要构建线上测试环境最直接的想法就是额外购买一台硬件参数、环境配置与生产环境服务器完全一致的服务器。对接微信平台方面,还需额外注册一个开通微信认证的微信公众号。
这么做虽然能解决问题,但增加了额外成本,而且增加了服务器、微信账号等等的额外维护工作,对于我们这种初创技术团队来讲不是上策。

有没有更低成本和更便于系统维护迭代的方案呢?
经过一番折腾,我们摸索出如下方案,分享出来欢迎交流。

解决方案分享

问题的关键在于如何解决解决微信接入问题

做过微信开发的朋友都知道,接入微信,需要在微信公众号后台填写一个绑定服务器的备案域名

未分类

问题是 URL 这一项,可填值是唯一的且区分子域名。
例如:

http://www.project.com/login 指向生产环境
http://dev.project.com/login 指向线上测试环境
那么微信公众号只能选择接入一个域名。

我们采取Nginx反向代理的功能去解决,即当请求访问 http://www.project.com 这一域名,Nginx服务器通过路径规则匹配,实现请求转抛,以指向不同的项目目录。
例如:

http://www.project.com/login 指向生产环境
http://www.project.com/dev/login 指向线上测试环境

我们通过配置 Nginx 服务器,使得请求匹配到 /dev/ 则将请求转抛给测试环境下的项目去处理

下面是 Nginx 配置的代码实现

代理管理配置文件 /etc/nginx/sites-available/project.proxy.conf

server {
    listen 80;

    server_name 127.0.0.1 www.project.com;

    index index.html index.php;

    charset utf-8;
    access_log /var/log/nginx/project.proxy.access.log;
    error_log /var/log/nginx/project.proxy.error.log;

    # 生产环境
    location / {
        proxy_pass http://127.0.0.1:9001;
        proxy_set_header Host $host:$server_port;
    }

    # 线上测试环境
    location ^~ /dev/ {
        proxy_pass http://127.0.0.1:9002;
        proxy_set_header Host $host:$server_port;
    }
}

除了 Nginx 服务器的配置,还需要对项目的配置环境做设置。
主要是进入项目的请求加上统一前缀,这个不同框架有不同的实现,下面仅以 php 框架 Laravel 为例:
Laravel 项目下,
先在 .env 配置环境中增加变量 PREFIX

PREFIX=/dev  // 前面加 / 是为了解决后续静态资源处理的问题,算是一个小坑

然后修改 app/Providers/RouterServiceProvider.php 文件中的 mapWebRouters() :

protected function mapWebRoutes()
    {
        // 从配置文件中获取前缀
        $prefix = env("PREFIX") === "" ? "" : explode('/',env("PREFIX"))[1];
        Route::prefix($prefix) // 给路由添加统一前缀 
             ->middleware('web')
             ->namespace($this->namespace)
             ->group(base_path('routes/web.php'));
    }

实际部署中生产环境和线上测试环境的 .env 文件中的 PREFIX 分别配置为 “” 和 “/dev” 即可。

到这里,本以为万事俱备。

一访问却发现静态文件404。

排查后发现,问题在于静态文件引用路径,例如:

<script src="/dev/test/test.js"></script>

实际页面访问时,这个资源请求路径会被拼接上域名,即最终变为:

http://www.project.com/dev/test/test.js

而 Laravel 的静态资源全部放置在 public 目录下,由于添加了统一路由前缀,所以上面的 URL 并不会指向 public 目录所在的资源目录,而是被当作路由请求处理了…

最终我的处理方案是,修改 Nginx 配置,对请求的 URI 路径做规则校验,匹配到 .js 或 .css 结尾的请求,去除 URL 中的 /dev/ 字符串。

修改后的 /etc/nginx/sites-available/project.proxy.conf 配置代码如下:

server {
    listen 80;

    server_name 127.0.0.1 www.project.com;

    index index.html index.php;

    charset utf-8;
    access_log /var/log/nginx/project.proxy.access.log;
    error_log /var/log/nginx/project.proxy.error.log;

    # 生产环境
    location / {
        proxy_pass http://127.0.0.1:9001;
        proxy_set_header Host $host:$server_port;
    }

    # 线上测试环境
    location ^~ /dev/ {
        proxy_pass http://127.0.0.1:9002;
        # 静态资源过滤 /dev/
        if ($request_uri ~* .(?:js|css)$) {
                rewrite /dev/(.+)$ /$1 break;
        }
        proxy_set_header Host $host:$server_port;
    }
}

现在当访问

http://www.project.com/dev/test/test.js

请求转抛后,会被处理成

http://www.project.com/test/test.js

因此能正常访问到 Laravel 项目中的 public 资源目录

坑记录

过程中还是踩了不少的坑:

1.Nginx 服务器代理配置时

proxy_set_header Host $host:$server_port;

这行代码必不可少,否则初次访问 http://www.project.com/dev/login 能去到正确目录,但后续项目内所有请求都会被设置为 http://127.0.0.1:9002 开头…

2.项目中的静态资源引用路径也要改变,如:

<script src="/dev/test/test.js"></script>

上面的是编译后的运行代码,实际开发时的代码应该是( Laravel 框架的 blade 模板语法):

<script src="{{ env('PREFIX') }}/test/test.js"></script>

3.静态资源方面还存在的隐患,如果资源是异步引用的,那就凉凉。这种情况多出现在引用工具库,另外在引用图片等静态资源方面也会比较麻烦…

待优化问题

静态资源引用方面存在的隐患促使需要更优解决方案,目前想到的优化方案是:

  1. 静态资源全部采用 CDN 方式引用,避免路径问题。
  2. 样式 icon 全部采用 font-icon 方式,避免图标元素的路径问题。

总结

本文主要分享了单一服务器构建微信项目线上测试环境的方式(同一个公众号),主要有以下要点:

  1. 通过 Nginx 反向代理机制,实现请求分发
  2. 通过 项目环境配置,实现请求在项目内添加统一路由前缀
  3. 项目中静态资源路径问题导致的坑,对此的处理方法及优化思路

服务器部署前端&node 项目(包括阿里云服务器、nginx 以及 mongoDB 的配置)

服务器购买&配置

  1. 打开阿里云,选择购买云服务器 ECS,这里可以选择一键购买进行快速配置,操作系统选择 CentOS 7.2 64 位,其他默认或根据实际需求来,若选择自定义购买请自行搜索;
  2. 购买成功设置账号密码后,就可以通过 ftp 工具(我用的是 FileZilla)或者 git 连接我们的服务器了,这个时候我们也可以打开阿里云的控制台/云服务器 ECS 查看购买的服务器;
  3. 打开阿里云的控制台/云服务器 ECS/网络和安全/安全组,在安全组列表点击配置规则,点击快速创建规则,就可以暴露端口了。比如暴露 80 端口,选择 HTTP(80),授权对象填0.0.0.0/0,其他默认就可以了。暴露其他端口你就在自定义端口选择,比如暴露 7001 端口,你就在自定义端口选择 TCP,输入7001/7001即可。
  4. 开启node 性能平台,点击创建新应用按照操作提示来就行,成功开启后在项目配置(具体配置看下文))就可以监控数据了。

连接服务器

1.git 连接

# ssh remote_username[@remote_ip](/user/remote_ip) 然后输入密码即可

如果ssh不存在,执行以下命令即可
# yum install openssh-client 下载客户端ssh

2.ftp 工具连接(这里以 FileZilla 为例),下载 filezilla 后,点击新建站点,输入主机 ip,选择 sftp 协议,选择登录类型为正常,输入账号密码即可

部署 node 环境

1.部署 node 环境

# ssh remote_username[@remote_ip](/user/remote_ip) 连接服务器
# wget https://nodejs.org/dist/v6.9.5/node-v6.9.5-linux-x64.tar.xz 下载node压缩文件
# tar xvf node-v6.9.5-linux-x64.tar.xz 解压
# ln -s /root/node-v6.9.5-linux-x64/bin/node /usr/local/bin/node 创建软连接
# ln -s /root/node-v6.9.5-linux-x64/bin/npm /usr/local/bin/npm 创建软连接
# node -v 查看node版本
# npm -v 查看npm版本

2.其他

# yum install vim 下载vim

nginx 安装&配置

1.nginx 安装

# yum install epel-release 下载epel-release
# yun install nginx 下载nginx
# cd /etc/nginx
# vim nginx.conf 用vim打开nginx.conf

2.修改 nginx.conf

  • 修改 user 为 root
  • 修改 server 如下,这里 admin 是指向管理后台项目,app 是指向移动端项目,wx 是指向代理另一个 node 微信公众号项目(运行在 7002 端口,但微信公众号配置 http 只允许 80 端口,所以设置代理,我们的 elm 接口运行在 7001 不用代理)
server {
    listen       80 default_server;
    listen       [::]:80 default_server;
    server_name  _;
    root         /root/www/;

    # Load configuration files for the default server block.
    include /etc/nginx/default.d/*.conf;

    location /admin/ {
    # 为空即可,前端打包文件放在/root/www/admin里面就可访问到,注意默认是index.html
    }

    location /app/ {

    }

    location /wx/ {
        proxy_pass   http://127.0.0.1:7002/;
    }

    error_page 404 /404.html;
        location = /40x.html {
    }

    error_page 500 502 503 504 /50x.html;
        location = /50x.html {
    }
}

3.启动 nginx

nginx -t 测试nginx语法是否有误
nginx 启动nginx
nginx -s reload 重启nginx,修改nginx.conf后记得重启

4.其他命令

ps -ef | grep nginx 显示nginx进程
nginx -s stop 停止nginx
nginx -v 查看nginx版本

部署 mongodb

1.安装 mongodb

# ssh remote_username[@remote_ip](/user/remote_ip) 连接服务器
# curl -O https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.0.6.tgz 下载
# tar -zxvf mongodb-linux-x86_64-3.0.6.tgz 解压
# mkdir data 创建数据库文件夹
# touch mongodb.log 创建日志文件
# cd /usr/local/mongodb/bin
# ./mongod -dbpath=/usr/local/mongodb/data -logpath=/usr/local/mongodb/mongodb.log -logappend -port=27017 -fork 注意fork是后台启动,避免又要再开窗口重新连接服务器再能进行其他操作
# ./mongo 连接mongodb

2.配置随 linux 启动

在/etc/rc.local 添加如下即可:

# rm /usr/local/mongodb/data/mongod.lock 停止可能在运行的mongo
# /.../bin/mongod -dbpath /usr/local/mongodb/data -logpath /usr/local/mongodb/mongodb.log -logappend -fork -port 27017

3.设置权限

# cd /usr/local/mongodb/bin
# ./mongod -dbpath=/usr/local/mongodb/data -logpath=/usr/local/mongodb/mongodb.log -logappend -port=27017 -fork
# ./mongo
> use admin
> db.createUser(
>  {
>    user: "root",
>    pwd: "123456",
>    roles: [ { role: "root", db: "admin" } ]
> }
> )
> db.shutdownServer();
# ./mongod -dbpath=/usr/local/mongodb/data -logpath=/usr/local/mongodb/mongodb.log -logappend -port=27017 -fork --auth
# db.auth("root","123456")

4.项目中连接 mongodb(这里以 koa 框架 egg 项目为例,其他 node 请自行查找)

# cnpm i egg-mongoose -S

// config/plugin.js
exports.mongoose = {
  enable: true,
  package: 'egg-mongoose',
}

// config/config.default.js
config.mongoose = {
    url: 'mongodb://127.0.0.1/eggadmin',
    options: {
        // 如果设置了密码
        // auth: { "authSource": "admin" },
        // user: "root",
        // pass: "123456",
    }
}

部署 node 项目

部署环境 阿里云 CentOS 7.2 64 位

1.本地项目根目录(删除 node_modules,建议依赖在服务器下载)

# tar -zcvf ../file_name.tgz . 打包
# scp ../file_name.tgz remote_username[@remote_ip](/user/remote_ip):/root/www/server 上传到服务器

2.服务器

# ssh remote_username[@remote_ip](/user/remote_ip) 连接服务器
# cd /root/www
# mkdir server 这里创建server文件夹放node项目代码
# cd server
# tar -zxvf file_name.tgz . 解压
# cnpm install --production 安装生产环境依赖

1. koa项目(express项目类似)
# cnpm i -g pm2 下载pm2
# pm2 start bin/www 守护进程启动
# pm2 restart app_name|app_id 重启
# pm2 stop app_name|app_id 停止
# pm2 list 查看进程状态
# pm2 stop all 停止所有应用
# pm2 start ./bin/www --watch 监听更改自动重启

2. egg项目
# npm start 运行
# npm stop 停止

3.阿里 node 性能平台监控

1)koa 项目(express 项目类似)

# wget -O- https://raw.githubusercontent.com/aliyun-node/tnvm/master/install.sh | bash 安装版本管理工具 tnvm
# source ~/.bashrc
# tnvm ls-remote alinode 查看需要的版本
# tnvm install alinode-v3.11.4 安装需要的版本
# tnvm use alinode-v3.11. 使用需要的版本

新建 config.json 文件如下,从node 性能平台获取对应的接入参数

{
 "appid": "<YOUR APPID>",
 "secret": "<YOUR SECRET>"
 }
# cnpm install [@alicloud](/user/alicloud)/agenthub -g 安装 agenthub
# agenthub start config.json 启动agenthub
# agenthub list 查看 agenthub 列表
# ENABLE_NODE_LOG=YES pm2 start bin/www 使用pm2管理的应用

2)egg 项目

# cnpm i nodeinstall -g
# nodeinstall --install-alinode ^3
# cnpm i egg-alinode --save
# npm start
// config/plugin.js
exports.alinode = {
  enable: true,
  package: 'egg-alinode',
};

// config/config.default.js
config.alinode = {
  appid: '<YOUR_APPID>',
  secret: '<YOUR_SECRET>',
};

部署前端项目(这里以 vue 为例)

在项目根目录npm run build,然后把 dist 文件夹里的内容传到服务器,这里我们把两个项目分部传到/root/www/app 和/root/www/admin,记得提前创建 app 和 admin 文件夹