http2tcp: 一个通过 HTTP 转发 TCP 流量的小工具
我所在的小组做了一个平台级产品,部署在集团下各公司内,一共几十个站点,站点之间完全独立。从之前的物理机部署迁移到了 K8s 容器环境。 为了解决每次排查问题需要好几个烦人的地方:
- 找对应环境的机器 IP
- 申请机器的权限
- K8s 的 kubeconfig 文件
- 安装常用工具:kubectl、kafka、网络工具等
这一套流程走下来,很是费时费力。 后来我想到了一个我认为非常优雅的解决方案。
解决方案原型
在每个站点部署这个产品的时候,我在对应的 K8s 集群内创建了一个专门用于 debug 的容器。 这个容器里面,有各种各样的常用工具,有此站点的 kubeconfig 等。 那我怎么快速进这个容器呢?我在这个 debug 容器内跑了一个 sshd 服务,所以,如果能够通过 ssh 登录到这个容器环境内,所有的工具都一应俱全了,并且还在同样的网络内。 我以前写了一篇文章《在容器内运行 sshd 服务》介绍了如何在容器内启动 sshd 服务,可以参考。
所以,现在的问题变成了:如何快速登录到各个站点的 debug 容器内的 sshd 服务? 很显然,各个站点最容易区分的标识应该就是站点的域名了。所以,通过域名登录 sshd 是我需要解决的问题。 因为前端的 API 全部跑在容器环境内,所以我只需要将我需要的流量转发到我的 debug 容器内即可。 我在 nginx 内添加了一条简单的规则来做到:
1 2 3 4 5 6 7 8 |
|
现在如果我在我本地访问某个站点,比如:http://example.com/~debug/
,就能把流量转发到我的 debug 容器中。
通过主域名转发流量有一个特别明显的好处:不用再额外开端口了,体验非常丝滑。
但是,这还不够。因为这是一条 HTTP 的连接,如何把它“降级”为 TCP 连接?即:从 OSI 协议的应用层协议转换为传输层协议。
以前写过的相关文章或项目参考:
因为 NGINX 不支持 HTTP 的 CONNECT
动词,所以这条路走不通。那就按第二个参考的方案:用 HTTP 的 Upgrade 头部升级协议。
协议非常简单:如果你往 HTTP 的请求头部添加以下两个头部:
1 2 |
|
如果服务端支持协议升级,那它就会返回类似:
1 2 3 |
|
HTTP 的 101 状态码可能是非常多人都不知道的状态码,但是它真的实用。
在服务器返回上述响应后,不同于以往的 HTTP 流程——一个往返就结束,此时这条 HTTP 连接会变成 TCP 连接,也就是这条连接会变成双向的全双工连接。 此时就可以在条连接上进行任何传输层协议的进行。如果此时 HTTP 客户端把连接交给 ssh,HTTP 服务器端把连接交给 sshd。 那 ssh 和 sshd 就可以进行通信了。你可能听说过 WebSocket,它也是走的这条路。
上述的所有工作,就是我今天要介绍的小工具的原型。
http2tcp
这是一个不到 300 行 Go 语言写的小工具。具体的使用文档可以见代码仓库:https://github.com/movsb/http2tcp。
1 2 3 4 5 6 7 8 9 |
|
这里以我前面要解决的问题——通过站点域名代理 ssh 协议的使用方式来讲解如何使用此工具。
几个角色:
- ssh 用来登录的 ssh 客户端
- http2tcp 将 HTTP 转换成 TCP 协议的小工具
- nginx 站点域名反向代理网关
- sshd sshd 服务端
以下是使用步骤:
-
首先确保 sshd 在容器内(或你自己的服务器上)运行:比如监听 22 号端口。
-
在容器或服务器上运行 http2tcp(服务端角色)
1
$ ./http2tcp -s -t $TOKEN -l $SERVER_IP_OR_DOMAIN:12345
这条命令运行在服务器上,或者容器内。监听地址和端口任意,只要 nginx 可达即可。
-
在 nginx 上配置将指定的路径路由到 http2tcp:
1 2 3 4 5 6 7 8
location /~http2tcp/ { proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_read_timeout 600s; proxy_pass http://http2tcp:12345; }
-
配置
~/.ssh/config
,以使用 ssh 代理:Host example.com ProxyCommand http2tcp -c -d localhost:22 -e https://example.com/~http2tcp/ -t $TOKEN
上述
localhost:22
是 http2tcp 客户端告诉 http2tcp 服务端我要连接localhost:22
,即服务端的 sshd 服务端口。https://example.com/~http2tcp/
即是站点域名和转发路径。$TOKEN
是 http2tcp 服务端和客户端事先商量好的密码,不是必须的。 -
本地登录:
1
$ ssh example.com
完成!
附录
几点说明:
- 为了安全,只建议在 HTTPS 站点下使用此能力;
一些可能的不完善:
- http2tcp 服务器没有限制可以访问的地址,所以你要好好保存与服务器共享的 $TOKEN。
如果你有你自己的虚拟主机,可以把 ssh 端口走 HTTPS 的 443 端口代理出来(但是默认的 22 不要关,以防 web 服务器挂掉后你就彻底上不去服务器了), 这样有一个好处:做流量监控的人或设备,只能监听到标准端口(443)上的流量,然后一般来说这是一个正常的网站,所以很难察觉异常,于是放行?
如果 SSH 走 HTTPS 了,你也就可以在本地放心地 ssh -D
建立 SOCKS5 代理了,只会有一条 HTTPS 连接。是不是藏匿得比较深?
小组的专用版本
http2tcp 是通用的 http 转 tcp 工具。我在小组内定制的版本只需要指定一个域名即可,鉴权使用 ssh 自带的公钥鉴权,非常安全。所以少好几个参数,简直小而美。
学习了,弄到最后发现托管服务器sshd端口转发是关闭的.
哈哈哈,测试之前应该保证原始的端口转发是可用的呀!对照组实验。
非常好,完美解决问题
竟然敢用 WebSocket 分支😄,Issues 里面有人说不稳定,但是我实测大半年高强度使用下来很稳定,也就没有修复。
对于在国内使用,main 分支应该是足够用的。WebSocket 的目的主要是:
(你网站字体看着很舒服啊。)
谢谢大佬夸赞🥰,字体用了 霞鹜文楷,也是从别的 blog 看到偷过来用的。
用 WebSocket 分支的原因是我的场景是需要走大量数据的 SQL 查询,所以寻求更稳定的 TCP 长连接。因为是调试环境而非生产环境,所以果断尝试,暂时没发现什么问题~~(不过 dalao 出品能有什么问题呢)~~。
谬赞谬赞,能用就好!
字体,用上了用上了!先尝试一下,但是初看有点奇怪,还是觉得你的更好看,也不知道是哪里不对🥹
另外:
怎能在妇女节这天说不行?🥵
因为改用 qwq.me 了,所以就没再弄,你这么说我赶紧回去看了一下,发现是 ACME 的 FileSystem 验证路径出了问题,现在应该好了。
我刚想上来看看你的跟随系统的夜间模式主题是怎么实现的,就发现访问速度慢了不少。没载出来的时候 F12 看网络面板,正好是霞鹜文楷的 css 加载过慢。还在想你是不是换了字体哈哈哈。我没挂梯子。也许你应该换个 CDN?
我用的是 elemecdn。自测挂不挂梯子速度都还可以。
个人感觉霞鹜文楷,以及其他 Serif 字体,蛮挑字体大小的,你的网页,我浏览器上缩放到 90% 以后,感官好了不少。
一个不成熟的想法,sans-serif 字体相比于 serif,更适合拿来做技术类博客,成堆的文字,前者看起来更不易疲劳。
西文建议还是 sans-serif,我自己用的是 Varela Round。
代码还是用 mono,我看你现在代码也被霞鹜文楷覆盖了。
看了下,确实已经好了。
是的呀,我也注意到了,尝试切换之前我就有这个顾虑,我看到仓库大小有 60+ MB 时就有点儿慌了。 虽然作者说已经按 Unicode 范围拆分字体文件,但是实际体验下来还是差点儿意思。
确实不应该用 GitHub,毕竟 GitHub 在国内早就不能顺利访问了。那我待会儿有空换个源试试。
换之前是 15px,换之后发现由于是衬线字体,相比于非衬线字体来说,有些笔画还是略显苗条。所以我改成了 18px。确实稍微大了点,现在准备改成 16px。
霞鹜文楷有非衬线的版本吗?没有吧?那不然岂不是不能用这个字体了。😅
再看了一眼,觉得这个字体用作代码的时候有点手写体的感觉,竟然还蛮好看的😋,先暂时用用看。
可供参考的两篇博文:
找到了,是
@media(prefers-color-scheme: dark)
🤤霞鹜文楷没有非衬线,我的意思是,干脆就不用霞鹜文楷了,原来的字体其实蛮好看的,蛮适合你博客。 并且我还因此考虑要不要也把我的博客也改成非衬线字体,结果尝试了一下,效果并不好,就算了。
啊!那岂不是白搞了,哈哈哈哈…… 先保留几天吧!体验下新鲜感!确实蛮好看的。你的好看,别换,哈哈哈哈……
电脑上看了看,jsdeliver 快多啦,字体大小也刚刚好。😍
受不了,你这么一说,我再仔细审视了一番,简直赏心悦目 ❤️❤️❤️ (又熬夜了)
这两天我也总是上来看看,想夸两句,又怕在这里回复太多影响观感。好了,现在我忍不住了,确实好看🤩🤩
问题不大,我不介意。哈哈哈哈……
不过,我的博客评论是可以任意跨文章转移到其它文章的评论下面去的,如果哪天有空专门写篇记录博客日志(关于字体的),我就转移过去。
另外,有鉴于最近一年的旅游超多,最近竟然产生了记录生活的想法……🥹 尽管觉得有点儿不妥,建站十年了快,还几乎没有写过生活。
为啥会不妥呢。记录生活是好事,过程中也许会对人生有更深刻的感悟。我赞同你这么做,实在觉得不妥的,写成私密日志就好啦。
我有写日记📔的习惯,并且也写了很久了,在一个独立的仓库。但是写日记比较随意,也并不认真,马马虎虎记录一下。哈哈哈🤦♂️,心想不是给大家看的,所以就没啥注意的。没事,本身也就是个作为记录日常的载体。如果写成文章公开给大家看,那就要比较用心地写了。
那我就不客气了😋
头像改了,字体颜色也改了~蓝色清爽很多
哈哈哈,睹物思人罢了……🤡 人都没了,才想起换个情侣头像…… 这是病,得治。
有点伤了
很酷的思路!
谢谢~网站做得真漂亮🤩
服务端想把http2tcp做成服务,不知道为什么总是启动超时,还有这个去掉token(不含-t)后好像无法连接,提示未认证
“启动超时”是什么意思?目前 master 分支的代码中 token 是必须的,你可以随意设置一个就行。
可以像下面这样测试:
服务端:
客户端:
我手动执行服务端是可以的,关键是我想做成开机启动的服务,在服务中一直staring。
这个应该是创建服务哪里有问题了,可能我对service不熟,找不到原因,一般来说我的其他服务都没有遇到过这种情况
可以发一下你的 service 文件,我这边帮你调试一下。
好的,谢谢
/usr/bin/http2tcp -s -l 127.0.0.1:2222 -t test >/dev/null 2>&1 & 也同样试过了
有点奇怪,我找了几台电脑试了一下都没有问题。
要不试试设置一下超时时间:
TimeoutSec=0
。可以了,我发现了问题的可能原因,/etc/systemd/system/http2tcp.service和/lib/systemd/system/http2tcp.service文件内容不一致造成的,谢谢了,我明天测试下还有没有断联的问题,我这两天试了下,不定时会自动断开,我不知道你用的时候遇到过这种问题没。
断开问题我倒是没怎么遇到,可以看看是不是因为服务端的连接数太多,有没有报错之类。
断联的问题知道了,配置增加发送空包保持连接就可以解决了,应该是http长时间没有活动所以断开的。
还有关于github我有个小建议,增加个action,弄个自动发布的,我fork你的源码后弄了个自动发布,不过仓储我设为私有了,需要的话我可以公开,很简单
这个我是一直想弄的,太懒了,一直拖。 周末我搞一搞,有问题我再请教你。
好,我也是瞎弄的
弄好了,现在是每次 push 到 main 的时候自动覆盖发布到 latest Release。
感觉用 Docker 的场景好像不大,暂时没有做 Docker 的推送。
挺好的,我个人的还增加了windows版本