杯子茶室

关注有趣的事物

Kubernetes学习日记03:在K8s里面部署PostgreSQL高可用集群

网络 0 评

在第一篇我把博客迁移到了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
Kubernetes学习日记04:使用Cloudflared将K8s内部服务暴露到公网
发表评论
撰写评论