摘要

金融信创场景下达梦是数据库首选,但传统物理机部署扩容慢、运维重,容器化又常踩单节点单点故障、集群组网依赖固定 IP、故障切换人工介入慢、性能跟不上生产要求等坑。本文基于一线金融项目落地经验,从零拆解达梦 DM9 在 K8s 环境的生产级高可用方案,覆盖 StatefulSet 持久化部署、基于 Headless Service 的集群自动发现、守护进程 + 监视器双层故障自愈、生产级性能调优、自动化备份体系,附全套可直接复制的 Dockerfile、YAML 配置与 15 个生产级避坑点、完整故障排查手册。

政务选金仓,金融选达梦;MySQL 迁移选金仓,Oracle 迁移选达梦。本文所有配置均在 K8s 1.26 集群 + 达梦 DM9 企业版实测验证,复制即用。


目录

一、架构选型:达梦容器化为什么必须走 StatefulSet + 主备架构 二、前置环境与生产级镜像制作 三、基础资源全量配置(命名空间 / ConfigMap/Secret) 四、单节点生产级部署:StatefulSet + 持久化存储 五、主备集群自动发现:零手动配置组网全实现 六、自动监视器部署与故障自愈全流程 七、生产级性能调优(K8s 层 + 数据库层) 八、自动化备份方案:CronJob + dmrman 全量 + 增量备份 九、生产级避坑 15 条(踩过的坑直接帮你避) 十、常见故障排查手册 十一、常用运维命令与状态验证 总结


一、架构选型:达梦容器化为什么必须走 StatefulSet + 主备架构

📌 核心结论:达梦属于强有状态数据库,生产级容器化必须采用「StatefulSet + Headless Service + 本地块存储 + 原生主备集群」架构,绝对禁止用 Deployment 单节点裸跑。

很多团队初次容器化达梦时图省事用 Deployment,最终都会遇到数据丢失、IP 漂移后主备断连、节点重启后集群脑裂等问题,本质是选错了工作负载类型。

1.1 Deployment vs StatefulSet 数据库场景对比

能力维度 Deployment StatefulSet 达梦为什么必须选后者
Pod 身份 随机后缀,重启即变 固定序号(dmdb-0、dmdb-1),重建身份不变 主备角色绑定、配置文件固定依赖节点身份
网络标识 无固定内网地址 配合 Headless Service 生成永久 DNS 域名 主备同步、集群组网需要稳定的节点地址
存储绑定 多副本共享卷,无独立绑定 每个 Pod 对应独立 PVC,重建自动挂载原卷 主备数据独立,故障恢复不能错乱数据卷
部署顺序 并发创建销毁 有序部署(0→1)、逆序销毁 必须先启主库再启备库,避免集群状态异常

1.2 达梦高可用方案选型建议

方案模式 实现方式 适用场景 推荐指数
单节点持久化 单 Pod + PVC 开发测试、非核心边缘系统 ⭐⭐⭐
一主一备 Data Guard 双 Pod + 原生守护进程 + 手动切换 中小规模生产、非核心交易系统 ⭐⭐⭐⭐
一主一备 + 自动监视器 双 Pod + dmwatcher + dmmonitor 自动切换 金融核心业务、高可用要求高的场景 ⭐⭐⭐⭐⭐
DSC 共享存储集群 多实例 + 共享存储 超高并发、Oracle RAC 迁移场景 ⭐⭐⭐(K8s 落地复杂度高)

💡 落地建议:90% 以上金融信创项目优先选择「一主一备 + 自动监视器」方案,基于达梦原生 Data Guard 能力,成熟稳定、排障简单、改造成本可控;DSC 方案对共享存储性能要求极高,K8s 环境落地复杂度高,非必要不优先选用。

1.3 整体高可用架构总览

业务应用(配置连接服务名自动重连)
          ↓
   dmdb-svc(ClusterIP)
          ↓
┌─────────────┐    流复制同步    ┌─────────────┐
│   dmdb-0    │ ───────────────→ │   dmdb-1    │
│  主库实例   │                  │  备库实例   │
│ dmwatcher   │                  │ dmwatcher   │
└─────────────┘                  └─────────────┘
       ↓                                ↓
  pvc-dmdb-0                       pvc-dmdb-1
  (独立数据卷)                    (独立数据卷)
          ↘                    ↙
         dmdb-monitor(独立监视器Pod,自动裁决切换)

1.4 为什么现阶段优先原生方案而非 Operator

很多团队一开始就想自研或引入 Operator,但实际落地中,达梦官方 Operator 成熟度有限,自研 Operator 运维成本高、排障难度大。对于绝大多数项目,原生方案具备三个核心优势:

  • 排障简单:所有问题都可以用达梦原生运维思路排查,没有额外黑盒
  • 稳定性高:依赖的都是 K8s 和达梦最成熟的核心能力
  • 改造成本低:从物理机迁移到容器,运维逻辑基本不变,团队学习成本低

当数据库实例规模超过 30 套、需要统一管控平台时,再逐步演进到 Operator 方案更合理。


二、前置环境与生产级镜像制作

2.1 集群基础要求

  • K8s 版本:1.24+(本文基于 1.26 实测)
  • 容器运行时:containerd 1.6+ / Docker 20.10+
  • 存储:至少 1 套生产级块存储 StorageClass,支持 ReadWriteOnce
  • 节点:至少 2 个工作节点,单节点预留 4C8G 以上资源
  • 网络:节点间网络延迟 < 3ms,保证主备同步效率

2.2 生产级镜像制作(附完整 Dockerfile)

采用多阶段构建,减小镜像体积,剔除安装包、调试工具等冗余文件,同时保留所有运维工具。

# 构建阶段:静默安装达梦数据库
FROM centos:7 AS builder
WORKDIR /opt/dm_install
# 拷贝达梦安装包(需提前放入构建目录,替换为实际安装包名)
COPY dm9_enterprise_xxx.iso /opt/dm_install/
# 挂载镜像并执行静默安装
RUN mkdir -p /mnt/dmiso && mount -o loop /opt/dm_install/dm9_enterprise_xxx.iso /mnt/dmiso && \
    echo "
KEY 0
INSTALL_TYPE 1
LANG 0
INSTALL_PATH /opt/dmdbms
" > /tmp/install_rsp && \
    /mnt/dmiso/DMInstall.bin -q /tmp/install_rsp && \
    umount /mnt/dmiso && rm -rf /opt/dm_install /tmp/install_rsp

# 运行阶段:最小化运行镜像
FROM centos:7
LABEL maintainer="信创运维团队"
# 安装系统依赖
RUN yum install -y glibc libgcc libstdc++ numactl-libs net-tools && \
    yum clean all && rm -rf /var/cache/yum
# 创建达梦运行用户与用户组(UID/GID 固定为5236,与官方一致)
RUN groupadd -g 5236 dinstall && \
    useradd -u 5236 -g dinstall -m -d /home/dmdba -s /bin/bash dmdba
# 从构建阶段拷贝完整达梦安装目录
COPY --from=builder /opt/dmdbms /opt/dmdbms
# 配置环境变量
ENV DM_HOME=/opt/dmdbms \
    PATH=$PATH:/opt/dmdbms/bin \
    LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/dmdbms/bin \
    TZ=Asia/Shanghai
# 创建数据、日志、归档、备份目录
RUN mkdir -p /dm/data /dm/log /dm/arch /dm/backup /dm/template && \
    chown -R dmdba:dinstall /dm /opt/dmdbms
# 切换为非root用户运行
USER dmdba
WORKDIR /opt/dmdbms/bin
# 拷贝启动脚本与健康检查脚本
COPY --chown=dmdba:dinstall entrypoint.sh /opt/dmdbms/bin/entrypoint.sh
COPY --chown=dmdba:dinstall health_check.sh /opt/dmdbms/bin/health_check.sh
RUN chmod +x /opt/dmdbms/bin/entrypoint.sh /opt/dmdbms/bin/health_check.sh

EXPOSE 5236 5536
ENTRYPOINT ["/opt/dmdbms/bin/entrypoint.sh"]

💡 镜像优化要点:

  1. 多阶段构建,镜像体积从 2G + 压缩到 800M 左右
  2. 全程使用非 root 用户运行,符合生产安全规范
  3. 统一时区为东八区,避免业务时间错乱
  4. 保留 disql、dmrman、dmwatcher 等所有运维工具,不裁剪功能

2.3 授权文件适配说明

⚠️ 生产环境授权必须提前适配容器场景,三类授权适配方式不同:

  • 硬件绑定型授权:绑定 MAC/CPU 的授权无法直接在容器漂移场景使用,需固定 Pod 调度节点 + hostNetwork 模式部署
  • 软授权 / 容器适配授权:向达梦厂商申请容器场景专用授权,通过 Secret 挂载,支持 Pod 跨节点漂移
  • 试用授权:严禁用于生产,到期后数据库会自动关停,造成业务中断

三、基础资源全量配置(可直接复制)

3.1 命名空间创建

# dmdb-base.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: dmdb
  labels:
    app: dmdb

3.2 核心配置 ConfigMap

存放数据库参数、守护进程、监视器模板配置,避免硬编码进镜像。

# dmdb-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: dmdb-config
  namespace: dmdb
data:
  dm.ini.template: |
    [INSTANCE]
    INSTANCE_NAME = {INST_NAME}
    PORT_NUM = 5236
    [MEMORY]
    MEMORY_TARGET = 2048
    BUFFER = 1024
    MAX_BUFFER = 2048
    [LOG]
    LOG_PATH = /dm/log
    LOG_FILE_SIZE = 256
    LOG_SPACE_LIMIT = 4096
    [ARCHIVE]
    ARCH_INI = 1
    ARCH_DEST = /dm/arch
    ARCH_FILE_SIZE = 256
    ARCH_SPACE_LIMIT = 10240
    [CHECKPOINT]
    CHECKPOINT_INTERVAL = 300
    CHECKPOINT_COMPLETED_TARGET = 0.9
  dmwatcher.ini.template: |
    [WATCHER]
    WATCHER_TYPE = GLOBAL
    WATCHER_INTERVAL = 3
    SWITCH_INTERVAL = 10
    INST_ERROR_TIME = 10
    RECOVER_TIME = 60
    AUTO_RECOVER = 1
    [INST0]
    INST_NAME = dmdb0
    INST_HOST = {MASTER_HOST}
    INST_PORT = 5236
    WATCHER_PORT = 5536
    [INST1]
    INST_NAME = dmdb1
    INST_HOST = {SLAVE_HOST}
    INST_PORT = 5236
    WATCHER_PORT = 5536
  dmmonitor.ini.template: |
    [MONITOR]
    MONITOR_TYPE = GLOBAL
    MONITOR_INTERVAL = 2
    MONITOR_LOG_PATH = /dm/log
    [DG1]
    WATCHER_NUM = 2
    WATCHER_HOST0 = {MASTER_HOST}
    WATCHER_PORT0 = 5536
    WATCHER_HOST1 = {SLAVE_HOST}
    WATCHER_PORT1 = 5536

3.3 敏感信息 Secret

密码、授权文件统一通过 Secret 管理,禁止明文写在配置中。

# dmdb-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: dmdb-secret
  namespace: dmdb
type: Opaque
data:
  sysdba_password: U1lTREJBMTIzNDU2  # base64编码后的SYSDBA密码,替换为实际值
---
apiVersion: v1
kind: Secret
metadata:
  name: dmdb-license
  namespace: dmdb
type: Opaque
data:
  dm.lic: 你的授权文件base64编码内容

3.4 RBAC 权限配置(角色标签同步用)

用于 Sidecar 自动更新 Pod 角色标签,实现流量自动切换。

# dmdb-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: dmdb-role-sync
  namespace: dmdb
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: dmdb-role-sync
  namespace: dmdb
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: dmdb-role-sync
  namespace: dmdb
subjects:
- kind: ServiceAccount
  name: dmdb-role-sync
  namespace: dmdb
roleRef:
  kind: Role
  name: dmdb-role-sync
  apiGroup: rbac.authorization.k8s.io

四、单节点生产级部署:StatefulSet + 持久化存储

先跑通单节点持久化部署,验证存储、网络、镜像无问题,再演进到主备集群,降低调试成本。

4.1 Service 配置

同时配置 Headless Service(集群内部发现用)和业务访问 Service。

# dmdb-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: dmdb-headless
  namespace: dmdb
  labels:
    app: dmdb
spec:
  type: ClusterIP
  clusterIP: None  # Headless Service核心配置
  selector:
    app: dmdb
  ports:
    - port: 5236
      name: db
    - port: 5536
      name: watcher
---
# 业务统一访问入口,自动绑定主库角色
apiVersion: v1
kind: Service
metadata:
  name: dmdb-svc
  namespace: dmdb
  labels:
    app: dmdb
spec:
  type: ClusterIP
  selector:
    app: dmdb
    role: master  # 绑定主库角色标签,切换后自动漂移
  ports:
    - port: 5236
      targetPort: 5236
      name: db

4.2 StatefulSet 完整部署配置

# dmdb-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: dmdb
  namespace: dmdb
spec:
  serviceName: "dmdb-headless"
  replicas: 1
  selector:
    matchLabels:
      app: dmdb
  template:
    metadata:
      labels:
        app: dmdb
        role: master
    spec:
      serviceAccountName: dmdb-role-sync
      terminationGracePeriodSeconds: 180  # 数据库优雅关停时长
      # 安全上下文:非root运行,自动修正PVC权限
      securityContext:
        runAsUser: 5236
        runAsGroup: 5236
        fsGroup: 5236
        runAsNonRoot: true
      # 调度策略:主备强制分散节点,优先SSD节点
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - dmdb
            topologyKey: kubernetes.io/hostname
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            preference:
              matchExpressions:
              - key: disk-type
                operator: In
                values:
                - ssd
      initContainers:
        # 初始化:目录权限校验与配置生成
        - name: init-config
          image: your-registry/dm9:v9.1.2.100
          command: ["/bin/sh", "-c"]
          args:
            - |
              POD_ORDINAL=$(hostname | awk -F'-' '{print $NF}')
              INST_NAME="dmdb${POD_ORDINAL}"
              
              # 生成实例专属dm.ini
              sed -e "s/{INST_NAME}/$INST_NAME/g" /dm/template/dm.ini.template > /dm/data/dm.ini
              
              # 初始化实例(仅首次启动执行)
              if [ ! -f /dm/data/SYSTEM.DBF ]; then
                  dminit PATH=/dm/data DB_NAME=dmdb INSTANCE_NAME=$INST_NAME SYSDBA_PWD=$DM_SYSDBA_PWD
              fi
          env:
            - name: DM_SYSDBA_PWD
              valueFrom:
                secretKeyRef:
                  name: dmdb-secret
                  key: sysdba_password
          volumeMounts:
            - name: dmdb-data
              mountPath: /dm/data
            - name: dmdb-config
              mountPath: /dm/template
      containers:
        - name: dmdb
          image: your-registry/dm9:v9.1.2.100
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 5236
              name: db
          env:
            - name: DM_SYSDBA_PWD
              valueFrom:
                secretKeyRef:
                  name: dmdb-secret
                  key: sysdba_password
          volumeMounts:
            - name: dmdb-data
              mountPath: /dm/data
            - name: dmdb-log
              mountPath: /dm/log
            - name: dmdb-arch
              mountPath: /dm/arch
            - name: license
              mountPath: /opt/dmdbms/bin/dm.lic
              subPath: dm.lic
            - name: shm
              mountPath: /dev/shm
          resources:
            requests:
              cpu: "2"
              memory: "4Gi"
            limits:
              cpu: "8"
              memory: "16Gi"
          # 优雅关停钩子:正常关闭数据库
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "dm_ctl stop -D /dm/data -m immediate"]
          # 存活检测
          livenessProbe:
            exec:
              command: ["sh", "-c", "disql SYSDBA/$DM_SYSDBA_PWD@localhost:5236 -c 'select 1;' || exit 1"]
            initialDelaySeconds: 90
            periodSeconds: 15
            timeoutSeconds: 5
            failureThreshold: 3
          # 就绪检测
          readinessProbe:
            exec:
              command: ["sh", "-c", "disql SYSDBA/$DM_SYSDBA_PWD@localhost:5236 -c 'select status$ from v\\$instance;' || exit 1"]
            initialDelaySeconds: 30
            periodSeconds: 10
            timeoutSeconds: 3
      volumes:
        - name: dmdb-config
          configMap:
            name: dmdb-config
        - name: license
          secret:
            secretName: dmdb-license
        - name: shm
          emptyDir:
            medium: Memory
            sizeLimit: 2Gi
  # 持久化卷模板,每个Pod自动创建独立PVC
  volumeClaimTemplates:
    - metadata:
        name: dmdb-data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: "local-ssd"  # 替换为你的存储类
        resources:
          requests:
            storage: 100Gi
    - metadata:
        name: dmdb-log
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: "local-ssd"
        resources:
          requests:
            storage: 50Gi
    - metadata:
        name: dmdb-arch
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: "local-ssd"
        resources:
          requests:
            storage: 100Gi

4.3 部署与基础验证

# 依次部署基础资源
kubectl apply -f dmdb-base.yaml
kubectl apply -f dmdb-config.yaml
kubectl apply -f dmdb-secret.yaml
kubectl apply -f dmdb-rbac.yaml
kubectl apply -f dmdb-service.yaml
kubectl apply -f dmdb-statefulset.yaml

# 验证Pod与PVC状态
kubectl get pods -n dmdb -o wide
kubectl get pvc -n dmdb

✅ 验证标准:Pod 状态 Running,PVC 全部 Bound,能通过 dmdb-svc 正常连接数据库。


五、主备集群自动发现:零手动配置组网全实现

💡 核心思路:全程不写死任何 IP,完全依赖 StatefulSet 的固定 DNS 域名实现节点自动发现,Pod 重建后自动重连集群,无需人工修改配置。

5.1 自动发现原理

StatefulSet + Headless Service 会为每个 Pod 生成永久固定的 DNS 域名,格式为:

pod名称.service名称.namespace.svc.cluster.local

对应部署就是:

  • 0 号节点:dmdb-0.dmdb-headless.dmdb.svc.cluster.local
  • 1 号节点:dmdb-1.dmdb-headless.dmdb.svc.cluster.local

无论 Pod 如何重建、IP 如何漂移,域名永远不变。启动时通过 Pod 序号自动判断角色,拼接对端域名写入配置文件,实现零手动组网。

5.2 主备自动初始化逻辑

升级 initContainer 脚本,自动判断角色并搭建主备集群:

  1. 读取 Pod 主机名,提取序号(0/1)
  2. 0 号节点:初始化为主库实例,启动数据库
  3. 1 号节点:循环等待主库就绪,通过 dmrman 热备份搭建备库
  4. 自动替换守护进程配置模板中的主备域名,生成最终配置

将 StatefulSet 的 initContainer 脚本替换为以下内容:

POD_ORDINAL=$(hostname | awk -F'-' '{print $NF}')
MASTER_HOST="dmdb-0.dmdb-headless.dmdb.svc.cluster.local"
SLAVE_HOST="dmdb-1.dmdb-headless.dmdb.svc.cluster.local"
INST_NAME="dmdb${POD_ORDINAL}"

# 生成实例专属dm.ini
sed -e "s/{INST_NAME}/$INST_NAME/g" /dm/template/dm.ini.template > /dm/data/dm.ini

if [ "$POD_ORDINAL" = "0" ]; then
    # 0号节点:主库初始化
    if [ ! -f /dm/data/SYSTEM.DBF ]; then
        dminit PATH=/dm/data DB_NAME=dmdb INSTANCE_NAME=$INST_NAME SYSDBA_PWD=$DM_SYSDBA_PWD
        # 开启归档模式
        dm_ctl start -D /dm/data -m mount
        disql SYSDBA/$DM_SYSDBA_PWD@localhost:5236 -c "alter database archivelog;"
        dm_ctl stop -D /dm/data
    fi
    echo "role=master" > /dm/data/role.conf
else
    # 1号节点:备库初始化,等待主库就绪后备份还原
    until disql SYSDBA/$DM_SYSDBA_PWD@$MASTER_HOST:5236 -c "select 1;" > /dev/null 2>&1; do
        echo "等待主库就绪..."
        sleep 5
    done
    
    if [ ! -f /dm/data/SYSTEM.DBF ]; then
        # 主库热备份
        dmrman CTLSTMT="BACKUP DATABASE 'SYSDBA/$DM_SYSDBA_PWD@$MASTER_HOST:5236' FULL BACKUPSET '/dm/arch/master_full_bak';"
        # 还原备库
        dmrman CTLSTMT="RESTORE DATABASE '/dm/data/dm.ini' FROM BACKUPSET '/dm/arch/master_full_bak';"
        dmrman CTLSTMT="RECOVER DATABASE '/dm/data/dm.ini' FROM BACKUPSET '/dm/arch/master_full_bak';"
        dmrman CTLSTMT="RECOVER DATABASE '/dm/data/dm.ini' UPDATE DB_MAGIC;"
    fi
    echo "role=slave" > /dm/data/role.conf
fi

# 自动生成守护进程配置
sed -e "s/{MASTER_HOST}/$MASTER_HOST/g" -e "s/{SLAVE_HOST}/$SLAVE_HOST/g" \
    /dm/template/dmwatcher.ini.template > /dm/data/dmwatcher.ini

5.3 新增守护进程 Sidecar

在 StatefulSet 的 containers 中追加守护进程容器,独立运行 dmwatcher:

- name: dmwatcher
  image: your-registry/dm9:v9.1.2.100
  command: ["/bin/sh", "-c"]
  args:
    - |
      # 等待数据库就绪后启动守护进程
      until disql SYSDBA/$DM_SYSDBA_PWD@localhost:5236 -c "select 1;" > /dev/null 2>&1; do
          sleep 5
      done
      dmwatcher /dm/data/dmwatcher.ini
  env:
    - name: DM_SYSDBA_PWD
      valueFrom:
        secretKeyRef:
          name: dmdb-secret
          key: sysdba_password
  volumeMounts:
    - name: dmdb-data
      mountPath: /dm/data
    - name: dmdb-log
      mountPath: /dm/log
  resources:
    requests:
      cpu: "0.5"
      memory: "512Mi"
    limits:
      cpu: "2"
      memory: "2Gi"

5.4 集群部署与验证

  1. 将 StatefulSet 副本数调整为 replicas: 2
  2. 重新应用配置:kubectl apply -f dmdb-statefulset.yaml
  3. 验证主备同步状态
# 进入主库查看主备同步状态
kubectl exec -it dmdb-0 -n dmdb -- disql SYSDBA/SYSDBA123456 -c "select * from v\\$dataguard_status;"

✅ 正常结果:备库状态为 VALID,同步延迟为 0,无异常告警。物理备库默认处于 MOUNT 状态,开启实时查询功能后可转为 OPEN 只读状态。


六、自动监视器部署与故障自愈全流程

生产级高可用的核心是故障自愈,本方案实现两层自愈机制,覆盖进程故障、节点故障、主库宕机全场景。

6.1 自动监视器独立部署

监视器作为集群裁决节点,必须独立部署,不能和数据库实例同节点,避免单点故障。

# dmdb-monitor.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dmdb-monitor
  namespace: dmdb
spec:
  replicas: 1
  selector:
    matchLabels:
      app: dmdb-monitor
  template:
    metadata:
      labels:
        app: dmdb-monitor
    spec:
      securityContext:
        runAsUser: 5236
        runAsGroup: 5236
        runAsNonRoot: true
      # 尽量调度到非数据库节点
      nodeSelector:
        node-role: database-backup
      containers:
        - name: dmmonitor
          image: your-registry/dm9:v9.1.2.100
          command: ["/bin/sh", "-c"]
          args:
            - |
              # 生成监视器配置
              MASTER_HOST="dmdb-0.dmdb-headless.dmdb.svc.cluster.local"
              SLAVE_HOST="dmdb-1.dmdb-headless.dmdb.svc.cluster.local"
              sed -e "s/{MASTER_HOST}/$MASTER_HOST/g" -e "s/{SLAVE_HOST}/$SLAVE_HOST/g" \
                  /dm/template/dmmonitor.ini.template > /dm/log/dmmonitor.ini
              # 启动监视器
              dmmonitor /dm/log/dmmonitor.ini
          volumeMounts:
            - name: monitor-config
              mountPath: /dm/template
            - name: monitor-log
              mountPath: /dm/log
          resources:
            requests:
              cpu: "0.5"
              memory: "512Mi"
            limits:
              cpu: "1"
              memory: "1Gi"
      volumes:
        - name: monitor-config
          configMap:
            name: dmdb-config
        - name: monitor-log
          emptyDir: {}

6.2 流量自动切换:角色标签同步 Sidecar

在每个数据库 Pod 中加入轻量 Sidecar,定期检测本地数据库的主备角色,自动更新 Pod 的role标签,实现 Service 流量自动切换,无需人工干预。

在 StatefulSet 的 containers 中追加:

- name: role-sync-sidecar
  image: alpine:3.18
  command: ["/bin/sh", "-c"]
  args:
    - |
      # 安装kubectl
      apk add --no-cache curl
      curl -LO "https://dl.k8s.io/release/v1.26.0/bin/linux/amd64/kubectl"
      chmod +x kubectl && mv kubectl /usr/local/bin/
      
      # 循环检测角色并同步标签
      while true; do
        # 查询数据库角色:0=主库,1=备库
        ROLE=$(disql SYSDBA/$DM_SYSDBA_PWD@localhost:5236 -c "select mode$ from v\\$instance;" | grep -E "^[01]" | tr -d ' ')
        if [ "$ROLE" = "0" ]; then
          ROLE_LABEL="master"
        else
          ROLE_LABEL="slave"
        fi
        
        # 更新Pod标签
        kubectl label pod $HOSTNAME role=$ROLE_LABEL --overwrite -n $POD_NAMESPACE
        sleep 10
      done
  env:
    - name: DM_SYSDBA_PWD
      valueFrom:
        secretKeyRef:
          name: dmdb-secret
          key: sysdba_password
    - name: POD_NAMESPACE
      valueFrom:
        fieldRef:
          fieldPath: metadata.namespace
  resources:
    requests:
      cpu: "0.1"
      memory: "128Mi"
    limits:
      cpu: "0.5"
      memory: "256Mi"

💡 备选方案:业务端配置达梦连接服务名,内置主备两个地址,客户端自动识别当前主库,切换时自动重连,无需依赖 K8s 标签。两种方案可叠加使用,双重保障流量不中断。

6.3 完整故障自愈时序

主库节点宕机
    ↓
K8s健康检测失败 + 守护进程检测异常
    ↓
监视器确认故障,达到故障阈值
    ↓
触发主备切换:备库升为主库
    ↓
角色Sidecar更新Pod标签,Service流量自动切到新主库
    ↓
业务连接自动重连(秒级闪断)
    ↓
K8s重建故障节点Pod,挂载原数据卷
    ↓
新Pod自动同步数据,作为新备库重新加入集群
    ↓
集群恢复一主一备状态

6.4 第一层自愈:K8s 原生能力

  • 进程崩溃:健康检测失败后,K8s 自动重启容器,数据保留,重启后自动恢复角色
  • 节点故障:StatefulSet 自动将故障节点上的 Pod 调度到健康节点重建,挂载原有 PVC 数据卷
  • 自愈效果:备库故障完全不影响业务;主库故障时触发数据库层面切换,K8s 负责重建故障实例

七、生产级性能调优(K8s 层 + 数据库层)

容器化数据库性能损耗是很多团队的顾虑,做好以下调优,容器化性能可接近物理机 95% 以上。

7.1 K8s 层面性能调优

1. CPU 绑定与独占

开启 Kubelet 的 CPU 管理器 static 策略,为数据库 Pod 分配独占 CPU 核心,避免上下文切换损耗。

  • 节点开启 CPU 管理器:kubelet 参数 --cpu-manager-policy=static
  • Pod 配置 Guaranteed QoS 等级:requests 与 limits 的 CPU、内存完全一致,自动进入 static 分配池
resources:
  requests:
    cpu: "8"
    memory: "16Gi"
  limits:
    cpu: "8"
    memory: "16Gi"
2. 大页内存配置

达梦数据库大量使用共享内存,开启大页可显著降低 TLB 缺失,提升内存访问性能。

  • 节点提前配置大页,建议大页总容量为数据库共享内存的 1.2 倍
  • Pod 中挂载大页,禁用透明大页(THP),避免 IO 抖动
# volumeMounts 中追加
- name: hugepage
  mountPath: /dev/hugepages

# volumes 中追加
- name: hugepage
  emptyDir:
    medium: HugePages

# limits 中追加大页限额
limits:
  hugepages-2Mi: 2Gi
  memory: 16Gi
3. IO 性能优化
  • 存储类使用块存储,禁用 atime,减少元数据写入
  • 本地 SSD 存储优先使用 Local PV,性能远超分布式存储
  • 避免使用 overlay2 存储层存放数据,所有数据必须走 PVC 挂载
4. 网络优化
  • 主备节点部署在同一机架,降低网络延迟
  • 开启 SR-IOV 网络虚拟化,降低网络 IO 损耗
  • 数据库流量走独立网卡,与业务流量隔离

7.2 达梦数据库层面调优

针对容器化场景调整核心参数,最大化利用容器资源。

参数 推荐值 说明
MEMORY_TARGET 容器内存上限的 50%~60% 总内存池大小,预留足够内存给系统和连接进程
BUFFER 容器内存上限的 30%~40% 数据缓冲区,OLTP 场景可适当调大
MAX_CONNECTIONS 业务峰值 * 1.2 连接数不宜过大,避免内存溢出
CHECKPOINT_INTERVAL 300 检查点间隔,延长可减少磁盘 IO,需平衡恢复时间
LOG_FILE_SIZE 512M 重做日志文件大小,OLTP 场景建议 256M~1G
UNDO_RETENTION 300 回滚段保留时间,避免大查询快照过旧

💡 调优原则:容器内存必须预留至少 30% 给系统进程、连接进程、文件系统缓存,不能把所有内存都分给数据库,否则容易触发 OOM Kill。


八、自动化备份方案:CronJob + dmrman 全量 + 增量备份

容器化不等于不需要备份,生产环境必须建立完整的备份体系,这里实现「每周全量 + 每日增量」的自动备份策略,备份文件存入独立持久化存储,支持定期清理。

8.1 备份策略设计

  • 全量备份:每周日凌晨 2 点执行,保留 30 天
  • 增量备份:周一至周六凌晨 2 点执行,保留 7 天
  • 备份验证:每次备份完成后自动校验备份集有效性
  • 备份存储:独立 PVC 存储,与数据库数据盘分离

8.2 完整备份脚本与 CronJob 配置

# dmdb-backup.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: dmdb-backup-pvc
  namespace: dmdb
spec:
  accessModes: ["ReadWriteOnce"]
  storageClassName: "local-ssd"
  resources:
    requests:
      storage: 500Gi
---
# 全量备份CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
  name: dmdb-full-backup
  namespace: dmdb
spec:
  schedule: "0 2 * * 0"  # 每周日凌晨2点
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          securityContext:
            runAsUser: 5236
            runAsGroup: 5236
          containers:
            - name: backup
              image: your-registry/dm9:v9.1.2.100
              command: ["/bin/sh", "-c"]
              args:
                - |
                  BACKUP_DATE=$(date +%Y%m%d)
                  BACKUP_PATH=/dm/backup/full_$BACKUP_DATE
                  # 执行压缩全量备份
                  dmrman CTLSTMT="BACKUP DATABASE 'SYSDBA/$DM_SYSDBA_PWD@dmdb-svc:5236' FULL BACKUPSET '$BACKUP_PATH' COMPRESSED;"
                  # 校验备份集有效性
                  dmrman CTLSTMT="CHECK BACKUPSET '$BACKUP_PATH';"
                  # 清理30天前的全量备份
                  find /dm/backup -name "full_*" -type d -mtime +30 -exec rm -rf {} \;
              env:
                - name: DM_SYSDBA_PWD
                  valueFrom:
                    secretKeyRef:
                      name: dmdb-secret
                      key: sysdba_password
              volumeMounts:
                - name: backup-storage
                  mountPath: /dm/backup
              resources:
                requests:
                  cpu: "2"
                  memory: "4Gi"
          volumes:
            - name: backup-storage
              persistentVolumeClaim:
                claimName: dmdb-backup-pvc
---
# 增量备份CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
  name: dmdb-incr-backup
  namespace: dmdb
spec:
  schedule: "0 2 * * 1-6"  # 周一至周六凌晨2点
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          securityContext:
            runAsUser: 5236
            runAsGroup: 5236
          containers:
            - name: backup
              image: your-registry/dm9:v9.1.2.100
              command: ["/bin/sh", "-c"]
              args:
                - |
                  BACKUP_DATE=$(date +%Y%m%d)
                  BACKUP_PATH=/dm/backup/incr_$BACKUP_DATE
                  # 执行压缩增量备份
                  dmrman CTLSTMT="BACKUP DATABASE 'SYSDBA/$DM_SYSDBA_PWD@dmdb-svc:5236' INCREMENT BACKUPSET '$BACKUP_PATH' COMPRESSED;"
                  # 校验备份集有效性
                  dmrman CTLSTMT="CHECK BACKUPSET '$BACKUP_PATH';"
                  # 清理7天前的增量备份
                  find /dm/backup -name "incr_*" -type d -mtime +7 -exec rm -rf {} \;
              env:
                - name: DM_SYSDBA_PWD
                  valueFrom:
                    secretKeyRef:
                      name: dmdb-secret
                      key: sysdba_password
              volumeMounts:
                - name: backup-storage
                  mountPath: /dm/backup
              resources:
                requests:
                  cpu: "1"
                  memory: "2Gi"
          volumes:
            - name: backup-storage
              persistentVolumeClaim:
                claimName: dmdb-backup-pvc

💡 进阶建议:核心业务系统建议额外将备份文件同步到对象存储或异地存储,应对机房级故障。


九、生产级避坑 15 条(踩过的坑直接帮你避)

⚠️ 坑 1:共享内存不足导致实例启动失败 达梦 MEMORY_TARGET 依赖系统共享内存,容器默认 /dev/shm 只有 64M,必须通过 emptyDir 挂载扩容,否则启动直接报共享内存不足错误。

⚠️ 坑 2:硬件授权无法在容器使用 绑定宿主机 MAC/CPU 的授权文件,容器重建后硬件信息变化会导致授权失效。生产环境必须申请容器适配版授权,或固定节点 + hostNetwork 部署。

⚠️ 坑 3:优雅关停时间不足,数据文件损坏 达梦大事务回滚、检查点刷盘耗时较长,默认 30 秒关停时间会被强制 kill,导致数据文件损坏。必须设置terminationGracePeriodSeconds ≥ 180,并配置 preStop 执行正常关闭命令。

⚠️ 坑 4:数据目录权限不匹配 容器内运行达梦的用户 UID 与 PVC 挂载目录权限不匹配,会导致实例无法读写数据。通过 securityContext 的 fsGroup 自动修正,或在 initContainer 中执行 chown。

⚠️ 坑 5:主备实例名相同,守护进程启动失败 达梦 Data Guard 要求集群内所有实例名唯一,主备必须使用不同的 INSTANCE_NAME(如 dmdb0、dmdb1),否则守护进程无法识别节点,集群搭建失败。

⚠️ 坑 6:守护进程与数据库启停顺序混乱 必须先启动数据库实例,再启动守护进程,否则守护进程会误判实例故障,触发异常切换甚至脑裂。Sidecar 模式下要加启动延迟或就绪检测。

⚠️ 坑 7:主备 Pod 调度到同一节点,高可用失效 未配置 Pod 反亲和性时,K8s 可能将主备两个 Pod 调度到同一个工作节点,节点宕机后主备一起挂,高可用完全失效。必须强制配置节点级反亲和性。

⚠️ 坑 8:用共享存储跑 OLTP 数据库 ReadWriteMany 共享存储 IO 延迟高、锁冲突多,完全不适合达梦这类 OLTP 数据库。生产必须用 ReadWriteOnce 块存储,优先本地 SSD。

⚠️ 坑 9:日志归档未持久化 只持久化数据目录,日志和归档放在容器可写层,故障后日志丢失无法排障,且归档打满会导致容器磁盘爆满。

⚠️ 坑 10:主备切换后备份任务失效 备份任务固定连接 0 号节点,主备切换后 0 号变成备库,备份有效性下降。必须通过 Service 连接主库,自动跟随主库漂移。

⚠️ 坑 11:未配置大页,内存性能损耗严重 容器化场景默认不开启大页,达梦共享内存访问性能下降 10%~30%,高并发场景尤为明显。生产环境必须开启大页并禁用透明大页。

⚠️ 坑 12:时区不一致,业务数据时间错乱 容器默认 UTC 时区,达梦实例继承系统时间,会导致业务时间差 8 小时。必须挂载宿主机 localtime 或设置 TZ 环境变量为Asia/Shanghai

⚠️ 坑 13:容器以 root 用户运行,存在安全风险 默认很多镜像用 root 运行数据库,存在权限溢出风险。生产环境必须使用非 root 用户运行,配置 securityContext 限制权限。

⚠️ 坑 14:迷信自愈能力,忽略备份 自愈只能解决硬件、进程级故障,无法应对误删表、逻辑错误、存储层故障。必须定期执行逻辑备份 + 存储快照,双重保障数据安全。

⚠️ 坑 15:未配置网络策略,数据库端口暴露全集群 默认 K8s 集群内所有 Pod 都可以访问数据库端口,存在安全隐患。生产环境必须配置 NetworkPolicy,只允许业务命名空间访问数据库端口。


十、常见故障排查手册

10.1 实例启动失败类

现象:Pod 启动后很快 CrashBackOff

排查步骤:

  1. 查看 Pod 日志:kubectl logs -f dmdb-0 -n dmdb
  2. 常见原因与解决方案:
    • 共享内存不足:增大 /dev/shm 的 sizeLimit,或调小 MEMORY_TARGET
    • 权限不足:检查 PVC 目录权限,确认 fsGroup 生效
    • 授权失效:检查授权文件是否正确,是否为容器适配版
    • 端口被占用:检查节点是否有端口冲突
现象:实例启动卡在 mount 状态

排查步骤:

  1. 查看数据库告警日志
  2. 检查是否有未完成的恢复操作
  3. 确认归档日志是否完整,是否存在归档缺失

10.2 主备同步异常类

现象:备库同步中断,归档日志堆积

排查步骤:

  1. 主库执行查询:select * from v$dataguard_status;
  2. 检查主备网络连通性:在备库 Pod 中 telnet 主库 5236 端口
  3. 检查归档日志是否被误删
  4. 解决方案:
    • 网络问题:修复网络后自动追平
    • 归档缺失:重新全量备份搭建备库
    • 延迟过大:优化网络,调大同步缓冲区
现象:守护进程报实例名不匹配

排查步骤:

  1. 检查 dm.ini 中 INSTANCE_NAME 与 dmwatcher.ini 中是否一致
  2. 确认主备实例名唯一,无重复
  3. 检查 Host 配置是否正确,DNS 解析是否正常

10.3 性能问题类

现象:数据库 IO 延迟高

排查步骤:

  1. 查看存储层 IOPS、延迟指标
  2. 检查是否有大量小 IO、随机写入
  3. 优化方案:
    • 调大 BUFFER 缓存,减少磁盘 IO
    • 调整检查点参数,平滑写入
    • 更换更高性能的存储介质
现象:数据库频繁 OOM Kill

排查步骤:

  1. 查看 K8s 事件,确认是否为内存超限
  2. 检查实际内存占用,是否连接数过多
  3. 优化方案:
    • 调小 MEMORY_TARGET,预留更多系统内存
    • 限制最大连接数,启用连接池
    • 增大容器内存 limits

10.4 授权与许可类

现象:数据库运行一段时间后自动关停

排查步骤:

  1. 查看数据库日志,是否有授权到期提示
  2. 确认授权类型是否支持容器漂移
  3. 解决方案:更换正式商用授权,申请容器场景专用授权

十一、常用运维命令与状态验证

11.1 集群资源检查

# 查看StatefulSet与Pod状态
kubectl get statefulset -n dmdb
kubectl get pods -n dmdb -o wide

# 查看PVC绑定情况
kubectl get pvc -n dmdb

# 查看Service
kubectl get svc -n dmdb

# 查看监视器状态
kubectl get pods -n dmdb -l app=dmdb-monitor

11.2 数据库核心状态查询

# 查看实例基本信息
kubectl exec -it dmdb-0 -n dmdb -- disql SYSDBA/SYSDBA123456 -c "select name,status$,mode$ from v\\$instance;"

# 查看主备同步状态
kubectl exec -it dmdb-0 -n dmdb -- disql SYSDBA/SYSDBA123456 -c "select * from v\\$dataguard_status;"

# 查看归档状态
kubectl exec -it dmdb-0 -n dmdb -- disql SYSDBA/SYSDBA123456 -c "select * from v\\$arch_status;"

11.3 运维操作命令

# 滚动更新配置
kubectl rollout restart statefulset dmdb -n dmdb

# 查看滚动更新进度
kubectl rollout status statefulset dmdb -n dmdb

# 查看数据库日志
kubectl logs -f dmdb-0 -n dmdb -c dmdb

# 查看守护进程日志
kubectl logs -f dmdb-0 -n dmdb -c dmwatcher

总结

达梦 DM9 容器化高可用落地,核心是抓住三个本质:

  1. 选对工作负载:用 StatefulSet + Headless Service 解决有状态应用的固定身份与自动发现问题,这是一切的基础
  2. 用原生能力:基于达梦原生 Data Guard 主备架构做高可用,不重复造轮子,稳定性有保障
  3. 全体系保障:从调度策略、性能调优、故障自愈到备份体系,全方位覆盖生产级需求

这套方案已在多个金融信创项目落地验证,完全满足生产级高可用要求,改造成本可控,运维难度低。

政务选金仓,金融选达梦;MySQL 迁移选金仓,Oracle 迁移选达梦。

📚 专栏推荐:专注 SpringBoot3 + 人大金仓 + 达梦信创实战,持续输出生产级落地方案、避坑指南、性能调优干货,关注不迷路。

觉得文章有用的话,欢迎点赞、收藏、关注三连,后续更新更多达梦性能调优、Oracle 迁移实战的硬核内容。

更多推荐