
最近在把达梦数据库从裸机迁到容器环境顺手把客户端到服务端的连接改成SSL加密。这套流程涉及docker和k8s两种部署形态踩坑最多的地方反而不是数据库本身而是证书生成、文件挂载、配置持久化这些容器环境特有的环节。我把完整的配置流程、关键步骤和排障经验整理出来给正在给达梦数据库配SSL的同学一份能直接参考的实操记录。1. 达梦SSL连接的基本概念与环境差异1.1 初次接触SSL配置时先搞懂这三件事如果之前只搞过MySQL或PostgreSQL的SSL到了达梦这里第一件事不是去找“一键开启”的按钮而是先理解达梦SSL连接的本质。达梦的SSL加密链路可以看作两层一层是服务端证书让客户端可以确认“我连的这个确实是达梦服务端”另一层是客户端证书当需要双向认证时客户端也要向服务端出示自己的身份。如果只是防止SQL语句在网络上裸奔做单向认证就够了。第二个要理解的点是端口变化。达梦默认的数据库端口是5236未开启SSL时这个端口上跑的是纯文本协议。开启SSL之后握手阶段会升级成TLS协议。客户端如果走普通连接老版本驱动可能会握手失败。所以配置完成后不光要改服务端还要改客户端连接方式和连接参数。第三个点是配置文件的生效方式。达梦的服务端配置集中在数据目录下的dm.ini里这个文件在容器化之后很容易被忽略。很多人把证书放进镜像却忘了改dm.ini或者改了dm.ini但没持久化容器一删配置又回到初始状态。整个配置链路本质就是“证书文件 dm.ini 客户端信任”这三样东西对齐。1.2 Docker和K8s下配置SSL的主要差异物理机上配置达梦SSL大家已经很熟了生成证书、编辑dm.ini、重启服务。但到了容器环境同样的动作要换一套思路来落地。Docker环境下容器是临时性的。证书不能只塞进容器内部因为容器重建后文件就没了。正确做法是使用卷挂载把宿主机上的证书目录挂载到容器内固定路径同时把数据库数据目录也用volume持久化。这样即使容器被删dm.ini里配置的证书路径依然存在数据也不会丢。K8s环境下配置管理变成了声明式。证书和私钥这类敏感文件应该用Secret保存而不是直接写在镜像里。dm.ini这类非密钥配置文件可以留在PVC持久化数据卷里或者用ConfigMap管理具体取决于你是否想保留实例初始化时自动生成的默认配置。K8s下还有一个物理机没有的问题Pod的IP不固定证书SAN里不能只写一个IP否则每次Pod重建都可能出现证书校验失败。另一个差异在于权限。物理机上证书目录通常属于DBA用户容器内达梦进程一般以dmdba身份运行uid不一定是0。如果证书文件权限是644、私钥是600但属主是宿主机root容器内进程可能根本读不到。这个问题在Docker和K8s都会遇到我后面单独讲。1.3 完整配置链路拆成五步不用把这件事想得太复杂整个流程可以拆成五步分别在下面几个小节展开。第一步生成CA证书、服务端证书以及可选客户端证书。第二步把证书文件放到容器或Pod内可达的目录并且确认权限正确。第三步修改达梦数据目录下的dm.ini打开SSL开关指定证书路径。第四步重启达梦实例让配置生效。第五步从客户端用SSL方式连接验证并做基础排障。这个顺序不能乱。我见过有人跳过第一步直接在dm.ini里写了一个不存在的证书路径结果达梦服务起不来日志报错也看不明白。证书是前提配置是联动验证是收尾按顺序来能省掉很多不必要的排查。2. 证书准备用OpenSSL生成一套可用证书2.1 CA、服务端、客户端三件套怎么生成达梦官方文档里推荐使用OpenSSL或第三方CA签发证书。生产环境如果有公司内部CA直接向CA申请服务端证书就好。没有CA本地用OpenSSL自签一套测试证书是成本最低的方案。我这里给出一套完整命令先生成CA再用CA签发服务端和客户端证书。mkdir -p ~/dm-ssl/{ca,server,client} # 1. 生成CA私钥和自签名CA证书 openssl req -new -x509 -days 3650 \ -keyout ~/dm-ssl/ca/ca.key \ -out ~/dm-ssl/ca/ca.crt \ -subj /CCN/STBeijing/LBeijing/OExample/CNDameng-CA # 2. 生成服务端私钥和证书请求 openssl req -newkey rsa:2048 -nodes \ -keyout ~/dm-ssl/server/server.key \ -out ~/dm-ssl/server/server.csr \ -subj /CCN/OExample/CNdmserver # 3. 准备扩展文件把服务端可能被访问的IP和DNS都写进SAN cat ~/dm-ssl/server/server.ext EOF subjectAltName IP:127.0.0.1, IP:192.168.1.100, DNS:dm8, DNS:dm8-0.dm8-headless.default.svc EOF # 4. 用CA签发服务端证书 openssl x509 -req -in ~/dm-ssl/server/server.csr \ -CA ~/dm-ssl/ca/ca.crt \ -CAkey ~/dm-ssl/ca/ca.key \ -CAcreateserial \ -out ~/dm-ssl/server/server.crt \ -days 825 \ -extfile ~/dm-ssl/server/server.ext客户端证书的生成方式类似只是CN不同而且通常不需要SAN扩展。如果只是做单向SSL认证客户端证书可以跳过。但为了以后可能开启双向认证建议一起生成。# 生成客户端私钥和证书请求 openssl req -newkey rsa:2048 -nodes \ -keyout ~/dm-ssl/client/client.key \ -out ~/dm-ssl/client/client.csr \ -subj /CCN/OExample/CNdmclient # 用CA签发客户端证书 openssl x509 -req -in ~/dm-ssl/client/client.csr \ -CA ~/dm-ssl/ca/ca.crt \ -CAkey ~/dm-ssl/ca/ca.key \ -CAcreateserial \ -out ~/dm-ssl/client/client.crt \ -days 825生成完毕后用openssl verify -CAfile ~/dm-ssl/ca/ca.crt ~/dm-ssl/server/server.crt验证服务端证书是否受信任。这一步很重要如果这里都验证不过后面客户端更不可能通过。2.2 证书文件的目录设计与权限证书目录我习惯分成ca、server、client三个子目录这样挂载到容器之后路径清晰排查问题也方便。目录结构类似~/dm-ssl ├── ca │ ├── ca.crt │ └── ca.key ├── server │ ├── server.crt │ ├── server.csr │ ├── server.ext │ └── server.key └── client ├── client.crt └── client.key权限方面私钥文件必须收紧权限。宿主机上可以执行chmod 600 ~/dm-ssl/ca/ca.key ~/dm-ssl/server/server.key ~/dm-ssl/client/client.key chmod 644 ~/dm-ssl/ca/ca.crt ~/dm-ssl/server/server.crt ~/dm-ssl/client/client.crt这里有个细节需要注意。如果直接挂载到容器里宿主机上的uid和容器内dmdba用户的uid不一定一致。达梦容器里运行数据库进程的用户通常是dmdbauid往往不是0。如果证书文件属主是宿主机的root容器进程读不了。一个常见的解决办法是用chown指定容器内用户uid或者在容器启动后进入容器执行chown dmdba:dinstall /dm-ssl -R。K8s环境则可以借助initContainer来修正属主后面会写对应示例。2.3 证书匹配问题的坑很多人配置完成后服务端日志正常但客户端就是报“主机名验证失败”十有八九是证书SAN没有覆盖连接地址。比如在K8s里客户端通过Service DNS连接证书SAN里只有IP没有DNS那即使IP能连上TLS握手时主机名校验依然会失败。反过来如果通过Pod IP连接SAN里没有对应IP也同样报错。所以生成服务端证书时SAN尽量把可能的IP和DNS都放进去。如果你不确定未来会用什么地址连接宁可多写几个也不要少写。另外证书有效期不要图省事只签一年。容器环境重建频繁证书轮换一次就要重新发布配置并重启数据库能签2到3年就签长一点。当然生产环境的证书周期要按照企业安全策略来我这里说的是测试环境省事。3. Docker单机环境配置并验证达梦SSL3.1 启动达梦容器并挂载证书目录在Docker环境里我倾向于先用一个临时容器完成数据库初始化再修改配置。这里的达梦镜像我以常见的dameng/dmserver为例实际使用的时候要替换成你们镜像仓库里真实的镜像名和标签。先启动容器把证书目录和数据卷都挂载好docker run -d --name dm8-ssl \ -p 5236:5236 \ -v ~/dm-ssl:/dm-ssl:ro \ -v dm8_data:/opt/dmdbms/data \ dameng/dmserver:latest命令里的-v ~/dm-ssl:/dm-ssl:ro把宿主机证书目录以只读方式挂进容器防止容器内进程意外修改证书文件。-v dm8_data:/opt/dmdbms/data是数据卷数据库的数据文件、默认配置和日志都持久化在这里后面重启容器不会丢。启动之后观察容器日志等待数据库初始化完成。达梦第一次初始化通常需要一点时间不要急着马上去改配置。看到日志输出显示数据库启动完成再继续操作。3.2 修改dm.ini启用SSL并重启实例数据库起来后需要找到dm.ini的实际路径。不同镜像路径可能不同可以用命令在容器内搜索docker exec -it dm8-ssl bash find / -name dm.ini 2/dev/null常见的路径是/opt/dmdbms/data/DAMENG/dm.ini。找到之后先备份原文件再查看当前SSL配置项cp /opt/dmdbms/data/DAMENG/dm.ini /opt/dmdbms/data/DAMENG/dm.ini.bak grep -i ssl /opt/dmdbms/data/DAMENG/dm.ini不同达梦版本对SSL参数的支持略有差异常见配置项类似下面这样ENABLE_SSL 1 SSL_SERVER_CERT /dm-ssl/server/server.crt SSL_SERVER_KEY /dm-ssl/server/server.key SSL_CA_CERT /dm-ssl/ca/ca.crt如果你的dm.ini里已经有类似参数把0改成1把路径改对。如果完全没有SSL相关参数可以手动在文件末尾追加但最好先确认你用的达梦版本支持哪些参数名查阅官方《数据库安全管理》手册。追加参数后必须用tail或grep确认没有写错。修改完dm.ini直接重启容器docker restart dm8-ssl重启后再次查看日志确认达梦正常启动。如果启动失败先检查证书路径是否存在以及容器进程是否有权限读取。把备份的dm.ini还原再重新改是比较稳妥的恢复方式。3.3 用openssl s_client检查服务端SSL是否生效判断SSL是否真的生效最直接的方法是在容器所在宿主机上执行openssl s_client -connect 127.0.0.1:5236 \ -CAfile ~/dm-ssl/ca/ca.crt \ -servername dmserver如果服务端SSL已经开启这条命令会输出完整的证书链并显示握手结果。如果显示连接后被重置或者没有证书返回说明服务端的SSL配置没生效或者达梦端口还没就绪。这里有一个容易被误解的点。达梦开启SSL后5236端口不再直接接受明文协议握手而是走TLS握手。openssl s_client只能用来验证TLS层不能用来执行SQL。真正要连数据库还需要用达梦客户端或支持达梦的数据库工具。3.4 从客户端建立SSL连接客户端连接时需要让客户端信任刚才生成的CA证书。如果你用的是达梦自带的disql可以在容器内测试docker exec -it dm8-ssl /opt/dmdbms/bin/disql SYSDBA/密码localhost:5236如果连接成功说明基本链路没问题。如果用的图形工具比如DBeaver或Navicat需要在连接配置里找到SSL选项指定CA证书路径。DBeaver里要填CA证书文件Navicat也要在连接属性的SSL标签页里把CA证书选上。客户端证书根据你的认证模式决定是否填写。验证SQL是否真的走了加密通道有一个简单笨办法在客户端大量执行带特定内容的SQL同时在网络上抓包看包内容是不是密文。不过大多数场景下确认TLS握手成功就够了。4. K8s环境使用Secret与StatefulSet配置达梦SSL4.1 把证书和私钥放进SecretK8s环境的首要原则是不要把证书写死在镜像里。用Secret管理之后证书更新不需要重新构建镜像只需更新Secret对象并重启Pod。把之前生成的文件塞进Secretkubectl create secret generic dm-ssl-certs \ --from-fileca.crt$HOME/dm-ssl/ca/ca.crt \ --from-fileserver.crt$HOME/dm-ssl/server/server.crt \ --from-fileserver.key$HOME/dm-ssl/server/server.key注意私钥文件也进了SecretSecret本身会做base64编码但这不是绝对安全。如果你的集群启用了加密存储或者接入了外部密钥管理优先走那条路。生产环境考虑更严格的权限控制不要让普通用户有读取该Secret的权限。我这里给的是最直接的落地方式。创建完Secret后用下面命令确认内容存在kubectl get secret dm-ssl-certs4.2 使用StatefulSet挂载证书并持久化数据达梦是有状态服务建议使用StatefulSet而不是Deployment。StatefulSet能保证Pod名稳定配合Headless Service可以拿到稳定的DNS。我这里给一个精简但完整的YAML示例。apiVersion: v1 kind: Service metadata: name: dm8-headless spec: clusterIP: None selector: app: dm8 ports: - name: dm port: 5236 targetPort: 5236 --- apiVersion: apps/v1 kind: StatefulSet metadata: name: dm8 spec: serviceName: dm8-headless replicas: 1 selector: matchLabels: app: dm8 template: metadata: labels: app: dm8 spec: initContainers: - name: fix-cert image: busybox command: - sh - -c - chown -R 1001:1001 /dm-ssl chmod 600 /dm-ssl/server.key volumeMounts: - name: ssl-cert mountPath: /dm-ssl containers: - name: dm8 image: dameng/dmserver:latest ports: - containerPort: 5236 env: - name: TZ value: Asia/Shanghai volumeMounts: - name: ssl-cert mountPath: /dm-ssl readOnly: true - name: dm8-data mountPath: /opt/dmdbms/data volumes: - name: ssl-cert secret: secretName: dm-ssl-certs defaultMode: 0400 volumeClaimTemplates: - metadata: name: dm8-data spec: accessModes: [ ReadWriteOnce ] resources: requests: storage: 50Gi这个YAML里有两点比较关键。第一initContainer先把Secret挂载的证书目录属主改成达梦容器内进程需要用的uid避免权限问题。第二数据目录用volumeClaimTemplates声明cert文件和数据显示分离证书来自Secret数据在PVC里两者互不影响。后续更新证书只需要更新Secret并重启Pod。数据不会丢。创建资源kubectl apply -f dm8-statefulset.yaml等待Pod变为Running状态。如果Pod一直CrashLoopBackOff大概率是证书路径或权限问题。用kubectl logs看达梦日志定位原因。4.3 在Pod中修改配置并重启PodK8s环境下dm.ini在PVC里所以可以先进入Pod修改再重启Pod让配置生效。这里和物理机不太一样重启Pod不是删除重建那么简单但StatefulSet会拉起一个新的Pod并复用PVC。进入Podkubectl exec -it dm8-0 -- bash然后按照Docker环境里同样的方法找到dm.ini备份修改SSL相关参数退出Pod。执行kubectl delete pod dm8-0Pod被删掉后StatefulSet会根据模板重新创建一个新的Pod新Pod挂载同一个PVC读取到的dm.ini已经是改过的版本。这样SSL配置就生效了。如果你不习惯进入Pod改配置可以在镜像或者启动命令层面做一个初始化脚本但会增加复杂度和运维成本。对于大多数场景进入Pod修改一个配置文件再重启Pod反而是最保守、最容易排障的做法。4.4 K8s环境验证SSL连接K8s里客户端访问数据库地址一般不是Pod IP而是Service域名比如dm8-0.dm8-headless.default.svc。这个地址的解析只有集群内部才有效。在集群外测试时需要把Service类型改成NodePort或LoadBalancer再用对应IP。证书里的SAN必须包含这个域名。如果你的SAN没写连接时会报主机名校验失败。重新生成证书后更新Secret再重启Pod问题就能解决。验证方式还是用opensslopenssl s_client -connect dm8-0.dm8-headless.default.svc:5236 \ -CAfile ~/dm-ssl/ca/ca.crt如果证书链和握手都正常接着从一个临时Pod里执行disql连接测试。临时Pod可以使用网络相对独立、且与目标Pod同集群的镜像kubectl run dm-client --rm -it --imagebusybox --restartNever \ --command -- sh -c wget -qO- ... 不过busybox里没有disql。更实用的是直接用DBeaver从本机通过NodePort或LoadBalancer地址连接把CA证书填到连接配置里。只要DBeaver能正常打开表说明K8s环境下的SSL连接已经通了。4.5 证书轮换时怎么平滑更新证书快到期了不需要动数据库数据只要更新Secret再滚动重启。步骤是用新证书文件覆盖宿主机或本地目录。更新Secretkubectl create secret generic dm-ssl-certs \ --from-fileca.crt$HOME/dm-ssl/ca/ca.crt \ --from-fileserver.crt$HOME/dm-ssl/server/server.crt \ --from-fileserver.key$HOME/dm-ssl/server/server.key \ --dry-runclient -o yaml | kubectl apply -f -滚动重启StatefulSetkubectl rollout restart statefulset dm8Secret挂载到Pod里是软链接方式文件内容会在底层自动更新但达梦不会重新读取证书文件所以必须重启Pod。这也是我把Secret和数据卷分离的原因证书更新不影响数据滚动重启不影响工作负载稳定性。5. 常见问题速查与排障经验5.1 客户端报“SSL握手失败/证书验证失败”这可能是最让人头疼的问题。先按顺序排查第一确认服务端dm.ini里的SSL开关真的是1并且改对了路径第二确认达梦服务确实重启过第三在客户端用openssl s_client -connect检查能否正常显示证书链。如果openssl能看到证书链但数据库客户端报证书验证失败那问题基本出在客户端信任配置上。任何客户端工具连接达梦时都需要显式指定CA证书否则它不会自动信任你自建的CA。如果openssl s_client直接看不到证书说明服务端压根没有启用SSL或者监听端口不对。回到第3.2节重新检查dm.ini。5.2 容器内出现Permission Denied这个问题在Docker和K8s都会碰到。Docker环境里用docker exec进入容器执行ls -l /dm-ssl看属主和权限。如果文件属主是root而达梦进程用户是dmdba就在容器内把属主改掉chown -R dmdba:dinstall /dm-sslK8s环境里因为Pod重建后文件系统会被Secret重新挂载直接进Pod改属主不持久。最佳方式是增加initContainer在容器主进程启动前把权限修正我在4.2节的YAML已经写了这个思路。需要注意Secret挂载默认是只读的private key文件权限默认可能是0444如果达梦程序要求私钥权限必须更严格需要用defaultMode: 0400。5.3 用Navicat或DBeaver连不上报主机名不匹配连接工具里填的IP或Host是192.168.1.10但证书SAN只写了DNS:dm8那TLS主机名校验必然失败。解决方式只有重新生成证书把连接工具实际使用的IP和DNS都放进SAN。换证书的成本不小所以第一步生成证书时就要规划好。在K8s里有一种常见误区证书写的是Service名但客户端连接用的是LoadBalancer公网IP。这种情况下SAN要把公网IP和Service域名一起写入。如果公网IP不固定应该考虑使用证书里的DNS并通过域名访问数据库。5.4 配置修改完重启后配置又还原了这类问题通常是因为dm.ini不在持久化卷里。Docker环境检查一下启动命令里是否真的挂载了数据卷或者当前容器是不是重新创建的新容器。K8s环境检查StatefulSet是否使用了volumeClaimTemplates以及PVC的状态是否正常。还有一个隐性坑有时候你改了dm.ini容器内数据卷正常但容器重启前被编排工具重新调度到了新节点依然使用同一个PVC一般没问题。问题往往出在你自己本地测试时用了匿名卷容器一删数据就没了。建议任何重要配置修改后先确认dm.ini的文件修改时间和内容再重启。5.5 达梦版本不同SSL参数名对不上我遇到过在某个版本里参数叫ENABLE_SSL升级后在另一个版本里变成SSL_ENABLE或带了前缀。处理没别的捷径先打开dm.ini用grep -i ssl搜一遍看看当前版本实际识别哪些参数。然后在官方文档里查这些参数的确切含义。如果dm.ini里完全没有SSL参数也可以先查一下安装目录下的文档或示例配置文件不要盲目追加一个看似合理的参数。在我实际操作中最稳妥的方式是备份原文件后把当前版本手册给出的SSL参数按说明改好再用openssl s_client验证。这次配置达梦SSL最大的体会是数据库层面的“开启”本身并不复杂真正复杂的是证书和配置在容器重建之后依然可靠。所以第一步别急着敲命令先把证书生成、权限、持久化方案想清楚。后面无论Docker还是K8s都会顺畅很多。最后再分享一个小技巧证书里的SAN一定要提前规划把可能会用到的IP和服务名都写进去否则换一次证书比第一次配SSL还要痛苦。