自建内网 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 里,新节点起来自动跑一遍,不然每加一台都得手动配,又是一坨重复劳动。