- 发布于
使用 Nginx 转发容器服务
- 作者
- 作者
- 霍浩东
- @huohaodong
总体设想
总体而言,需要在服务器上搭建网络服务(Clash)、网络管理服务(Clash-GUI)、文件服务(Filebrowser)、服务器监控服务(InfluxDB)和容器监控服务(Portainer)。由于这些服务分别工作在不同的端口上,直接部署在裸机上就可以满足要求,但是为了方便后期的维护、升级、重新部署和管理,最终决定将这些服务全部容器化并通过 Docker 统一管理。
想要在单个主机的不同端口上暴露多个服务,有两种比较简单且通用的方式:第一种是按需编写 iptables 规则,通过域名和端口来转发服务请求,第二种是通过部署 Nginx 服务并编写相应规则实现服务转发。二者的区别是 iptables 工作在网络层而 Nginx 主要工作在应用层,性能上来讲 iptables 是占优的,但是在扩展性和规则配置的难易程度上,Nginx 更优,综合考虑后采用 Nginx 的方案。使用 Nginx 的另外一个好处是 Nginx 本身也可以作为容器部署在服务器上,更方便管理。
总体设想如下:
服务部署
Clash 和 Clash-GUI
由于实验室有网络访问需求,因此需要部署 Clash 服务,此外为了方便管理 Clash,还需要额外部署 Clash-GUI。这里为了服务器重启后可以快速恢复服务,还需要在启动命令后添加 --restart always
选项,此外由于 Clash 需要代理本机网络,因此还需要设置 --network host
。
docker run --name clash --network host --restart always \
-v /home/configs/clash/config.yaml:/root/.config/clash/config.yaml \
-d dreamacro/clash-premium
docker run --name clash-ui --restart always -p 9091:80 -d haishanh/yacd
Portainer 和 InfluxDB
Portainer 是开源的容器管理工具,部署后可以可视化监控容器运行状态、管理和部署容器服务。
docker run -p 8000:8000 -p 9443:9443 -p 9000:9000 --name portainer --restart always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data portainer/portainer-ce:latest
InfluxDB 则是开源的服务器监控工具,可以通过自定义规则实现不同类型的时序数据监控,比如服务器历史 CPU 核心占用情况、内存使用率、网络请求情况等。
docker run -d --name InfluxDB --restart always -p 8086:8086 \
-v /tmp/testdata/influx:/root/.influxdb2 --net=monitoring-net influxdb
Filebrowser
实验室内部有时需要存储和共享一些数据,一般的文档类数据我们保存在公共的飞书知识库中进行动态修改、协作、分享,而一些冷数据则需要有其他地方作存储。服务器本身除开 1T 的 NVME SSD 系统盘外还有一块 8T 的机械盘没有得到充分使用。服务器的文件系统采用 LVM 来配置,方便后期扩容、备份和管理,如下所示:
起初的想法是使用成熟的自建网盘方案,比如 Nextcloud 和 Cloudreve 来存储和管理冷数据。但是考虑到一般数据共享的情况我们已经通过飞书解决了,没必要多此一举,因而最终采用了更轻量的 Filebrowser 来作为冷数据管理的方案。
docker run \
-v /public:/srv \
-v /home/configs/filebrowser/database.db:/database.db \
-v /home/configs/filebrowser/settings.json:/.filebrowser.json \
-p 12345:80 \
--name filebrowser \
--restart=always \
-d filebrowser/filebrowser
其中 settings.json 文件的内容如下:
{
"address": "0.0.0.0",
"port": 80,
"locale": "zh-cn",
"baseURL": "/",
"log": "stdout",
"database": "/database.db",
"root": "/srv"
}
使用 Nginx 转发容器服务
实际上我们实验室本身拥有一个公共的域名 seu-netsi.net,而现在服务器又有了一个固定的内网 IP:10.201.0.222,理论上在内网内访问服务是不需要用到域名的,这里为了方便记忆各种服务的地址,额外配置了域名解析规则,如下所示,可以看到所有域名都指向了同一个内网服务器地址。
域名 | 记录 | 服务 |
---|---|---|
server.seu-netsi.net | 10.201.0.222 | 解析服务器地址,方便 SSH 连接 |
clash.seu-netsi.net | 10.201.0.222 | Clash Web GUI |
docker.seu-netsi.net | 10.201.0.222 | Portainer Web GUI |
dashboard.seu-netsi.net | 10.201.0.222 | InfluxDB Web GUI |
filebrowser.seu-netsi.net | 10.201.0.222 | Filebrowser |
在 Docker 中配置 Nginx 需要先拿到 Nginx 的配置文件,这里先启动一个 Nginx 容器,之后使用 docker cp
命令将 Nginx 容器默认的配置文件复制出来后以做后续修改:
docker cp nginx:/etc/nginx/nginx.conf /home/huohaodong/nginx.conf
之后按照域名、端口、服务之间的对应关系编写规则即可,最终得到相应的 Nginx 配置文件:
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name server.seu-netsi.net;
return 301 https://seunetsi.feishu.cn/wiki/wikcnmON8L4XBNML3I13R0k9gOf;
}
server {
listen 80;
server_name filebrowser.seu-netsi.net;
location / {
proxy_pass http://localhost:12345;
}
}
server {
listen 80;
server_name dashboard.seu-netsi.net;
location / {
proxy_pass http://localhost:8086;
}
}
server {
listen 80;
server_name docker.seu-netsi.net;
location / {
proxy_pass http://localhost:9000;
}
}
server {
listen 80;
server_name api.clash.seu-netsi.net;
location / {
proxy_pass http://localhost:9090;
}
}
server {
listen 80;
server_name clash.seu-netsi.net;
location / {
proxy_pass http://localhost:9091;
}
}
include /etc/nginx/conf.d/*.conf;
}
这里有一点需要注意的是,在没有指定 --network
参数的情况下,Docker 默认会为每个新创建的容器分配一个子网合并与默认的 docker0 虚拟网络桥接。在上面的 Nginx 配置文件中,proxy_pass
设置的主机为地址为 localhost,这对于裸机部署的 Nginx 是可行的,但对于容器内部署的 Nginx 而言,其处于 Docker 为其分配的子网中,此时 localhost 指向的是这个子网而不是容器主机对应的 localhost。这时又由于其他服务不在 Nginx 的子网内,因此在默认网络配置情况下 Nginx 容器无法成功转发外部请求到对应的容器中。
解决上面的问题有几种方案,第一种方案也是最简单的方案,只需要在启动 Nginx 容器时指定 Nginx 容器网络为 host 即可,此时可以认为 Nginx 和主机处于同一网络环境内,因而可以正常转发服务。第二种方案则是利用 Docker 提供的网络管理服务来定制容器之间的网络通信行为,可以参考 docker 官方手册或者这个教程,具体实现细节这里不再赘述。
启动 Nginx 容器的命令如下:
docker run --name nginx -d --restart always \
--network host \
-v /home/configs/nginx/nginx.conf:/etc/nginx/nginx.conf nginx
总结
相比直接在裸机上配置各类服务,使用容器可以减少很多不必要的麻烦,部署、管理和升级起来都更加方便。多个容器之间进行互通需要留意 Docker 容器默认的网络行为,按需配置各个容器的网络互通关系。网络问题调试起来是比较困难且痛苦的,配置容器网络时要多加小心。