自建内网 K8s 测试集群与 CI/CD 工作流

背景#
目前在公司内我负责的活主要是 AI 的一些基础管线的开发,包括对外提供服务的接入端点,然后一些管理系统的搭建,再就是部署集群的搭建,基础管线实际上是之前就有一版本 Python 的,按照那个架构进行重构就行了,我做的部分比较少,主要还是一些稳定性的功能优化。最近这一周主要的工作还是在 K8S 集群搭建上面,虽然熟练使用 Docker,但是也是在真实环境下自己搭建内部的开发 K8S 集群,还是感觉学到不少东西的。所以感觉有必要开一篇文章记录一下。
技术栈#
其实在这方面我也算是有一丢丢经验,之前在第二家公司就搭建了一整套的内网开发环境,包括自建 Gitea、Github Action、Runner 等等,不过这次微服务比较多,而且也是需要模拟线上环境,就得使用 K8s 了。
在和 Claude Opus 4.7 Copilot 后的推荐方案大概如下,也是目前比较主流的开源方案:
- 设备:Mac Studio M2 Max 32GB
- Kubernetes / K3S
- Rancher 可视化面板
- ArgoCD 自动发布
- GitOps 管理集群环境和配置
- Cloudflare Tunnal 内网穿透
需求#
我们小组是承担了业务方面的 90% 的开发了,比如底层基座,还有我负责的面向开发者和 C 端用户的后端以及管理后台的前后端,所以搭建下来的微服务组件大概就有 20 多个,并且需要提供一套稳定的系统给其他组的同事进行测试,也需要一套我们组内自己快速迭代的集群用于测试,所以这个活也就落到我头上了,为了满足这个需求也是踩了不少坑。
1. Docker Compose#
为了快速拉起服务,就选择了这个方案,写好 docker-compose.yaml 就能迅速拉起来多套集群,并且使用 mac 上的 OrbStack 也是非常迅速,这部分还是很得心应手的,只是一些基础服务都没搭建,比如镜像仓库、Github Action 等等。
使用的方案也是非常简单,直接本地 Docker Build 打包后直接 SCP 到服务器上,然后启动。然后使用 Traefik 来做内网域名映射,Dnsmasq 来支持内网的 DNS 解析。
虽然非常简陋,但是也是能够支持快速的上线调试要求了,就是苦了我 🥹,每个人都得让我手动给他们部署组件,不过好在还有 Claude 辅助,能大大提高工作效率了。(这部分还是需要谨慎的,一旦出事直接就会爆炸)
2. Komodo#
每个组件的一些配置需要经常变动,而且直接操作服务器的话,也不太方便,就想着搭建一个可视化的管理面板,通过面板来管理集群,这样也方便。于是检索了一些方案,经过对比之后决定使用 Komodo 来搭建。

- Komodo:可视化多服务器管理、容器管理、Stacks、编排构建等
- Dockge:几乎就是"compose 文件的 Web 编辑器 + 启停按钮",极简。
- Portainer CE:功能多但界面更复杂,compose 在它那叫 “Stacks” 但体验不如 Komodo 原生。
- Dozzle:只看日志,不做管理。
Komodo 支持按照 Stacks 来管理集群,也支持多服务器部署,但是由于我们只有一台 Mac 所以就用不到这个功能了。部署好以后,界面感觉还是非常干净的,没有花里胡哨的功能,一眼能看到那个功能该去哪里找。但是因为不支持 K8S 所以这个搭建好了之后,也没有使用几天就废弃了,转到 Rancher + Argo CD 了。
3. K8S#
因为公司提供的设备就只有一台内网环境下的 Mac Studio,也就是说只能部署单节点的 K8S,由于经常性的使用 OrbStack,并且还集成了 K8s 的功能,直接可以开始部署集群,所以也是紧锣密鼓的开始搭建了,然后选择了 Rancher 可视化管理和 ArgoCD 来做自动发布。
直接开了两个集群 stable 和 dev,一股脑将组件拉起来,算上一些基础依赖组件两套集群拉起来后大概就有 90 多个 pod,至此能够满足公司目前的使用是没有问题的,按理来说到这里部署的工作就应该完成了,该去做自动发布构建的了。但是我看了一眼 Rancher 的后台,最多只支持 110 个 Pod,这个怎么能行,按照组件的资源量预估了一下,单机抗 500 Pod 应该是没啥问题,所以就想着将 Pod 数量调整到 500,由于 K8s 中每个 Pod 都是需要占用一个 IP 的,所以这里就必须调整划分给 OrbStack 的 CIDR,默认是 /25 128 个 ip,那直接将这个数字改成 /23 就能支持 512 个 IP,也就能满足我的需求,但是修改 OrbStack 的配置死活不生效,修改好了,就会被修改回去。
大概查了一下,OrbStack 自带的 K8S 没有开放修改的端点,应该还是直接写死的,所以这里没法满足我的需求,就开始想办法了,既然自带的不能满足,那就自己搭一个就行了,于是这里我就选择了使用 OrbSatck 里面的 VM 虚拟机,创建了一个 Ubuntu 24.04,然后在这个里面去搭建 K3S 集群,就能自己随意配置了。
说干就干,立马创建好了虚拟机环境,就得开始迁移 pod 了,这里偷懒使用 Codex GPT 5.5 来进行迁移,按照理想情况下,应该是在迁移完成以后,写好项目的组件服务文档,但是意外出现了,在迁移的过程中,OrbStack 突然闪退,我正在监控的活动监视器中的内存使用突然骤降,我就感觉事情不太妙,结果一看 OrbStack 挂了,里面的所有镜像和 VM 都丢了。
这下炸了,近乎 10 几个组件的 images 都是托管在这个里面的 Harbor 内的,导致所有数据全部丢失,并且没有备份,查了一下这个情况出现的挺多的,也没法找回,只能重新搭建了。
4. GitOps#
于是赶紧和 Claude 商讨了一版方案出来,推荐将集群的管理抽离到 Git 仓库中,这样方便 Action 修改集群配置,于是新版本最终定的架构是 Rancher 做可视化面板进行管理,ArgoCD 来做自动发布,GitOps 来做集群、配置管理。这样子如果一旦服务器崩溃,数据在没有备份的情况下,还能通过 GitOps 中的配置来快速进行恢复,并且也方便后续发布上线。
所以最终的架构是修改 GitOps 仓库中的配置 push 后,就自动能更新对应集群的 pod,并且各个组件的仓库的 push 和 tag 也会触发 Action 去执行修改 GitOps 仓库的配置,以实现控制集群并且能够进行追溯回滚的效果。
个人感觉这种方案是目前的最优解,既能对配置进行管理,还能实现版本控制,也能实现自动化。
所以目前公司的两套服务都部署在 Ubuntu 上的 K8S 内,截止现在还稳定运行,希望能撑过 5.1 休假期间,也能够验证服务的稳定性了。
踩坑#
部署中其他组件都比较顺利,唯一遇到的比较坑的就是因为使用的 Cloudflare 的 Tunnal 穿透,然后像让 Github Action Runner 到 Harbor 走内网,并且接入的端点域名不变,也就是在内网环境下就走内网,内网不通就走外网。
这里 Cloudflare 的话是只支持 100MB 的 TCP 传输的,一旦镜像大小超过 100MB,就没法支持了,所以这里只能做切块或者走内网了,为了速度和安全,这块就配置内网。
但是内网的话,一直报 tls 问题,三种错前后都赶上了一遍。
最开始是 Runner 跑 docker push 直接 x509: certificate signed by unknown authority,Harbor 起来用的是自签证书,containerd 默认根本不认这条链。想着把 CA 装进去就完事了,结果一看证书的 SAN,跟实际访问的内网域名压根对不上,错误立马变成 x509: certificate is valid for X, not Y。
更头疼的是 DNS 劈裂这一刀:同一个 harbor.xxx 域名,公网走 Cloudflare 边缘(拿到的是 CF 证书)、内网通过 Dnsmasq 解析到 Mac Studio 的内网 IP(拿到的是 origin 自签证书),公私两套完全分裂。Runner 在内网拿到的永远是 origin cert,本地信任链里啥都没有,怎么连都不对。
最后分两头修:
- Harbor 那边:重新签证书,把内网域名、内网 IP、公网域名全部塞进 SAN,至少让"看到什么证书"这件事先统一掉,不再因为域名对不上来回跳错。
- Runner / containerd 这边:把 Harbor 的 CA 挂进
/etc/containerd/certs.d/<host>/ca.crt,让 containerd 真信任这条链。个别历史遗留的 Runner 时间紧没法马上重启刷配置的,临时上insecure_skip_verify = true先把流水线跑通再说,后面排期重启时再补。
K8s 多节点还有个隐藏的烦人点:每加一台 worker 都得把 CA 同步进去,不像 docker 那么直接,所以这套证书分发也顺手写进 GitOps 仓库的 init job 里,新节点起来自动跑一遍,不然每加一台都得手动配,又是一坨重复劳动。