详细介绍Zalando公司开源的Postgresql On Kubernetes方案。

Postgres HA

Zalando公司开源的Postgres HA方案,整体架构主要包括以下三个:

  • zalando/postgres-operator:运行在Kubernetes上Postgres Operator,负责K8S上创建Postgres容器。
  • zalando/patroni:基于Python3的Postgres HA框架,支持在ZooKeeper, etcd , Consul等环境中搭建高可用的PG数据库。
  • zalando/spilo:spilo是镜像构建仓库,主要用于构建整合了patroni、postgres以及众多插件的独立镜像。

Postgres Operator

Operator只提供创建Kubernetes相关资源的能力,最大限度解耦Operator和Postgres服务,配置初始化、主备切换、HA配置等能力则由patroni以及容器内部的脚本完成。

Operator项目中定义了一下三个CRD:

  • postgresqls.acid.zalan.do
  • operatorconfigurations.acid.zalan.do
  • postgresteams.acid.zalan.do

通过创建postgresqls可以在Kubernetes上运行对应的Postgres HA Cluster,一下是最小配置的3实例集群:

1
2
3
4
5
6
7
8
9
apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
  name: rccp-minimal-cluster
spec:
  teamId: "rccp"
  volume:
    size: 10Gi
  numberOfInstances: 3

安装上述CR后,Postgres Operator创建以下资源(其中cluster-name表示postgresql实例的名称):

资源 说明
statefulset.apps/ postgresql实例,运行spilo镜像
service/ master实例的svc地址,没有指定selector字段,即对应ep是通过operator管理的
service/-config postgresql实例的svc地址,无头服务ClusterIP为None
service/-repl standy实例的svc地址,没有指定selector字段,即对应ep是通过operator管理的
secret/..credentials.postgresql.acid.zalan.do 账号信息,默认情况下每个postgresql集群会创建postgres、standby两个账号

通过以下命令可以获取集群的连接信息:

1
2
3
4
PGPASSWORD=$(kubectl get secret postgres.<cluster-name>.credentials.postgresql.acid.zalan.do -o 'jsonpath={.data.password}' | base64 -d)
echo $PGPASSWORD
MASTER_PODNAME=$(kubectl get pod -l spilo-role=master,cluster-name=<cluster-name> -oname)
kubectl port-forward --address 0.0.0.0 $MASTER_PODNAME  5432:5432

postgres暴露的service均没有指定selector字段,因此无法使用直接kubectl port-forward直接暴露service的地址到K8S之外

通过postgres对应pod的spilo-role标签,可以判断postgres实例的运行状态,即master和replica

执行以下命令可以查看所有postgres实例的状态:

1
kubectl get pods -l application=spilo -L spilo-role -w

配置

Postgres Operator

目前zanlan提供了两种互斥的方法来设置Postgres Operator:

当用户定义ConfigMaps或者Operatorconfigurations后,在Operator容器中注入相应的环境变量,即可启用对应的配置,对应的环境变量如下:

  • CONFIG_MAP_NAME
  • POSTGRES_OPERATOR_CONFIGURATION_OBJECT

使用Operatorconfigurations定义配置时,需要从示例中复制一份配置, 并修改相应的内容,否则Yaml文件中的未定义值会像Operator中传递空置,导致服务不可用。

通过Helm安装时,默认使用OperatorConfigurationCRD指定相关配置,配置项说明请参考官方文档

Cluster Manifest

postgresql字段说明,参考:https://postgres-operator.readthedocs.io/en/latest/reference/cluster_manifest/

功能

提供以下能力Postgres-Operator

Role初始化

Postgres Operator允许在数据库集群初始化时创建的Roles,包括以下方式:

Manifest Roles

postgresql的定义中设置role,这种方式适合针对某个pg实例创建指定role,operator会自动为该角色创建随机密码并且记录在secret中

1
2
3
4
5
6
spec:
  users:
  # 创建rccp.rccp用户,rccp.rccp表示该账户生成的secret在rccp命名空间中,需要修改 enable_cross_namespace_secret = true 开启该配置
    rccp.rccp:  # 支持一下字段来指定权限:superuser, inherit, login, nologin, createrole, createdb, replication, bypassrls
    - createdb
    - login

Infrastructure Roles

通过Infrastructure方式创建的用户,会被注入所有Operator管理的集群中。

这种模式下,用户提供一个Role模板(可以是Secret或者Configmap),以下是Secret格式的模板:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
kind: Secret
apiVersion: v1
metadata:
  name: infrastructure-roles
type: Opaque
data:
  user1: Zm9v # foo
  password1: MTIzNDU= # 12345
  inrole1: cG9zdGdyZXM= # postgres
  user2: YmFy # bar
  password2: MTIzNDU= # 12345

上述Secret模板中定义了foo和bar两个账户,密码均为12345,其中foo账户被关联到postgres对应的role(即作为超级用户),bar没有关联其他role不具备任何权限。

为了在Postgres中注入上述账号,用户需要修改Operator的配置,指定kubernetes.infrastructure_roles_secret_name=infrastructure-roles并重启operator。

此时重新创建Postgres实例,数据库即包含以下roles:

上图中,admin、robot_zmon、standby、zalandos均是patroni的框架默认创建的roles

Zalando还提供了其他方式配置Infrastructure Roles,参考:https://postgres-operator.readthedocs.io/en/latest/user/#infrastructure-roles

Teams API Roles

Teams API Roles方式可以配合PAM,第三方系统中的账号创建对应Role,具体可以参考用户手册teams-api-roles

preparedDatabases

Postgres Operator在Mainfest中提供了preparedDatabases字段,使用该字段可以为数据库创建细粒度的权限管理。

使用preparedDatabases可以实现以下功能:

  1. 为DB以及Schema定义Owner、Reader、Writer角色
1
2
3
4
5
spec:
  preparedDatabases:
    foo:
      schemas:
        bar: {}

上述定义会在Postgres实例中,创建数据库foo、foo.bar 以及以下Role: img.png

  1. 指定需要在Schema中创建的 PostgreSQL extensions
1
2
3
4
5
6
spec:
  preparedDatabases:
    foo:
      extensions:
        pg_partman: public
        postgis: data

关于preparedDatabases的详细配置可以参考官方文档:https://postgres-operator.readthedocs.io/en/stable/user/#prepared-databases-with-roles-and-default-privileges

亲和性和污点配置

在实例定义中只允许定义节点亲和性以及污点配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
  name: acid-minimal-cluster
spec:
  teamId: "ACID"
  # 通过在node添加postgres污点,保证其他节点不会在该node上调度
  tolerations:
  - key: postgres
    operator: Exists
    effect: NoSchedule
  # 在需要运行的acid-minimal-cluster集群的节点上添加标签 postgres=acid-minimal-cluster,强制调度实例到该节点
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: postgres
          operator: In
          values:
          - acid-minimal-cluster

为了配置Postgres实例的Pod反亲和性,需要打开Operator中的enable_pod_antiaffinity配置,此时sts被注入一下Pod反亲和配置:

1
2
3
4
5
6
7
8
9
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                application: spilo
                cluster-name: rccp-minimal-spilo-cluster
                team: rccp
            topologyKey: kubernetes.io/hostname

PS:zanlan设计的亲和性和污点配置过于繁琐,并且灵活性比较差(不能配置软亲和、Pod亲和、Node反亲和),可以考虑重构

Sidecar/InitContainers

1. Sidecar

Operator 支持在Manifest定义中注入Sidecar容器:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
spec:
  sidecars:
    - name: "container-name"
      image: "company/image:tag"
      resources:
        limits:
          cpu: 500m
          memory: 500Mi
        requests:
          cpu: 100m
          memory: 100Mi
      env:
        - name: "ENV_VAR_NAME"
          value: "any-k8s-env-things"

在Sidecar容器中,POSTGRES_USER/POSTGRES_PASSWORD 会作为环境变量被自动插入容器中,并且PG容器的数据库目录也会被挂载到/home/postgres/pgdata。

在OperatorConfiguration中可以定义全局Sidecar容器,参考

参考:https://postgres-operator.readthedocs.io/en/stable/user/#sidecar-support

2. InitContainers

Operator支持InitContainers:

1
2
3
4
5
6
7
spec:
  initContainers:
    - name: "container-name"
      image: "company/image:tag"
      env:
        - name: "ENV_VAR_NAME"
          value: "any-k8s-env-things"

其他功能

1.Postgres Clone操作

参考: https://postgres-operator.readthedocs.io/en/stable/user/#how-to-clone-an-existing-postgresql-cluster

2.Standby Cluster

通过Operator可以创建Standby Cluster。 Standby Cluster启动时会从S3上复制存档的WAL文件,之后实时同步对应的更改。 由于Standby Cluster和目标集群的通信是根据S3上的WAL文件实现的,因此两者可以运行在不同环境。

Operator无法将Standby Cluster提升为正常其群,但是可以通过patronictl命令手工实现。

参考:https://postgres-operator.readthedocs.io/en/stable/user/#setting-up-a-standby-cluster

3.Logical backups

Operator可以基于Kubernetes Crontab创建定时任务,实时dump数据库的内容,这个功能默认是不开启的。

参考:https://postgres-operator.readthedocs.io/en/stable/user/#logical-backups

4.Connection Pooler

Operator支持基于PgBouncer为实例定义连接池

参考:https://postgres-operator.readthedocs.io/en/stable/user/#connection-pooler

5.自定义TLS证书

参考:https://postgres-operator.readthedocs.io/en/stable/user/#custom-tls-certificates

参考文档