构建机房运维基础架构(十): 如何实现Nginx和程序的100%无损发布

我们的负载均衡架构是这样的:

请求 —> 网络 —> LVS集群 —> Nginx 集群 —> APP

这里,LVS、Nginx 和APP(后端程序) 都需要发布,LVS和 Nginx 一般是修改配置文件,APP则是程序更新。对于LVS,我们是用模板生成配置,然后reload,reload 的时候LVS会把当前请求继续处理完,而且LVS改的频率较小,所以问题不大。

主要看一下Nginx 和 APP 如何做到100%无损发布,我觉得理想的方式是发布Nginx 的时候把Nginx 从LVS上T掉,发布APP 的时候从Nginx上把 APP T掉,T掉通过「健康检查」的方式,而不是修改配置reload 。

 

Nginx发布

事实上,LVS支持 HTTP 的检查方式,我们只要在 Nginx 端 创建一个专门用于检测的文件。

LVS的 HTTP_GET 指令用于实现HTTP CHECK,解释如下:

HTTP_GET: Working at layer5. Performs a GET HTTP to a specified URL. The get result is
then summed using the MD5 algorithm. If this sum does not match with the expected value,
the test is wrong and the server is removed from the server pool. This module implements a
multi-URL get check on the same service. This functionality is useful if you are using a server
hosting more than one application server. This functionality gives you the ability to check if an
application server is working properly. The MD5 digests are generated using the genhash
utility (included in the keepalived package).

同样,LVS 支持 SSL_GET 的方式,用于 检查后端的 443 端口。

SSL_GET: Same as HTTP_GET but uses a SSL connection to the remote webservers. 

这里,我先配置Nginx,Nginx配置文件目录 是 /home/work/nginx/conf ,我把检查的配置写在了default配置里,LVS检查请求的Host 是 Nginx的内网IP,所以要保证没有其他配置文件的server_name 里面包含 Nginx内网IP。

server {
    listen 80 default_server;
    listen 443 ssl default_server;

    # Do not delete it, otherwise lvs http check will fail, it’s terrible!!!
    location /http_check/index.html {
        root /home/work/nginx/conf/;
        open_file_cache off;
    }

    location /nginx_status {
        stub_status on;
        access_log off;
        allow 10.0.0.0/8;
        deny all;
    }
}

在这里,我们用 open_file_cache off; 把文件缓存关掉,为了不让缓存影响 LVS 的检测;另外要注意,listen 443 的时候要启用 ssl,不然可能会有严重的问题,我遭受过惨烈的 教训

在Nginx 上:
cd /home/work/nginx/conf/

$ cat http_check/index.html

Do not change this file, otherwise lvs http check will fail, it’s terrible!!!

http check ok!

$ md5sum http_check/index.html
2a94d9d1703ca63c35b24fc7a41d89e1  http_check/index.html
在LVS上用 genbash 检查(10.19.29.3是Nginx IP):
$ genhash -s 10.19.29.3 -p 80 -u /http_check/index.html
MD5SUM = b4ec1928222cfee3512e328ad5c98be4

MD5之后的值要写在LVS配置文件里,如下:

delay_loop 6

real_server 10.19.29.3 80 {
weight 100
HTTP_GET(或者 SSL_GET) {
            url {
              path /http_check/index.html
              digest 2a94d9d1703ca63c35b24fc7a41d89e1
            }
            connect_timeout 3
            nb_get_retry 3
            delay_before_retry 3
}
}

解释下下面三个指令的含义:
delay_loop :specify in seconds the interval between checks
nb_get_retry :maximum number of retries
delay_before_retry :delay between two successive retries

Nginx 的http_check/index.html 删掉之后,等待15s LVS就会把Nginx T掉了;同样 http_check/index.html 恢复之后,等15s 也足够了。

 

APP发布

Nginx 的健康检查我们用 ngx_http_upstream_check_module 模块来做, 文档(我们用的是Tengine):
http://tengine.taobao.org/document_cn/http_upstream_check_cn.html

用法类似:
upstream test-nodes {
server pxe0.hy01:8888;
server pxe1.hy01:8888;

    check interval=3000 rise=3 fall=2 timeout=1000 type=http port=1023;’
    check_keepalive_requests 100;
    check_http_send “HEAD / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n”;
    check_http_expect_alive http_2xx http_3xx;
}

check_keepalive_requests 指令表示一共连接检查多少次之后关闭,默认是 1,所以改大点比较好(但是我在线上配置的时候发现没有这个指令 !! )。

我们可以在后端APP(比如pxe0.hy01 和 pxe1.hy01 ) 起一个单独的服务,端口 1023 ,专门用作健康检查,检查失败Nginx 即把后端机器 T掉。

这个服务越轻量越稳定越好,甚至可以用精简版Nginx ,虽然看起来很挫,但很work。

另外要注意的两点:

1.如果后端用 lighttpd 做检测,check_http_send 需要加 Host,因为 lighttpd 对长连接要求有 Host 头(HTTP1.1 规范,不知道 Nginx 是否也是这样),不然报 400 bad request ,check_http_send 可以这么写:

     check_http_send “HEAD / HTTP/1.1\r\nConnection: keep-alive\r\nHost:nginx.check\r\n\r\n”;

自己把 nginx.check 替换成想要的 Host 头就行了。

2.我在把这段配置正式上线的时候遇到了一个坑,导致线上所有服务都挂了一下,我在加上配置 reload 的时候 Nginx 开始启用新检查方式,而在新的检查方式成功之前,Nginx 认为后端都是不可用的,会报  502 Bad Gateway ,好惨烈的坑,所以最佳方式是 先 stop 后 start 。

 

构建机房运维基础架构(九): NGINX配置文件的管理和发布

我们的 Nginx 配置现在是用 git 管理的, 开发可以自主修改然后给 sa review,过了之后发测试环境,然后发正式环境。

 

一开始我们的所有域名的配置文件都是放在一起的,所有的 Nginx 机器配置文件也一样,这样有几个问题:

  1. 产品线之间互相有影响,比如一个产品线流量徒增可能会影响其他产品线;

  2. 每个机房都有一份单独的 Nginx git 仓库,不便于管理;

  3. 内网域名和外网域名混在一起,事实上改 HOSTS 可以通过 外网域名的 IP 访问内网域名,有安全风险。

 

为了解决这三个问题,我们对 Nginx 配置文件进行了分拆,定义了三个维度:

  1. 产品线(比如 apps );

  2. 内网或者外网(internal 和 external );

  3. 机房(比如 hy );

 

流程是这样:

1.所有机房有一份 Nginx 配置,每次发布的时候会 git pull ;

2.发布一台 Nginx 机器,找到这个 Nginx 在运维系统里对应的 path,比如 /sre/nginx/apps/internal/hy,这个 path 能标识唯一的 产品线/[内网|外网]/机房,然后把 git 仓库 sites-available/产品线/[内网|外网]/机房 里面的真正配置 软链到 sites-enabled 下面(sites-enabled 目录下的配置由 nginx.conf 加载);

3.对于 日志格式,每个域名可能会自定义日志格式,所以新建了 log_format-available 目录用于日志格式,和 sites-available 类似;

4.对于 upstream nodes,可以自动生成,只要定义格式就行了,具体的机器也从运维系统里拉取,我线上用的 upstream 模板是:

{% for _data in data %}
upstream {{_data["name"]}} {
  {% if _data["ip_hash"] == 1 %}
  ip_hash;
  {% endif %}

  {% for _server in _data["servers"] %}
  server {{_server["hostname"]}}:{{_data["port"]}} weight={{_server["weight"]}};
  {% endfor %}

  check interval=3000 rise=2 fall=3 timeout=1000 type=http port=1023;
    check_http_send "HEAD /index.html HTTP/1.1\r\nConnection: keep-alive\r\nHost:nginx.check\r\n\r\n";
    check_http_expect_alive http_2xx http_3xx;
}
{% endfor %}

这里我们用到了 ngx_http_upstream_check_module 模块,对后端采用 七层检测 来判断服务是否存活;另外还加了 ip_hash 的选项 (代码)。

5.打包,返回 Nginx 配置包的下载链接;

 

事实上,上面的 5 步 都是在 Nginx 机器上通过 API 获取的(需要写一个简单的后台服务),传入参数是 Nginx 机器名。

 

下面几步继续在 Nginx 机器上执行。

6.通过 链接 下载 配置包;

7.删除 用于 LVS HTTP CHECK 的标志文件,sleep 15 秒;

和 Nginx 的七层检测一样, LVS 通过 HTTP CHECK 检查 Nginx 是否存活,删除标志文件后,LVS 自然把 Nginx T 掉(具体看 这里 )。

8.停止 Nginx (此时已经没请求过来);

9.删除老配置文件,解压新配置文件;

10.检查 Nginx 配置是否有语法错误;

11.启动 Nginx,sleep 15 秒,sleep 是为了让 Nginx 先检查后端的服务存活,否则如果 LVS 的请求过来会失败;

12.创建 用于 LVS HTTP CHECK 的标志文件;

13.sleep 15 秒,让 LVS 检查 Nginx 存活。

 

这个流程还有一个好处:

发布过程 100% 无损。