在前一陣子 EDB 曾經在 K8s/Openshift 平台還沒變成壓倒性主流容器平台方案時,就已經提供 Docker Container 的資料庫解決方案,當時也做了一點點練習(筆記在這)。
不過當時得努力的搞清楚最原生的 K8s YAML 設置,其實很複雜。。。
隨著 K8s 整體的推進,出現了 Helm 與 Operator 這類「套件管理」的工具,加上 K8s 強化 Stateful Container 的支援,資料庫陸陸續續也可以輕易登陸 K8s 平台。
EDB 團隊基於這些變化,加上原 2ndQ 團隊的一同合作,便推出這個成熟的 K8s 解決方案。
這邊就要來初步體驗一下~
- 原生版 PostgreSQL 與企業版 EDB Postgres Advanced Server 資料庫
- 基於 K8s 調度機制的資料庫高可用功能
- 支援 S3 協定的資料庫全備份、WAL 備份
- 自動設置資料傳輸安全性
- 滾動式小版本更新支援、K8s 底層維護支援
- K8s 節點磁碟壓測工具 cnp-bench
這邊就使用好心的 K8s 測試環境 KinD 作簡易的三節點主從 PGSQL 資料庫練習,並且偷偷潛入容器裡面喵一下。
以下會進行的步驟大致如下
- 操作預備好的 K8s in Docker 三節點環境
- 安裝 CNP Operator:有這個才能佈署 EDB 資料庫解決方案
- 設置 1 Primary/2 Standby 的 PGSQL 資料庫
- 偷偷溜進去 Container 觀察一下
- 觸發簡單的高可用切換
以上的第一步就是沿用上一篇筆記環境~
現在要安裝 CNP Operator:這需要利用 kubectl apply 指令佈署 Operator,利用 -f 指定 YAML 檔案。這邊直接參照以下頁面操作。
EDB Docs - Installation and upgrades
EDB Docs - Installation, Configuration and Deployment Demo
[goodgame@k8sindocker ~]$ curl https://get.enterprisedb.io/cnp/postgresql-operator-1.7.1.yaml -o postgresql-operator-1.7.1.yaml % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 218k 100 218k 0 0 221k 0 --:--:-- --:--:-- --:--:-- 220k [goodgame@k8sindocker ~]$ kubectl apply -f postgresql-operator-1.7.1.yaml namespace/postgresql-operator-system created customresourcedefinition.apiextensions.k8s.io/backups.postgresql.k8s.enterprisedb.io created customresourcedefinition.apiextensions.k8s.io/clusters.postgresql.k8s.enterprisedb.io created customresourcedefinition.apiextensions.k8s.io/scheduledbackups.postgresql.k8s.enterprisedb.io created Warning: admissionregistration.k8s.io/v1beta1 MutatingWebhookConfiguration is deprecated in v1.16+, unavailable in v1.22+; use admissionregistration.k8s.io/v1 MutatingWebhookConfiguration mutatingwebhookconfiguration.admissionregistration.k8s.io/postgresql-operator-mutating-webhook-configuration created serviceaccount/postgresql-operator-manager created clusterrole.rbac.authorization.k8s.io/postgresql-operator-manager created clusterrolebinding.rbac.authorization.k8s.io/postgresql-operator-manager-rolebinding created service/postgresql-operator-webhook-service created deployment.apps/postgresql-operator-controller-manager created Warning: admissionregistration.k8s.io/v1beta1 ValidatingWebhookConfiguration is deprecated in v1.16+, unavailable in v1.22+; use admissionregistration.k8s.io/v1 ValidatingWebhookConfiguration validatingwebhookconfiguration.admissionregistration.k8s.io/postgresql-operator-validating-webhook-configuration created [goodgame@k8sindocker ~]$
[goodgame@k8sindocker ~]$ kubectl get deploy -n postgresql-operator-system postgresql-operator-controller-manager NAME READY UP-TO-DATE AVAILABLE AGE postgresql-operator-controller-manager 1/1 1 1 72s [goodgame@k8sindocker ~]$ [goodgame@k8sindocker ~]$ kubectl get pods -n postgresql-operator-system NAME READY STATUS RESTARTS AGE postgresql-operator-controller-manager-684bb9c549-5rjbh 1/1 Running 0 83s [goodgame@k8sindocker ~]$
CNP Operator 安裝完成之後,就可以佈署 DB 叢集了。相關的叢集範例可以參照 Configuration Samples - Cloud Native PostgreSQL(EDB Docs - Configuration Samples)。基本上都是高可用配置的範本。單節點不需要用 Operator。
資料庫初始化選項可以參考 EDB Docs - Bootstrap,這邊直接選用下面這個範本
https://docs.enterprisedb.io/cloud-native-postgresql/1.7.1/samples/cluster-example.yaml
執行後就一邊觀察 pod 狀態。這邊顯示一開始跟完成後的狀況:可以撘配 watch 觀察會更清楚~
[goodgame@k8sindocker ~]$ curl https://docs.enterprisedb.io/cloud-native-postgresql/1.7.1/samples/cluster-example.yaml # Example of PostgreSQL cluster apiVersion: postgresql.k8s.enterprisedb.io/v1 kind: Cluster metadata: name: cluster-example spec: instances: 3 # Require 1Gi of space storage: size: 1Gi [goodgame@k8sindocker ~]$ [goodgame@k8sindocker ~]$ curl https://docs.enterprisedb.io/cloud-native-postgresql/1.7.1/samples/cluster-example.yaml -o cluster-example.yaml % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 141 100 141 0 0 173 0 --:--:-- --:--:-- --:--:-- 173 [goodgame@k8sindocker ~]$ [goodgame@k8sindocker ~]$ kubectl apply -f cluster-example.yaml cluster.postgresql.k8s.enterprisedb.io/cluster-example created [goodgame@k8sindocker ~]$
[goodgame@k8sindocker ~]$ # 馬上觀察 [goodgame@k8sindocker ~]$ kubectl get pods NAME READY STATUS RESTARTS AGE cluster-example-1-initdb-nvhp8 0/1 PodInitializing 0 19s [goodgame@k8sindocker ~]$
[goodgame@k8sindocker ~]$ # 中間過程 [goodgame@k8sindocker ~]$ kubectl get pods NAME READY STATUS RESTARTS AGE cluster-example-1 1/1 Running 0 2m6s cluster-example-1-initdb-nvhp8 0/1 Completed 0 3m37s cluster-example-2 1/1 Running 0 8s cluster-example-2-join-8xn92 0/1 Completed 0 2m4s cluster-example-3-join-lnq24 0/1 Pending 0 5s [goodgame@k8sindocker ~]$
[goodgame@k8sindocker ~]$ #叢集完成 [goodgame@k8sindocker ~]$ kubectl get pods NAME READY STATUS RESTARTS AGE cluster-example-1 1/1 Running 0 2m22s cluster-example-2 1/1 Running 0 24s cluster-example-3 1/1 Running 0 12s [goodgame@k8sindocker ~]$
等到完成後,就可以用 kubectl 原生指令查看資料庫叢集資訊
[goodgame@k8sindocker ~]$ kubectl get cluster cluster-example -o yaml apiVersion: postgresql.k8s.enterprisedb.io/v1 kind: Cluster metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"postgresql.k8s.enterprisedb.io/v1","kind":"Cluster","metadata":{"annotations":{},"name":"cluster-example","namespace":"default"},"spec":{"instances":3,"storage":{"size":"1Gi"}}} creationTimestamp: "2021-09-10T10:30:29Z" generation: 1 name: cluster-example namespace: default resourceVersion: "8495" uid: 465XXX96-9e2c-4O8f-8OO0-7b6XXXX28a2e spec: affinity: podAntiAffinityType: preferred topologyKey: "" bootstrap: initdb: database: app owner: app imageName: quay.io/enterprisedb/postgresql:13.3 instances: 3 postgresql: parameters: log_destination: csvlog log_directory: /controller/log log_filename: postgres log_rotation_age: "0" log_rotation_size: "0" log_truncate_on_rotation: "false" logging_collector: "on" max_parallel_workers: "32" max_replication_slots: "32" max_worker_processes: "32" shared_preload_libraries: "" wal_keep_size: 512MB resources: {} storage: size: 1Gi status: certificates: clientCASecret: cluster-example-ca expirations: cluster-example-ca: 2021-12-09 10:25:29 +0000 UTC cluster-example-replication: 2021-12-09 10:25:29 +0000 UTC cluster-example-server: 2021-12-09 10:25:29 +0000 UTC replicationTLSSecret: cluster-example-replication serverAltDNSNames: - cluster-example-rw - cluster-example-rw.default - cluster-example-rw.default.svc - cluster-example-r - cluster-example-r.default - cluster-example-r.default.svc - cluster-example-ro - cluster-example-ro.default - cluster-example-ro.default.svc serverCASecret: cluster-example-ca serverTLSSecret: cluster-example-server configMapResourceVersion: {} currentPrimary: cluster-example-1 healthyPVC: - cluster-example-1 - cluster-example-2 - cluster-example-3 instances: 3 instancesStatus: healthy: - cluster-example-1 - cluster-example-2 - cluster-example-3 latestGeneratedNode: 3 licenseStatus: isImplicit: true isTrial: true licenseExpiration: "2021-10-10T10:30:29Z" licenseStatus: Implicit trial license repositoryAccess: false valid: true phase: Cluster in healthy state pvcCount: 3 readService: cluster-example-r readyInstances: 3 secretsResourceVersion: applicationSecretVersion: "6616" clientCaSecretVersion: "6612" replicationSecretVersion: "6614" serverCaSecretVersion: "6612" serverSecretVersion: "6613" superuserSecretVersion: "6615" targetPrimary: cluster-example-1 writeService: cluster-example-rw [goodgame@k8sindocker ~]$
觀察一下 service
[goodgame@k8sindocker ~]$ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE cluster-example-any ClusterIP 10.96.194.146 <none> 5432/TCP 5h50m cluster-example-r ClusterIP 10.96.173.199 <none> 5432/TCP 5h50m cluster-example-ro ClusterIP 10.96.238.83 <none> 5432/TCP 5h50m cluster-example-rw ClusterIP 10.96.24.26 <none> 5432/TCP 5h50m kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 6h32m [goodgame@k8sindocker ~]$
以上就完成資料庫叢集的設置了。
不過,為了接觸這組服務,EDB K8s operator 有附一個管理 CNP 資料庫叢集的 kubectl 外掛 kubectl cnp,這邊就把這裝起來。
[goodgame@k8sindocker ~]$ curl -sSfL https://github.com/EnterpriseDB/kubectl-cnp/raw/main/install.sh -o install.sh [goodgame@k8sindocker ~]$ [goodgame@k8sindocker ~]$ chmod +x install.sh [goodgame@k8sindocker ~]$ sudo ./install.sh -b /usr/local/bin EnterpriseDB/kubectl-cnp info checking GitHub for latest tag EnterpriseDB/kubectl-cnp info found version: 1.7.1 for v1.7.1/linux/x86_64 EnterpriseDB/kubectl-cnp info installed /usr/local/bin/kubectl-cnp [goodgame@k8sindocker ~]$
查看資料庫叢集狀態
[goodgame@k8sindocker ~]$ kubectl cnp status cluster-example Cluster in healthy state Name: cluster-example Namespace: default PostgreSQL Image: quay.io/enterprisedb/postgresql:13.3 Primary instance: cluster-example-1 Instances: 3 Ready instances: 3 Instances status Pod name Current LSN Received LSN Replay LSN System ID Primary Replicating Replay paused Pending restart Status -------- ----------- ------------ ---------- --------- ------- ----------- ------------- --------------- ------ cluster-example-1 0/5000060 7006250831794221074 ✓ ✗ ✗ ✗ OK cluster-example-2 0/5000060 0/5000060 7006250831794221074 ✗ ✓ ✗ ✗ OK cluster-example-3 0/5000060 0/5000060 7006250831794221074 ✗ ✓ ✗ ✗ OK [goodgame@k8sindocker ~]$
來看一下這個 plugin 提供的功能。
[goodgame@k8sindocker ~]$ kubectl cnp --help A plugin to manage your Cloud Native PostgreSQL clusters Usage: kubectl-cnp [command] Available Commands: certificate Create a client certificate to connect to PostgreSQL using TLS and Certificate authentication completion generate the autocompletion script for the specified shell help Help about any command promote Promote the pod named [cluster]-[node] or [node] to primary reload Reload the cluster restart Restart the cluster status Get the status of a PostgreSQL cluster version Prints version, commit sha and date of the build Flags: --as string Username to impersonate for the operation --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. --cache-dir string Default cache directory (default "/home/goodgame/.kube/cache") --certificate-authority string Path to a cert file for the certificate authority --client-certificate string Path to a client certificate file for TLS --client-key string Path to a client key file for TLS --cluster string The name of the kubeconfig cluster to use --context string The name of the kubeconfig context to use -h, --help help for kubectl-cnp --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure --kubeconfig string Path to the kubeconfig file to use for CLI requests. -n, --namespace string If present, the namespace scope for this CLI request --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") -s, --server string The address and port of the Kubernetes API server --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used --token string Bearer token for authentication to the API server --user string The name of the kubeconfig user to use Use "kubectl-cnp [command] --help" for more information about a command. [goodgame@k8sindocker ~]$
以上從 K8s 觀察資料庫同步資訊之後,然後再來是想辦法進去資料庫裡面,檢驗一下同步。進入 Container 的方法在這 Get a Shell to a Running Container | Kubernetes。
不過值得一提的是,除了資料目錄(PGDATA)之外,Container 裡面是唯讀的 OS 環境。
[goodgame@k8sindocker ~]$ kubectl exec --stdin --tty cluster-example-1 -- /bin/bash Defaulted container "postgres" out of: postgres, bootstrap-controller (init) bash-4.4$ hostname cluster-example-1 bash-4.4$ bash-4.4$ psql psql (13.3) Type "help" for help. postgres=# \l List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges -----------+----------+-----------+---------+-------+----------------------- app | app | SQL_ASCII | C | C | postgres | postgres | SQL_ASCII | C | C | template0 | postgres | SQL_ASCII | C | C | =c/postgres + | | | | | postgres=CTc/postgres template1 | postgres | SQL_ASCII | C | C | =c/postgres + | | | | | postgres=CTc/postgres (4 rows) postgres=# select * from pg_stat_replication ; pid | usesysid | usename | application_name | client_addr | client_hostname | client_port | backend_start | backend_xmin | state | sent_lsn | write_lsn | flush_lsn | replay_lsn | write_lag | flush_lag | replay_lag | sync_priority | sync_state | reply_time -----+----------+-------------------+-------------------+-------------+-----------------+-------------+-------------------------------+--------------+-----------+-----------+-----------+-----------+------------+-----------+-----------+------------+---------------+------------+------------------------------- 201 | 16386 | streaming_replica | cluster-example-2 | 10.244.1.5 | | 49320 | 2021-09-10 10:34:02.023046+00 | | streaming | 0/8000000 | 0/8000000 | 0/8000000 | 0/8000000 | | | | 0 | async | 2021-09-10 13:11:19.97608+00 343 | 16386 | streaming_replica | cluster-example-3 | 10.244.1.8 | | 44016 | 2021-09-10 10:34:14.565879+00 | | streaming | 0/8000000 | 0/8000000 | 0/8000000 | 0/8000000 | | | | 0 | async | 2021-09-10 13:11:20.267473+00 (2 rows) postgres=# postgres=# select * from pg_hba_file_rules ; line_number | type | database | user_name | address | netmask | auth_method | options | error -------------+---------+---------------+---------------------+---------+---------+-------------+------------------------+------- 2 | local | {all} | {all} | | | peer | {map=local} | 5 | hostssl | {postgres} | {streaming_replica} | all | | cert | {clientcert=verify-ca} | 6 | hostssl | {replication} | {streaming_replica} | all | | cert | {clientcert=verify-ca} | 9 | host | {all} | {all} | all | | md5 | | (4 rows) postgres=# \q bash-4.4$ ls /var/lib/pgsql/13/data/ bash-4.4$ ls /var/lib/postgresql/data/pgdata/ PG_VERSION pg_commit_ts pg_notify pg_subtrans postgresql.conf base pg_dynshmem pg_replslot pg_tblspc postmaster.opts current_logfiles pg_hba.conf pg_serial pg_twophase postmaster.pid custom.conf pg_ident.conf pg_snapshots pg_wal global pg_logical pg_stat pg_xact log pg_multixact pg_stat_tmp postgresql.auto.conf bash-4.4$ bash-4.4$ cat /var/lib/postgresql/data/pgdata/custom.conf archive_command = '/controller/manager wal-archive %p' archive_mode = 'on' archive_timeout = '5min' cluster_name = 'cluster-example' full_page_writes = 'on' hot_standby = 'true' listen_addresses = '*' log_destination = 'csvlog' log_directory = '/controller/log' log_filename = 'postgres' log_rotation_age = '0' log_rotation_size = '0' log_truncate_on_rotation = 'false' logging_collector = 'on' max_parallel_workers = '32' max_replication_slots = '32' max_worker_processes = '32' port = '5432' shared_preload_libraries = '' ssl = 'on' ssl_ca_file = '/controller/certificates/client-ca.crt' ssl_cert_file = '/controller/certificates/server.crt' ssl_key_file = '/controller/certificates/server.key' unix_socket_directories = '/controller/run' wal_keep_size = '512MB' wal_level = 'logical' wal_log_hints = 'on' cnp.config_sha256 = '058ae20f8aa96974535cbf449X1X4XcXabaXb74e6Xe0XXcb81a85Xe77d34197f' bash-4.4$
[goodgame@k8sindocker ~]$ kubectl exec --stdin --tty cluster-example-2 -- /bin/bash Defaulted container "postgres" out of: postgres, bootstrap-controller (init) bash-4.4$ hostname cluster-example-2 bash-4.4$ psql -c 'select pg_is_in_recovery();' -c 'show restore_command ;' pg_is_in_recovery ------------------- t (1 row) restore_command --------------------------------------- /controller/manager wal-restore %f %p (1 row) bash-4.4$
最後,作一個簡易高可用切換測試:這邊進入 primary DB 的 container 執行 pg_ctl stop 指令後,在回到外面觀察 CNP 叢集狀態
[brandon_hsu@k8sindocker ~]$ kubectl exec --stdin --tty cluster-example-1 -- /bin/bash Defaulted container "postgres" out of: postgres, bootstrap-controller (init) bash-4.4$ pg_ctl stop waiting for server to shut down....command terminated with exit code 137 [brandon_hsu@k8sindocker ~]$
[brandon_hsu@k8sindocker ~]$ kubectl cnp status cluster-example Cluster in healthy state Name: cluster-example Namespace: default PostgreSQL Image: quay.io/enterprisedb/postgresql:13.3 Primary instance: cluster-example-2 Instances: 3 Ready instances: 3 Instances status Pod name Current LSN Received LSN Replay LSN System ID Primary Replicating Replay paused Pending restart Status -------- ----------- ------------ ---------- --------- ------- ----------- ------------- --------------- ------ cluster-example-1 0/8004958 0/8004958 7006250831794221074 ✗ ✓ ✗ ✗ OK cluster-example-2 0/8004958 7006250831794221074 ✓ ✗ ✗ ✗ OK cluster-example-3 0/8004958 0/8004958 7006250831794221074 ✗ ✓ ✗ ✗ OK [brandon_hsu@k8sindocker ~]$
以上觀察到,Primary 切換到第二個 pod,而第一個 pod 也自動轉換為 standby DB 復活了~
以上簡單測試就到這結束~
參考資料
都附在上面惹~
沒有留言:
張貼留言