kubegres 源码解析(五) 总结

kubegres 源码解析(一)

kubegres 源码解析(二)

kubegres 源码解析(三)

kubegres 源码解析(四)

kubegres controller 结构图

 

数据库是典型的有状态应用, 所以至少要是用 StatefulSet 进行部署, Spec 的设定比较常规, kubegres 的主要特色在于 KubegresStatus 和 BlockingOperation 的定义, 提炼了 Reconcile Loop 中集群的中间状态,  并通过 BlockOperation 对 Kubernetes 的操作延迟(调用 kube-apiserver 成功并不代表状态立刻发生变化, 也不代表调谐成功) 进行了合理抽象. 

KubegresStatus

如前文所述, Status 定义了不同的 State 转换中的中间态, kubegres 定义了四个属性
type KubegresStatus struct {
    LastCreatedInstanceIndex  int32                     `json:"lastCreatedInstanceIndex,omitempty"`
    BlockingOperation         KubegresBlockingOperation `json:"blockingOperation,omitempty"`
    PreviousBlockingOperation KubegresBlockingOperation `json:"previousBlockingOperation,omitempty"`
    EnforcedReplicas          int32                     `json:"enforcedReplicas,omitempty"`
}
LastCreatedInstanceIndex 用于对新建的 Replica Postgres StatefulSet 进行编号, EnforcedRplicas 用于标记已部署的 Replicas 的数量, 同时, 也可以用于表明, Postgres 集群是否已经部署过. 最核心是 BlockingOperation 和 PreviousBlockingOperation.

KubegresBlockingOperation

通过 client-go 对 kube-apiserver 发起的 Create, Update, Delete 操作成功返回, 并不意味着 Kubernetes 对相应资源的调谐就立即成功, 与此同时, 某些操作应该是互斥的, 以保证 Kubernetes 调谐成功和集群运行在正确的状态上, kubegres 使用 BlockingOperation 来对该场景进行抽象.
Blocking 并不意味着阻塞当前 reconcile 函数执行直到 CompletionChecker 判定执行成功, 而是阻塞整个 reconcile loop, 在 Blocking Operation 执行成功之前, 互斥操作的事件触发的 reconcile 调用在 Blocking Operation 相关的判断会失败, 不会继续执行, 而是 Requeue 等待下一次触发.
type BlockingOperationConfig struct {
    OperationId       string
    StepId            string
    TimeOutInSeconds  int64
    CompletionChecker IsOperationCompleted

    // 默认为 false, 意味着一旦操作完成 (由 operationId + stepId 定义一次操作) 它就会被自动从 active operation 中删除
    // 并加入 previous active operation.
    //
    // 如果该 flag 被设置为 flase 且操作超时, 该操作不会被删除而是保持作为 active operation 直到人工删除.
    //
    // 如果该 flag 被设置为 true, 在 stepId 的操作完成后, 我们会添加一个新的 stepId 且保持 operationId 不变.
    // 新的 stepId 会被设置为 "BlockingOperation.transition_step_id".
    // 这个逻辑允许直到当前的 active operation 要么被人工终止, 要么开启下一步 step 之前, 其他类型的 operations(比如 FailOver) 不会开始.
    AfterCompletionMoveToTransitionStep bool
}
通过 TransitionStep, 就保证了一部分操作一定是互斥的, 不会交织执行.
BlockingOperation 将操作分为了 4 类, 分别是 BaseConfigCountSpecEnforcement, PrimaryDbCountSpecEnforcement, ReplicaDbCountSpecEnforcement 和 StatefulSetSpecEnforcing. 每一类操作都有自己的 StepId, OperationId + StepId 就定义了一次 Blocking Operation. 在发起特定操作前, 通过检查 active Operation 的相关字段, 就可以判定是否可以继续进行操作.
承载主要调谐功能的函数 enforceSpec 中就有大量的针对 BlockingOperation 的比较和判断, 以确定是否可以进行调谐. BlockingOperation 的抽象非常值得借鉴.
const (
    TransitionOperationStepId = "Transition step: waiting either for the next step to start or for the operation to be removed ..."

    // enforcement 实施, 施行, 那这个项目里的 enforce 就不是强制的意味, 而可能是实施
    OperationIdBaseConfigCountSpecEnforcement = "Base config count spec enforcement"
    OperationStepIdBaseConfigDeploying        = "Base config is deploying"

    OperationIdPrimaryDbCountSpecEnforcement         = "Primary DB count spec enforcement"
    OperationStepIdPrimaryDbDeploying                = "Primary DB is deploying"
    OperationStepIdPrimaryDbWaitingBeforeFailingOver = "Waiting few seconds before failing over by promoting a Replica DB as a Primary DB"
    OperationStepIdPrimaryDbFailingOver              = "Failing over by promoting a Replica DB as a Primary DB"

    OperationIdReplicaDbCountSpecEnforcement = "Replica DB count spec enforcement"
    OperationStepIdReplicaDbDeploying        = "Replica DB is deploying"
    OperationStepIdReplicaDbUndeploying      = "Replica DB is undeploying"

    OperationIdStatefulSetSpecEnforcing         = "Enforcing StatefulSet's Spec"
    OperationStepIdStatefulSetSpecUpdating      = "StatefulSet's spec is updating"
    OperationStepIdStatefulSetPodSpecUpdating   = "StatefulSet Pod's spec is updating"
    OperationStepIdStatefulSetWaitingOnStuckPod = "Attempting to fix a stuck Pod by recreating it"
)

主从复制

主从复制使用了 initContainer, 脚本 copy_primary_data_to_replica.sh 把数据复制到环境变量 PGDATA 指向的文件夹之后, initContainer 退出. container Replica Postgres 启动后, 直接加载来自 Primary 的数据.
在这里区分一个概念, initContainer 和 sidecar, sidecar 的 containers 一般与提供实际服务的 container 并排运行, 并为该 container 提供各种服务, initContainer 不是 sidecar.
      initContainers:
        - name: setup-replica-data-directory
          image: postgres:latest
          imagePullPolicy: IfNotPresent
          env:
            - name: PRIMARY_HOST_NAME
            - name: PGPASSWORD
            - name: PGDATA

          command:
            - sh
            - -c
            - /tmp/copy_primary_data_to_replica.sh

进阶

使用 kubegres 的架构实现 MySQL 5.7 (使用 XtraBackup) 和 MySQL 8.0 的 Operator.
posted @ 2023-04-09 21:34  Herbert_Kwok  阅读(49)  评论(0编辑  收藏  举报