在第一篇我把博客迁移到了K8s,但储存在OpenEBS Jiva的SQLite性能堪忧,经常出现无法响应的情况。部署一个基于K8s的高可用PostgreSQL集群可能是一个选择。
CloudNative PG
CloudNative PG是一个PostgreSQL Operator,Operator模式是一个用来部署和管理应用的一种模式,而CloudNativePG可以用于管理PostgreSQL容器群。我们可以用CloudNative PG创建和管理PostgreSQL on K8s集群。
Cloud Native PG的架构
高可用性和流复制
在核心层,CNPG同时支持异步和同步流复制。它允许配置一个主实例,以及多个热备份副本以实现同一Kubernetes集群内的高可用性。服务可供应用程序连接到主实例(-rw)、热备份副本(-ro)或任何实例进行只读工作负载(-r)。
共享无体系结构
CNPG推荐采用共享无体系结构以增强韧性。这意味着PostgreSQL实例位于不同的Kubernetes工作节点上,跨越不同的可用区,不共享存储。每个节点理想情况下使用本地卷(local volumes)存储PostgreSQL数据,从而提高集群的容错性。CNPG自动化地更新服务以适应集群拓扑的变化。例如,在故障转移期间,它会更新**-rw**服务,将应用程序流量重定向到新晋升的主实例,确保无缝的连续性。
读写和只读工作负载
应用程序可以连接到**-rw**服务以在主实例上执行写操作。对于只读工作负载,应用程序使用**-ro**服务连接到热备份副本,从而分担主节点的查询负载。这种设置旨在优化集群中的工作负载分布。
多集群部署和灾难恢复
CNPG通过一个名为副本集群(Replica Cluster)的功能,支持跨多个Kubernetes集群的部署。这种设置涉及在一个集群中拥有一个可写主实例,以及在其他集群中拥有只读副本集群,促进全球性的灾难恢复,并减少RPO(恢复点目标)和RTO(恢复时间目标)。
部署Operator
直接使用以下Yaml清单即可以部署Operator。
# 安装Operator
kubectl apply -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.20/releases/cnpg-1.20.4.yaml
# 检查部署状态
kubectl get deploy -n cnpg-system cnpg-controller-manager
等待CNPG Operator安装完成。
部署PostgreSQL集群
一个最简单的CNPG集群范例如下:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cnpg-cluster
spec:
instances: 3 # 3个节点
primaryUpdateStrategy: unsupervised
storage:
size: 1Gi # 数据库储存容量配置
使用以下命令监视是否部署完毕。
watch kubectl get pod
部署完毕后,可以运行以下命令获取数据库管理账户密码
kubectl get secret cnpg-cluster-app -o=jsonpath='{.data.password}' | base64 -d
然后使用以下命令获取svc地址,应用程序可以使用地址连接数据库。
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cnpg-cluster-r ClusterIP 10.43.201.77 <none> 5432/TCP 17h
cnpg-cluster-ro ClusterIP 10.43.135.171 <none> 5432/TCP 17h
cnpg-cluster-rw ClusterIP 10.43.202.59 <none> 5432/TCP 17h
安装pgcli访问数据库
我的路由器也接入了K8s,所以可以直接访问svc。在开发机上面使用pip安装pgcli访问数据库。
pip install pgcli
pgcli -h 10.43.202.59 -p 5432 -U postgres
Password for postgres: # 密码填写刚刚获取的密码
Server: PostgreSQL 15.4 (Debian 15.4-2.pgdg110+1)
Version: 4.1.0
Home: http://pgcli.com
postgres>
将博客从SQLite迁移到PostgreSQL
先下载SQLite文件
# 获取原Typecho POD名
kubectl get pod
# 获取Typecho SQLite DB文件名
kubectl exec -it <pod-name> ls
# 拷贝DB文件
kubectl cp <pod-name>:/data/<db-name>.db typecho.db
先安装需要的包
pip install pandas sqlalchemy sqlite3 psycopg2
然后使用以下脚本转换SQLite文件到PostgreSQL
import pandas as pd
from sqlalchemy import create_engine
# 配置SQLite数据库连接
sqlite_db = 'sqlite:///path_to_your_sqlite_db.db'
sqlite_engine = create_engine(sqlite_db)
# 配置PostgreSQL数据库连接
postgres_db = 'postgresql://<postgresql>:<password>@host:5432/'
postgres_engine = create_engine(postgres_db)
# 定义要创建的新用户信息
new_username = "typecho"
new_password = "typecho-password"
target_database = "typecho"
create_user_query = text(f"CREATE USER {new_username} WITH PASSWORD :password")
grant_database_query = text(f"GRANT CONNECT ON DATABASE {target_database} TO {new_username};")
grant_schema_query = text(f"GRANT USAGE ON SCHEMA public TO {new_username};")
grant_all_tables_query = text(f"GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO {new_username};")
create_database_query = text(f"CREATE DATABASE {target_database};")
# 执行创建数据库的命令
with engine.connect() as conn:
conn.execute(create_database_query)
print(f"Database {target_database} created successfully.")
# 执行创建用户的命令
with engine.connect() as conn:
conn.execute(create_user_query, {"password": new_password})
print(f"User {new_username} created successfully.")
# 执行授予权限的命令
with engine.connect() as conn:
conn.execute(grant_database_query)
conn.execute(grant_schema_query)
conn.execute(grant_all_tables_query)
print(f"Permissions granted to user {new_username} on database {target_database} and schema public.")
postgres_db = 'postgresql://<postgresql>:<password>@host:5432/typecho'
postgres_engine = create_engine(postgres_db)
# 获取SQLite数据库中的所有表名
with sqlite_engine.connect() as conn:
table_names = conn.execute("SELECT name FROM sqlite_master WHERE type='table';").fetchall()
table_names = [name[0] for name in table_names]
# 迁移每个表的数据
for table_name in table_names:
# 从SQLite读取数据
df = pd.read_sql_table(table_name, sqlite_engine)
# 写入PostgreSQL
df.to_sql(table_name, postgres_engine, if_exists='replace', index=False)
print(f"Table {table_name} migrated successfully.")
print("All tables migrated successfully.")
转换完毕后,编辑typecho的yaml文件,更新deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: typecho-blog
spec:
replicas: 2
selector:
matchLabels:
app: typecho-blog
template:
metadata:
labels:
app: typecho-blog
spec:
containers:
- name: typecho-blog
image: joyqi/typecho:nightly-php7.4-apache
ports:
- containerPort: 80
env:
- name: PHP_TZ
value: "Asia/Shanghai"
- name: PHP_MAX_EXECUTION_TIME
value: "600"
volumeMounts:
- name: tmpfs
mountPath: /tmp
- name: config-volume
mountPath: /app/config.inc.php
subPath: typecho-config.php
volumes:
- name: tmpfs
emptyDir:
medium: Memory
- name: config-volume
configMap:
name: typecho-config
typecho-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: typecho-config
data:
typecho-config.php: |
<?php
// site root path
define('__TYPECHO_ROOT_DIR__', dirname(__FILE__));
// plugin directory (relative path)
define('__TYPECHO_PLUGIN_DIR__', '/usr/plugins');
// theme directory (relative path)
define('__TYPECHO_THEME_DIR__', '/usr/themes');
// admin directory (relative path)
define('__TYPECHO_ADMIN_DIR__', '/admin/');
// register autoload
require_once __TYPECHO_ROOT_DIR__ . '/var/Typecho/Common.php';
// init
\Typecho\Common::init();
// config db
$db = new \Typecho\Db('Pdo_Pgsql', 'typecho_');
$db->addServer(array (
'host' => '<你的rw数据库svc地址>',
'port' => 5432,
'user' => 'typecho',
'password' => 'typecho-password',
'charset' => 'utf8',
'database' => 'typecho',
'sslVerify' => false,
), \Typecho\Db::READ | \Typecho\Db::WRITE);
\Typecho\Db::set($db);
最后应用即可
kubectl apply -f typecho-config.yaml -f deployment.yaml