在我们团队将产品的部署形态迁移到 kubernetes 上之后,在研发过程中,开发和联调代码的过程非常的痛苦,决定寻找在 k8s 环境下的云原生时代的打通本地和开发环境的解决方案。
回顾一下,过去的开发过程,我们的进程一共有 2 种模式
分析k8s 无关进程
由于项目依赖了 oracle 连接库,安装 oracle 驱动,在团队中既有 window,unbuntu,和 mac 的情况下,团队成员的本地开发环境的搭建非常复杂进程依赖的环境变量非常多,如果开发环境的 ip 换掉,配置环境变量非常麻烦进程内依赖了一些二进制 bin 文件,格式是 ELF 的,这些文件在 windows 和 mac 的环境上是无法执行的
依赖了 k8s 的进程
需要复制开发环境上 kubeconfig 文件到本地,一个人如果同时开发多个环境,切换 kubeconfig 文件非常繁琐本地开发无法将开发环境上的流量导到本地,需要打包替换环境上镜像来完成联调
现状因此日常的开发流程便是:
- 配置环境变量和 kubeconfig
- 使用 postman 等其他工具进行本地开发
- 提交代码、跑 CI 流程,出镜像
- 替换环境上镜像
- 自测
显然,为了保证在本地开发的代码,在环境上真实运行的效果完全一致,带来了很大的工作效率问题,本地编码 2 分钟,上环境自测 5 分钟
需求因此我们急需一种便捷的,学习成本低的,基于配置的,一劳永逸的,团队统一的解决方案来打通本地和开发环境,节省本地开发环境的搭建和配置的时间,节省团队成员联调的时间,提高团队效率
方案选型telepresence参考https://docs.microsoft.com/zh-cn/visualstudio/bridge/overview-bridge-to-kubernetes?view=vs-2022
介绍Telepresence 是一款为 Kubernetes 微服务框架提供快速本地化开发功能的开源软件。Telepresence 在 Kubernetes 集群中运行的 Pod 中部署双向网络代理,该 Pod 将 Kubernetes 环境(如 TCP 连接,环境变量,卷)中的数据代理到本地进程。本地进程透明地覆盖其网络,以便 DNS 调用和 TCP 连接通过代理路由到远程 Kubernetes 集群,能够获取
特性- 基于在本地计算机使用 docker,fuse/sshfs 和流量转发技术,来将本地和远程之间的流量和文件系统打通,以在本地使用 ide 进行调试
- 本地访问远端的服务, 跨 namespace
- 本地服务可以完全访问远程群集中的其他服务;
- 本地服务可以完全访问 Kubernetes 的环境变量,Secrets 和 ConfigMap
- K8S 中运行的远程服务也可以完全访问本地服务
- 通过在本地使用 docker 来运行本地代码,并和远程 k8s 服务通信,本支行承担了类似 kubelet 的角色
- 不支持 ide 插件
- 不能将本地代码同步到远端进行调试
- 没有做隔离,进入调试模式后,服务的可用性取决于本地服务
- 只能使用命令操作,操作不能配置化,操作繁琐
https://docs.microsoft.com/zh-cn/visualstudio/bridge/overview-bridge-to-kubernetes?view=vs-2022
介绍Bridge to Kubernetes 可重定向已连接的 Kubernetes 群集与开发计算机之间的流量。 Kubernetes 群集中的本地代码和服务可以像在同一 Kubernetes 群集中一样进行通信。
Bridge to Kubernetes 插件只可用于 Visual Studio 和 VS Code,支持其他 ide 的计划还在官方的 RoadMap 上没有开始
工作原理
- 提示你在群集上配置要替换的服务,在开发计算机上配置用于代码的端口,并将代码的启动任务配置为一次性操作。
- 将群集上 pod 中的容器替换为远程代理容器,它会将流量重定向到开发计算机。
- 在开发计算机上运行 kubectl port-forward,将流量从开发计算机转发到群集中运行的远程代理。
- 使用远程代理从群集收集环境信息。 此环境信息包括环境变量、可见服务、卷装载和机密装载。
- 在 Visual Studio 中设置环境,以便开发计算机上的服务可以访问相同变量,就像它在该群集上运行一样。
- 更新 主机文件 ,将群集上的服务映射到开发计算机上的本地 IP 地址。 这些主机 文件条目允许开发计算机上运行的代码向群集中运行的其他服务请求。 若要更新 主机文件 ,Bridge to Kubernetes 计算机上需要管理员访问权限。
- 开始在开发计算机上运行和调试代码。 如有必要,Bridge to Kubernetes 停止当前使用这些端口的服务或进程,以释放开发计算机上所需的端口。
- 打通本地和远程 k8s 环境的流量
- 将环境信息包括环境变量、service、volume,secret,configmap 克隆到本地
- 通过自动修改/etc/hosts 文件,将集群上的服务映射到开发计算机上的本地 IP 地址缺陷
Bridge to Kubernetes 具有以下限制:
- 要使 Bridge to Kubernetes 成功连接,一个 pod 只能有一个容器在该 pod 中运行,但是现实中有很多多容器的 pod。
- 目前,Bridge to Kubernetes pod 必须是 Linux 容器。 Windows 容器。
- 如果 k8s 环境是集群环境,若要更新 /etc/hosts 文件 ,Bridge to Kubernetes 计算机上需要管理员访问权限
Nocalhost 是一款开源的基于 IDE 的云原生应用开发工具
- 直接在 Kubernetes 集群中构建、测试和调试应用程序
- 提供易于使用的 IDE 插件(支持 VS Code 和 jetbrains),即使在 Kubernetes 集群中进行开发和调试,Nocalhost 也能保持和本地开发一样的开发体验
- 使用即时文件同步进行开发: 即时将您的代码更改同步到远端容器,而无需重建镜像或重新启动容器。
https://nocalhost.dev/zh-CN/docs/introduction
介绍Nocalhost 是一款开源的基于 IDE 的云原生应用开发工具:
- 直接在 Kubernetes 集群中构建、测试和调试应用程序
- 提供易于使用的 IDE 插件(支持 VS Code 和 JetBrains),即使在 Kubernetes 集群中进行开发和调试,Nocalhost 也能保持和本地开发一样的开发体验
- 使用即时文件同步进行开发: 即时将您的代码更改同步到远端容器,而无需重建镜像或重新启动容器。
nocalhost 的野心相比 Bridge to Kubernetes 和 Telepresence 更大,具有开发环境的管理体系。深度整合 vscode / IntelliJ IDEA 实现了一键部署上云,一键远程 debug 等式开发的贴心功能。其设计的核心就是自动同步:
- 自动同步和热加载代码
- 文件
- 终端
Nocalhost 目前是完全开源的,并已进入 CNCF Landscape:
并且支持 goland, idea,pycharm, vscode
工作原理Nocalhost 由单个二进制 CLI 和 IDE 插件组成。 理想情况下,您可以直接将它与您熟悉的 IDE 一起使用。
Nocalhost 不需要服务器端组件,因为它通过 KubeConfig 直接与您的 Kubernetes 集群通信,就像 kubectl 一样。
特性实时线上编码Nocalhost 预配置为与你喜欢的 IDE 配合使用,你可以一键连接到任何 Kubernetes 集群,开始享受在集群内进行编码,摆脱烦人的本地环境配置。
实时同步Nocalhost 可以在你每次进行修改时自动将代码同步到 K8s 容器中。 这样能免去了镜像提交、构建和推送等步骤,极大提速了开发的反馈循环。 因此代码修改可以实现秒级生效。
易于协作
Nocalhost 可帮助你的团队实现标准化开发工作流程,而无需让团队中的每个人都成为 Kubernetes 专家。
- 您团队中的 Kubernetes 和 DevOps 专家可以通过 Nocalhost Server 配置和管理集群、应用程序、DevSpace 和用户。阅读有关 Nocalhost Server 的更多信息
- 您团队中的开发人员无需成为 Kubernetes 专家,即可轻松查看项目并即时在 Kubernetes 集群中开始编码和调试。
Nocalhost 由单个二进制 CLI 和 IDE 插件组成。 理想情况下,您可以直接将它与您熟悉的 IDE 一起使用。
为什么使用 Nocalhost?构建 Kubernetes 应用并不简单,尤其对于大型开发团队来说更具挑战。 Nocalhost 提供了最高效的方式来构建云原生应用程序。
使用 Nocalhost 直接在 Kubernetes 中进行开发的优势有:
- 生产环境相似性 - 开发环境与你的生产环境非常相似,让你更有信心在发布新功能时一切都将在生产环境中正常工作。
- 提速反馈循环 - 通过文件同步,你所有代码更改可以在容器中立即生效,而无需重建镜像或重新部署容器。
- 灵活的扩展性 - 开发人员无需再担心本地资源不足。
- 降低成本 - 更有效地使用资源并降低 IT 设施成本。
在下述环境里,在 Kubernetes 集群中进行开发很有用:
- 本地资源的限制
- 由于环境的特殊性,在本地无法搭建能够运行的环境
- 想要在类似生产环境中测试你的应用
- 想要调试在本地计算机上难以重现的问题
- 应用程序需要访问集群内部服务 (例如集群 DNS)
三者比较来看,nocalhost 是特性最多,支持的场景最多,并且支持 IDE 的插件最多,用户体验最好,完胜其他两位,是其他两个工具支持的特性的超集,因此我们选择了 nocalhost
开始使用安装vs code- 打开 VScode 编辑器,然后单击左侧栏中的方块图标
- 在搜索输入框中键入 nocalhost
- 选择 Nocalhost 插件,然后单击安装按钮进行安装。
jetbrains
Nocalhost 支持 JetBrains 全系列 IDE, 请参阅 安装 JetBrains 插件。
当安装 Nocalhost 插件时,会自动帮你安装 nhctl
Windows`文件 > 设置 > 插件 > 浏览插件仓库... > 搜索 "Nocalhost" > 安装插件"
MacOS`首选项> 设置 > 插件 > 浏览插件仓库. 。> 搜索"Nocalhost" > 安装插件
编写配置
需要在项目根目录下创建.nocalhost 文件夹,并在其中创建 config.yaml 文件。这个文件编写好后可以传到版本控制系统中去,整个团队共享,易于协作, 一个项目只需要编写一次,有一个人负责维护,其他人都可以无须知道该技术的细节,从而将精力和时间聚焦在核心业务之上。
目录结构如下:python 项目设置
注意: 使用 pycharm 需要使用商业版,社区版没有 remote debug server 的插件,无法使用 remote debug 的功能
config.yaml 设置如下
configProperties:
version: v2
application:
name: cloud-api-server
services:
# name 与 serviceType 表明这是属于 dock 这个 deployment 的配置
# 配置的内容集中在 containers 中
- name: cloud-api-server
serviceType: deployment
dependLabelSelector:
pods:
- "app.kubernetes.io/name=cloud-api-server"
# 可以为单个工作负载下的多个容器分别定制不同的配置
containers:
# 必须设置对应容器的名字,以区分不同的容器
# 如果有多个容器,但是只需要调试一个容器的话,只设置一个container即可
- name: cloud-api-server
dev:
# 开发容器配置
# 开发镜像
# must install pydevd in dev image
image: registry.***.com/***/cloud-api-server:v18.0.0
# 开发容器默认 Shell
shell: "bash"
# 文件同步的远程目录
workDir: /app/
# 开发容器资源的 request 和 limit
resources:
limits:
cpu: "2"
memory: 2Gi
requests:
cpu: "2"
memory: 2Gi
# 热加载
hotReload: true
command:
# 一键运行
run:
- /venv/bin/python
- run.py
- --port=11101
- --debug=true
# 一键调试, must install pydevd in dev image
debug:
- /venv/bin/python
- -m
- pydevd
- --client
- 127.0.0.1
- --port
- 8008
- --file
- run.py
# debug 协议的端口
debug:
remoteDebugPort: 8008
# Ports to be forwarded to local when enter devMode
# 进入 Devmode 后是否自动开启端口转发功能
portForward:
- 11101:11101
useDevContainer: true
# https://nocalhost.dev/zh-CN/docs/config/config-pattern
sync:
type: "send" # 一般设置send就行
mode: "pattern"
# 需要同步的文件的模式
filePattern:
- "."
ignoreFilePattern:
- ".DS_Store"
- ".idea"
- "logs/**"
- ".git"
- ".github"
- ".vscode"
- "bin"
- "docs"
- "examples"
- "tests"
- "venv"
通过编写这份配置文件,来描述本地环境和远程 k8s 环境的 流量,文件同步,远程 debug 的通信端口的关系。nocalhost 插件会通过加载该配置文件来完成所有工作
golang 项目设置
configProperties:version: v2
application:name: dock
services:
# name 与 serviceType 表明这是属于 dock 这个 deployment 的配置
# 配置的内容集中在 containers 中
- name: dock
serviceType: deployment
dependLabelSelector:
pods:
- "app.kubernetes.io/name=dock"
# 可以为单个工作负载下的多个容器分别定制不同的配置
containers:
# 必须设置对应容器的名字,以区分不同的容器
# 如果有多个容器,但是只需要调试一个容器的话,只设置一个container即可
- name: dock
dev:
# 开发容器配置
# 开发镜像
image: golang:1.17
# 开发容器默认 Shell
shell: "bash"
# 文件同步的远程目录
workDir: /opt/dock-template/
# storageClass 持久化需要 storageClass 的能力来提供支持
storageClass: "csi-localpv"
persistentVolumeDirs:
- path: "./local-pv" # Dir to be persisted in DevContainer
capacity: 1Gi # Capability of the dir
# 开发容器资源的 request 和 limit
resources:
limits:
cpu: "2"
memory: 2Gi
requests:
cpu: "2"
memory: 2Gi
# 热加载
hotReload: true
command:
# 一键运行
run:
- go
- run
- dock-server.go
# 一键调试
debug:
- go get -d github.com/go-delve/delve/cmd/dlv &&
- dlv
- --headless
- --log
- --listen
- :9009
- --api-version 2
- --accept-multiclient
- debug
- dock-server.go
# debug 协议的端口
debug:
remoteDebugPort: 9009
# Ports to be forwarded to local when enter devMode
# 进入 Devmode 后是否自动开启端口转发功能
portForward:
- 8080:8080
useDevContainer: false
# https://nocalhost.dev/zh-CN/docs/config/config-pattern
sync:
type: "send"
mode: "pattern"
filePattern:
- "./"
ignoreFilePattern:
- ".DS_Store"
- ".git"
- ".idea"
- "logs"
- "dock.tar.gz"
# Specify dev mode environment parameters
env:
- name: GOPROXY
value: https://goproxy.cn
# 当存在 go.mod 文件时或处于 GOPATH 外, 其行为均会等同于 GO111MODULE=on。相当于 Go 1.13 下你可以将所有的代码仓库均不存储在 GOPATH 下
- name: GO111MODULE
value: on
当编辑好配置后,开始进行开发之前,需要进行一次将本地代码和远程环境上的工作负载关联的动作,因为 nocalhost 插件是在 ide 中跨项目共享的。意思是,你只需要配置一次远程服务,在使用 pycharm/goland 打开多个 repo 的时候,为了避免混乱,你需要将本地的某一个确定目录和远程的某一个 pod 中的某一个确定容器进行精确关联。从而开始打通流量和开始同步文件。
开发模式
开发模式的本质是使用 nhctl 工具,在本地计算机和远程环境之间建立 VPN 隧道和使用 syncing 文件同步工具建立实时同步机制。
nocalhost 会将原本的 pod 中将要开发的容器替换为两个容器,一个是 nocalhost_dev 开发容器,一个是 nocalhost_sidecar 容器。
nocalhost_dev 容器负责来运行我们的代码,nocalhost_sidecar 来利用 pod 内共享存储的机制来处理文件同步。
vpn 模式
在没有开启开发模式的情况下,我们开发远程程序还有另外一种方式,如果我们的应用本身非常简单,没有太多的依赖,也不依赖于 k8s 环境,不依赖于特定的存储,只是在网络层面上依赖于环境中的其他服务。
这种情况下,完全可以在本地开发,无须在本地和远程之间建立文件同步,将程序跑在远端的 pod 内。
通过运行 ProxyMode 模式,nocalhost 可以只帮我们在本地和远端 pod 容器之间建立 vpn 隧道。我们直接在本地 run 程序或者 debug 程序,在远端 pod 内能访问的服务,在本地都可以访问到。不需要写/etc/hosts 文件等操作。也无须配置环境变量。
远程 run
如果你的程序依赖非常复杂,依赖了:
- k8s 集群
- 集群中其他服务
- 依赖了 k8s 集群中的存储
或者本地环境难以搭建:
- 比如很多依赖无法在 windows 上安装
- 或者本地计算机性能弱,无法在本地愉快的开发
这些问题,当然都可以解决掉,可以在本地模拟出来这些依赖,也可以想办法安装上该装的依赖,但是需要付出的成本很大。
nocalhost 提供的方案则非常简单,通过实时同步本地文件直接将本地程序 run 在远端环境中。并且可以直接通过插件执行,和 ide 原生的 run 体验类似。如果是 Telepresence 则需要编写非常长的命令来执行。
远程 debug
注意: 使用 pycharm 需要使用商业版,社区版没有 remote debug server 的插件,无法使用 remote debug 的功能
原理
python 通过 pydevd 在 pod 中启动应用进程,除了应用本身会 listen 多个 port。
同时会在 pod 的 nocalhost_dev 容器上的 127.0.0.1 上 bind 一个 remote debug 的端口,该端口是 config.yaml 中定义的 remoteDebugPort。
由于本地和远程 pod 之间已经建立了 vpn 隧道通信,此时 remoteDebugPort 端口上的流量会被 route 到本地计算机的 remoteDebugPort 端口之上,至此本地和远端进程的 remote debug 通信建立,我们可以在就可以在本地 ide 中 debug 运行在 pod 中的进程了,
和在本地开发没有任何区别,并且此时并不是我们在本地模拟了应用运行所需要的资源,而是该进程本就运行在真实的环境中,没有任何区别。
python官方的示例配置是:
但这个例子使用了 Flask 框架来做演示,并且命令是错误的。对于 python 程序,常见的 IDE,比如 Jetbrains pycharm,vs code,pydev 等,都是使用 pydevd 的库来做 debug 的,而且 pydevd 支持 remote debug。
因此我们需要一个和框架无关,可以 remote debug 所有 python 程序的命令。因此更通用的 debug 配置应该是:
pydevd --client 127.0.0.1 --port 8008 --file run.py
注意:
config.yaml 中配置的开发镜像中应提前安装 pydevd 依赖,才可以在 debug 参数中直接配置 pydevd 的命令,否则就需要将 debug 的值配置为:
pip install pydevd && pydevd --client 127.0.0.1 --port 8008 --file run.pygolang
golang 使用了 dlv 来 debug 进程,goland 本身也是使用该工具。因此通过预先在开发容器中安装 dlv 工具或者在 debug 时候立即安装,都可以来使用该工具。
golang 项目的 remote debug 的指令是:
dlv --headless --log --listen :9009 --api-version 2 --accept-multiclient debug app.goFAQ
如何设置开发镜像?首先解释下开发镜像,开发镜像是 nocalhost 在替换原始容器时候的我们指定的容器,nocalhost 官方本身已经提供了很多开发容器,这些容器中都内置了很多调试工具和参数,比如 pydevd 和 dlv。
nocalhost-docker.pkg.coding.net/nocalhost/dev-images/node:latest
nocalhost-docker.pkg.coding.net/nocalhost/dev-images/node:14
nocalhost-docker.pkg.coding.net/nocalhost/dev-images/golang:latest
nocalhost-docker.pkg.coding.net/nocalhost/dev-images/golang:1.16
nocalhost-docker.pkg.coding.net/nocalhost/dev-images/python:latest
nocalhost-docker.pkg.coding.net/nocalhost/dev-images/python:3.9
nocalhost-docker.pkg.coding.net/nocalhost/dev-images/java:latest
nocalhost-docker.pkg.coding.net/nocalhost/dev-images/java:11
nocalhost-docker.pkg.coding.net/nocalhost/dev-images/ruby:latest
nocalhost-docker.pkg.coding.net/nocalhost/dev-images/ruby:3.0
复制代码
针对 node,python,java,ruby 这些语言的项目,一般以来包非常的大,特别是 node 的依赖,体积和数量较为夸张,建议在开发镜像中预置依赖,减少初次初始化开发环境同步文件的时间。一般可以使用当前代码仓库分支的最新/较新镜像作为开发镜像,一般来讲依赖比较并不是很频繁。
针对 golang 的项目,依赖一般量比较小,或者在线安装也比较快,开发镜像可以使用官方原始镜像,通过在 run/debug 指令中实时安装的方式来安装依赖。可以避免频繁改动开发镜像
热加载的作用是?如何开启?热加载:在 IDE 中对源代码文件的修改将实时同步到远端容器中,同时你配置的 Run/Debug 命令将会被重新执行。
开启方法:
参考
https://nocalhost.dev/zh-CN/docs/reference/nh-config
,