ngrokd server & client 安装部署

一、背景 & ngrok功能介绍

自己家中设备(PC、NAS网盘、树莓派等)可能运行了一些接口服务,如遇放假或出远门时又想远程访问到这些服务,需要一些解决方案。

家用网络的公网IP一般是浮动的,之前一直使用的方案是花生壳动态域名服务+路由器公网IP绑定+路由器内网端口映射,方案如下图所示。

DDNS方案缺点还是挺多的,比如要自己找免费的DDNS服务,路由器繁琐的配置等。(但也有优点,比如家里电脑可以关机,远程发一个magic包就可以远程启动自己的电脑,主板开启网卡启动)。随着后来搬家次数增多,以及家里的运营商及自购路由器组网结构越来越复杂,端口映射配置也极为繁琐,慢慢就懒得折腾了,最近又有类似需求,这次换另一种方式来解决这个问题。

新方案采用ngrok Tunnel代理的方案,ngrok是一个开源的穿透工具,提供ngrok-server供服务端部署 和ngrok-client供被代理终端连接,ngrok-server可以部署在用户自己的公网服务器上,没有的话也可以用ngrok官方提供的或第三方提供的免费接入,整体方案如下。

Tunnel本质上就是一条tcp连接,再通过内部约定的具体应用层报文协商代理链路,本文不重点关注ngrok的Tunnel层具体实现。

下图是一个隧道建立和代理过程,我们在自己的pc上部署了若干API接口,现在想在公网上访问到这些本地服务:

  • ①:本地pc安装ngrok-client,并配置接入地址到我们的公网ngrokd服务(ngrok.xietiandi.tech:4443);启动后,client会主动发起到server的tcp Tunnel连接;
  • ②:client通过此Tunnel连接向server发起请求,指定server侧要为此Tunnel暴露出来的端口,如”Remote-Port:8081 -> current Tunnel”;同时,client内部也要维护此Tunnel接收流量到本机target的映射关系。
  • ③:Server端调用socket syscall,listen对应端口,维护该端口和Tunnel的映射关系,并将所有该端口流量转发至该Tunnel。
  • ④:公网流量发起请求到ngrokd服务的代理端口,端口流量会被ngrokd代理至ngrok-client,再由ngrok-client代理至本机服务。

二、docker部署运行实践

ngrokd - server侧部署

为了简化部署过程,我们在腾讯云虚机上通过docker对ngrokd(服务端)进行部署,参考自:https://github.com/jueying/docker-ngrok-server。
原文镜像做的已经很好了,但是对一些细节新手容易搞不明白,我将自己做错的步骤额外通过注释进行了说明,具体见后续介绍。

DockerFile如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 指定基础镜像
FROM centos:7

# 维护者信息
MAINTAINER jueying hhbvictory@163.com

# 复制执行脚本文件到容器目录中
COPY entrypoint.sh /sbin/entrypoint.sh

# 安装环境依赖并下载源码
RUN chmod 755 /sbin/entrypoint.sh \
&& yum install -y epel-release \
&& yum install -y golang openssl \
&& curl -o /tmp/ngrok.tar.gz "http://files.git.oschina.net/group1/M00/07/11/PaAvDFyZwgKAM7opAK7VK6NTB802069.gz?attname=ngrok.tar.gz" \
&& tar -zxvf /tmp/ngrok.tar.gz -C /usr/local \
&& rm -rf /tmp/ngrok.tar.gz

# 允许指定的端口expose
EXPOSE 80/tcp 443/tcp 8081/tcp 8082/tcp

# 指定ngrok运行入口
ENTRYPOINT ["/sbin/entrypoint.sh"]

docker entrypoint.sh 如下:

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
#!/bin/sh -e

# 设定工作目录
NGROK_HOME=/usr/local/ngrok

cd $NGROK_HOME

# init ngrok server if build.info is not exist.
if [ ! -f "build.info" ]; then
echo "init ngrok server!"
DOMAIN=$1 #服务端domain,签名证书时会设置证书domain为此参数的值
HTTP_PORT=$2 #http代理模式下,统一提供http请求入口的端口
HTTPS_PORT=$3 #http代理模式下,统一提供https请求入口的端口,自签名的证书会不受浏览器信任
TUNNEL_PORT=$4 #隧道端口,即给client接入的tcp端口

## 每次docker run会重新生成自签名根证书,并用root证书签发device证书
## 编译时会使用这两份生成的证书,并在client和server建立Tunnel时tls层会用这两份证书做校验,如果用的client和server不match,连接时server端会报错:error: tls: bad certificate

openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -subj "/CN=$DOMAIN" -days 5000 -out rootCA.pem
openssl genrsa -out device.key 2048
openssl req -new -key device.key -subj "/CN=$DOMAIN" -out device.csr
openssl x509 -req -in device.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out device.crt -days 5000

\cp rootCA.pem assets/client/tls/ngrokroot.crt
\cp device.crt assets/server/tls/snakeoil.crt
\cp device.key assets/server/tls/snakeoil.key

## 编译生成ngrok server和多平台clients,后面需要docker cp对应的client到自己的pc使用,才能正常通过tls认证
make release-server
make release-client
GOOS=windows GOARCH=386 make release-client
GOOS=windows GOARCH=amd64 make release-client
GOOS=darwin GOARCH=386 make release-client
GOOS=darwin GOARCH=amd64 make release-client
GOOS=linux GOARCH=386 make release-client
GOOS=linux GOARCH=amd64 make release-client
GOOS=linux GOARCH=arm make release-client

# save build info to file
echo "$DOMAIN" >> build.info
echo "$HTTP_PORT" >> build.info
echo "$HTTPS_PORT" >> build.info
echo "$TUNNEL_PORT" >> build.info
fi

# start ngrok server
DOMAIN=$(sed -n "1p" build.info)
HTTP_PORT=$(sed -n "2p" build.info)
HTTPS_PORT=$(sed -n "3p" build.info)
TUNNEL_PORT=$(sed -n "4p" build.info)

## 启动ngrokd。自己学习时可以修改entrypoint为 /bin/bash,自己做一遍此bash脚本的指令,会更有心得
./bin/ngrokd -tlsKey=device.key -tlsCrt=device.crt -domain="$DOMAIN" -httpAddr=":$HTTP_PORT" -httpsAddr=":$HTTPS_PORT" -tunnelAddr=":$TUNNEL_PORT"

ngrok-client 配置

  1. 拷贝配套的client到需要被代理的本地设备上

    1
    2
    # 将server配套的client从镜像中cp出来,并scp正确系统版本到自己的pc电脑上
    docker cp ngrok-server:/usr/local/ngrok/bin .
  2. tcp模式启动client

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # 新建配置文件 ngrok.cfg
    server_addr: "ngrok.xietiandi.tech:4443"
    trust_host_root_certs: false
    tunnels:
    gpt:
    remote_port: 8081
    proto:
    tcp: 6006

    # run client
    .\ngrok.exe -config="ngrok.cfg" start gpt

    # status log
    Tunnel Status online
    Version 1.7/1.7
    Forwarding tcp://ngrok.xietiandi.tech:8081 -> 127.0.0.1:6006
    Web Interface 127.0.0.1:4040
    # Conn 0
    Avg Conn Time 0.00ms
  3. http模式启动client

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 新建配置文件 ngrok.cfg
    server_addr: "ngrok.xietiandi.tech:4443"
    trust_host_root_certs: false

    # run client, 6006为本地被代理的端口,server端端口为entrypoint中指定的httpAddr,如果你是docker运行的,需要额外注意host到docker的端口映射
    .\ngrok.exe -config="ngrok.cfg" 6006

    # status log, http://7e8bc833.ngrok.xietiandi.tech 是ngrokd随机出来的多级域名,需要保证你的dns解析了*.domainName -> public ip
    Tunnel Status online
    Version 1.7/1.7
    Forwarding https://7e8bc833.ngrok.xietiandi.tech -> 127.0.0.1:6006
    Forwarding http://7e8bc833.ngrok.xietiandi.tech -> 127.0.0.1:6006
    Web Interface 127.0.0.1:4040
    # Conn 0
    Avg Conn Time 0.00ms

三、效果实测

我本地用有显卡的台式机运行着一个ChatGLM2量化版的接口服务,并运行在6006端口,用postman测试,baseUrl设为 http://127.0.0.1http://ngrok.xietiandi.tech:8081 时均可正常返回结果。