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 进行源码解析, 以学习其功能实现和代码组织.
posted @ 2023-04-04 18:00  Herbert_Kwok  阅读(74)  评论(0编辑  收藏  举报