前言

利用这个国庆假期,将威盒网站+服务端迁移到新的服务器上。
全部服务容器化,利用Docker进行部署。

网站

由于多年前网站全部使用php编写,除了不同的网站需要的php版本不同外,没有什么坑。使用webdevops/php-nginx镜像进行部署。
这个镜像有有比较详细的文档
可以直接通过环境变量修改配置文件中的关键内容
主要的环境变量配置:
WEB_DOCUMENT_INDEX 覆盖nginx配置文件中index配置项
WEB_DOCUMENT_ROOT 覆盖nginx配置文件中ROOT配置项

一个容器配置一个网站。使用Docker-compose来管理所有的容器(别问我为啥不用k8s,访问量没这么大)

Caddy2

本次使用Caady2进行反向代理,由Caddy2吧不同的域名转发到不同的容器中。同时Caddy2自动进行ssl证书的申请和续期。
但是!caddy2最低支持TLS1.2协议!这里就为下面留下了坑。
贴一下Caddyfile例子

1
2
3
4
5
store.vlabpro.com {
reverse_proxy localhost:xxxx {
header_up X-Real-IP {remote}
}
}

把全部网站配置部署之后。通过浏览器测试网站以及威盒的api正常。之后就以为结束了。

proxy protocol

问题

过了一天,我查看威盒的统计系统,发现没有任何使用记录。就感觉有一点不对劲,打开威盒,发现全部是无网络连接。于是临时将反代切换成nginx,威盒正常。
然后排查各种问题,初步怀疑到证书和TLS版本的问题。最后排除法确认是TLS版本问题

因为威盒已经停止更新多年了,并不支持TLS1.2。
caddy2删除了对TLS1.2以下版本的支持,强制使用者使用TLS1.2以上的版本。

但是!我们已经不可能为了支持TLS1.2去升级威盒,发布新的版本,只能去找兼容性的方案。
本来使用Caddy2就是为了全自动申请证书的功能(之前自动申请Let’s Encrypt证书的脚本过于不稳定,经常发生证书过期的问题)
只有一个容器可以监听443端口,而且caddy2监听443会拒绝威盒服务器的连接。

Nginx 开启

经过和酱哥讨论通过Nginx的L4的反向代理,在不对https请求解包的情况下,通过sni按照域名区分,把tcp请求直接转发到不同的容器中。

这样就有一个问题,就会导致被转发的容器无法获取到用户真实的ip。再次查询资料后得知nginx支持proxy_protocol协议,通过为tcp添加一个很小的头信息,来方便的传递客户端信息(协议栈、源IP、目的IP、源端口、目的端口等),在网络情况复杂又需要获取用户真实IP时非常有用。其本质是在三次握手结束后由代理在连接中插入了一个携带了原始连接四元组信息的数据包。

proxy protocol 插入配置

1
2
3
4
5
6
7
8
9
10
stream {

server {
listen 12345;

proxy_pass backend.example.com:8080;
proxy_protocol on;
}

}

proxy protocol 接收配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
http {
log_format elb_log '$proxy_protocol_addr';

server {

listen 8080 proxy_protocol;
root /usr/local/nginx/html;
index index.html index.htm;

server_name hello-world.com;
set_real_ip_from 192.168.10.0/24;
real_ip_header proxy_protocol;
}

location / {
try_files $uri $uri/ /index.html;

proxy_pass http://backend.example2.com:8088;
proxy_set_header X-Forwarded-For $proxy_protocol_addr;
}

access_log /usr/local/nginx/stream.log elb_log;
}

最终Nginx配置大概是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
...
http {
...
server {
listen 80;
server_name _;
location / {
proxy_pass http://127.0.0.1:xxxx; # 转发caddy2
proxy_set_header Host $host; # 修改转发请求头,让应用可以受到真实的请求
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

server {
listen 5001 ssl http2 proxy_protocol;
#转发给威盒服务端的容器
....
}
...
}
...
stream {
map $ssl_preread_server_name $name {
localbackend.com local_backend; # 这里指定使用http中的server
default backend;
}

upstream backend {
server 127.0.0.1:8443;
}

# 对应http中的server
upstream local_backend {
server 127.0.0.1:5001;
}

server {
listen 443;
proxy_pass $name;
ssl_preread on;
proxy_protocol on;
}
}
...

Caddy2 编译

再次测试,发现威盒服务端正常,但是网站全部打不开。

原因Caddy2并没有打开proxy protocol,caddy2无法解析添加了proxy protocol协议后的tcp请求。
好在caddy2有插件可以支持proxy protocol,但是需要我去手动编译一个caddy2,官方也提供的教程

1
2
3
4
5
6
7
FROM caddy:builder AS builder

RUN xcaddy build --with github.com/mastercactapus/caddy2-proxyprotocol

FROM caddy:latest

COPY --from=builder /usr/bin/caddy /usr/bin/caddy

这样就编译出来带proxy protocol插件的caddy2镜像
在caddyfile 最上面添加全局配置,开启proxy protocol插件

1
2
3
4
5
6
7
8
9
10
11
12
13
{
http_port 8080
https_port 8443
servers {
listener_wrappers {
proxy_protocol {
timeout 2s
}
tls
}
}

}

总结

网站

网站的请求的转发顺序是这样
Nginx =》 Caddy2 =》 对应网站的容器
这里的Nginx不会对https请求进行解包,直接把tcp请求转发给caddy2,所有的处理都在caddy上

客户端

客户端请求的顺序是这样
Nginx = 》 服务端容器
这里的nginx会吧https进行解包处理
至此,威盒的服务器迁移成功!