[NOTE] Frp原理深入实践、Nginx容器实践

近期将阿里云上的博客服务迁移到了本地机房服务器,同时oj平台的搭建也开始启动,目前多个服务通过不同端口访问的方式显得太过粗糙,本文将通过frp、Nginx、docker等工具以实现阿里云主机对多个本地服务的反向代理,对每个服务都分出对应的二级域名。

在frp的使用上,本文相较之前的文章更加深入,对frp的多个功能作出更多的探索。另外,也将初步地学习实践Nginx引擎。

一、相关说明

本文涉及到的环境:

  • Ubuntu Server 18LTS (local)
  • CentOS 7.3 (Aliyun)
  • frp 0.28.0
  • go 1.10.4

二、frp原理

内网穿透的原理在前文中已有说明,但基于需求的变化,我们需要使用frp更多的特性,了解相关的原理有助于减少在实现过程中的错误。

第一步:配置无误的情况下,frp服务端frp客户端先后启动,建立通信隧道,其中:

  • frp服务端监听http 7071端口(此端口可自定义),接收此端口下所有外网用户请求,注意此处的7071端口要和通信隧道所用的端口7000作出区别
  • frp客户端代理本地想要暴露给外网的web服务端口,本文以80, 8080端口为例

第二步:通过配置nginx反向代理,将指向本台公网服务器的nuzar.top下的子域名,映射到服务器的7071端口,也就是frp监听的那个端口。 外网用户访问gitlab.nuzar.topwordpress.nuzar.top下的子域名,例如 :

  • gitlab.nuzar.top
  • wordpress.nuzar.top

等同于访问nuzar.top:7071,会触发frp服务端和客户端的互动,从而http请求由frp服务端传递到frp客户端

第三步:frp客户端收到http请求后,基于自定义配置,则做如下处理:

  • 监听到http请求中的域名为 gitlab.nuzar.top,则将请求转发到我本地的8585web服务端口
  • 监听到http请求中的域名为 wordpress.nuzar.top,则将请求转发到我本地的8686web服务端口

第四步:本地的web服务收到http请求后,对请求做处理,并完成响应

第五步:frp客户端将响应结果回传给frp的服务端。服务端最终将响应回传给外网用户

第六步:最终的实测效果为:

  • 访问 gitlab.nuzar.top,等同于访问我本地的192.168.1.253:80
  • 访问 wordpress.nuzar.top,等同于访问我本地的192.168.1.253:8080

三、Configuration

话不多说,直接上配置信息,相关的属性会分别进行说明。

1.frps.ini

1
2
3
4
[common]
bind_port = 7000
vhost_http_port = 7071
subdomain_host = nuzar.top

bind_port为frp服务端与客户端之间通讯的端口,为事实上数据传输的端口;

vhost_http_port为http服务的代理端口,代理所有发往7071端口的http请求,之后通过bind_port将包装后的请求发送给bing_port相连接的服务器;

subdomain_host指定父域名,便于划分子网(但事实上最后还是通过nginx来划分的子网,一直到都弄完之后,才发现前面多打了个d,前面frp方案走不通,恐怕是这个的锅)。

为了便于调试,你也开启frp的dashboard界面,参照官网Dashboard,在frps.ini中加入:

1
2
3
4
5
[common]
dashboard_port = 7500
# dashboard's username and password are both optional,if not set, default is admin.
dashboard_user = admin
dashboard_pwd = admin

如果你之前有通过systemctl将frps加入到系统服务中的话,那么此时通过systemctl restart frps就可以让新配置生效了。

dashboard的效果图如下:

Screen Shot 2019-09-11 at 15.50.42.png

通过dashboard面板,可以很容易的观察到自己的配置是否生效。

2.frpc.ini

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
[common]
server_addr = REMOTE_IPADDR
server_port = 7000

[http-OJ]
type = tcp
local_ip = 192.168.1.252
local_port = 8080
remote_port = 28080

[ssh]
type = tcp
local_ip = 192.168.1.253
local_port = 26
remote_port = 6000

[ssh-gitlab]
type = tcp
local_ip = 192.168.1.253
local_port = 26
remote_port = 8082

[http-gitlab]
type = tcp
local_ip = 192.168.1.253
local_port = 80
remote_port = 6080

[http-nextcloud]
type = tcp
local_ip = 192.168.1.253
local_port = 40080
remote_port = 40080

frpc.ini配置文件,见名知义。需要注意的两点:1.配置文件中不要出现相同的名称,中括号中的名称只作代称,并不具有实际的意义;2.肯定有人会奇怪为什么明明是使用的http协议,但类型却标注的type = tcp,原因是官方并没有提供多web服务的穿透,在一些issue中有人发现使用tcp可以绕过单个web穿透的限制,最终达到理想的效果,具体的讨论可以前往#914 (comment).

3.nginx

本文中nginx采用docker的方式进行部署,由于nginx在启动时会加载相关的配置文件,因此在部署前需要在相应路径(之后挂载到容器中)编写一些配置文件。

  • /root/nginx/nginx.conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    user  nginx;
    worker_processes auto;

    error_log /var/log/nginx/error.log warn;
    pid /var/run/nginx.pid;

    events {
    worker_connections 2048;
    }

    http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    sendfile on;
    keepalive_timeout 65;
    client_max_body_size 10M;

    include /etc/nginx/conf.d/*.conf;
    }

    nginx.conf常规配置没什么好说的。

  • /root/nginx/conf.d/default.conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    server {
    listen 80;
    listen [::]:80;
    server_name nuzar.top;

    location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $http_host;
    proxy_pass http://nuzar.top:8080;
    }
    }
    server {
    listen 80;
    listen [::]:80;
    server_name gitlab.nuzar.top;

    location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $http_host;
    proxy_pass http://127.0.0.1:6080;
    }
    }

    nginx监听80端口,将对应的服务请求转发到对应的端口,由于目前我个人对于nginx也是个拿来即用的状态,了解不多。

四、添加域名解析

如果你也是使用的阿里云的云服务,可以直接登录域名解析管理页面。

以本文中的gitlab.nuzar.top为例:

  • 添加记录:A
  • 主机记录:gitlab
  • 解析路线:默认
  • 记录值:SERVER_IP_ADDR

如果使用的其他厂商的主机,也有相应的操作可以实现。

五、部署nginx容器

nginx部署的方式还是采用docker run,在部署时需要将宿主机的一些路径挂载到容器中的对应路径,因此,可以参考下我的文件树:

1
2
3
4
5
6
7
|-- conf.crt
|-- conf.d
| `-- default.conf
|-- html
| `-- index.html
|-- nginx
`-- nginx.conf

conf.crt用于存放证书,这个暂时不会用到;

nginx.conf为nginx配置文件;

default.conf为配置文件的补充文件,对于nginx主要的配置工作也是集中在这个文件之中;

index.html顾名思义,一个普通的页面,之后用certbot来申请证书的时候需要用到这个页面。

1
2
3
4
5
6
7
8
docker run -d -p 80:80 -p 443:443 \
-v $(pwd)/nginx/conf.d:/etc/nginx/conf.d:ro \
-v $(pwd)/nginx/conf.crt:/etc/nginx/conf.crt:ro \
-v $(pwd)/nginx/nginx.conf:/etc/nginx/nginx.conf:ro \
-v $(pwd)/logs/nginx:/var/log/nginx \
-v $(pwd)/nginx/html:/usr/share/nginx/html \
--restart=always --name=gateway --network=host \
nginx

需要注意的一点是,标注--network=host后,nginx容器才有能力代理主机的请求,否则nginx只在容器内部有效,host表示容器网络为主机模式;

ro表示挂载的文件为只读状态,另外在docker社区中,文件级别的挂载好像是不太推荐的,但连官网上对nginx的示例也是如此,所以就先这样用了。

五、总结

对于二级域名的配置,总的来说还是比较简单的,总的来说存在两套方案:一者是直接通过frp提供的subdomain进行配置,但前文也提到,由于后来才发现的一些错误,导致我过早地认为这套方案不适用;二是在frp实现内穿的基础之上,通过一个nginx引擎来完成一个类似反向代理的工作,这层nginx在现在看来有些多余,但出于熟悉nginx和之后加入https支持的目的,我将二级域名到指定端口的工作交由nginx来完成了。

另外,frp的功能远不止如此,目前项目比较活跃,加之也是由Golang编写,值得持续跟进。