开发者
国产数据库 K8s 进阶:手把手带你用 Helm 部署 openGauss 单机与集群
国产数据库 K8s 进阶:手把手带你用 Helm 部署 openGauss 单机与集群
原创
发表于03/29
5410

在云原生时代,数据库容器化已经不再是“洪水猛兽”,而是变成了提效增能的“倚天剑”。openGauss 作为一款高性能、高安全、高可靠的企业级开源关系型数据库,其与 Kubernetes 的结合是国产基础软件落地的重要一环。

很多朋友在本地玩 openGauss 都是用 Docker 或二进制,但在生产环境中,我们需要的是可自愈、易扩容、免运维的云原生数据库。

今天,我们就来一场实战。我将带你从零开始,使用 Kubernetes 生态的包管理神器 —— Helm,分别部署 openGauss 单机版 和 openGauss 集群版(主备) 。

在本文中,你将学到:

  1. Helm 的 Chart 结构解析。
  2. ConfigMap 如何优雅管理数据库配置。
  3. Service (ClusterIP & NodePort) 如何暴露数据库服务。
  4. Ingress 虽然主要用于 HTTP,但我会教你如何“骚操作”透传 TCP(适用于管理界面)。
  5. 最后,使用 Python 编写脚本进行读写验证。

阅读前提:已安装 Kubernetes 集群 (1.19+)、Helm 3 客户端,并配置好 StorageClass。

第一部分:准备工作与 Helm Chart 结构设计

在动手敲命令之前,我们需要明白 Helm 是如何管理复杂应用的。一个标准的 openGauss Helm Chart 目录结构如下:

text

opengauss/
├── Chart.yaml          #  Chart 基本信息(名称、版本)
├── values.yaml         #  默认配置参数(副本数、密码、资源大小)
├── templates/          #  核心 K8s 资源模板目录
│   ├── _helpers.tpl    #  公共辅助函数
│   ├── configmap.yaml  #  配置文件(postgresql.conf, pg_hba.conf)
│   ├── statefulset.yaml#  有状态工作负载(数据库必备)
│   ├── svc.yaml        #  服务发现(ClusterIP, NodePort)
│   └── secret.yaml     #  存储密码

为什么选择 StatefulSet?

数据库是有状态的。StatefulSet 能为 Pod 提供稳定的网络标识(如 opengauss-0)和稳定的持久化存储,这是 Deployment 无法做到的。

第二部分:核心配置 —— ConfigMap 的妙用

在 openGauss 中,配置是灵魂。如果我们每次修改 postgresql.conf 都要重新打包镜像,那就太“传统”了。在 K8s 中,我们使用 ConfigMap 将配置文件剥离出来。

1. 配置 postgresql.conf

我们创建一个 ConfigMap 来存放主配置文件。这里特别注意 监听地址 和 连接数 的设置。

yaml

# templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "opengauss.fullname" . }}-config
data:
  postgresql.conf: |
    # 监听所有 IP,允许集群内其他 Pod 访问
    listen_addresses = '*'
    port = 5432
    max_connections = 1000
    # 内存配置 (根据 values.yaml 动态调整)
    shared_buffers = {{ .Values.resources.sharedBuffers }}
    log_directory = '/var/lib/opengauss/logs'
    # 集群模式必须开启的归档和复制参数
    archive_mode = 'on'
    archive_command = 'cp %p /var/lib/opengauss/archive/%f'
    wal_level = 'logical'
    max_wal_senders = 10
    max_replication_slots = 10
    # 密码加密规则
    password_encryption_type = 2  # 默认 SHA-256

2. 配置 pg_hba.conf (访问控制)

如果不配置这个,你的 Python 代码会直接 Connection Refused。我们需要允许所有 Pod 网段访问,但强烈建议在生产环境中不要使用 0.0.0.0/0,而是指定集群 CIDR。

yaml

# templates/configmap.yaml (同一ConfigMap内的另一个数据项)
  pg_hba.conf: |
    # TYPE  DATABASE        USER            ADDRESS                 METHOD
    # 本地连接信任
    local   all             all                                     trust
    # IPv4 本地连接
    host    all             all             127.0.0.1/32            trust
    # Kubernetes Pod 网段访问(关键!)
    host    all             all             0.0.0.0/0               sha256
    # 允许复制(主备同步)
    host    replication     replicator      0.0.0.0/0               sha256

3. 挂载 ConfigMap

在 statefulset.yaml 中,我们需要将 ConfigMap 里的文件挂载到容器内,覆盖默认配置。

yaml

# templates/statefulset.yaml (片段)
volumes:
  - name: opengauss-config
    configMap:
      name: {{ include "opengauss.fullname" . }}-config
containers:
  - name: opengauss
    image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
    volumeMounts:
      - name: opengauss-config
        mountPath: /opt/opengauss/data/postgresql.conf
        subPath: postgresql.conf
      - name: opengauss-config
        mountPath: /opt/opengauss/data/pg_hba.conf
        subPath: pg_hba.conf

第三部分:网络暴露 —— ClusterIP 与 NodePort

在 K8s 内部,我们使用 ClusterIP 让其他微服务访问;如果需要外部(如你的本地电脑)连接,则需要 NodePort

1. 内部服务 (ClusterIP)

这是默认的 Service,仅集群内部可见。

yaml

# templates/svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ include "opengauss.fullname" . }}-headless
spec:
  clusterIP: None  # Headless Service 用于 StatefulSet 网络标识
  ports:
    - name: tcp-sql
      port: 5432
      targetPort: 5432
  selector:
    app: {{ include "opengauss.name" . }}

---
apiVersion: v1
kind: Service
metadata:
  name: {{ include "opengauss.fullname" . }}-clusterip
spec:
  type: ClusterIP
  ports:
    - port: 5432
  selector:
    app: {{ include "opengauss.name" . }}

2. 外部访问 (NodePort)

如果你是在云上,通常会搭配云厂商的 LoadBalancer。但在本地或私有云,NodePort 是最简单的暴露方式。

yaml

# templates/svc-nodeport.yaml (可选,根据 values 开关)
{{- if .Values.service.nodePort.enabled }}
apiVersion: v1
kind: Service
metadata:
  name: {{ include "opengauss.fullname" . }}-nodeport
spec:
  type: NodePort
  ports:
    - port: 5432
      nodePort: {{ .Values.service.nodePort.port }}  # 范围 30000-32767
  selector:
    app: {{ include "opengauss.name" . }}
{{- end }}

第四部分:进阶暴露 —— Ingress 的 TCP 配置

通常 Ingress 处理 HTTP(S) 流量,但 nginx-ingress-controller 支持通过 ConfigMap 代理 TCP/UDP 流量。这对于想要通过域名(如 opengauss.internal.example.com)访问数据库非常有用。

实现步骤:

  1. 修改 nginx-ingress-controller 的 ConfigMap,开放新端口。
  2. 创建指向数据库 Service 的 Ingress 资源(虽然看起来很怪,但确实可行)。

注意:这需要集群管理员修改 Ingress Controller 的部署,如果只是普通用户,建议直接用 NodePort。

如果你是全栈管理员,可以这样配置 Ingress Controller 的 ConfigMap:

yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: tcp-services
  namespace: ingress-nginx
data:
  "5432": "default/opengauss-clusterip:5432"

第五部分:部署 openGauss 单机版

一切就绪,开始实战。假设你已经写好了上述的 Chart,或者使用社区 Chart。

1. 创建 values.yaml

定制我们的单机版参数。

yaml

# values-single.yaml
global:
  storageClass: "local-path"  # 你的存储类

image:
  repository: opengauss
  tag: 3.0.0
  pullPolicy: IfNotPresent

auth:
  username: "gaussdb"
  password: "OpenGauss@123"  # 必须符合复杂度要求
  database: "appdb"

replicaCount: 1  # 单机模式

resources:
  requests:
    memory: "2Gi"
    cpu: "1000m"
  sharedBuffers: "512MB"

service:
  type: ClusterIP
  nodePort:
    enabled: true
    port: 30432

2. 执行安装

bash

# 添加仓库(以社区或本地路径为例)
helm repo add opengauss https://opengauss-charts.obs.cn-east-3.myhuaweicloud.com

# 部署 release 名为 my-og
helm install my-og opengauss/opengauss -f values-single.yaml -n database --create-namespace

3. 验证

bash

kubectl get pods -n database
# 预期看到 my-og-opengauss-0  Running
kubectl logs -f my-og-opengauss-0 -n database

第六部分:部署 openGauss 集群版(主备)

集群版是 openGauss 的高可用形态,通常是一主一备或多备。Helm 可以通过脚本和启动探针自动配置主备关系。

1. 集群配置难点

  • Datanode 角色:需要区分谁是 Primary,谁是 Standby。
  • 同步流复制:主库提交事务时,必须等待备库接收日志。

2. 构建集群 StatefulSet

我们需要利用 StatefulSet 的序号特性:约定 -0 节点为主节点,其他为备节点。

在 statefulset.yaml 中添加 lifecycle 钩子和启动脚本:

yaml

# templates/statefulset.yaml 环境变量部分
env:
  - name: POD_IP
    valueFrom:
      fieldRef:
        fieldPath: status.podIP
  - name: POD_NAME
    valueFrom:
      fieldRef:
        fieldPath: metadata.name
  - name: REPLICATE_FROM
    value: "{{ .Values.primaryNode }}"  # 例如 my-og-opengauss-0
  - name: IS_PRIMARY
    value: "{{ if eq (list .Release.Name .Values.role) }}true{{ end }}"

3. 使用 Bitnami 风格 Chart

对于复杂的主备切换,建议使用成熟的 Helm Chart(如果存在)。我们可以通过 values-cluster.yaml 来配置:

yaml

# values-cluster.yaml
replicaCount: 2  # 1主1备

primary:
  persistence:
    size: 20Gi
  service:
    type: ClusterIP

secondary:
  replicaCount: 1
  persistence:
    size: 20Gi
  service:
    type: ClusterIP

syncReplication: true  # 开启强同步

部署命令

bash

helm install my-og-cluster opengauss/opengauss -f values-cluster.yaml -n database

查看状态

bash

kubectl exec -it my-og-cluster-opengauss-0 -n database -- gs_ctl query -D /var/lib/opengauss/data
# 输出结果应显示 cluster_state: Normal, role: Primary
kubectl exec -it my-og-cluster-opengauss-1 -n database -- gs_ctl query -D /var/lib/opengauss/data
# 输出结果应显示 role: Standby

第七部分:Python 连接验证(落地实战)

不管黑猫白猫,能连上的就是好数据库。我们将分别测试单机集群的连接。

1. 环境准备

你需要安装 psycopg2-binary,这是 Python 连接 PostgreSQL/ openGauss 最通用的驱动。

bash

pip install psycopg2-binary

2. Python 连接代码

我们写一个 test_og.py,实现连接 -> 建表 -> 插入 -> 查询的全流程。

python

import psycopg2
import sys

def test_connection(host, port, user, password, database, is_cluster=False):
    """
    测试 openGauss 连接
    """
    conn = None
    try:
        # 连接数据库
        # 注意:openGauss 默认兼容 PostgreSQL 协议
        conn = psycopg2.connect(
            host=host,
            port=port,
            user=user,
            password=password,
            database=database,
            connect_timeout=10
        )
        conn.autocommit = False
        cur = conn.cursor()
        
        # 1. 验证数据库版本
        cur.execute("SELECT version();")
        version = cur.fetchone()
        print(f"[✓] 连接成功! 数据库版本: {version[0].split(',')[0]}")
        
        # 2. 创建测试表 (验证写权限)
        print("[*] 正在创建测试表...")
        cur.execute("""
            CREATE TABLE IF NOT EXISTS k8s_test (
                id SERIAL PRIMARY KEY,
                message TEXT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            );
        """)
        conn.commit()
        print("[✓] 建表成功")
        
        # 3. 插入数据
        print("[*] 正在插入数据...")
        cur.execute("INSERT INTO k8s_test (message) VALUES (%s) RETURNING id;", ("Hello from K8s Helm",))
        inserted_id = cur.fetchone()[0]
        conn.commit()
        print(f"[✓] 插入成功,记录 ID: {inserted_id}")
        
        # 4. 查询数据
        print("[*] 正在查询数据...")
        cur.execute("SELECT id, message, created_at FROM k8s_test WHERE id = %s;", (inserted_id,))
        row = cur.fetchone()
        print(f"[✓] 查询结果: ID={row[0]}, Msg={row[1]}, Time={row[2]}")
        
        # 5. 如果是集群模式,额外检查节点信息(可选)
        if is_cluster:
            cur.execute("SELECT node_name, node_type FROM pgxc_node;")
            nodes = cur.fetchall()
            print(f"[✓] 集群节点信息: {nodes}")
        
        cur.close()
        return True
        
    except Exception as e:
        print(f"[✗] 发生错误: {e}")
        return False
    finally:
        if conn:
            conn.close()

if __name__ == "__main__":
    # ================= 单机测试 =================
    print("\n" + "="*30)
    print("测试 1: 连接单机 openGauss (NodePort)")
    print("="*30)
    
    # 这里填入你 K8s 集群任意节点的 IP,和 NodePort 端口 (30432)
    single_host = "192.168.1.100"  # 你的 K8s Node IP
    single_port = 30432
    
    test_connection(
        host=single_host,
        port=single_port,
        user="gaussdb",
        password="OpenGauss@123",
        database="appdb",
        is_cluster=False
    )
    
    # ================= 集群测试 =================
    print("\n" + "="*30)
    print("测试 2: 连接集群 openGauss (ClusterIP via Port Forward)")
    print("="*30)
    print("提示: 如果测试集群,请先运行以下命令进行端口转发:")
    print("kubectl port-forward -n database service/my-og-cluster-opengauss-clusterip 5432:5432")
    print("然后按 Enter 继续...")
    input()
    
    test_connection(
        host="127.0.0.1",
        port=5432,
        user="gaussdb",
        password="OpenGauss@123",
        database="appdb",
        is_cluster=True
    )

3. 运行测试

bash

python3 test_og.py

预期输出

text

==============================
测试 1: 连接单机 openGauss (NodePort)
==============================
[✓] 连接成功! 数据库版本: openGauss 3.0.0
[*] 正在创建测试表...
[✓] 建表成功
[*] 正在插入数据...
[✓] 插入成功,记录 ID: 1
[*] 正在查询数据...
[✓] 查询结果: ID=1, Msg=Hello from K8s Helm, Time=2024-05-20 10:00:00.123456

第八部分:故障排查与避坑指南

在部署过程中,你可能会遇到以下几个“大坑”,这里提供解决方案:

  1. 密码策略失败
    • 现象:Pod 不断重启,日志显示 password does not meet policy
    • 解决:openGauss 默认密码需要包含大写、小写、数字、特殊符号(如 @#$),长度至少 8 位。例如 MyPass@2024
  2. ConfigMap 更新后不生效
    • 现象:修改了 ConfigMap,但数据库参数没变。
    • 解决:ConfigMap 是通过 Volume 挂载的,Pod 内通常不会热加载。需要手动重启 Pod:kubectl rollout restart statefulset/my-og-opengauss -n database
  3. 权限不足 (Permission Denied)
    • 现象:日志显示无法写入 /var/lib/opengauss/data
    • 解决:openGauss 容器内默认使用 gaussdb 用户 (UID 1000)。确保你的 PV 或 HostPath 目录有写入权限,或者修改 SecurityContext。
  4. Python 连接报错 Authentication method 10 not supported
    • 现象psycopg2 报错。
    • 解决:你的 pg_hba.conf 中 method 使用了 sha256,但 psycopg2 版本过旧,或者客户端缺少 SASL 认证。解决办法是降级 pg_hba.conf 中的加密方式为 md5,或者升级客户端驱动,并在 openGauss 配置中允许 MD5(虽然不推荐,但内网测试可用)。
  5. 集群模式下备库无法连接主库
    • 现象:备库日志显示 could not connect to the primary server
    • 解决:检查 pg_hba.conf 是否允许 replication 连接。确保主备之间网络互通(Service Name 解析正确)。

总结

至此,我们已经完成了从零开始,利用 Helm 在 Kubernetes 上部署 openGauss 单机和集群的全过程。我们不仅使用了 StatefulSet 来管理数据库拓扑,还通过 ConfigMap 实现了配置与镜像的解耦,利用 NodePort 和 Ingress TCP 转发实现了外部访问,最后用 Python 脚本验证了数据的读写。

这种方案落地的意义在于:

  • 基础设施即代码:通过 Helm values 文件,DBA 可以将复杂的数据库配置参数 Git 化管理。
  • 快速交付:原本需要半天搭建的主备环境,现在只需 helm install 一分钟。
  • 统一运维:结合 Prometheus 和 Operator,未来的数据库自治能力将完全基于 K8s 构建。

收藏举报
Level 1
0
帖子
0
粉丝
0
获赞