文章目录
- 环境
- 背景
- SSH tunnel和正向/反向代理
- 步骤
- 第一步
- 第二步
- 效果
- 考一考
- 其它
- 多次跳转
- 另一种方法:正向代理
- 参考
环境
- 服务器:Ubuntu 22.04
- 客户端:Mac 14.2.1
背景
在远程Linux服务器上搭建了minikube环境。minikube提供了dashboard功能,可以在浏览器里以图形化的方式和Kubernetes集群交互。访问的URL为 http://127.0.0.1:45891/xxxxx
。
注:关于minikube,参见我另一篇文档( https://mp.csdn.net/mp_blog/creation/success/135585639
)。
但是,远程服务器只有命令行,没有图形界面。而且由于网络设置问题,服务器里的服务并没有暴露在公有IP上,在客户端无法直接访问服务器的URL。
client ⇒ (inaccessible) ⇒ server(private IP)
一种解决办法是给远程服务器安装图形界面,然后用VNC或远程桌面连接等工具连接,打开浏览器。通过私有IP访问URL,但是这样会消耗大量资源,而且巨慢无比,点一下鼠标好几秒都没反应。
另一种解决办法是,通过mobaxterm等软件,在客户端打开服务器的浏览器(注意是在客户端显示),和在服务器桌面上打开浏览器的效果一样,同样在浏览器里通过私有IP访问URL。我没有试过,是否需要在服务器安装图形界面。但是同样会消耗大量资源,而且网络也很慢,貌似还有字符集等乱七八糟的问题。此外,mobaxterm貌似只有Windows版,没有Mac版。我没有深入研究,应该也有别的替代工具或者手工方法。
本文介绍的SSH tunnel方法,简单直观,只用几条SSH命令,不需要借助其它工具和命令,就能在客户端浏览器通过访问localhost,达到访问远程服务器URL的效果。
SSH tunnel和正向/反向代理
首先需要了解一下SSH tunnel和正向/反向代理,可参见我另外两篇文档:
- ssh端口转发实例:
https://blog.csdn.net/duke_ding2/article/details/106878081
- Nginx正向代理与反向代理的简单例子:
https://blog.csdn.net/duke_ding2/article/details/107108962
本文使用的是SSH正向tunnel(ssh连接的方向和tunnel的方向是一致的)和反向代理(代理的是服务器,客户端无需知道服务器,只需访问代理)。
创建tunnel的命令为:
ssh -qTfnN -L <source port>:<dest host>:<dest port> <ssh server>
其中:
-L
:表示local,也就是正向tunnel,tunnel的方向和ssh的方向是一致的。<source port>
:tunnel的client端的端口,对于正向tunnel,tunnel的client端就是SSH的client端,所以也就是运行ssh命令的主机的端口。<dest host>
:把请求转发到哪个主机,可以是localhost(注意是tunnel的server端),也可以是指定的主机(比如github)。<dest port>
:把请求转发到目的主机的哪个端口上。<ssh server>
:ssh命令登录的server,对于正向tunnel,ssh的server端就是tunnel的server端。
所以,该命令会把tunnel client端(也就是运行该ssh命令的机器)的 <source port>
的请求转发 <dest host>:<dest>
上。
注意: <dest host>
是从tunnel server端(也就是 <ssh server>
)指定的(所以localhost指的是tunnel server端本身),从tunnel server到 <dest host>
应该确保是可访问的。
步骤
网络拓扑结构如下:
client ⇒ server (public IP) ⇒ server(private IP)
说白了就是,客户端无法直接访问服务器的私有IP地址,所以通过服务器的公有IP地址转发一下。
所以,需要两步走:
- 在服务器上,创建一个tunnel,把公有IP地址特定端口(为了方便,也用45891)的请求转发到私有IP地址的45891端口上。
- 在客户端上,创建一个tunnel,把访问本地(localhost)特定端口(为了方便,也用45891)的请求转发到服务器的公有IP地址的45891端口上。
第一步实现了网络拓扑结构里的右边箭头,第二步实现了网络拓扑结构里的左边箭头。
你可能觉得,第二步貌似是多余的,只需要第一步搭建好转发规则,然后在客户端直接访问服务器的公有IP地址不就行了。但是,第二步还是必需的,因为做完第一步之后,在客户端nc服务器的公有IP地址和45891是nc不通的:
➜ ~ nc -zv 9.30.123.105 31578
nc: connectx to 9.30.123.105 port 31578 (tcp) failed: Connection refused
原因我也不太明白,有待研究。总之,需要在客户端本机上再转一道才行。
第一步
首先,在服务器上,创建一个tunnel,把公有IP地址45891端口的请求转发到127.0.0.1的45891端口上:
ssh -qTfnN -L 45891:127.0.0.1:45891 127.0.0.1
注意:该命令里有两个 127.0.0.1
:
- 第一个
127.0.0.1
是转发的目的地址,因为在服务器本机上能访问的URL是http://127.0.0.1:45891/xxxxx
,所以转发的目的地址就是127.0.0.1
。记住,此处和URL保持一致就对了。 - 第二个
127.0.0.1
是SSH的登录地址,因为登录的是本机,所以这个地方只要写127.0.0.1
就行了。
第二步
接下来,在客户端,创建一个tunnel,把localhost的45891端口的请求转发到服务器公有IP地址的45891端口上:
ssh -qTfnN -L 45891:localhost:45891 root@9.30.123.105
注意:该命令里的两个地址:
localhost
:表示转发地址。前面提到,此处的localhost并不是运行ssh命令的机器的localhost,而是tunnel server端的localhost,本例中,tunnel server端就是ssh的server端,所以,转发的地址是9.30.123.105
。9.30.123.105
:ssh的server端(也就是tunnel的server端)。
效果
现在,在客户端打开浏览器,访问 http://127.0.0.1:45891/xxxxx
,就能访问服务器了:
考一考
现在,我在服务器 9.30.188.63
上部署了一个应用,并且把它暴露到了 http://192.168.49.2:30633
:
root@kai0116ubuntu1:~# minikube service hello-node --url
http://192.168.49.2:30633
同时,使用 minikube tunnel
命令,把应用暴露到了 http://10.105.215.100:8080
:
root@kai0116ubuntu1:~# kubectl get services hello-node
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-node LoadBalancer 10.105.215.100 10.105.215.100 8080:30633/TCP 7m23s
该如何设置tunnel,以便在客户端通过浏览器访问呢?
答:
- 对于
http://192.168.49.2:30633
:
- 在服务器:
ssh -qTfnN -L 30633:192.168.49.2:30633 127.0.0.1
- 在客户端:
ssh -qTfnN -L 30633:localhost:30633 root@9.30.188.63
在客户端打开浏览器,访问 http://localhost:30633
,如下:
- 对于
http://10.105.215.100:8080
:
- 在服务器:
ssh -qTfnN -L 8080:10.105.215.100:8080 127.0.0.1
- 在客户端:
ssh -qTfnN -L 8080:localhost:8080 root@9.30.188.63
在客户端打开浏览器,访问 http://localhost:8080
,如下:
再次说明,在服务器端设置tunnel时,目的地址和能访问的URL里的地址保持一致:
- 第一个URL里是
192.168.49.2
,所以就设置为192.168.49.2
。 - 第二个URL里是
10.105.215.100
,所以就设置为10.105.215.100
。
其它
多次跳转
你可能会想,这个问题其实应该去调整服务器的网络设置,把服务暴露在公网IP地址上。这确实有道理,本文只是是一个示例,如果做端口转发。
但是,考虑以下场景:
client ⇒ jump server (public IP) ⇒ internal server(private IP)
服务器在内网里,内网和外界隔离,在外界只能通过jump server跳转,才能访问服务器。
此时SSH tunnel就非常有用了。该模型其实和本文的tunnel模型是完全一样的,只不过在本文的模型里,右侧箭头指向的是服务器本身而已,其实在处理上并无差异。
另一种方法:正向代理
文本介绍的方法,其实是一个“笨办法”,如果服务器暴露了新的端口,或者重启服务导致端口发生变化时,都得重新建立SSH tunnel。
还有一种方法是使用正向代理,说白了就是使用浏览器代理,和通过代理访问google是一样。
第一步:在客户端上建立一个到服务器的动态转发:
ssh -o ExitOnForwardFailure=yes -qTfN -D 6666 root@9.30.188.63
其中,端口可以任意指定。
第二步:把客户端的浏览器的代理设置为 localhost:6666
。
我使用的是Chrome浏览器和SwitchyOmega插件,设置如下:
这样,当在浏览器里访问 http://127.0.0.1:39783/xxxxx
的时候,通过代理,也就是 localhost:6666
,会把请求转发到 9.30.188.63
,也就是相当于在服务器上访问 http://127.0.0.1:39783/xxxxx
,效果如下:
注意:默认情况下 127.0.0.1
的访问是不通过代理的,需要显式指定一下。
同理,如果访问 http://192.168.49.2:30633
,通过代理转发,就相当于在服务器上访问 http://192.168.49.2:30633
,效果如下:
如果失败了,检查一下 192.168.49.2
是否设置为通过代理转发了。
同理,如果访问 http://10.105.215.100:8080
,通过代理转发,就相当于在服务器上访问 http://10.105.215.100:8080
,效果如下:
如果失败了,检查一下 10.105.215.100
是否设置为通过代理转发了。
这种方法看起来更简便一些。只需设置一次SSH tunnel,而之前的“笨办法”则需要对不同的端口设置多次。
我之所以没采用这个方法,是因为设置浏览器代理是一个“重量级”操作,而且我的浏览器本身已经设置了其它代理,不想改变。前面的“笨办法”适合于临时的、一次性的设置。大家可根据所遇到的情况以及个人喜好,自行选择。
参考
https://blog.csdn.net/duke_ding2/article/details/106878081
https://blog.csdn.net/duke_ding2/article/details/107108962