Giới thiệu
Ở bài này chúng ta sẽ tìm hiểu về cách triển khai một hệ thống Database với chế độ Master-Slave Replication ở trên Kubernetes.
Trước khi trển khai thì ta sẽ nói qua về cơ chế Database Master-Slave Replication là gì?
Database Master-Slave Replication
Đây là một hệ thống Database bao gồm một Master Database và một hoặc nhiều Slave Replication Database. Với Master sử dụng cho việc ghi dữ liệu và các Replication được dùng cho việc đọc dữ liệu. Dữ liệu được ghi vào Master sẽ được chuyển qua các Replication để dữ liệu trên toàn hệ thống DB của ta được đồng bộ với nhau.
Trong một ứng dụng thông thường ta chỉ xài một DB cho cả việc đọc và ghi, nếu ứng dụng có lượt truy cập không cao thì sử dụng một DB cũng đủ đáp ứng yêu cầu cho người dùng. Nhưng đối với các ứng dụng mà có lượt truy cập cao thì chỉ sử dụng một DB cho cả việc đọc và ghi sẽ dẫn tới DB bị quá tải và ứng dụng không thể xử lý nổi tất cả các yêu cầu của người dùng.
Để giải quyết vấn đề trên, khái niệm Database Master-Slave Replication được sinh ra. Trong Database Master-Slave Replication thì việc ghi dữ liệu sẽ được thực hiện trên Master và việc đọc dữ liệu được thực hiện trên Replication. Giúp tăng hiệu suất và tốc độ xử lý của Database.
Viết tệp tin cấu hình
Bây giờ ta sẽ tiến hành viết tệp tin cấu hình để triển khai hệ thống Database Master-Slave Replication (PostgreSQL).
Tạo Master DB
Tạo một tệp tin tên là postgres-password-cm.yaml
để chứa thông tin Password của PostgreSQL.
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-password
data:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
TIMESCALEDB_TELEMETRY: "off"
kubectl apply -f postgres-password-cm.yaml
Ta sử dụng ConfigMaps với mục đích Demo (sử dụng Secret cho Production). Tạo tệp tin tên là postgres-master-sts.yaml
cho Master DB:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres-master
labels:
component: postgres-master
spec:
selector:
matchLabels:
component: postgres-master
serviceName: postgres-master
template:
metadata:
labels:
component: postgres-master
spec:
containers:
- name: postgres
image: postgres:11
command:
[
"sh",
"-c",
"docker-entrypoint.sh -c config_file=/var/config/postgresql.conf -c hba_file=/var/config/pg_hba.conf",
]
ports:
- containerPort: 5432
envFrom:
- configMapRef:
name: postgres-password
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: postgres-data-master
- mountPath: /var/config
name: postgres-master-configmap
volumes:
- name: postgres-master-configmap
configMap:
name: postgres-master-configmap
volumeClaimTemplates:
- metadata:
name: postgres-data-master
spec:
accessModes:
- ReadWriteOnce
storageClassName: hostpath
resources:
requests:
storage: 5Gi
Ở tệp tin cấu hình trên ta sử dụng Image postgres:11
, ở thuộc tính command
là câu lệnh để báo cho Postgres biết nơi chứa tệp tin cấu hình.
Để truyền hai tệp tin cấu hình trên vào DB ta sẽ sử dụng ConfigMaps dạng volumeMounts
. Tạo một thư mục tên là config
, mở nó ra và tạo thêm hai tệp tin tên là postgresql.conf
và pg_hba.conf
.
listen_addresses = '*'
max_connections = 100
shared_buffers = 128MB
dynamic_shared_memory_type = posix
max_wal_size = 1GB
min_wal_size = 80MB
log_timezone = 'Etc/UTC'
datestyle = 'iso, mdy'
timezone = 'Etc/UTC'
lc_messages = 'en_US.utf8'
lc_monetary = 'en_US.utf8'
lc_numeric = 'en_US.utf8'
lc_time = 'en_US.utf8'
default_text_search_config = 'pg_catalog.english'
#------------------------------------------------------------------------------
# CUSTOMIZED OPTIONS
#------------------------------------------------------------------------------
# Add settings for extensions here
wal_level = replica
max_wal_senders = 2
max_replication_slots = 2
synchronous_commit = off
Từ chỗ # Add settings for extensions here
trở xuống là cấu hình cho Master DB. Còn nội dung của tệp tin pg_hba.conf
thì như sau.
# TYPE DATABASE USER ADDRESS METHOD
local all all trust
# IPv4 local connections:
host all all 127.0.0.1/32 trust
# IPv6 local connections:
host all all ::1/128 trust
# Allow replication connections from localhost, by a user with the
# replication privilege.
local replication all trust
host replication all 127.0.0.1/32 trust
host replication all ::1/128 trust
host replication repuser 0.0.0.0/0 scram-sha-256
host all all all md5
Tạo ConfigMaps.
kubectl create cm postgres-master-configmap --from-file=config
Tạo StatefulSet.
kubectl apply -f postgres-master-sts.yaml
Kiểm tra.
$ kubectl get sts
NAME READY AGE
postgres-master 1/1 4s
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
postgres-master-0 1/1 Running 0 7s
Tiếp theo ta truy cập vào Master DB và thực thi câu lệnh tạo user
. Đây là user
mà Slave DB sử dụng để kết nối tới Master DB.
$ kubectl exec -it postgres-master-0 -- bash
root@postgres-master-0:/# su - postgres
postgres@postgres-master-0:~$ psql
postgres=# SET password_encryption = 'scram-sha-256';
SET
postgres=# CREATE ROLE repuser WITH REPLICATION PASSWORD 'postgres' LOGIN;
CREATE ROLE
postgres=# SELECT * FROM pg_create_physical_replication_slot('replica_1_slot');
slot_name | lsn
----------------+-----
replica_1_slot |
(1 row)
Cuối cùng là ta tạo Kubernetes Service cho Master DB, tạo tệp tin tên là postgres-master-svc.yaml
.
apiVersion: v1
kind: Service
metadata:
name: postgres-master
spec:
selector:
component: postgres-master
ports:
- port: 5432
kubectl apply -f postgres-master-svc.yaml
Chuẩn bị dữ liệu cho Slave DB
Để tạo được Slave DB PostgreSQL yêu cầu ta phải đồng bộ dữ liệu từ Master DB ra, sau đó khởi tạo các Slave DB với dữ liệu đồng bộ này. Ta tiến hành theo các bước sau:
- Tạo một PVC
- Tạo Job để đồng bộ dữ liệu từ Master DB vào PVC
- Tạo Slave DB với dữ liệu trong PVC
Tạo một tệp tin tên là pvc-slave.yaml
.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data-slave
spec:
accessModes:
- ReadWriteOnce
storageClassName: hostpath
resources:
requests:
storage: 5Gi
kubectl apply -f pvc-slave.yaml
Ta tạo Job để chạy đồng bộ dữ liệu từ Master DB vào PVC trên, tạo tệp tin tên là sync-master-data.yaml
.
apiVersion: batch/v1
kind: Job
metadata:
name: sync-master-data
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: sync-master-data
image: postgres:11
command:
[
"sh",
"-c",
'PGPASSWORD="postgres" pg_basebackup -h postgres-master -D /var/lib/slave-postgresql/data -U repuser -vP',
]
volumeMounts:
- mountPath: /var/lib/slave-postgresql/data
name: postgres-data
volumes:
- name: postgres-data
persistentVolumeClaim:
claimName: postgres-data-slave
kubectl apply -f sync-master-data.yaml
Kiểm tra.
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
postgres-master-0 1/1 Running 0 89m
sync-master-data-59jhb 0/1 Completed 0 3s
$ kubectl logs sync-master-data-59jhb
pg_basebackup: initiating base backup, waiting for checkpoint to complete
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/2000028 on timeline 1
pg_basebackup: starting background WAL receiver
pg_basebackup: created temporary replication slot "pg_basebackup_143"
0/23770 kB (0%), 0/1 tablespace (...ave-postgresql/data/backup_label)
23779/23779 kB (100%), 0/1 tablespace (...ostgresql/data/global/pg_control)
23779/23779 kB (100%), 1/1 tablespace
pg_basebackup: write-ahead log end point: 0/20000F8
pg_basebackup: waiting for background process to finish streaming ...
pg_basebackup: base backup completed
Nếu bạn thấy được dòng pg_basebackup: base backup completed
thì Job đã chạy đồng bộ dữ liệu vào PVC thành công.
Tạo Slave DB
Giờ ta sẽ tạo Slave DB. Đầu tiên ta sẽ tạo ConfigMaps để chứa cấu hình Slave DB, tạo thư mục tên là slave-config
với hai tệp tin là postgresql.conf
với recovery.conf
.
listen_addresses = '*'
max_connections = 100
shared_buffers = 128MB
dynamic_shared_memory_type = posix
max_wal_size = 1GB
min_wal_size = 80MB
log_timezone = 'Etc/UTC'
datestyle = 'iso, mdy'
timezone = 'Etc/UTC'
lc_messages = 'en_US.utf8'
lc_monetary = 'en_US.utf8'
lc_numeric = 'en_US.utf8'
lc_time = 'en_US.utf8'
default_text_search_config = 'pg_catalog.english'
#------------------------------------------------------------------------------
# CUSTOMIZED OPTIONS
#------------------------------------------------------------------------------
# Add settings for extensions here
hot_standby = on
wal_level = replica
max_wal_senders = 2
max_replication_slots = 2
synchronous_commit = off
standby_mode = on
primary_conninfo = 'host=postgres-master port=5432 user=repuser password=postgres application_name=r1'
primary_slot_name = 'replica_1_slot'
trigger_file = '/var/lib/postgresql/data/change_to_master'
Tạo ConfigMaps.
kubectl create cm postgres-slave-configmap --from-file=slave-config
Tiếp theo ta tạo StatefulSet mà sử dụng PVC có thông tin dữ liệu từ Master DB.
Tạo một tệp tin tên là postgres-slave-sts.yaml
.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres-slave
labels:
component: postgres-slave
spec:
selector:
matchLabels:
component: postgres-slave
serviceName: postgres-slave
template:
metadata:
labels:
component: postgres-slave
spec:
initContainers:
- name: busybox
image: busybox
command:
- sh
- -c
- "cp /var/config/postgresql.conf /var/lib/postgresql/data/postgresql.conf && cp /var/config/recovery.conf /var/lib/postgresql/data/recovery.conf"
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: postgres-data
- mountPath: /var/config/postgresql.conf
subPath: postgresql.conf
name: postgres-slave-configmap
- mountPath: /var/config/recovery.conf
subPath: recovery.conf
name: postgres-slave-configmap
containers:
- name: postgres
image: postgres:11
ports:
- containerPort: 5432
envFrom:
- configMapRef:
name: postgres-password
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: postgres-data
volumes:
- name: postgres-slave-configmap
configMap:
name: postgres-slave-configmap
- name: postgres-data
persistentVolumeClaim:
claimName: postgres-data-slave
Ở trên vì mình không kiếm được câu lệnh để Postgres chỉ định tệp tin recovery.conf
là tệp tin cấu hình của nó, nên mình dùng initContainers
để khởi tạo cấu hình bằng cách mount
ConfigMaps vào initContainers
, sau đó thực hiện câu lệnh sao chép tệp tin cấu hình từ initContainers
sang PVC.
kubectl apply -f postgres-slave-sts.yaml
Kiểm tra DB thì ta sẽ thấy nó có dòng hiển thị là DB chỉ dùng để đọc, không thể ghi được.
$ kubectl logs postgres-slave-0
PostgreSQL Database directory appears to contain a database; Skipping initialization
...
2021-12-10 08:57:29.790 UTC [1] LOG: database system is ready to accept read only connections
Vậy là Slave DB của ta đã được tạo thành công, bước cuối là kiểm tra xem khi ta ghi dữ liệu vào Master DB thì Slave DB của ta có dữ liệu giống với Master hay không, truy cập vào Master DB.
$ kubectl exec -it postgres-master-0 -- psql -h localhost -U postgres -d postgres
psql (11.12 (Debian 11.12-1.pgdg90+1))
Type "help" for help.
postgres=# CREATE TABLE test (id int not null, val text not null);
CREATE TABLE
postgres=# INSERT INTO test VALUES (1, 'foo');
INSERT 0 1
postgres=# INSERT INTO test VALUES (2, 'bar');
INSERT 0 1
Truy cập vào Slave DB.
$ kubectl exec -it postgres-slave-0 -- psql -h localhost -U postgres -d postgres
psql (11.12 (Debian 11.12-1.pgdg90+1))
Type "help" for help.
postgres=# SELECT * FROM test;
id | val
----+-----
1 | foo
2 | bar
(2 rows)
Thực hiện câu lệnh select
và bạn sẽ thấy dữ liệu của Slave DB giống với Master DB, code của bài này nằm ở Github Repo Postgresql Master Slave.
Tạo bằng Helm
Với môi trường Production thì ta có thể triển khai nhanh hơn bằng cách sử dụng công cụ có sẵn của Kubernetes là Helm, nó sẽ giúp ta triển khai mô hình này một cách nhanh chóng chỉ bằng vài câu lệnh.
Helm là một Package Manager cho Kubernetes, các bạn xem cách cài ở đây: Helm Install. Sau khi cài xong thì ta chạy câu lệnh sau đây để triển khai Database.
$ helm repo add bitnami https://charts.bitnami.com/bitnami
"bitnami" has been added to your repositories
$ helm install postgresql bitnami/postgresql-ha --set postgresqlPassword=postgres --set replication.password=postgres
Kiểm tra.
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
postgresql-postgresql-ha-pgpool-65fbd4fb4b-vzsjn 0/1 Running 0 114s
postgresql-postgresql-ha-postgresql-0 1/1 Running 0 114s
postgresql-postgresql-ha-postgresql-1 1/1 Running 0 114s
Kết luận
Vậy là ta đã tìm hiểu xong cách triển khai mô hình Database dạng Master-Slave Replication trên Kubernetes bằng cách thủ công và tự động bằng Helm, và ở môi trường Production thì bạn nên sử dụng Helm.
Tác giả @Quân Huỳnh
Nếu bài viết có gì sai hoặc cần cập nhật thì liên hệ Admin.
Tham gia nhóm chat của DevOps VN tại Telegram.
Kém tiếng Anh và cần nâng cao trình độ giao tiếp: Tại sao bạn học không hiệu quả?