我所在的小组做了一个平台级产品,部署在集团下各公司内,一共几十个站点,站点之间完全独立。从之前的物理机部署迁移到了 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 自带的公钥鉴权,非常安全。所以少好几个参数,简直小而美。