kubegres 源码解析(一) 简介和部署
摘要
Kubegress 是一个 Kubernetes operator, 允许部署一个或多个PostgreSql pods集群,并启用开箱即用的数据复制和故障转移。考虑到Kubernetes管理状态集的生命周期和数据复制的复杂性,它让使用 PostgresSql 变得简单. 本文的主要目的, 是在 minikube 下尝试对 kubegres 进行部署, 先把项目跑起来, 分析 Kubegres 的配置文件, 它创建了哪些 resource? 这些 resource 的组织方式是怎样的, 相互之间的关系如何? 数据库主从, 读写分离如何实现? 故障转移和数据备份如何进行? 尝试建立一个框架式的印象, 有助于在源码分析时理清思路.
特性
- 它可以管理一个或多个 Postgres 实例的集群。每个 Postgres 实例集群都使用 YAML 中的 "kind: Kubegres" 创建。每个集群都是独立的,并由其独一无二的名称和命名空间来识别。
- 它创建了一个启用了流复制的 PostgreSql 服务器集群:它创建了一个 Primary PostgreSql pod 和一些Replica PostgreSql pod,并将 primary 的数据库实时复制到 Replica pod。
- 它管理故障转移:如果Primary PostgreSql 崩溃了,它会自动将 Replica PostgreSq 提升为 Primary。
- 它有一个数据备份选项,允许在一个给定的卷中定期转储 PostgreSql 数据。
- 它提供了一个非常简单的YAML,具有专门针对 PostgreSql 的属性。
- 它是有弹性的,有超过85个自动测试案例,并且已经在生产中运行了。
与众不同之处
Kubegres 与 Kubernetes 的生命周期完全集成,因为它作为一个用 Go 编写的操作程序运行。
与其他开源 Postgres 运营商相比,它的代码库是最小的。它拥有管理 Kubernetes 上 PostgreSql 集群所需的最小而强大的功能。项目小而简单。
推荐 Kubegres 的 5 个主要原因。
- 标准: 为了管理复制、故障转移和备份,Kubegres 100% 依赖于 PostgreSql 容器中绑定的PostgreSql 标准库。它不带有任何自定义或第三方库来管理这些功能。
- 学习曲线小: 如果你已经熟悉了标准的 PostgreSql 库,你可以轻松地理解和管理 Kubegres。
- 安全:我们评估的所有其他开源项目都需要他们自己的定制 Docke 容器,这些容器带有定制库。为了减少由额外的依赖关系引起的攻击面,我们决定只依赖由 Docker 官方图像团队创建和维护的 PostgreSql 容器。
- 可移植性:上述方法允许 Kubegres 与任何衍生自这些 PostgreSql 容器的容器兼容。
- 纯 Go:Kubegres 完全是用 Go 编写的,没有使用其他语言(如 Python,......)。而且它使用了最新的 Kubebuilder 第三版,该版本由官方的 Kubernetes API(SIG)维护。
部署
安装 Kubegres operator
1 | kubectl apply -f https: //raw .githubusercontent.com /reactive-tech/kubegres/v1 .16 /kubegres .yaml |
网络不佳的, 可以修改 kubegres.yaml 源文件, 使用 bitnami/kube-rbac-proxy:0.13.0 替换 gcr.io/kubebuilder/kube-rbac-proxy:v0.13.0 完成部署.
StorageClass
1 2 3 | kubectl get sc NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE standard (default) k8s.io /minikube-hostpath Delete Immediate false 10d |
使用的是 Kubernetes 集群默认的 StorageClass.
1 2 3 4 5 6 7 8 9 10 11 12 | kubectl describe sc standard Name: standard IsDefaultClass: Yes Annotations: kubectl.kubernetes.io /last-applied-configuration ={ "apiVersion" : "storage.k8s.io/v1" , "kind" : "StorageClass" , "metadata" :{ "annotations" :{ "storageclass.kubernetes.io/is-default-class" : "true" }, "labels" :{ "addonmanager.kubernetes.io/mode" : "EnsureExists" }, "name" : "standard" }, "provisioner" : "k8s.io/minikube-hostpath" } ,storageclass.kubernetes.io /is-default-class = true Provisioner: k8s.io /minikube-hostpath Parameters: <none> AllowVolumeExpansion: < unset > MountOptions: <none> ReclaimPolicy: Delete VolumeBindingMode: Immediate Events: <none> |
通过将名为 standard 的 StorageClass 对象的 Annotations 中设置 "storageclass.kubernetes.io/is-default-class":"true", 将 standard 设置为默认 StorageClass.
在 minikube 中, 使用的存储插件是, "provisioner":"k8s.io/minikube-hostpath". hostPath 将主机节点文件系统上的文件或目录挂载到 Pod 中, 这违背了 "hostPath 仅做只读使用" 和 "一个PV一块盘" 的使用原则, 仅应该作为测试使用.
一般数据库应用应该使用 local 类型的插件, Kubegres 并没有与哪一种具体的 local 插件做绑定, 在实际部署时可以进行定制.
创建 Secret
Secret 属于 Projected Volume, 将加密数据放到 etcd 中, 通过在 Pod 中挂载 Volume 的方式提供访问, 并可以自动更新, 但是更新可能会有延迟, 所以在使用 Secret 的时间敏感相关业务中要增加重试和超时机制.
1 | vim my-postgres-secret.yaml |
1 2 3 4 5 6 7 8 9 | apiVersion: v1 kind: Secret metadata: name: mypostgres-secret namespace: default type : Opaque stringData: superUserPassword: postgresSuperUserPsw replicationUserPassword: postgresReplicaPsw |
1 | kubectl apply -f my-postgres-secret.yaml |
创建 PostgresSQL 实例集群
我们已经在 kubegres.yaml 中定义了 Kubegres Kind, 现在是要使用它的时候了, 具体地说, 我们要创建一个 Kubegres resource. "A resource is a use of a Kind". 下面的 Kubegres YAML 文件包含了让下面的集群跑起来的最小配置需求:
- 使用 Docker 官方维护的 PostgreSql Docker container 创建的 PostgreSql pods 集群
- "replica: 3", Kubegres 会创建一个 Primary PostgresSql pod 和 两个 Replica PostgresSql pods
- 数据将从 Primary PostgreSql pod 实时地复制到两个 Replica PostgreSql pods
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | vim my-postgres.yaml apiVersion: kubegres.reactive-tech.io /v1 kind: Kubegres metadata: name: mypostgres namespace: default spec: replicas: 3 image: postgres:14.1 database: size: 200Mi env : - name: POSTGRES_PASSWORD valueFrom: secretKeyRef: name: mypostgres-secret key: superUserPassword - name: POSTGRES_REPLICATION_PASSWORD valueFrom: secretKeyRef: name: mypostgres-secret key: replicationUserPassword |
这里直接将 mypostgres-secret 中的 Password 用作了环境变量.
在 "spec.database" 中, 除了 "size", 即存储空间的大小(注意 MiB 和 MB 的区别), 还可以在此指定 "storageClassName" 和 "volumeMount". 因为这里没有指定, 所以一律采用默认值. 只要 storageClass 有值了, Kubernetes 就会基于此为每一个 Postgres Pod 自动创建 PV 和 PVC, 是为 Dynamic Provisioning.
部署 Kubegres
1 | kubectl apply -f my-postgres.yaml |
至此, 一个包含 3 个 PostgreSql 实例的集群就跑起来了, 每个 PostgreSql 实例就是一个 pod.
1 2 3 4 5 | kubectl get pods NAME READY STATUS RESTARTS AGE mypostgres-1-0 1 /1 Running 0 118m mypostgres-2-0 1 /1 Running 0 118m mypostgres-3-0 1 /1 Running 0 117m |
同时, Kubegres 会记录每个 PostgreSql 集群的事件, 这些事件将有助于调试.
1 | kubectl get events |
PostgreSql 集群内部
示意图:
可以查看一下已经创建的各种 resource:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | kubectl get pod,statefulset,svc,configmap,pv,pvc -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod /mypostgres-1-0 1 /1 Running 0 126m 10.244.0.4 minikube <none> <none> pod /mypostgres-2-0 1 /1 Running 0 126m 10.244.0.5 minikube <none> <none> pod /mypostgres-3-0 1 /1 Running 0 126m 10.244.0.6 minikube <none> <none> NAME READY AGE CONTAINERS IMAGES statefulset.apps /mypostgres-1 1 /1 126m mypostgres-1 postgres:14.1 statefulset.apps /mypostgres-2 1 /1 126m mypostgres-2 postgres:14.1 statefulset.apps /mypostgres-3 1 /1 126m mypostgres-3 postgres:14.1 NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR service /kubernetes ClusterIP 10.96.0.1 <none> 443 /TCP 10d <none> service /mypostgres ClusterIP None <none> 5432 /TCP 126m app=mypostgres,replicationRole=primary service /mypostgres-replica ClusterIP None <none> 5432 /TCP 126m app=mypostgres,replicationRole=replica NAME DATA AGE configmap /base-kubegres-config 7 126m configmap /kube-root-ca .crt 1 10d NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE VOLUMEMODE persistentvolume /pvc-4cf2cea9-608d-4d1c-a469-f8face387e91 200Mi RWO Delete Bound default /postgres-db-mypostgres-3-0 standard 126m Filesystem persistentvolume /pvc-6d292ccf-cb85-42c4-b182-c7e85c758068 200Mi RWO Delete Bound default /postgres-db-mypostgres-1-0 standard 126m Filesystem persistentvolume /pvc-b958ca70-0598-4b53-bcd6-53ff43eb767f 200Mi RWO Delete Bound default /postgres-db-mypostgres-2-0 standard 126m Filesystem NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE VOLUMEMODE persistentvolumeclaim /postgres-db-mypostgres-1-0 Bound pvc-6d292ccf-cb85-42c4-b182-c7e85c758068 200Mi RWO standard 126m Filesystem persistentvolumeclaim /postgres-db-mypostgres-2-0 Bound pvc-b958ca70-0598-4b53-bcd6-53ff43eb767f 200Mi RWO standard 126m Filesystem persistentvolumeclaim /postgres-db-mypostgres-3-0 Bound pvc-4cf2cea9-608d-4d1c-a469-f8face387e91 200Mi RWO standard 126m Filesystem |
Kubegres 创建了 3 个 pod, "mypostgres-1-0", "mypostgres-2-0", "mypostgres-3-0". 每个 pod 都运行了一个 PostgreSql DB 并且关联同名的 StatefulSet. 理论上每个 pod 会部署在不同的 Node 之上, 不过这是 minikube 所以例外. 题外话, pod 的均匀部署可以通过 3 个方式实现, 1. podAntiAffinity, 2. SelectorSpreadPriority 同 Service 的其他 pod 尽量不在一个 Node 上, 3. Hash.
进一步, Kubegres 创建了两个 Headless Service, "mypostgres" 和 "mypostgres-replica", 也就意味着, 访问 "mypostgres.default.svc.cluster.local' 返回的将是 "mypostgres-1-0" 的 IP 地址. 通过这两个 Headless Service 也就实现了读写分离和负载均衡, 负载均衡策略默认是轮询.
Kubegres 还创建了一个 ConfigMap, 名叫 "base-kubegres-config", ConfigMap 也属于 ProjectedVolume. 在 Kubegres 运行的每个 Namespace, 它都会创建这么一个 ConfigMap.
最后, 针对每一个 Postgres Pod, Kubegres 都使用 StorageClass 创建了一个 PV 和 一个 PVC.
Kubegres 使用预定义的模板来创建这些 Kubernetes resources.
连接客户端 apps 到 PostgreSql
基于 Kubegres YAML 创建的集群, 在集群中的 client app 可以使用下面的参数访问 PostgreSql 数据库:
- host: mypostgres
- port: 5432
- username: postgres
- password: [value of mypostgres-secret/superUserPassword]
Host
使用 mypostgres 访问 Primary PostgreSql, 使用 mypostgres-replica 访问 Replica PostgreSql.如果 Primary PostgreSql 发生宕机, 故障转移机制将会把一个 Replica PostgreSql 提升为 Primary, 这个过程对客户端透明.
Port
默认使用 5432, 可以自定义.
Username and Password
默认的 "Postgres" 是一个 super user. 自用还是建议添加用户并做好权限控制.
删除 Postgres 集群
1 | kubectl delete kubegres mypostgres |
该操作会删除为集群 mypostgres 创建的所有 resources, 除了 PV 和 PVC, 因为 PV 中存储着数据库, 所以需要手动删除.
上面的操作, 不会删除 Kubegres 这个 Kind, 还有针对这个 Kubegres Kind 的 controller, 通过以下操作将 Kubegres Operator 完整删除
1 | kubectl delete -f https: //raw .githubusercontent.com /reactive-tech/kubegres/v1 .16 /kubegres .yaml |
备份
可以选择开启备份选项, 对 Primary PostgreSql database 执行定时备份.
创建一个 PVC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | vim my-backup-pvc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-backup-pvc namespace: default spec: storageClassName: "standard" accessModes: - ReadWriteOnce resources: requests: storage: 20Mi kubectl apply -f my-backup-pvc.yaml |
开启备份选项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | vim my-postgres.yaml apiVersion: kubegres.reactive-tech.io /v1 kind: Kubegres metadata: name: mypostgres namespace: default spec: replicas: 3 image: postgres:14.1 port: 5432 database: size: 200Mi backup: schedule: "0 */1 * * *" pvcName: my-backup-pvc volumeMount: /var/lib/backup env : - name: POSTGRES_PASSWORD valueFrom: secretKeyRef: name: mySecretResource key: superUserPassword - name: POSTGRES_REPLICATION_PASSWORD valueFrom: secretKeyRef: name: mySecretResource key: replicationUserPassword kubectl apply -f my-postgres.yaml |
总结
简单地过了一遍 Kubegres 的部署, 对于需要自定义的 YAML 文件和它所创建的各种 resources 有个大概印象, 从下一篇开始尝试对 Kubegres 进行源码解析, 以学习其功能实现和代码组织.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏