后浪笔记一零二四

1. 七层反向代理

主要的指令:

Syntax:  upstream name {...}
Default: --
Context: http

Syntax:  server address [parameters];
Default: --
Context: upstream
  1. server指令的通用参数: backup: 指定当前server为备份服务,仅当非备份server不可用时,请求才会转发到该server down: 标识某台服务已经下线,不再服务

  2. upstream模块提供的变量(不含cache)

upstream_addr:  上游服务器的IP地址,格式为可读的字符串,例如127.0.0.1:8012
upstream_connect_time:  与上游服务建立连接消耗的时间,单位为秒,精确到毫秒
upstream_header_time: 接受上游服务发回响应中http头部所消耗的时间,单位为妙,精确到毫秒
upstream_response_time: 接受完整的上游服务响应所消耗的时间,单位为妙,精确到毫秒
upstream_http_名称:从上游服务返回的响应头部的值
upstream_bytes_received: 从上游服务接受到的响应长度,单位为字节
upstream_response_length: 从上游服务返回的响应包体长度,单位为字节
upstream_status: 上游服务返回的HTTP响应中的状态码。如果未连接上,该变量值为502
upstream_cookie_名称:从上游服务发回的响应头Set-Cookie中取出的cookie的值
upstream_trailer_名称:从上游服务的响应尾部取到的值

1.1 对上游服务建立keepalive连接的ngx_http_upstream_keepalive_module

  1. 模块:ngx_http_upstream_keepalive_module,默认编译进nginx,通过--without-http_upstream_keepalive_module移除

  2. 指令:

Syntax:   keepalive connections;
Default:  --
Context:  upstream
功能: 向upstream配置的这组上游服务中最多保持多少个空闲的tcp连接,用于keepalive请求

1.15.3非稳定版本新增指令:
Syntax:   keepalive_requests number;
Default:  keepalive_requests 100;
Context:  upstream
功能: 在一条TCP连接上最多可以跑多少个请求

Syntax:   keepalive_timeout timeout;
Default:  keepalive_timeout 60s;
Context:  upstream
功能: 一个TCP连接在处理完一个keepalive请求之后,最多等待60s,如果没有第二个http请求,就关闭该TCP连接。
  1. 指定上游服务域名解析的resolver指令:
Syntax:  resolver address ... [valid=time] [ipv6=on|off]
Default: --
Context: http,server,location

Syntax:  resolver_timeout time;
Default: resolver_timeout 30s;
Context: http,server,location
  1. 测试: 上游应用服务的配置(使用nginx来模拟上游服务):
server {
    listen 8011;
    default_type text/plain;
    return 200 '8011 server response.\n';
}
server {
    listen 8012
    default_type text/plain;
    return 200 '8012 server response.\n';
}

nginx作为7层反向代理的配置:

upstream rrups {
    server 127.0.0.1:8011 weight=2 max_conns=2 max_fails=2 fail_timeout=5;
    server 127.0.0.1:8012;
    keepalive 32;
}
server  {
    server_name rrups.taohui.tech;
    error_log myerror.log info;

    location / {
        proxy_pass http://rrups;
        #http1.0是不支持keepalive的,为了防止浏览器发来的请求是使用http 1.0的,
        #我们需要重置http版本,以启用keepalive功能。
        proxy_http_version 1.1; 
        #浏览器发来的请求中的Connection头部的值可能为close,
        #所以需要使用下面的指令重置下
        proxy_set_header Connection "";
    }
}

因为使用keepalive,所以上游服务处理完请求之后,并不会关闭该连接,可以使用tcpdump来验证:

1
2
3
4
5
6
$ tcpdump -i lo port 8011
# 另起一个终端,访问
$ curl rrups.taohui.tech
8011 server response
$ curl rrups.taohui.tech
8011 server response

查看tcpdump抓取的报文,可以发现,这两次连接,并没有出现FIN包,所以连接没有关闭。

1.2 负载均衡算法

1.2.1 Round-Robin算法

Round-Robin负载均衡算法是默认集成在upstream框架中,使用server指令的参数来配置。 它提供了如下4个参数:

  1. weight: 服务访问的权重,默认是1
  2. max_conns: server的最大并发连接数,仅作用于单worker进程。默认是0,表示没有限制。
  3. max_fails: 在fail_timeout时间段内,最大的失败次数。当达到最大失败时, 会在fail_timeout秒内这台server不允许再次被选择
  4. fail_timeout:

    单位为秒,默认值为10秒。具有2个功能: 指定一段时间内,最大的失败次数max_fails。 到达max_fails后,该server不能访问的时间。

1.2.2 最小连接优先负载均衡算法

默认编译进nginx,通过--without-http_upstream_ip_hash_module禁用。

语法:

Syntax:   least_conn;
Default:  --
Context:  upstream

1.2.3 哈希算法

  1. ngx_http_upstream_ip_hash_module模块 默认编译进nginx,通过--without-http_upstream_ip_hash_module禁用模块。

功能:以客户端的IP地址作为hash算法的关键字,映射到特定的上游服务器中。 > 对IPV4地址使用前3个字节作为关键字,对IPV6则使用完整地址 > 可以基于realip模块修改用于执行算法的IP地址

语法:

Syntax:   ip_hash;
Default:  --
Context:  upstream
  1. upstream_hash模块 默认编译进nginx,通过--without-http_upstream_ip_hash_module禁用。

功能: 和ip_hash模块的区别是,它的key可以是任意的,它的值可以包含变量、字符串。

语法:

Syntax:  hash key [consistent];
Default: --
Context: upstream

示例:

upstream iphashups {
    #ip_hash;
    # $arg_username表示uri问号后面的username参数的值
    hash user_$arg_username;
    #由于使用hash模块,所以下面的weight参数不会生效。
    server 127.0.0.1:8011 weight=2 max_conns=2 max_fails=2 fail_timeout=5;
    server 127.0.0.1:8012 weight=1;
}

server {
    set_real_ip_from 116.62.160.193;
    real_ip_recursive on;
    real_ip_header X-Forwarded-For;
    server_name iphash.taohui.tech;
    error_log myerror.log info;
    access_log logs/upstream_access.log varups;

    location / {
        proxy_pass http://iphashups;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}
  1. 一致性哈希算法 hash算法的问题:
# 使用hash算法:key % 5
key=5    ->      server0
key=6    ->      server1
key=7    ->      server2
key=8    ->      server3
key=9    ->      server4

# 若发生宕机或者扩容,hash算法会引发大量路由变更,
# 这会导致缓存大范围失效。
# 假设server4宕机了,这个时候hash算法就变成了: key % 4
key=5    ->      server1
key=6    ->      server2
key=7    ->      server3
key=8    ->      server0
key=9    ->      server1

一致性hash算法: 假设我们有k个机器(k是变化的),数据的hash值的范围是[0,MAX],hash值的范围相对是恒定的。 我们将整个范围分为m个小区间(m远大于k,当不划分区间的时候,相当于区间的宽度为1),每个机器负责m/k个小区间。 当有新机器加入的时候,我们需要选中某个比较繁忙的机器,假设它的小区间范围是[left, right]。 这时,我把[left, mid]分配给之前的机器,把(mid, right]分配给新加入的机器,这个时候, (mid, right]对应的请求会失效,但是这个范围是非常小的。

   node3                         node1
       \                         /
      hash         0 / 2^32   hash
    键     \         V         /
       \   (+)-*->-----*-->(+)   键
       hash |               *    /
          \ ^               v  hash
            *               | /
            |               *
            |               v
           (+)-<-**----<-**(+)
          /                  \
        hash                 hash
        /                      \
     node4                    node2

对node4进行扩容,因为它的负载比较繁忙
   node3                         node1
       \                         /
      hash         0 / 2^32   hash
    键     \         V         /
       \   (+)-*->-----*-->(+)   键
       hash |               *    /
          \ ^               v  hash
            *               | /
            |               *
            |               v
           (+)-<-**-(+)<***(+)
          /          |   ^   \
        hash       hash  |   hash
        /            |   |     \
     node4       node5   |     node2
                         |
                   只对这三个连接有影响,并迁移到node5下。

1.2.4 upstream_zone模块

默认编译进nginx,通过--without-http_upstream_zone_module禁用。

功能: 分配出共享内存,将其他upstream模块定义的负载均衡策略数据、运行时每个上游服务的状态数据存放在共享内存上,以对所有nginx worker进程生效。

语法:

Syntax:  zone name [size];
Default: --
Context: upstream

1.2.5 upstream模块间的顺序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
ngx_module_t *ngx_modules[] = {
    ... ...
    &ngx_http_upstream_hash_module,
    &ngx_http_upstream_ip_hash_module,
    &ngx_http_upstream_least_conn_module,
    &ngx_Http_upstream_random_module,
    &ngx_http_upstream_keepalive_module,
    &ngx_http_upstream_zone_module,
    ... ...
};

启动的时候,是从下至上的。

1.3 proxy模块

http反向代理的流程:

                      V                     +------>{接收响应头部}
     {处理content阶段: proxy_pass指令}       |              V
                      V                     |       {处理响应头部}
                     <?>----cache命中-------+-+            V proxy_buffering on
                      |                     | |           <?>---------->{接收完整的}
          cache未命中或未开启cache           | |            |off         {响应包体  }
                      V                     | |            V              /
     {根据指令生成发往上游的http头部及包体}    | +---->{发送响应头部}<-------
                      |                     |              |
  proxy_request_bu    V                     |              V
  +-ffering on ------<?>                    |       {发送响应包体}
  V                   |                     |       {边读包体边发}
{读取请求}            V                      |              |
{完整包体}  proxy_request_buffering off      |              V
  |                   V                     | +-关闭cache-<?>
  +--------->{根据负载均衡策略}               | |            |
             { 选择上游服务器 }               | |         打开cache
                      V                     ^ V            V
          {根据参数连接上游服务器}            | |     {包体加入缓存}
                      V                     | |            V
                {发送请求(边}-------->-----+ +-->{关闭或者复用连接}
                {读包体边发)}

1.3.1 proxy_pass指令

默认编译进nginx,通过--without-http_proxy_module禁用。

  1. 指令:
Syntax:   proxy_pass URL;
Default:  --
Context:  location,if in location,limit_except
  1. URL参数规则:
  • URL必须以http://或者https://开头,接下来是域名、IP、unix socket地址或者upstream的名字,前两者可以在域名或者IP后加端口。最后是可选的URI。
  • 当URL参数中携带URI与否,会导致发向上游请求的URL不同:

    不携带URI,则将客户端请求中的URL直接转发给上游 * location后使用正则表达式、@名字时,应采用这种方式 携带URI,则对用户请求中的URL作如下操作: * 将location参数中匹配上的一段替换为该URI

  • 该URL参数中可以携带变量
  • 更复杂的URL替换,可以在location内的配置添加rewrite break语句
  1. 测试携带URI与否的不同效果 服务端的配置(使用Nginx来模拟上游服务端):
server {
    listen 8012;
    default_type text/plain;
    return 200 '8012 server  response.
uri: %uri\n';
}

反向代理端的配置:

server {
    location /a {
        proxy_pass http://proxyups;
        #proxy_method POST;
        proxy_pass_request_headers off;
        #proxy_pass_request_body off;
        proxy_set_body 'hello world!';
        proxy_set_header name '';
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

访问和结果:

1
2
3
$ curl proxy.taohui.tech/a/b/c
8012 server response.
uri: /a/b/c

proxy_pass后面加上字符串,会怎样呢?

server {
    location /a {
        #在后面加上一个www
        proxy_pass http://proxyups/www;
        #proxy_method POST;
        proxy_pass_request_headers off;
        #proxy_pass_request_body off;
        proxy_set_body 'hello world!';
        proxy_set_header name '';
        proxy_http_version 1.1;
        proxy_set_header Connection "keepalive";
    }
}

访问和结果:

1
2
3
$ curl proxy.taohui.tech/a/b/c
8012 server response.
uri: /www/b/c

可以看到,location后面的/a被/www替换掉了

1.3.2 生成发往上游的请求

  1. 生成请求Line
Syntax:   proxy_method method;
Default:  --
Context:  http,server,location

# 在使用keepalive的使用,需要该为http 1.1,因为1.0不支持
Syntax:   proxy_http_version 1.0 | 1.1
Default:  proxy_http_version 1.0;
Context:  http,server,location
  1. 生成请求Head
Syntax:   proxy_set_header field value;
Default:  proxy_set_header Host $proxy_host;
          proxy_set_header Connection close;
Context:  http,server,location
注意:若value的值为空字符串,则整个header都不会向上游发送
功能:修改或添加http头部。

Syntax:   proxy_pass_request_headers on | off;
Default:  proxy_pass_request_headers on;
Context:  http,server,location
功能:是否把浏览器端发来的请求头部发给上游服务器
  1. 生成请求体
Syntax:   proxy_pass_request_body on | off;
Default:  proxy_pass_request_body on;
Context:  http,server,location
功能:是否把浏览器端发来的请求包体发给上游服务器

Syntax:   proxy_set_body value;
Default:  --
Context:  http,server,location
功能:手动构造发往上游服务器的内容,value的值就是请求包体
  1. 接收用户请求包体的方式
Syntax:  proxy_request_buffering on |off;
Default: proxy_request_buffering on;
Context: http,server,location
功能: 指定收完再转发还是边收边转发

on: > 客户端网速较慢 > 上游服务并发处理能力低 > 适应高吞吐量场景 off: > 更及时的响应 > 降低nginx读写磁盘的消耗 > 一旦开始发送内容,proxy_next_upstream功能失败

设置接收包体所分配的内存:

Syntax:   client_body_buffer_size size;
Default:  client_body_buffer_size 8k|16k;
Context:  http,server,location
# 因为接收head的时候,可能也收到了一点点的body
# 1. 如果接收到的body已经包含了完整的包体,则不分配client_body_buffer_size这块内存。
# 2. 如果Content-Length表示还有部分内容没有接收到,并且剩余的长度小于client_body_buffer_size,
#    则仅分配所需要内存,而不是一次把8k|16k分配完。
# 3. 如果剩余的长度远大于client_body_buffer_size,就会分多次接收,还是使用client_body_buffer_size这段内存。
# 4. 接收到以后,会根据client_body_in_single_buffer开关,来决定
#    是直接返回给上游,还是等包体全部接受完再发送给上游。
#    如果client_body_buffer_size用完了,就使用临时硬盘文件。

Syntax:   client_body_in_single_buffer on|off;
Default:  client_body_in_single_buffer off;
Context:  http,server,location
功能: 如果设置为on,表示请求的body全部放在内存中。

最大包体长度限制:

Syntax:  client_max_body_size size;
Default: client_max_body_size 1m;
Context: http,server,location

仅对请求头部中含有Content-Length有效超出最大长度后,返回413错误

临时文件路径格式:

Syntax:  client_body_temp_path path [level1 [level2 [level3]]];
Default: client_body_temp_path client_body_temp;
Context: http,server,location
功能:指定在哪个目录下存放请求包体
level1、level2、level3表示多级子目录,会从临时文件名末尾开始数,
每n为作为一级子目录。之所以这样做,是因为,一个目录下不能存放太多的文件。

Syntax:  client_body_in_file_only on |clean |off;
Default: client_body_in_file_only off;
Context: http,server,location
功能:off表示包体比较大的时候必须存放在文件中,如果包体的大小非常小,
            完全可以存放在client_body_buffer_size下,这个时候是不会存放在文件中的。
      on表示包体必须存放在文件中,而且文件会一直保存下去
      clean表示包体必须存放在文件中,请求结束后就会删除该文件。

读取包体时的超时:

Syntax:   client_body_timeout time;
Default:  client_body_timeout 60s;
Context:  http,server,location

读取包体时超时,则返回408错误

1.3.3 接收上游的响应

  1. 与上游服务建立连接 设置TCP握手超时时间:
Syntax:   proxy_connect_timeout time;
Default:  proxy_connect_timeout 60s;
Context:  http,server,location

超时后,会向客户端生成http响应,响应码为502:

设置当出现502错误的时候,就换一个上游服务器

Syntax:   proxy_next_upstream http_502|...;
Default:  proxy_next_upstream error timeout;
Context:  http,server,location

上游连接启用TCP keepalive:

Syntax:   proxy_socket_keepalive on | off;
Default:  proxy_socket_keepalive off;
Context:  http,server,location

修改TCP连接中的local address:

Syntax:  proxy_bind address [transparent] | off;
Default: --
Context: http,server,location
  • 可以使用变量:

    proxy_bind $remote_addr;

  • 可以使用不属于所在机器的IP地址:

    proxy_bind $remote_addr transparent;

当客户端关闭连接时,是否继续保持nginx与上游服务的连接:

Syntax:   proxy_ignore_client_abort on|off;
Default:  proxy_ignore_client_abort off;
Context:  http,server,location

向上游发送HTTP请求的超时时间:

Syntax:   proxy_send_timeout time;
Default:  proxy_send_timeout 60s;
Context:  http,server,location
  1. 接收上游的响应
Syntax:   proxy_buffer_size size;
Default:  proxy_buffer_size 4k|8k;
Context:  http,server,location
功能: 能接收的上游响应头部的最大长度

如果上游响应的头部过长,就会导致nginx无法正常处理,并在error.log上打印这条日志: upstream sent too big header

Syntax:   proxy_buffers number size;
Default:  proxy_buffers 8 4k|8k;
Context:  http,server,location
功能: 能接收的上游响应包体的最大长度
       如果是接收到完整的响应包体后再返回,并且包体超过了proxy_buffers的大小,
       就会存放在磁盘中

接收上游的HTTP包体:

Syntax:   proxy_buffering on|off;  X-Accel-Buffering:是nginx特定的Head
Default:  proxy_buffering on;      yes:表示强制nginx先接受完上游的响应包体再发送
Context:  http,server,location     no:

Syntax:   proxy_max_temp_file_size size;
Default:  proxy_max_temp_file_size 1024m;
Context:  http,server,location
限制写入磁盘中文件的最大值,如果上游服务了一个非常大的文件,超出了
proxy_max_temp_file_size的话,就会出错

Syntax:   proxy_temp_file_write_size size;
Default:  proxy_temp_file_write_size 8k|16k;
Context:  http,server,location
限制每次向磁盘文件写入的字节数

Syntax:   proxy_temp_path path [level1 [level2 [level3]]];
Default:  proxy_temp_path proxy_temp;
Context:  http,server,location
设定临时文件存放目录,以及使用几级目录

Syntax:   proxy_busy_buffers_size size;
Default:  proxy_busy_buffers_size 8k|16k;
Context:  http,server,location
比如说,我们要收到1G的文件,当接收到前8k或者前16k的时候,我就先
向客户端响应我接收到的这部分响应

接收上游时网络速度相关指令:

Syntax:   proxy_read_timeout time;
Default:  proxy_read_timeout 60s;
Context:  http,server,location
两次读取上游响应之间的最长时间间隔

Syntax:   proxy_limit_rate rate;
Default:  proxy_limit_rate 0;
Context:  http,server,location
限制读取上游响应的网速

上游包体的持久化:

Syntax:   proxy_store_access users:permissions ...;
Default:  proxy_store_access user:rw;
Context:  http,server,location

Syntax:   proxy_store on|off|string;
Default:  proxy_store off;
Context:  http,server,location

上游包体持久化的例子:

upstream proxyups {
    server 127.0.0.1:8012 weight=1;
}

server {
    server_name store.taohui.tech;
    error_log logs/myerror.log debug;
    root /tmp;

    location / {
        proxy_pass http://proxyups;
        # 从上面http://proxyups收到的响应会被存放在root /tmp指令指定的/tmp目录下
        proxy_store   on;
        proxy_store_access user:rw group:rw all:r;
    }

    listen 80; # managed by Certbot
}
  1. 处理上游的响应头部
Syntax:  proxy_ignore_headers field ...;
Default: --
Context: http,server,location
  • 功能:某些响应头部可以改变nginx的行为,使用proxy_ignore_headers可以禁止它们生效
  • 可以禁用功能的头部

    X-Accel-Redirect: 由上游服务指定在nginx内部重定向,控制请求的执行 X-Accel-Limit-Rate: 由上游设置发往客户端的速度限制,等同limit_rate指令 X-Accel-Buffering: 由上游控制是否缓存上游的响应 X-Accel-Charset: 由上游控制Content-Type中的Charset 缓存相关:

    • X-Accel-Expires: 设置响应在nginx中的缓存时间,单位秒;@开头表示一天内某时刻
    • Expires: 控制nginx缓存时间,优先级低于X-Accel-Expires
    • Cache-Control: 控制nginx缓存时间,优先级低于X-Accel-Expires
    • Set-Cookie: 响应中出现Set-Cookie则不缓存,可通过proxy_ignore_headers禁止生效
    • Vary: 响应中出现Vary: *则不缓存,同样可禁止生效
Syntax:   proxy_hide_header field;
Default:  --
Context:  http,server,location

功能:对于上游响应中的某些头部,设置不向客户端转发

  • proxy_hide_header默认不转发响应头部:

    Date: 由ngx_http_header_filter_module过滤模块填写,值为nginx发送响应头部时的时间 Server: 由ngx_http_header_filter_module过滤模块填写,值为nginx版本 X-Pad: 通常是Apache为避免浏览器BUG生成的头部,默认忽略 X-Accel-: 用于控制nginx行为的响应,不需要向客户端转发

Syntax:   proxy_pass_header field;
Default:  --
Context:  http,server,location

功能:对于已经被proxy_hide_header的头部,设置向上游转发

修改返回的Set-Cookie头部:

Syntax:   proxy_cookie_domain off;
          proxy_cookie_domain domain replacement;
Default:  proxy_cookie_domain off;
Context:  http,server,location

Syntax:   proxy_cookie_path off;
          proxy_cookie_path path replacement;
Default:  proxy_cookie_path off;
Context:  http,server,location

修改返回的Location头部:

Syntax:  proxy_redirect default;
         proxy_redirect off;
         proxy_redirect redirect replacement;
Default: proxy_redirect default;
Context: http,server,location

演示:

upstream proxyupstream {
    server 127.0.0.1:8012 weight=1;
}

server {
    server_name proxy.taohui.tech;
    error_log logs/myerror.log debug;

    location / {
        proxy_pass http://proxyupstream;
        #proxy_method POST;

        proxy_hide_header aaa;
        #proxy_pass_header server;
        #proxy_ignore_headers X-Accel-Limit-Rate;

        #proxy_pass_request_headers off;
        #proxy_pass_request_body off;
        #proxy_set_body 'hello world!';
        #proxy_set_header name '';
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
    listen 80; # managed by Certbot
}

1.3.4 上游出现失败时的容错方案

上游返回失败时的处理办法:

Syntax:   proxy_next_upstream error| timeout| invalid_header| http_500| http_502| http_503| http_504| http_403| http_404| http_429| non_idempotent| off ...;
Default:  proxy_next_upstream error timeout;
Context:  http,server,location

error: 主要指网络错误
timeout: 
invalid_header:收到的上游服务的http head是不合法的
http_

限制proxy_next_upstream的时间与次数:

Syntax:   proxy_next_upstream_timeout time;
Default:  proxy_next_upstream_timeout 0;
Context:  http,server,location

Syntax:   proxy_next_upstream_tries number;
Default:  proxy_next_upstream_tries 0;
Context:  http,server,location

error_page拦截上游失败响应: 当上游响应的响应码大于等于300时,将响应返回给客户端还是按error_page指令处理

Syntax:   proxy_intercept_errors on| off;
Default:  proxy_intercept_errors off;
Context:  http,server,location

2. 搭建websocket反向代理

模块:由ngx_http_proxy_module模块实现 配置: ```ng server { listen 80; server_name websocket.taohui.tech; default_type text/plain; access_log logs/ws.log;

     location / {
         # 加上这3行就可以将http转换为上游的websocket
         proxy_http_version 1.1;
         proxy_set_header Upgrade $http_upgrade;
         proxy_set_header Connection "upgrade";

         proxy_pass http://echo.websocket.org;
     }
 }
 ```

特点: 数据分片必须有序传输,所以不支持多路复用

3. http2

  1. 模块:默认没有被编译进nginx中,需要通过--with-http_v2_module编译进nginx
  2. 功能:对客户端使用http2协议提供基本功能
  3. 前提:开启TLS协议
  4. 使用方法: listen 443 ssl http2;

3.1 推送资源

Syntax:  http2_push_preload on|off;
Default: http2_push_preload off;
Context: http,server,location

Syntax:  http2_push uri|off;
Default: http2_push off;
Context: http,server,location

Syntax:  http2_max_concurrent_pushes number;
Default: http2_max_concurrent_pushes 10;
Context: http,server

测试nginx http2协议的客户端工具:

测试:

server {
  server_name http2.taohui.tech;
  root html;
  location / {
    # 
    http2_push /mirror.txt;
    http2_push /video.mp4;
  }
  location /test {
    # 只要发的头部中,含有/style.css,我们就会主动去推style.css
    add_header Link "</style.css>; as=style; rel=preload";
    http2_push_preload on;
  }

  listen 4430 ssl http2;
  ssl_certificate XXX;
  ssl_certificate_key xxx;
  ...
}

3.2 超时控制

Syntax:  http2_recv_timeout time;
Defalut: http2_recv_timeout 30s;
Context: http,server

Syntax:  http2_idle_timeout time;
Default: http2-idle_timeout 3m;
Context: http,server

3.3 并发请求控制

Syntax:  http2_max_concurrent_pushes number;
Default: http2_max_concurrent_pushes 10;
Context: http,server

Syntax:  http2_max_concurrent_streams number;
Default: http2_max_concurrent_streams 128;
Context: http,server

Syntax:  http2_max_field_size size;
Default: http2_max_field_size 4k;
Context: http,server

3.4 连接最大处理请求数

Syntax:  http2_max_requests number;
Default: http2_max_requests 1000;
Context: http,server

Syntax:  http2_chunk_size size;
Default: http2_chunk_size 8k;
Context: http,server,location

3.5 设置缓冲区大小

Syntax:  http2_recv_buffer_size size;
Default: http2_recv_buffer_size 256k;
Context: http

Syntax:  http2_max_header_size size;
Default: http2_max_header_size 16k;
Context: http,server

Syntax:  http2_body_preread_size size;
Default: http2_body_preread_size 64k;
Context: http,server

4. 四层反向代理

4.1 stream四层反向代理的7个阶段及常用变量

stream模块处理请求的7个阶段

阶段 模块
POST_ACCEPT realip
PREACCESS limit_conn
ACCESS access
SSL ssl
PREREAD ssl_preread
CONTENT return,stream_proxy
LOG access_log

stream中的ssl:

Syntax:   stream {...}
Default:  --
Context:  main

Syntax:   server {...}
Default:  --
Context:  stream

Syntax:   listen address:port [ssl] [udp] [proxy_protocol]
          [backlog=number] [rcvbuf=size] [sndbuf=size] [bind] 
          [ipv6only=on|off] [reuseport] 
          [so_keepalive=on|off [keepidle]:[keepintvl]:[keepcnt]];
Default:  --
Context:  server

传输层相关的变量:

binary_remote_addr:    客户端地址的整型格式,对于IPv4是4字节,对于IPv6是16字节
connection:            递增的连接序号
remote_addr:           客户端地址
remote_port:           客户端端口
proxy_protocol_addr:   若使用了proxy_protocol协议则返回协议中的地址,否则返回空
proxy_protocol_port:   若使用了proxy_protocol协议则返回协议中的端口,否则返回空
protocol:              传输层协议,值为TCP或者UDP
server_addr:           服务器端地址
server_port:           服务器端端口
bytes_received:        从客户端接收到的字节数
bytes_sent:            已经发送到客户端的字节数
status: > 200: session成功结束
        > 400: 客户端数据无法解析,例如proxy_protocol协议的格式不正确
        > 403: 访问权限不足被拒绝,例如access模块限制了客户端IP地址
        > 500: 服务器内部代码错误
        > 502: 无法找到或者连接上游服务
        > 503: 上游服务不可用

Nginx系统变量:

time_local:        以本地时间标准输出的当前时间,例如14/Nov/2018:15:55:37 +0800
time_iso8601:      使用ISO 8601标准输出的当前时间,例如2018-11-14T15:55:37+08:00
ngixn_version:     Nginx版本号
pid:               所属worker进程的进程id
pipe:              使用了管道则返回p,否则返回.
hostname:          所在服务器的主机名,与hostname命令输出一致
mesc:              1970年1月1日到现在的时间,单位为秒,小数点后精确到毫秒

content阶段: return模块

Syntax:    return value;
Default:   --
Context:   server

演示:

stream {
  error_log logs/stream.log debug;
  server {
    listen 10002 proxy_protocol;
    return '10002 server get ip: $remote_addr!\n';
  }
  server {
    listen 10003 proxy_protocol;
    return '10003 server get ip: $remote_addr!\n';
  }
  server {
    listen 10004;
    #listen 10004 proxy_protocol;
    return '10004 vars:
bytes_received: $bytes_received
bytes_sent: $bytes_sent
proxy_protocol_addr: $proxy_protocol_addr
proxy_protocol_port: $proxy_protocol_port
remote_addr: $remote_addr
remote_port: $remote_port
server_addr: $server_addr
server_port: $server_port
session_time: $session_time
status: $status
protocol: $protocol
';
  }
}

4.2 proxy protocol协议与realip模块

https://trac.nginx.org/nginx/ticket/1639

  1. proxy_protocol协议
  • v1协议

    • PROXY TCP4 202.112.144.236 10.210.12.10 5678 80\r\n
    • PROXY TCP6 2001:da8:205::100 2400:89c0:2110:1::21 6324 80\r\n
    • PROXY UKNOWN\r\n
  • v2协议

     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
    
    typedef struct {
      //12字节签名: \r\n\r\n\0\r\nQUIT\n
      u_char     signature[12];
      //4位协议版本号: 2
      //4位命令:0表示LOCAL,1表示PROXY,nginx仅支持PROXY
      u_char     version_command;
      //4位地址族:1表示IPV4,2表示IPV6
      //4位传输层协议:1表示TCP,2表示UDP,nginx仅支持TCP协议
      u_char     family_transport;
      //2字节地址长度
      u_char     len[2];
    } ngx_proxy_protocol_header_t;
    
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                                                               |
    +                                                               +
    |     Proxy Protocol V2 Signature                               |
    +                                                               +
    |                                                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |version|command|   AF  |Proto. |   Address Length              |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                 IPv4 Source Address                           |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                 IPv4 Destination Address                      |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |    Source Port                |       Destination Port        |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    
    
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                                                               |
    +                                                               +
    |     Proxy Protocol V2 Signature                               |
    +                                                               +
    |                                                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |version|command|   AF  |Proto. |   Address Length              |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                                                               |
    +                                                               +
    |                 IPv6 Source Address                           |
    +                                                               +
    |                                                               |
    +                                                               +
    |                                                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                                                               |
    +                                                               +
    |                 IPv6 Destination Address                      |
    +                                                               +
    |                                                               |
    +                                                               +
    |                                                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |    Source Port                |       Destination Port        |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    
  1. 读取proxy_protocol协议的超时控制
Syntax:  proxy_protocol_timeout timeout;
Default: proxy_protocol_timeout 30s;
Context: stream,server
  1. stream处理proxy_protocol的流程
{连接建立成功,是否携带listen proxy_protocol?}
                   V
+--------------N--<?>
|                 y|
|                  v
| {加入读定时器proxy_protocol_timeout(默认30秒)}
|                  v
|  {读取107字节(proxy_protocol最大长度)}
|                  v
|     {判断前12字节是否匹配V2协议的头部}
|                  v
|   +-----------Y-<?>
|   |             N|
|   | {读取v1协议头部的真实IP地址}----+
|   |                               |
|   +>{读取v2协议头部的真实IP地址}    |
|                  v                |
+---->{进入7个阶段的stream模块处理}<--+
  1. post_accept阶段:realip模块 功能: 通过proxy_protocol协议取出客户端真实地址,并写入remote_addrremote_port变量。同时使用realip_remote_addrrealip_remote_port保留 TCP连接中获得的原始地址。 模块:默认不编译进nginx,使用--with-stream_realip_module启动该功能 指令:
Syntax:  set_real_ip_from address |CIDR |unix;
Default: --
Context: stream, server
  1. docker容器中获取到的x-forwarded-for是docker网桥的ip,原因?

4.3 限并发连接、限IP、记日志

  1. PREACCESS阶段的limit_conn模块 功能: 限制客户端的并发连接数。使用变量自定义限制依据,基于共享内存, 所有worker进程同时生效。 模块: 默认编译进nginx,通过--without-stream_limit_conn_module禁用模块。 指令:
Syntax:  limit_conn_zone key zone=name:size;
Default: --
Context: stream

Syntax:  limit_conn zone number;
Default: --
Context: stream,server

Syntax:  limit_conn_log_level info|notice|warn|error;
Default: limit_conn_log_level error;
Context: stream,server
  1. ACCESS阶段的access模块 功能: 根据客户端地址(realip模块可以修改地址)决定连接的访问权限 模块:模块编译进nginx,通过--without-stream_access_module禁用。 指令:
Syntax:  allow address|CIDR|unix:|all;
Default: --
Context: stream,server

Syntax:  deny address|CIDR|unix|all;
Default: --
Context: stream,server
  1. log阶段:stream_log模块
Syntax:  access_log path format [buffer=size] [gzip[=level]] [flush=time] [if=condition];
         access_log off;
Default: access_log off;
Context: stream, server
注意,这里的format指向log_format指令指定的name

Syntax:  log_format name [escape=default|json|none] string ...;
Default: --
Context: stream

Syntax:  open_log_file_cache max=N [inactive=time] [min_uses=N] [valid=time];
         open_log_file_cache off;
Default: open_log_file_cache off;
Context: stream,server

4.4 stream四层反向代理处理SSL下游流量

  1. stream中的ssl 功能:使stream反向代理对__下游__支持TLS/SSL协议 模块:默认不编译进nginx,通过--with-stream_ssl_module加入

  2. 对比stream ssl指令和http ssl指令

    stream ssl http ssl
    配置服务器证书 ssl_certificate ssl_certificate
    配置服务器证书私钥 ssl_certificate_key ssl_certificate_key
    指定安全套件 ssl_ciphers ssl_ciphers
    指定吊销证书链CRL ssl_crl ssl_crl
    密码交换DH算法参数 ssl_dhparam ssl_dhparam
    指定密码交换时使用哪条椭圆曲线 ssl_ecdh_curve ssl_ecdh_curve
    TLS握手的超时时间 ssl_handshake_timeout
    指定私钥的密码文件 ssl_password_file ssl_password_file
    是否启用服务器偏好的安全套件 ssl_prefer_server_ciphers ssl_prefer_server_ciphers
    指定支持的TLS协议 ssl_protocols ssl_protocols
    使用session缓存提升性能 ssl_session_cache ssl_session_cache
    指定使用ticket票据时的加解密文件,默认使用随机字串 ssl_session_ticket_key ssl_session_ticket_key
    是否启用ticket票据 ssl_session_ticket_key ssl_session_ticket_key
    指定session重用的超时时间 ssl_session_timeout ssl_session_timeout
    是否验证客户端的证书 ssl_verify_client ssl_verify_client
    可信CA证书,用于验证客户端证书 ssl_client_certificate ssl_client_certificate
    验证客户端证书是否可信 ssl_trusted_certificate ssl_trusted_certificate
    验证客户端证书链的深度 ssl_verify_depth ssl_verify_depth
  3. stream ssl模块提供的变量

  • 安全套件
    • ssl_cipher: 本地通信选用的安全套件,例如ECDHE-RSA-AES128-GCM-SHA256
    • ssl_ciphers: 客户端支持的所有安全套件
    • ssl_protocol: 本次通讯选用的TLS版本,例如TLSv1.2
    • ssl_curves: 客户端支持的椭圆曲线,例如secp384r1:secp521r1
  • 证书
    • ssl_client_raw_cert: 原始客户端证书内容
    • ssl_client_escaped_cert: 返回客户端证书做urlencode编码后的内容
    • ssl_client_cert: 对客户端证书每一行内容前加tab制表符空白,增强可读性。
    • ssl_client_fingerprint: 客户端证书的SHA1指纹。
  • 证书结构化信息
    • ssl_server_name: 通过TLS插件SNI(Server Name Indication)获取到的服务域名
    • ssl_client_i_dn: 依据RFC2253获取到证书issuer dn信息,例如:CN=…,O=…,L=…,C=…
    • ssl_client_i_dn_legacy: stream模块不再支持这种比较老的格式了
    • ssl_client_s_dn: 依据RFC2253获取到证书subject dn信息,例如:CN=…,OU=…,O=…,L=…,ST=…,C=…
    • ssl_client_s_dn_legacy: stream模块不再支持这种比较老的格式了
  • 证书有效期
    • ssl_client_v_end: 返回客户端证书的过期时间,例如Dec 1 11:56:11 2028 GMT
    • ssl_client_v_remain: 返回还有多少天客户端证书过期,例如针对上面的ssl_client_v_end其值为3649
    • ssl_client_v_start: 客户端证书的颁发日期,例如Dec 4 11:56:11 2018 GMT
  • 连接有效性
    • ssl_client_serial: 返回连接上客户端证书的序列号,例如8BE947674841BD44
    • ssl_early_data: stream模块目前还未提供,TLS1.3专用
    • ssl_client_verify: 如果验证失败则为FAILED:原因,如果没有验证证书则为NONE,验证成功则为SUCCESS
    • ssl_session_id: 已建立连接的sessionid
    • ssl_session_reused: 如果session被复用(参考session缓存)则为r,否则为.

4.5 stream_preread模块取出SSL关键信息

  1. 模块:默认不会被加载到nginx中,需要使用--with-stream_ssl_preread_module来启用

  2. 功能:解析下游TLS证书中信息,以变量方式赋能其他模块(不会剥离TLS证书)

  3. 提供的变量:

    • $ssl_preread_protocol
      • 客户端支持的TLS版本中最高的版本,例如TLSv1.3
    • $ssl_preread_server_name
      • 从SNI中获取的服务器域名
    • $ssl_preread_alpn_protocols
      • 通过ALPN中获取到的客户端建议使用的协议,例如h2, http/1.1
  4. 指令

Syntax:  preread_buffer_size size;
Default: preread_buffer_size 16k;
Context: stream,server

Syntax:  preread_timeout timeout;
Default: preread_timeout 30s;
Context: stream,server

Synatx:  ssl_preread on|off;
Default: ssl_preread off;
Context: stream,server

4.6 stream proxy四层反向代理的用法

  1. 模块:ngx_stream_proxy_module,默认在Nginx中
  2. 功能:
    • 提供TCP/UDP协议的反向代理
    • 支持与上游的连接使用TLS/SSL协议
    • 支持与上游的连接使用proxy protocol协议
  3. proxy模块对上下游的限速指令
限制读取上游服务数据的速度:
Syntax:  proxy_download_rate rate;
Default: proxy_download_rate 0;
Context: stream,server

限制读取客户端数据的速度:
Syntax:  proxy_upload_rate rate;
Default: proxy_upload_rate 0;
Context: stream,server

   {客户端}
   1|    ^2
    v    |
   {nginx}   1. proxy_upload_rate
   3|    ^4  4. proxy_download_rate
    v    |   2,3是无法做限速的
   {上游服务}
  1. Stream反向代理指令

    stream反向代理 http反向代理
    指定上游 proxy_pass proxy_pass
    连接绑定地址 proxy_bind proxy_bind
    该值同时用于定义接收上游数据、接收下游数据的缓存区大小 proxy_buffer_size proxy_buffer_size
    连接上游超时时间 proxy_connect_timeout proxy_connect_timeout
    TCP连接使用proxy_protocol协议 proxy_protocol
    出错时更换上游 proxy_next_upstream proxy_next_upstream
    更换上游超时 proxy_next_upstream_timeout proxy_next_upstream_timeout
    更换上游重试次数 proxy_next_upstream_tries proxy_next_upstream_tries
    读取响应超时时间 proxy_timeout proxy_read_timeout
    发送请求超时时间 proxy_tmeout proxy_send_timeout
    使用TCPkeepalive proxy_socket_keepalive proxy_socket_keepalive
  2. stream ssl指令与http proxy模块对照表

    stream http反向代理
    是否对上游使用ssl proxy_ssl
    配置服务器证书 proxy_ssl_certificate proxy_ssl_certificate
    配置服务器证书私钥 proxy_ssl_certificate_key proxy_ssl_certificate_key
    指定安全套件 proxy_ssl_ciphers proxy_ssl_ciphers
    指定吊销证书链CRL proxy_ssl_crl proxy_ssl_crl
    指定域名验证上游证书中域名 proxy_ssl_name proxy_ssl_name
    当私钥有密码时指定密码文件 proxy_ssl_password_file proxy_ssl_password_file
    指定具体某个版本的协议 proxy_ssl_protocols proxy_ssl_protocols
    传递SNI信息至上游 proxy_ssl_server_name proxy_ssl_server_name
    是否重用SSL连接 proxy_ssl_session_reuse proxy_ssl_session_reuse
    验证上游服务的证书 proxy_ssl_trusted_certificate proxy_ssl_trusted_certificate
    是否验证上游服务的证书 proxy_ssl_verify proxy_ssl_verify
    设置验证证书链的深度 proxy_ssl_verify_depth proxy_ssl_verify_depth
  3. 实验

# 1. 上游服务的配置
server {
  error_log logs/myerror.log debug;
  server_name "";
  listen 9001 proxy_protocol;

  location / {
    /return 200 'proxy_protocol_addr: $proxy_protocol_addr,proxy_protocol_port: $proxy_protocol_port\n';
  }
}

# 2. 反向代理的配置
log_format ssllog '$connection $remote_add [$time_total] '
                 '$protocol $status $bytes_sent $bytes_received $session_time';
error_log logs/stream_error.log debug;
access_log logs/stream_access.log ssllog;
server {
  listen 4435;
  proxy_pass localhost:9001;
  proxy_protocol on;
}

4.7 UDP反向代理

Syntax:  proxy_requests number;
Default: proxy_requests 0;
Context: stream,server
功能: 指定一次会话session中最多从客户端接收到多少报文就结束session。(1.15.7非稳定版本)
    * 仅会话结束时才会记录access日志
    * 同一个会话中,nginx使用同一个端口连接上游服务
    * 设置为0表示不限制,每次请求都会记录access日志

Syntax:  proxy_responses number;
Default: --
Context: stream,server
功能:指定对应一个请求报文,上游应返回多少个响应报文。
    * 与proxy_timeout结合使用,控制上游服务是否不可用

测试:

log_format udplog '$connection $remote_addr [$time_total]'
              '$protocol $status $bytes_sent $bytes_received $session_time';
server {
  listen 4436 udp;
  proxy_pass localhost:9999;
  proxy_requests 1;
  proxy_responses 2;
  proxy_timeout 2s;
  access_log logs/udp_access.log udplog;
}

4.8 透传IP地址的3个方案

  1. 使用proxy_protocol协议

  2. 修改IP报文:

    • 步骤
      • 修改IP报文中的源地址
      • 修改路由规则
    • 方案
      • IP地址透传:经由nginx转发上游返回的报文给客户端(TCP/UDP)
      • DSR: 上游直接发送报文给客户端(仅UDP)
  3. nginx如何实现ip地址透传

        {客户端: IP地址是A}
源地址:A  |            ^
目标地址:B|             |源地址:B
          v            |目的地址:A
+--------------------------------------------+
|       {      nginx      }                  |
|          |           ^设源地址为C的报文转发  |
|          |           |本机nginx监听端口     |
+--------------------------------------------+
源地址:B->A|         ^源地址:C
目标地址:C v         |目标地址: A
+--------------------------------------------+
|          |         |网关改为B               |
|          v         |                       |
|       {  上游服务进程   }                   |
+--------------------------------------------+
  • proxy_bind $remote_addr transparent;
    • 实现上图中的源地址:B->A
  • 调节上游服务所在主机上的网关为nginx所在主机:
    1
    2
    
    $ route del default gw 10.0.2.2(原网关IP)
    $ route add default gw 172.16.0.1(Nginx所在主机的IP)
    
  • 调节nginx所在主机上的路由规则,使它把接收自上游的、目标IP是客户端IP的报文转发给nginx进程
    1
    2
    3
    
    $ ip rule add fwmark 1 lookup 100
    $ ip route add local 0.0.0.0/0 dev lo table 100
    $ iptables -t mangle -A PREROUTING -p tcp -s 172.16.0.0/28 --sport 80 -j MARK --set-xmark 0x1/0xffffff
    
  1. nginx如何实现DSR
# 上游服务没有公网,需要借助nginx的公网ip
        {客户端: IP地址是A}
源地址:A    |           ^
目标地址:B  |           |源地址:B
            v           |目的地址:A
+---------------------------------------------+
|       {  nginx }      |                     |
|           |           |                     |
|           |           |源地址:C->B,并转发   |
+---------------------------------------------+
源地址:B->A |         ^
(源端口修改)|         |源地址:C
目标地址:C  v         |目标地址: A
+---------------------------------------------+
|          |         |网关改为B                |
|          v         |                        |
|       {  上游服务进程   }                    |
+---------------------------------------------+

###############################################
###############################################
# 上游服务有公网IP
        {客户端: IP地址是A}
源地址:A    |           ^
目标地址:B  |            |
            v           |
+-------------------+   |
|       {  nginx }  |   |
|           |       |   |源地址: C->B
|           |       |   |目标地址: A
+-------------------+   |
源地址:B->A |            |
(源端口修改)|            |
目标地址:C  v            |
+----------------------------------+
|          |            |          |
|          v            |          |
|       {  上游服务进程   }         |
+----------------------------------+
  • proxy_responses 0
  • proxy_bind $remote_addr:$remote_port transparent;
    • 对应上图的源地址:B->A(源端口修改)
  • 若上游服务在内网无公网ip,则可由nginx所在主机转发
    • 在上游服务所在主机上添加路由
      • route add default gw nginx-ip-address
  • 允许操作系统转发ip报文
    • sysctl -w net.ipv4.ip_forward=1
  • 转发时修改源地址为nginx所在主机的地址(源地址:C->B)
    1
    2
    3
    4
    5
    
    $ tc qdisc add dev eth0 root handle 10: htb
    $ tc filter add dev eth0 parent 10: protocol ip prio 10 u32 match ip src 172.16.0.11 match ip sport 53 action nat egress 172.16.0.11 192.168.99.10
    $ tc filter add dev eth0 parent 10: protocol ip prio 10 u32 match ip src 172.16.0.12 match ip sport 53 action nat egress 172.16.0.12 192.168.99.10
    $ tc filter add dev eth0 parent 10: protocol ip prio 10 u32 match ip src 172.16.0.13 match ip sport 53 action nat egress 172.16.0.13 192.168.99.10
    $ tc filter add dev eth0 parent 10: protocol ip prio 10 u32 match ip src 172.16.0.14 match ip sport 53 action nat egress 172.16.0.14 192.168.99.10
    
  • 问题:
    • nginx检查不到上游服务是否回包
    • 负载均衡策略受限

附录: 对上游使用SSL连接

双向认证时的指令示例:

                 指定自身使用的证书:                           验证客户端证书:
                 proxy_ssl_certificate                         ssl_verify_client
        http     proxy_ssl_certificate_key    https双向认证    ssl_client_certificate
客户端---------->   nginx                 -------------------->   nginx
      <----------                         <--------------------
                 验证服务器证书:                              指定自身使用的证书:
                 proxy_ssl_verify                              ssl_certificate
                 proxy_ssl_trusted_certificate                 ssl_certificate_key

对下游使用证书:

Syntax:   ssl_certificate file;
Default:  --
Context:  http,server

Syntax:   ssl_certificate_key file;
Default:  --
Context:  http,server

验证下游证书:

Syntax:   ssl_verify_client on| off| optional| optional_no_ca;
Default:  ssl_verify_client off;
Context:  http,server

Syntax:   ssl_client_certificate file;
Default:  --
Context:  http,server
功能: 指定验证用的ca根证书的位置

对上游使用证书:

Syntax:   proxy_ssl_certificate file;
Default:  --
Context:  http,server,location

Syntax:   proxy_ssl_certificate_key file;
Default:  --
Context:  http,server,location

验证上游的证书:

Syntax:   proxy_ssl_trusted_certificate file;
Default:  --
Context:  http,server,location
功能: 指定验证用的ca根证书的位置

Syntax:   proxy_ssl_verify on| off;
Default:  proxy_ssl_verify off;
Context:  http,server,location

ssl模块提供的变量:

  • 安全套件:

    ssl_cipher: 本次通讯选用的安全套件,例如ECDHE-RSA-AES128-GCM-SHA256 ssl_ciphers: 客户端支持的所有安全套件 ssl_protocol: 本地通讯选用的TLS版本,例如TLSv1.2 ssl_curves: 客户端支持的椭圆曲线,例如secp384r1:secp521r1

  • 证书:

    ssl_client_raw_cert: 原始客户端证书内容 ssl_client_escaped_cert: 返回客户端证书做urlencode编码后的内容 ssl_client_cert: 对客户端证书每一行内容前加tab制表符空白,增强可读性 ssl_client_fingerprint: 客户端证书的SHA1指纹

  • 证书结构化信息:

    ssl_server_name: 通过TLS插件SNI(Server Name Indication)获取到的服务域名 ssl_client_i_dn: 依据RFC2253获取到证书issuer dn信息,例如:CN=…,O=…,L=…,C=… ssl_client_i_dn_legacy: 依据RFC2253获取到证书issuer dn信息,例如: /C=…/L=…/O=…/CN=… ssl_client_s_dn: 依据RFC2253获取到证书subject dn信息,例如:CN=…,OU=…,O=…,L=…,ST=…,C=… ssl_client_s_dn_legacy: 同样获取subject dn信息,格式为:/C=…/ST=…/L=…/O=…/OU=…/CN=…

  • 证书有效期:

    ssl_client_v_end: 返回客户端证书的过期时间,例如Dec 1 11:56:11 2028 GMT ssl_client_v_remain: 返回还有多少天客户端证书过期,例如针对上面的ssl_client_v_end其值为3649 ssl_client_v_start: 客户端证书的颁发日期,例如Dec 4 11:56:11 2018 GMT

  • 连接有效性:

    ssl_client_serial: 返回连接上客户端证书的序列号,例如8BE947674841BD44 ssl_early_data: 在TLS1.3协议中使用了early data且握手未完成则返回1,否则返回空字符串 ssl_client_verify: 如果验证失败为FAILED:原因,如果没有验证证书则为NONE,验证成功则为SUCCESS ssl_session_id: 已建立连接的sessionid ssl_session_reused: 如果session被复用(参考session缓存),则为r,否则为.

创建证书命令示例:

  • 创建根证书:

    创建CA私钥: openssl genrsa -out ca.key 2048 制作CA公钥:openssl req -new -x509 -days 3650 -key ca.key -out ca.crt

  • 签发证书:

    创建私钥: openssl genrsa -out a.key 1024 生成签发请求 openssl req -new -key a.key -out a.csr 使用CA证书进行签发: openssl x509 -req -sha256 -in a.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -out a.crt 验证签发证书是否正确 openssl verify -CAfile ca.crt a.crt

上游配置a:

server {
    server_name cache.taohui.tech;
    root html/;
    error_code logs/cacherr.log debug;
    listen 443 ssl;
    ssl_certificate     examples/cert/a.crt;
    ssl_certificate_key examples/cert/a.key;
    ssl_verify_client optional;
    ssl_verify_depth 2;
    ssl_client_certificate examples/cert/ca.crt;

    location /test {
        default_type text/plain;
        return 200 '
ssl_client_escaped_cert: $ssl_client_escaped_cert
ssl_client_cert: $ssl_client_cert
ssl_client_raw_cert: $ssl_client_raw_cert
ssl_cipher: $ssl_cipher
ssl_ciphers: $ssl_ciphers
ssl_client_fingerprint: $ssl_client_fingerprint
ssl_client_i_dn: $ssl_client_i_dn
ssl_client_i_dn_legacy: $ssl_client_i_dn_legacy
ssl_client_s_dn: $ssl_client_s_dn
ssl_client_s_dn_legacy: $ssl_client_s_dn_legacy
ssl_client_serial: $ssl_client_serial
ssl_client_v_end: $ssl_client_v_end
ssl_client_v_remain: $ssl_client_v_remain
ssl_client_v_start: $ssl_client_v_start
ssl_client_verify: $ssl_client_verify
ssl_curves: $ssl_curves
ssl_protocol: $ssl_protocol
ssl_server_name: $ssl_server_name
ssl_session_id: $ssl_session_id
ssl_session_reused: $ssl_session_reused
';
    }
}

下游配置b:

server {
    listen 8090;
    location /test {
        error_log logs/error.log debug;
        proxy_ssl_verify on;
        proxy_ssl_trusted_certificate examples/cert/ca.crt;
        proxy_ssl_name a;
        proxy_ssl_verify_depth 4;

        proxy_ssl_certificate examples/cert/b.crt;
        proxy_ssl_certificate_key examples/cert/b.key;
        proxy_ssl_server_name on;
        proxy_pass https://cache.taohui.tech;
    }
}

本文发表于 0001-01-01,最后修改于 0001-01-01。

本站永久域名「 jiavvc.top 」,也可搜索「 后浪笔记一零二四 」找到我。


上一篇 « 下一篇 »

赞赏支持

请我吃鸡腿 =^_^=

i ysf

云闪付

i wechat

微信

推荐阅读

Big Image