Helm部署PostgreSQL高可用集群(使用SSL)

621 阅读9分钟

添加helm仓库

helm repo add kvaps https://kvaps.github.io/charts

查询下stolon

[root@master helm-stolon]# helm search repo stolon
NAME            CHART VERSION   APP VERSION     DESCRIPTION                                       
kvaps/stolon    1.7.0           0.16.0          Stolon - PostgreSQL cloud native High Availabil...

下载stolon的helm Chart文件

[root@master helm-stolon]# helm fetch kvaps/stolon
[root@master helm-stolon]# ls
stolon-1.7.0.tgz

# 解压下载下来的文件
[root@node1 stolon]# tar -xf stolon-1.7.0.tgz
# 进入目录
[root@node1 stolon]# cd stolon

生成自签名证书

生成key

# 该过程需要输入密码
[root@node1 cert2]# openssl genrsa -des3 -out server.key 2048
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
[root@node1 cert2]# ls
server.key
# 将生成的server.key去掉密码
[root@node1 cert2]# openssl rsa -in server.key -out server.key
Enter pass phrase for server.key:
writing RSA key
[root@node1 cert2]# ls
server.key

生成CA的crt

生成的ca.crt文件是用来签署下面的server.csr文件

[root@node1 cert2]# openssl req -new -x509 -key server.key -out ca.crt -days 3650
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN       # 信息填写这个和下面的就可以
State or Province Name (full name) []:SD
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:
Email Address []:

# 查看生成的ca.crt
[root@node1 cert2]# ls
ca.crt  server.key
[root@node1 cert2]# 

csr的生成方法

需要依次输入国家,地区,组织,email。最重要的是有一个common name,可以写你的名字或者域名。如果为了https申请,这个必须和域名吻合,否则会引发浏览器警报。生成的csr文件交给CA签名后形成服务端自己的证书。

[root@node1 cert2]# openssl req -new -key server.key -out server.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:SD
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:
# 这里输入域名或者IP
Common Name (eg, your name or your server's hostname) []:192.168.0.184  
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

# 查看生成的csr文件
[root@node1 cert2]# ls
ca.crt  server.csr  server.key

生成crt

CSR文件必须有CA的签名才可形成证书

[root@node1 cert2]#  openssl x509 -req -days 3650 -in server.csr -CA ca.crt -CAkey server.key -CAcreateserial -out server.crt
Certificate request self-signature ok
subject=C = CN, ST = SD, L = Default City, O = Default Company Ltd, CN = 192.168.0.184

# 查看生成的crt
[root@node1 cert2]# ls
ca.crt  ca.srl  server.crt  server.csr  server.key

修改values.yaml文件

# clusterName:
image:
  repository: sorintlab/stolon
  tag: v0.16.0-pg10
  pullPolicy: IfNotPresent
  ## Add secrets manually via kubectl on kubernetes cluster and reference here
  # pullSecrets:
  #   - name: "myKubernetesSecret"

# used by create-cluster-job when store.backend is etcd
etcdImage:
  repository: bitnami/etcd  # 这里修改下镜像地址,原来的地址国内无法访问
  tag: 2.3.7
  pullPolicy: IfNotPresent

debug: false

# Enable the creation of a shm volume
shmVolume:
  enabled: false

persistence:
  enabled: true
  ## If defined, storageClassName: <storageClass>
  ## If set to "-", storageClassName: "", which disables dynamic provisioning
  ## If undefined (the default) or set to null, no storageClassName spec is
  ##   set, choosing the default provisioner.  (gp2 on AWS, standard on
  ##   GKE, AWS & OpenStack)
  ##
  storageClassName: "rook-ceph-block"  # 修改下集群上部署的sc
  accessModes:
    - ReadWriteOnce
  size: 10Gi

rbac:
  create: true

serviceAccount:
  create: true
  # The name of the ServiceAccount to use. If not set and create is true, a name is generated using the fullname template
  name:

superuserSecret:
  name: ""
  usernameKey: pg_su_username    
  passwordKey: pg_su_password

replicationSecret:
  name: ""
  usernameKey: pg_repl_username
  passwordKey: pg_repl_password

superuserPasswordFile:

superuserUsername: "stolon"  # 用户名和密码根据自己的需要进行更改
## password for the superuser (REQUIRED if superuserSecret and superuserPasswordFile are not set)
superuserPassword: "pass_123456"

replicationPasswordFile:

replicationUsername: "repluser"
## password for the replication user (REQUIRED if replicationSecret and replicationPasswordFile are not set)
replicationPassword: "repli_654321"

## backend could be one of the following: consul, etcdv2, etcdv3 or kubernetes
store:
  backend: kubernetes
#  endpoints: "http://stolon-consul:8500"
  kubeResourceKind: configmap

pgParameters: {}
  # max_connections: "1000"

ports:
  stolon:
    containerPort: 5432
  metrics:
    containerPort: 8080

serviceMonitor:
  # When set to true then use a ServiceMonitor to collect metrics
  enabled: false
  # Custom labels to use in the ServiceMonitor to be matched with a specific Prometheus
  labels: {}
  # Set the namespace the ServiceMonitor should be deployed to
  # namespace: default
  # Set how frequently Prometheus should scrape
  # interval: 30s
  # Set timeout for scrape
  # scrapeTimeout: 10s

job:
  autoCreateCluster:
    enabled: true
    resources: {}
    initContainers:
      resources: {}
  initdbScripts:
    enabled: true
    resources: {}
    initContainers:
      resources: {}
  autoUpdateClusterSpec:
    enabled: true
    resources: {}
    initContainers:
      resources: {}
  annotations: {}

clusterSpec: {}
  # sleepInterval: 1s
  # maxStandbys: 5

## Enable support ssl into postgres, you must specify the certs.
## ref: https://www.postgresql.org/docs/10/ssl-tcp.html
##
tls:
  enabled: true     # 开启tls
  rootCa: |-        # 将上面生成的证书中的ca.crt内容拷贝进该文件
    -----BEGIN CERTIFICATE-----
    MIIDfzCCAmegAwIBAgIUItOvNfwTwKhvnWKki9F2VlZ1ugcwDQYJKoZIhvcNAQEL
    BQAwTzELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAlNEMRUwEwYDVQQHDAxEZWZhdWx0
    IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQwHhcNMjMwOTE5MjI0
    NDIzWhcNMzAxMjIxMjI0NDIzWjBPMQswCQYDVQQGEwJDTjELMAkGA1UECAwCU0Qx
    FTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55
    IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPragCJEn76XskpM
    qBdcvlJzo6rvuWkvGnmVCcJ2W/4rP2+tAtZTpN9MK2WGkaiQ5tSdWtxseqk2eLSp
    ofjkleDIDbTiYUrP+2D37qU1OO00v6AbGlFl9XMW/YkPiVRdj4n90CgIeKs7ju1d
    SHTEXDA+RF4bcxZA1bXtdCRmJwZ1un0NB5/1WKowuh+GGVPVSU8UUdrnv5omd2KI
    SEzJ7EMgUaV2sTb7XfIUN4Djcgjlm41WmrTqr47Zz+LpR8HHRBRXPkwqC48rk2st
    oUFmsANilWkOCVBGR2UVeFaf29DHQJdrGAHe/cdmDprSXAmnzCvHwqD0Pklo0ZI1
    OKokwQMCAwEAAaNTMFEwHQYDVR0OBBYEFDvkV6ctcSbW8RWJr1VrOgGkH5iEMB8G
    A1UdIwQYMBaAFDvkV6ctcSbW8RWJr1VrOgGkH5iEMA8GA1UdEwEB/wQFMAMBAf8w
    DQYJKoZIhvcNAQELBQADggEBAMpdwiJNDrHrAfW6M68Youtz484wy/UMiYAD4qsm
    LXY1eKgyaB5N/Dqym4gZ/m/M2hGftPu4zhKm9t6wTcm2KjAIfaM4+IpiMJlDLe4G
    5ZM2Ow/eJVMbT0yh8bZ/gkgCwIU3cdcUrbzmfSITVLGYp0F8Htst8ljJLzB4zoaf
    TlNyQ3Fe7a69eAiLlIoMvqthdEpkVWuJ+fq1Do9C5TYNGNtxVXMK8oXVlt3ESEcB
    K4x2rm5YNKooIkIyz6QKGKJoyFd/wYboByimCe64jgzuHt3Jnn+Xb6kVi5f6BCj9
    gF0WYcNUO+JwT5fcwzCqklQXyGeNOokmJGRFdZcubn6FErs=
    -----END CERTIFICATE-----
  serverCrt: |-  # 将上面生成的证书中的server.crt内容拷贝进该文件
    -----BEGIN CERTIFICATE-----
    MIIDPTCCAiUCFHwb1j0Bb6z/yOsftMI0ZQ413h9tMA0GCSqGSIb3DQEBCwUAME8x
    CzAJBgNVBAYTAkNOMQswCQYDVQQIDAJTRDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5
    MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMB4XDTIzMDkxOTIyNDYyOVoX
    DTMzMDkxNjIyNDYyOVowZzELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAlNEMRUwEwYD
    VQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQx
    FjAUBgNVBAMMDTE5Mi4xNjguMC4xODQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
    ggEKAoIBAQD62oAiRJ++l7JKTKgXXL5Sc6Oq77lpLxp5lQnCdlv+Kz9vrQLWU6Tf
    TCtlhpGokObUnVrcbHqpNni0qaH45JXgyA204mFKz/tg9+6lNTjtNL+gGxpRZfVz
    Fv2JD4lUXY+J/dAoCHirO47tXUh0xFwwPkReG3MWQNW17XQkZicGdbp9DQef9Viq
    MLofhhlT1UlPFFHa57+aJndiiEhMyexDIFGldrE2+13yFDeA43II5ZuNVpq06q+O
    2c/i6UfBx0QUVz5MKguPK5NrLaFBZrADYpVpDglQRkdlFXhWn9vQx0CXaxgB3v3H
    Zg6a0lwJp8wrx8Kg9D5JaNGSNTiqJMEDAgMBAAEwDQYJKoZIhvcNAQELBQADggEB
    AE6QkSfLlgA/KJABt1nKs98csqKbD4iP4U7BCYcRS+PWH16JZ3cB9OMMjn2LL9EN
    gO8VQ8GLg+OT8TZTZl4xRfG22wP7W62exbtcRKLwSscA8J3Jk7B+BQI0tW3xZ8i9
    LjFKVm7/LG/UnSX8Bf3Cs9abtyYJimcycdpSe94bQRrZI4SSbcW1beuQUF/+xbgT
    uMoUyJsl63i8/NtA9JYdsWWYBbyjWBgLQ8XNcDhTYU64HUVWaolDKKBhZPesFuYR
    9LkXQ/F+IQR5dDCbQrlD2ogFo3/9kWWE30qTN8PPcNtcLiIDHf+ryUdsvmeORIlx
    +UMMIGA8SAX1e3BFOA5lCts=
    -----END CERTIFICATE-----
  serverKey: |- # 将上面生成的证书中的server.key内容拷贝进该文件
    -----BEGIN PRIVATE KEY-----
    MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD62oAiRJ++l7JK
    TKgXXL5Sc6Oq77lpLxp5lQnCdlv+Kz9vrQLWU6TfTCtlhpGokObUnVrcbHqpNni0
    qaH45JXgyA204mFKz/tg9+6lNTjtNL+gGxpRZfVzFv2JD4lUXY+J/dAoCHirO47t
    XUh0xFwwPkReG3MWQNW17XQkZicGdbp9DQef9ViqMLofhhlT1UlPFFHa57+aJndi
    iEhMyexDIFGldrE2+13yFDeA43II5ZuNVpq06q+O2c/i6UfBx0QUVz5MKguPK5Nr
    LaFBZrADYpVpDglQRkdlFXhWn9vQx0CXaxgB3v3HZg6a0lwJp8wrx8Kg9D5JaNGS
    NTiqJMEDAgMBAAECggEANLlhDhLLWoVKwWgMOmDRVcYdY8uf9jYiw+Qp2XgEDM9t
    NV0xR/fros1BH8Uhb0MJaS2rj8GzJEahJyOyOfxdbhHOI7MjDNTa+n4R6CJgFSct
    kYqwxOP/q1FtN3YPAK7rgO7aZdel+SN6/nPJS1WMJM2pk/gD/+zfni3K5+2ajTYd
    1dj3xPgbShWJAsVLEwThVRg9gCR2t9PCLpj623QgOqSkIePy9mm7Wmgtiq3nkg0H
    c3bMbcbeI5bcpAEegpcXc4bz8GWNWnFrKPzae7yfgv722vB33/tDwRz75JCnQPca
    myR3I2geFjbgMornQy0reSRb304zwPhZ0bplYJ2zDQKBgQD8gaiSyxQPLn9Yls3Y
    hgX1bh3T/v2tzTtF9Vb0l7cQKKMbeC51lQs4zzByhTkMQznSOx79hWQ6D5BszMqi
    UXfyE5JkeaPiW9ccvLFQm9/TVu3mvC+1GCFJv7C+n7oHSoxfSQL9RaxZZpN28wNk
    Flja+HhBsfDKRH1YNE7eGn9mFwKBgQD+UvzLxg+MvS4+NRvyQfVFlloM0UsreGdQ
    MmCSMQlQHaHkt8uOyOOFVty+yaBWoEtsPOxkl7xIa4C6pwXMOMtbNa6ytn95lx4N
    zGqmhGf/M9bS0zQVfIHPPc4fChCprv1cuaHCZxeQU/sli3IfBiemjDchCnsX7VCZ
    bxGD/w179QKBgCRSNkBfS9RfONwyXQu/Q44pN8sty6m7csI+ZzKpvCBr6AJkiDJL
    rSCP1QU0Gp+j7+ZIsM2A6YjcgkbUMnWkyF8e55jiUmuWEFxG3C6fLVQGFhRxj186
    SFeGZlMVQLoZxBVUuERcSE1XzvB2Rk+YU1G0GgBBK0S9E1ajt5CFOTwjAoGAYwuI
    vgdVePcYMkvWpWNAlSg3y7QatUQ/4ACukWCdguD3cq6NjP75dK1ebMLzOalVlkKn
    wYlCX2XWjVqMrHsV32Cpt5nRTVYn8zG/+zenlMDokdSE/TUvDLnCM15lHOA0dc8p
    ix9BEwlRzs5e1Gw+NuN2eNyvEaNvd0HFLYXAB50CgYEA8NbrOtd+SNw2OoKR8LOQ
    2oGXat87DEpyY4QqYzjNKsPmrKBb5FTfBIGc9JoeZyocXjFSXEHupICQTQ0PArFo
    FgC/sVXhnXYJybMrtBLHqZSjEUlSvZK1hedhPIWU66Z6HPLSyDwwXAIP8uXmKVAu
    1BhwUVBBQ2GUhLF76Hgj5Ws=
    -----END PRIVATE KEY-----
  # existingSecret: name-of-existing-secret-to-postgresql

keeper:
  uid_prefix: "keeper"
  replicaCount: 2
  annotations: {}
  resources: {}
  priorityClassName: ""
  fsGroup: "1000"   # fsGroup要设置成启动pg的用户,要不然会报权限错误
  service:
    type: ClusterIP
    annotations: {}
    ports:
      keeper:
        port: 5432
        targetPort: 5432
        protocol: TCP
  nodeSelector: {}
  affinity: {}
  tolerations: []
  volumes: []
  volumeMounts: []
  hooks:
    failKeeper:
      enabled: false
  podDisruptionBudget:
    # minAvailable: 1
    # maxUnavailable: 1
  extraEnv: []
  #  - name: STKEEPER_LOG_LEVEL
  #    value: "info"

proxy:
  replicaCount: 2
  annotations: {}
  resources: {}
  priorityClassName: ""
  service:
    type: ClusterIP
#    loadBalancerIP: ""
    annotations: {}
    ports:
      proxy:
        port: 5432
        targetPort: 5432
        protocol: TCP
  nodeSelector: {}
  affinity: {}
  tolerations: []
  podDisruptionBudget:
    # minAvailable: 1
    # maxUnavailable: 1
  extraEnv: []
  #  - name: STPROXY_LOG_LEVEL
  #    value: "info"
  #  - name: STPROXY_TCP_KEEPALIVE_COUNT
  #    value: "0"
  #  - name: STPROXY_TCP_KEEPALIVE_IDLE
  #    value: "0"
  #  - name: STPROXY_TCP_KEEPALIVE_INTERVAL
  #    value: "0"

sentinel:
  replicaCount: 2
  annotations: {}
  resources: {}
  priorityClassName: ""
  nodeSelector: {}
  affinity: {}
  tolerations: []
  podDisruptionBudget:
    # minAvailable: 1
    # maxUnavailable: 1
  extraEnv: []
  #  - name: STSENTINEL_LOG_LEVEL
  #    value: "info"

## initdb scripts
## Specify dictionary of scripts to be run at first boot, the entry point script is create_script.sh
## i.e. you can use pgsql to run sql script on the cluster.
##
# initdbScripts:
#   create_script.sh: |
#      #!/bin/sh
#      echo "Do something."

## nodePostStart scripts
## Specify dictionary of scripts to be run at first boot, the entry point script is postStartScript.sh
## i.e. you can create tablespace directory here.
##
# nodePostStartScript:
#   postStartScript.sh: |
#     #!/bin/bash
#     echo "Do something."

创建名称空间

[root@node1 stolon]# kubectl create ns ssl-stolon
namespace/ssl-stolon created

部署Chart

# 在项目目录下可以用dry-run测试一下看看可不可以运行
[root@node1 stolon]# helm install stolon --dry-run . -n ssl-stolon

# 没问题的话就可以直接部署了
# 使用-f 指定变量文件
[root@node1 stolon]# helm install sslstolon kvaps/stolon -f ./values.yaml -n ssl-stolon

查看pod

[root@node1 stolon]# kubectl get pods -n ssl-stolon
NAME                                  READY   STATUS      RESTARTS   AGE
sslstolon-create-cluster-x7bsn        0/1     Completed   0          45s
sslstolon-keeper-0                    1/1     Running     0          45s
sslstolon-keeper-1                    1/1     Running     0          35s
sslstolon-proxy-8685fdb4bd-457kr      1/1     Running     0          45s
sslstolon-proxy-8685fdb4bd-8rls7      1/1     Running     0          45s
sslstolon-sentinel-6cf9cb965f-fdz8k   1/1     Running     0          45s
sslstolon-sentinel-6cf9cb965f-zd6fm   1/1     Running     0          45s

postgres连接测试

[root@node1 stolon]# kubectl get service -n ssl-stolon
NAME                        TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
sslstolon-keeper-headless   ClusterIP   None           <none>        5432/TCP   2m15s
sslstolon-proxy             ClusterIP   10.108.57.55   <none>        5432/TCP   2m15s

# 可以看到下面有ssl连接的提示
[root@node1 stolon]# psql -h  10.108.57.55 -U stolon -d postgres
Password for user stolon: 
psql (13.11, server 10.12 (Debian 10.12-1.pgdg90+1))
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

postgres=# 

遇到的问题

问题一

2023-09-20T04:05:34.381Z        INFO    postgresql/postgresql.go:319    starting database
2023-09-20 04:05:34.454 UTC [74] FATAL:  could not load server certificate file "/certs/serverCrt.crt": Permission denied

解决办法: 需要修改下fsGroup

image.png

参考文档