CiliKube开源啦!让小白一次学会K8s 运维 + Web 开发 + k8s二次开发(Vue3+Go 全栈,免费开源)
时间过得飞快,又是一周。天气愈加炎热了,窗外的声音也更加聒噪了。就这样开始了自己笔记本清灰的翻车旅程。
为什么说清灰翻车了呢,因为清完灰笔记本,屏幕就点不亮了,只有电源指示灯在亮,有没有小伙伴遇到过呢?
希里安的笔记本也有好几年时间了,现在散热不行,夏天发热尤其严重,趁着周末来清清灰,更主要的是想要看看键盘控制芯片的型号是什么,便于我购买更换。
因为我的电脑更换内存条之后,键盘莫名失灵了,应该和内存条无关,按网上教程来看大概率就是键盘芯片问题,按着以往拆机的经验拆盖,这次需要全部拆开,因为键盘控制芯片在风扇背面,所以光拆风扇是不行的,全部拆完后发现散热硅脂因为时间太久已经干了,没什么好的工具就把CPU和GPU的硅脂用纸巾一点点擦干净。随后找控制芯片型号,拍照,然后使用热风枪打算重新焊一下,看看是不是虚焊了,但是又怕把周边小电容给吹坏了就没有接着吹,因为我还没有买到对应型号的控制芯片,心想这下应该不会有什么问题了,原路返回,安装后插电开机,发现风扇呼呼转,屏幕不亮。
于是上网一搜,完犊子了,我看大部分都说CPU短路了,CPU烧了之类的,这下心里拔凉拔凉的,因小失大的感觉悠然生起。
然后心想坏都坏了,那就再拆一遍试试重装看行不行吧,然后全部又拆了,又擦了一下硅脂,全部安装了一遍,开机发现能点亮了,觉得电脑失而复得了,开心!(后来想了一下大概率是贴到散热铜管的导电金属片被压到主板上造成短路了)
随后先冷静了下,吃了一根玉米棒子,然后开始安装后壳,刚开始发现有一颗螺丝死活拧不进去,定睛一看,螺丝拧到主板上,把位置已经以前占好了,怪不得拧不进去,然后又拆了壳,接着装壳,发现又多了一颗螺丝,装机生涯头一次,一般都是装着装着少了螺丝,这次居然多出螺丝来了,又拆了壳,发现电池的螺丝有好几颗少拧了一颗。
前前后后,安装完已经过去两个小时了,怪不得这活外面店里得收不少钱,所以说如果动手能力不是很强,就不建议大家自己拆机清灰了,很容易翻车,但换换内存条、固态硬盘应该问题不大。
视频:拆机清灰
希里安CILLIAN
以上多出来的那颗螺丝,令我哭笑不得。让我深刻体会到,无论是硬件组装还是软件开发,一个清晰、规范、可维护的架构是多么重要。说到架构,这恰好也是我最近在自己的开源项目CiliKube上做的事情,顺便说一下自己的开源项目CiliKube发布新版本了,还不知道CiliKube是什么,可以看下这篇文章CiliKube开源啦!让小白一次学会K8s 运维 + Web 开发 + k8s二次开发(Vue3+Go 全栈,免费开源)
CiliKube 继上次【Cilikube v0.2.1全新发布】K8s多集群管理、RBAC权限、Pod操作增强等一系列更新助力云原生实践!最近完成了一次代码重构,主要是告别了为每个 K8s 资源对象重复编写一套逻辑,引入了优雅的泛型接口,实现资源管理的统一。同时,集群标识从“集群名字”升级为唯一“集群ID”,为多集群管理的稳定性和扩展性奠定了良好基础。对于所有关注 CiliKube 的开发者和贡献者来说,这不仅意味着更少的代码、更简便的维护,更重要的是未来易于扩展、更具灵活性。
在过去的一段时间里,CiliKube 项目一直在快速迭代。正如项目简介所说,它不仅是一个学习成果的体现,更是一把帮助更多初学者打开云原生大门的“钥匙”。为了让这把“钥匙”更精致、更好用,对后端架构进行了一次“升级”。
这次重构的核心有两个:
name 到 id,消除模糊性,拥抱唯一性接下来,就让希里安带各位读者了解这次重构背后的思考和实现细节,期间还换了不少中node节点信息展示的样式
1. 曾经的资源管理:为每种资源“定制”一套 API
在 CiliKube 的早期版本中,我们为每一种 Kubernetes 资源(如 Pod, Deployment, Service, ConfigMap...)都编写了一套独立的 Handler、Service 和 Route 逻辑
大概是这样的:
这种方式在项目初期非常直观,也更易于初学者理解,但随着支持的资源类型越来越多,问题也逐渐暴露:
看看 initialization/init.go 文件曾经的样子,密密麻麻的 registerResourceHandler 调用,每一种都是一次重复注册
tips:但是为了能让初学者看到变化的过程,0.1.x分支依旧维护单集群管理代码
2. 现在:泛型接口,实现资源统一管理
为了解决上述痛点,引入了泛型(Generics)特性,对资源处理层进行了重构
核心思路: 既然所有 K8s 资源的管理操作(CRUD)都遵循相似的模式,那我们就可以定义一套通用的、与具体资源类型无关的接口
关键实现:
BaseResourceService[T runtime.Object]: 在 internal/service/base_resource_service.go 中定义了一个基础资源服务。这里的 T 就是一个泛型参数,它可以是 *corev1.Pod、*appsv1.Deployment 或任何实现了 runtime.Object 接口的 K8s 资源类型ResourceHandler[T runtime.Object]: 相应的在 api/v1/handlers/resource_handler.go 中,创建了一个通用的 ResourceHandler,封装了对 ResourceService 的调用,处理来自 HTTP 请求的上下文,而无需关心 T 的具体类型。// file: api/v1/handlers/resource_handler.go
// ResourceHandler 通用处理器
type ResourceHandler[T runtime.Object] struct {
service service.ResourceService[T]
clusterManager *k8s.ClusterManager
resourceType string
}RegisterResourceRoutes: 路由注册也变得异常简洁,编写了一个泛型函数 RegisterResourceRoutes,只需传入资源类型名称(如 "pods"),就能自动注册好 GET /pods、POST /pods、DELETE /pods/:name 等所有标准 RESTful API 路由。// file: api/v1/routes/resource_routes.go
func RegisterResourceRoutes[T runtime.Object](router *gin.RouterGroup, handler *handlers.ResourceHandler[T], k8sManager *k8s.ClusterManager, resourceType string) {
group := router.Group("/" + resourceType)
{
group.GET("", handler.List)
group.POST("", handler.Create)
group.GET("/:name", handler.Get)
// ... more routes
}
}重构后的好处:
internal/service/resource_service_factory.go 中注册一行即可,真正实现“一行代码,支持新资源”1. 旧版本:用集群名称(Name)作为唯一标识
在最初的设计中,主要是为了简单地使用用户为集群指定的名称(name)作为其唯一标识。在 configs/config.yaml 中,我们通过 activeCluster: "default" 这样的名字来指定活动集群。
这样做的问题是:
name)应该是给人看的“显示名”,而系统内部需要一个稳定不变的“身份证号”2. 现在版本:引入唯一 ID,职责分离
为了让集群管理更加规范和健壮,每个集群引入了系统生成的唯一 ID
关键实现:
internal/store/models.go 的 Cluster 模型中,我们明确定义了 ID 和 Name 两个字段。ID 是 primary_key,由系统保证唯一(我们使用了 UUID)。// file: internal/store/models.go
type Cluster struct {
ID string `gorm:"type:varchar(36);primary_key" json:"id"`
Name string `gorm:"type:varchar(100);unique;not null" json:"name"`
// ...其他字段
}pkg/k8s/manager.go 中的 ClusterManager 是管理所有集群客户端的核心。现在,它内部的所有 map 都使用 clusterID 作为 key,并增加了一个 nameToID 的映射来兼容按名称查找的场景。// file: pkg/k8s/manager.go
type ClusterManager struct {
clients map[string]*Client // 键为集群 ID
clientInfo map[string]store.Cluster // 键为集群 ID
nameToID map[string]string // 名称到ID的映射
// ...
}name 操作改为按 id 操作。// file: api/v1/handlers/cluster_handler.go
// 原来的 /:name 改为 /:cluster_id
func (h *ClusterHandler) DeleteCluster(c *gin.Context) {
clusterID := c.Param("cluster_id")
// ...
h.service.DeleteClusterByID(clusterID)
}重构后的好处:
name 用于显示,id 用于操作,权责分明这次代码重构是 CiliKube 成长道路上的一次重要变化。提升了代码质量和可维护性,更重要的是,为项目未来的可用性铺平了道路——无论是支持更多的原生资源,还是快速集成社区中丰富的 CRD,都将变得更加友好简单
欢迎大家访问GitHub 仓库,体验最新的代码:
仓库地址在留言区置顶处更新
如果你觉得 CiliKube 对你有帮助,或者对这次重构有任何想法,请不要吝啬你的 Star ⭐ 和 Issue!CiliKube 是一个为爱发电的开源项目,你的支持是我们唯一的燃料!
感谢大家的支持,让我们共同见证 CiliKube 的成长!