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
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
kubectl get sc NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE standard (default) k8s.io/minikube-hostpath Delete Immediate false 10d
使用的是 Kubernetes 集群默认的 StorageClass.
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 的时间敏感相关业务中要增加重试和超时机制.
vim my-postgres-secret.yaml
apiVersion: v1 kind: Secret metadata: name: mypostgres-secret namespace: default type: Opaque stringData: superUserPassword: postgresSuperUserPsw replicationUserPassword: postgresReplicaPsw
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
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
kubectl apply -f my-postgres.yaml
至此, 一个包含 3 个 PostgreSql 实例的集群就跑起来了, 每个 PostgreSql 实例就是一个 pod.
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 集群的事件, 这些事件将有助于调试.
kubectl get events
PostgreSql 集群内部
示意图:
可以查看一下已经创建的各种 resource:
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 集群
kubectl delete kubegres mypostgres
该操作会删除为集群 mypostgres 创建的所有 resources, 除了 PV 和 PVC, 因为 PV 中存储着数据库, 所以需要手动删除.
上面的操作, 不会删除 Kubegres 这个 Kind, 还有针对这个 Kubegres Kind 的 controller, 通过以下操作将 Kubegres Operator 完整删除
kubectl delete -f https://raw.githubusercontent.com/reactive-tech/kubegres/v1.16/kubegres.yaml
备份
可以选择开启备份选项, 对 Primary PostgreSql database 执行定时备份.
创建一个 PVC
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
开启备份选项
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 进行源码解析, 以学习其功能实现和代码组织.