# 提高开发效率:打通 K8s 与本地之间的网络 ## 背景 部门开发的一套系统包含了很多组件,以及其依赖的三方组件,比如:Kafka、MySQL。 开发、测试人员都有各自的场景,所以不能共用同一套环境。 于是,我们基于 K8s 做了一套 “Pipeline” 系统。所谓的 Pipeline,其实就是每个人一个独立 K8s 名字空间, 这个空间内包含一整套系统所有需要的组件,各自互不影响,可一键启动、一键删除。 ## 问题 这套系统虽然看来非常优秀,但是在流程上也有一些严重的问题: 开发每次改完代码想要测试验证,需要:编译二进制、打包镜像、上传镜像、更新 K8s 的部署。 这简直是一项极其复杂的流程。整个过程走一遍,估计十分钟没了。 当然,我是相对于本地开发环境在 IDE 里面可以单步调试而言。 ## 目的 在项目组里面,我估计我是最反感这一套开发流程的,我不喜欢 Mock 写,我要在 IDE 里面调试,我要观察程序运行流程! 所以我花了不少时间,想把本机的网络和 K8s 的内部网络打通。 网络打通,这包括: - 能在本地通过域名访问 K8s 内的服务 - 能在 K8s 内通过域名访问本地的服务 以上就可以了。 ## 方案 这里给出一个实测可行的方案,但是作者目前还没有把它做成成品。 ### 本地能够解析 K8s 内的服务域名 K8s 内运行的应用都有对应的服务,所以通过以下命令可以找到所有的服务(域名)及其 IP 地址和端口(有省略): ```- $ kubectl -n xxx get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE adminer ClusterIP 10.107.83.151 80/TCP 8d es-3-1 ClusterIP 10.107.126.242 9200/TCP,9300/TCP 8d etcd ClusterIP 10.111.191.166 2379/TCP,2380/TCP 8d kafka-1 ClusterIP 10.110.59.149 9092/TCP 8d mysql ClusterIP 10.100.193.187 3306/TCP 8d nginx NodePort 10.109.151.73 80:31349/TCP 8d ``` `NAME` 就是 K8s 内的域名,`CLUSTER-IP` 就是其 IP。 可以简单地将它们提取出来保存到 `/etc/hosts` 文件后即可使用。 ```- $ cat /etc/hosts | grep nginx 10.109.151.73 nginx ``` ```- $ ping nginx PING nginx (10.109.151.73): 56 data bytes Request timeout for icmp_seq 0 Request timeout for icmp_seq 1 ^C --- nginx ping statistics --- 3 packets transmitted, 0 packets received, 100.0% packet loss ``` 域名已能手动方式解析正确。 也有其它方案可直接利用 K8s 的相关 API 来获取全量/监控变更,比如:[sercand/kuberesolver](https://github.com/sercand/kuberesolver)。 这样更简洁,维护性高。 ### 本地属于 K8s 内的流量转发到 K8s 内网 这一步其实是最难的。我一开始想着手动改路由表,后来我搜索到了一款非常好用的工具: > [sshuttle: where transparent proxy meets VPN meets ssh](https://github.com/sshuttle/sshuttle) > > Transparent proxy server that works as a poor man's VPN. Forwards over ssh. Doesn't require admin. Works with Linux and MacOS. Supports DNS tunneling. 这是一款非常容易使用的通过 SSH 建立 VPN 的工具。 VPN 的原理大家知道:把本机网络的流量通过 VPN 网络发送出去。 简单说就是:你不在办公室,却能连上办公室的网络并使用。完全就像处于办公室内一样。 所以我赶紧在我的 K8s 内部署了一个 sshd 服务,尝试把它搞起来。 #### 在 K8s 创建 sshd 服务 以下是 sshd 的部署文件(sshd.yaml): ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: sshd labels: spec: replicas: 1 selector: matchLabels: app.kubernetes.io/name: sshd app.kubernetes.io/instance: sshd template: metadata: labels: app.kubernetes.io/name: sshd app.kubernetes.io/instance: sshd spec: hostname: pipeline containers: - name: sshd image: panubo/sshd imagePullPolicy: Always env: - name: SSH_ENABLE_ROOT value: "true" command: - /bin/sh - -c args: - | set -euo pipefail /entry.sh echo 'ecdsa-sha2-nistp256 AAAAE3VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOsJRdlR0g2AatKk93rjkzbSnDTKXteJMQWtZLibJU7d/fUnsbgA71a8YltSz5qaBrQ4va5ShpQOVlaJi3YgSrs=' > /etc/ssh/keys/ssh_host_ecdsa_key.pub echo '-----BEGIN OPENSSH PRIVATE KEY----- b4BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS 1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTrCUXZUdINgGrSpPd645M20pw0yl7X iTEFrWS4myVO3f31J7G4AO9WvGJbUs+amga0OL2uUoaUDlZWiYt2IEq7AAAAsH2N+3d9jf t3AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOsJRdlR0g2AatKk 93rjkzbSnDTKXteJMQWtZLibJU7d/fUnsbgA71a8YltSz5qaBrQ4va5ShpQOVlaJi3YgSr sAAAAhANr5qpUS1qt0Thpli78qrLD61kUki9V2+ss3KlTPmsW/AAAAEXJvb3RAZTY5NzVl MjgyODE0AQIDBAUG -----END OPENSSH PRIVATE KEY----- ' > /etc/ssh/keys/ssh_host_ecdsa_key echo 'ssh-ed25519 AAAAC4NzaC1lZDI1NTE5AAAAIOOVKCHGjqlQB+SSfzOwqBbFgX59EVXR8fCyA/mw9aZE tao.yang@Taos-MacBook-Pro.local' >> /root/.ssh/authorized_keys sed -i 's/GatewayPorts no/GatewayPorts yes/' /etc/ssh/sshd_config sed -i 's/AllowTcpForwarding no/AllowTcpForwarding yes/' /etc/ssh/sshd_config apk add python2 /usr/sbin/sshd -D -e -f /etc/ssh/sshd_config --- apiVersion: v1 kind: Service metadata: name: sshd labels: spec: type: NodePort ports: - name: sshd nodePort: 2222 port: 22 targetPort: 22 protocol: TCP selector: app.kubernetes.io/name: sshd app.kubernetes.io/instance: sshd ``` 上面出现的服务器的公钥、私钥和我自己的公钥都是临时通过 ssh-keygen 生成的。 然后,部署它: ```- $ kubectl apply -f sshd.yaml $ kubectl get pods | grep sshd NAME READY STATUS RESTARTS AGE sshd-689d4b987-m6gbr 1/1 Running 0 1m $ kubectl get svc | grep sshd NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE sshd NodePort 10.43.179.7 22:2222/TCP 1m ``` 由于是 NodePort 服务,所以我可以在集群外部也能访问它。 然后我在 `.ssh/config` 里面增加了一条 ssh 快捷登录记录: ```ssh_config Host sshd # 这是 K8s 所在机器某一节点 HostName 192.168.1.6 # sshd 服务的 NodePort Port 2222 User root # root 的私钥 IdentityFile ~/.ssh/sshd_id_ed25519 ``` 然后通过以下方式 ssh 到 K8s 内部的 sshd 服务: ```- $ ssh sshd Welcome to Alpine! The Alpine Wiki contains a large amount of how-to guides and general information about administrating Alpine systems. See . You can setup the system with the command: setup-alpine You may change this message by editing /etc/motd. ssh:~# ``` #### 用 sshuttle 正向打通网络 查看 sshuttle 的帮助文档并执行以下命令即可: ```- $ sshuttle -r sshd 10.109.0.0/16 client: Connected. ``` 后面是需要打通的 K8s 的网段,可以从服务列表里面取得。 此时访问 K8s 内部的 nginx,应该就可以直达了: ```- $ curl 10.109.151.73 ``` ```html Welcome to nginx!

Welcome to nginx!

If you see this page, the nginx web server is successfully installed and working. Further configuration is required.

For online documentation and support please refer to nginx.org.
Commercial support is available at nginx.com.

Thank you for using nginx.

``` 从本机直接访问 K8s 的内部网络就算打通了,接下来。 ### 在 K8s 内部访问本机服务 前面已经创建了 sshd 服务,借助 ssh 的远程转发,要实现远程主机访问本机的服务简直不要太容易! 前面的 sshd 部署文件中有以下几行: ```bash sed -i 's/GatewayPorts no/GatewayPorts yes/' /etc/ssh/sshd_config sed -i 's/AllowTcpForwarding no/AllowTcpForwarding yes/' /etc/ssh/sshd_config ``` 这两行开启了 sshd 的端口转发服务。于是用以下一行代码即可实现在 K8s 内部访问本机服务: ```bash $ ssh -R '*:5601:localhost:5601' sshd ``` ## 结束语 我把把这个能力做成成品的任务留给了新入职的 3 个社招生来完成。 不过,他们找到了跟我想要实现的能力几乎完全一样的场景实现。来自于[阿里巴巴的 kt-connect 项目](https://github.com/alibaba/kt-connect)。