为 MySQL 客户端开启 TLS
本文主要描述了在 Kubernetes 上如何为 TiDB 集群的 MySQL 客户端开启 TLS。TiDB Operator 从 v1.1 开始已经支持为 Kubernetes 上 TiDB 集群开启 MySQL 客户端 TLS。开启步骤为:
为 TiDB Server 颁发一套 Server 端证书,为 MySQL Client 颁发一套 Client 端证书。并创建两个 Secret 对象,Secret 名字分别为:
${cluster_name}-tidb-server-secret和${cluster_name}-tidb-client-secret,分别包含前面创建的两套证书;其中,颁发证书的方式有多种,本文档提供两种方式,用户也可以根据需要为 TiDB 集群颁发证书,这两种方式分别为:
当需要更新已有 TLS 证书时,可参考更新和替换 TLS 证书。
部署集群,设置
.spec.tidb.tlsClient.enabled属性为true:- 如需跳过作为 MySQL 客户端的内部组件(如 TidbInitializer、Dashboard、Backup、Restore)的 TLS 认证,你可以给集群对应的
TidbCluster加上tidb.tidb.pingcap.com/skip-tls-when-connect-tidb="true"的 annotation。 - 如需关闭 TiDB 服务端对客户端 CA 证书的认证,你可以设置
.spec.tidb.tlsClient.disableClientAuthn属性为true,即在配置 TiDB 服务端启用安全连接 中不设置 ssl-ca 参数。 - 如需跳过作为 MySQL 客户端的内部组件(如 TidbInitializer、Dashboard、Backup、Restore)的 CA 证书认证,你可以设置
.spec.tidb.tlsClient.skipInternalClientCA属性为true。
- 如需跳过作为 MySQL 客户端的内部组件(如 TidbInitializer、Dashboard、Backup、Restore)的 TLS 认证,你可以给集群对应的
第一步:为 TiDB 集群颁发两套证书
使用 cfssl 系统颁发证书
首先下载
cfssl软件并初始化证书颁发机构:mkdir -p ~/bin curl -s -L -o ~/bin/cfssl https://pkg.cfssl.org/R1.2/cfssl_linux-amd64 curl -s -L -o ~/bin/cfssljson https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64 chmod +x ~/bin/{cfssl,cfssljson} export PATH=$PATH:~/bin mkdir -p cfssl cd cfssl cfssl print-defaults config > ca-config.json cfssl print-defaults csr > ca-csr.json在
ca-config.json配置文件中配置 CA 选项:{ "signing": { "default": { "expiry": "8760h" }, "profiles": { "server": { "expiry": "8760h", "usages": [ "signing", "key encipherment", "server auth" ] }, "client": { "expiry": "8760h", "usages": [ "signing", "key encipherment", "client auth" ] } } } }您还可以修改
ca-csr.json证书签名请求 (CSR):{ "CN": "TiDB Server", "CA": { "expiry": "87600h" }, "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "US", "L": "CA", "O": "PingCAP", "ST": "Beijing", "OU": "TiDB" } ] }使用定义的选项生成 CA:
cfssl gencert -initca ca-csr.json | cfssljson -bare ca -生成 Server 端证书。
首先生成默认的
server.json文件:cfssl print-defaults csr > server.json然后编辑这个文件,修改
CN,hosts属性:... "CN": "TiDB Server", "hosts": [ "127.0.0.1", "::1", "${cluster_name}-tidb", "${cluster_name}-tidb.${namespace}", "${cluster_name}-tidb.${namespace}.svc", "*.${cluster_name}-tidb", "*.${cluster_name}-tidb.${namespace}", "*.${cluster_name}-tidb.${namespace}.svc", "*.${cluster_name}-tidb-peer", "*.${cluster_name}-tidb-peer.${namespace}", "*.${cluster_name}-tidb-peer.${namespace}.svc" ], ...其中
${cluster_name}为集群的名字,${namespace}为 TiDB 集群部署的命名空间,用户也可以添加自定义hosts。最后生成 Server 端证书:
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server server.json | cfssljson -bare server生成 Client 端证书。
首先生成默认的
client.json文件:cfssl print-defaults csr > client.json然后编辑这个文件,修改
CN,hosts属性,hosts可以留空:... "CN": "TiDB Client", "hosts": [], ...最后生成 Client 端证书:
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client client.json | cfssljson -bare client创建 Kubernetes Secret 对象。
到这里假设你已经按照上述文档把两套证书都创建好了。通过下面的命令为 TiDB 集群创建 Secret 对象:
kubectl create secret generic ${cluster_name}-tidb-server-secret --namespace=${namespace} --from-file=tls.crt=server.pem --from-file=tls.key=server-key.pem --from-file=ca.crt=ca.pem kubectl create secret generic ${cluster_name}-tidb-client-secret --namespace=${namespace} --from-file=tls.crt=client.pem --from-file=tls.key=client-key.pem --from-file=ca.crt=ca.pem这样就给 Server/Client 端证书分别创建了:
- 一个 Secret 供 TiDB Server 启动时加载使用;
- 另一个 Secret 供 MySQL 客户端连接 TiDB 集群时候使用。
用户可以生成多套 Client 端证书,并且至少要生成一套 Client 证书供 TiDB Operator 内部组件访问 TiDB Server(目前有 TidbInitializer 会访问 TiDB Server 来设置密码或者一些初始化操作)。
使用 cert-manager 颁发证书
安装 cert-manager。
创建一个 Issuer 用于给 TiDB 集群颁发证书。
为了配置
cert-manager颁发证书,必须先创建 Issuer 资源。首先创建一个目录保存
cert-manager创建证书所需文件:mkdir -p cert-manager cd cert-manager然后创建一个
tidb-server-issuer.yaml文件,输入以下内容:apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: ${cluster_name}-selfsigned-ca-issuer namespace: ${namespace} spec: selfSigned: {} --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: ${cluster_name}-ca namespace: ${namespace} spec: secretName: ${cluster_name}-ca-secret commonName: "TiDB CA" isCA: true duration: 87600h # 10yrs renewBefore: 720h # 30d issuerRef: name: ${cluster_name}-selfsigned-ca-issuer kind: Issuer --- apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: ${cluster_name}-tidb-issuer namespace: ${namespace} spec: ca: secretName: ${cluster_name}-ca-secret上面的文件创建三个对象:
- 一个 SelfSigned 类型的 Issuer 对象(用于生成 CA 类型 Issuer 所需要的 CA 证书);
- 一个 Certificate 对象,
isCa属性设置为true; - 一个可以用于颁发 TiDB Server TLS 证书的 Issuer。
最后执行下面的命令进行创建:
kubectl apply -f tidb-server-issuer.yaml创建 Server 端证书。
在
cert-manager中,Certificate 资源表示证书接口,该证书将由上面创建的 Issuer 颁发并保持更新。首先来创建 Server 端证书,创建一个
tidb-server-cert.yaml文件,并输入以下内容:apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: ${cluster_name}-tidb-server-secret namespace: ${namespace} spec: secretName: ${cluster_name}-tidb-server-secret duration: 8760h # 365d renewBefore: 360h # 15d subject: organizations: - PingCAP commonName: "TiDB Server" usages: - server auth dnsNames: - "${cluster_name}-tidb" - "${cluster_name}-tidb.${namespace}" - "${cluster_name}-tidb.${namespace}.svc" - "*.${cluster_name}-tidb" - "*.${cluster_name}-tidb.${namespace}" - "*.${cluster_name}-tidb.${namespace}.svc" - "*.${cluster_name}-tidb-peer" - "*.${cluster_name}-tidb-peer.${namespace}" - "*.${cluster_name}-tidb-peer.${namespace}.svc" ipAddresses: - 127.0.0.1 - ::1 issuerRef: name: ${cluster_name}-tidb-issuer kind: Issuer group: cert-manager.io其中
${cluster_name}为集群的名字:spec.secretName请设置为${cluster_name}-tidb-server-secret;usages请添加上server auth;dnsNames需要填写这 6 个 DNS,根据需要可以填写其他 DNS:${cluster_name}-tidb${cluster_name}-tidb.${namespace}${cluster_name}-tidb.${namespace}.svc*.${cluster_name}-tidb*.${cluster_name}-tidb.${namespace}*.${cluster_name}-tidb.${namespace}.svc*.${cluster_name}-tidb-peer*.${cluster_name}-tidb-peer.${namespace}*.${cluster_name}-tidb-peer.${namespace}.svc
ipAddresses需要填写这两个 IP ,根据需要可以填写其他 IP:127.0.0.1::1
issuerRef请填写上面创建的 Issuer;- 其他属性请参考 cert-manager API。
通过执行下面的命令来创建证书:
kubectl apply -f tidb-server-cert.yaml创建这个对象以后,cert-manager 会生成一个名字为
${cluster_name}-tidb-server-secret的 Secret 对象供 TiDB Server 使用。创建 Client 端证书。
创建一个
tidb-client-cert.yaml文件,并输入以下内容:apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: ${cluster_name}-tidb-client-secret namespace: ${namespace} spec: secretName: ${cluster_name}-tidb-client-secret duration: 8760h # 365d renewBefore: 360h # 15d subject: organizations: - PingCAP commonName: "TiDB Client" usages: - client auth issuerRef: name: ${cluster_name}-tidb-issuer kind: Issuer group: cert-manager.io其中
${cluster_name}为集群的名字:spec.secretName请设置为${cluster_name}-tidb-client-secret;usages请添加上client auth;dnsNames和ipAddresses不需要填写;issuerRef请填写上面创建的 Issuer;- 其他属性请参考 cert-manager API。
通过执行下面的命令来创建证书:
kubectl apply -f tidb-client-cert.yaml创建这个对象以后,cert-manager 会生成一个名字为
${cluster_name}-tidb-client-secret的 Secret 对象供 TiDB Client 使用。创建多套 Client 端证书(可选)。
TiDB Operator 集群内部有 4 个组件需要请求 TiDB Server,当开启 TLS 验证后,这些组件可以使用证书来请求 TiDB Server,每个组件都可以使用单独的证书。这些组件有:
- TidbInitializer
- PD Dashboard
- Backup(使用 Dumpling 时)
- Restore(使用 TiDB Lightning 时)
如需要使用 TiDB Lightning 恢复 Kubernetes 上的集群数据,则也可以为其中的 TiDB Lightning 组件生成 Client 端证书。
下面就来生成这些组件的 Client 证书。
创建一个
tidb-components-client-cert.yaml文件,并输入以下内容:apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: ${cluster_name}-tidb-initializer-client-secret namespace: ${namespace} spec: secretName: ${cluster_name}-tidb-initializer-client-secret duration: 8760h # 365d renewBefore: 360h # 15d subject: organizations: - PingCAP commonName: "TiDB Initializer client" usages: - client auth issuerRef: name: ${cluster_name}-tidb-issuer kind: Issuer group: cert-manager.io --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: ${cluster_name}-pd-dashboard-client-secret namespace: ${namespace} spec: secretName: ${cluster_name}-pd-dashboard-client-secret duration: 8760h # 365d renewBefore: 360h # 15d subject: organizations: - PingCAP commonName: "PD Dashboard client" usages: - client auth issuerRef: name: ${cluster_name}-tidb-issuer kind: Issuer group: cert-manager.io --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: ${cluster_name}-backup-client-secret namespace: ${namespace} spec: secretName: ${cluster_name}-backup-client-secret duration: 8760h # 365d renewBefore: 360h # 15d subject: organizations: - PingCAP commonName: "Backup client" usages: - client auth issuerRef: name: ${cluster_name}-tidb-issuer kind: Issuer group: cert-manager.io --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: ${cluster_name}-restore-client-secret namespace: ${namespace} spec: secretName: ${cluster_name}-restore-client-secret duration: 8760h # 365d renewBefore: 360h # 15d subject: organizations: - PingCAP commonName: "Restore client" usages: - client auth issuerRef: name: ${cluster_name}-tidb-issuer kind: Issuer group: cert-manager.io其中
${cluster_name}为集群的名字:spec.secretName请设置为${cluster_name}-${component}-client-secret;usages请添加上client auth;dnsNames和ipAddresses不需要填写;issuerRef请填写上面创建的 Issuer;- 其他属性请参考 cert-manager API。
如需要为 TiDB Lignting 组件生成 Client 端证书,则可以使用以下内容并通过在 TiDB Lightning 的 Helm Chart
values.yaml中设置tlsCluster.tlsClientSecretName为${cluster_name}-lightning-client-secret:apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: ${cluster_name}-lightning-client-secret namespace: ${namespace} spec: secretName: ${cluster_name}-lightning-client-secret duration: 8760h # 365d renewBefore: 360h # 15d subject: organizations: - PingCAP commonName: "Lightning client" usages: - client auth issuerRef: name: ${cluster_name}-tidb-issuer kind: Issuer group: cert-manager.io通过执行下面的命令来创建证书:
kubectl apply -f tidb-components-client-cert.yaml创建这些对象以后,cert-manager 会生成 4 个 Secret 对象供上面四个组件使用。
第二步:部署 TiDB 集群
接下来将会创建一个 TiDB 集群,并且执行以下步骤:
- 开启 MySQL 客户端 TLS;
- 对集群进行初始化(这里创建了一个数据库
app); - 创建一个 Backup 对象对集群进行备份;
- 创建一个 Restore 对象对进群进行恢复;
- TidbInitializer,PD Dashboard,Backup 以及 Restore 分别使用单独的 Client 证书(用
tlsClientSecretName指定)。
创建三个
.yaml文件:tidb-cluster.yaml:apiVersion: pingcap.com/v1alpha1 kind: TidbCluster metadata: name: ${cluster_name} namespace: ${namespace} spec: version: v8.5.2 timezone: UTC pvReclaimPolicy: Retain pd: baseImage: pingcap/pd maxFailoverCount: 0 replicas: 1 requests: storage: "10Gi" config: {} tlsClientSecretName: ${cluster_name}-pd-dashboard-client-secret tikv: baseImage: pingcap/tikv maxFailoverCount: 0 replicas: 1 requests: storage: "100Gi" config: {} tidb: baseImage: pingcap/tidb maxFailoverCount: 0 replicas: 1 service: type: ClusterIP config: {} tlsClient: enabled: true --- apiVersion: pingcap.com/v1alpha1 kind: TidbInitializer metadata: name: ${cluster_name}-init namespace: ${namespace} spec: image: tnir/mysqlclient cluster: namespace: ${namespace} name: ${cluster_name} initSql: |- create database app; tlsClientSecretName: ${cluster_name}-tidb-initializer-client-secretbackup.yaml:apiVersion: pingcap.com/v1alpha1 kind: Backup metadata: name: ${cluster_name}-backup namespace: ${namespace} spec: backupType: full br: cluster: ${cluster_name} clusterNamespace: ${namespace} sendCredToTikv: true s3: provider: aws region: ${my_region} secretName: ${s3_secret} bucket: ${my_bucket} prefix: ${my_folder}restore.yaml:apiVersion: pingcap.com/v1alpha1 kind: Restore metadata: name: ${cluster_name}-restore namespace: ${namespace} spec: backupType: full br: cluster: ${cluster_name} clusterNamespace: ${namespace} sendCredToTikv: true s3: provider: aws region: ${my_region} secretName: ${s3_secret} bucket: ${my_bucket} prefix: ${my_folder}
其中
${cluster_name}为集群的名字,${namespace}为 TiDB 集群部署的命名空间。通过设置spec.tidb.tlsClient.enabled属性为true来开启 MySQL 客户端 TLS。部署 TiDB 集群:
kubectl apply -f tidb-cluster.yaml集群备份:
kubectl apply -f backup.yaml集群恢复:
kubectl apply -f restore.yaml
第三步:配置 MySQL 客户端使用 TLS 连接
可以根据官网文档提示,使用上面创建的 Client 证书,通过下面的方法连接 TiDB 集群:
获取 Client 证书的方式并连接 TiDB Server 的方法是:
kubectl get secret -n ${namespace} ${cluster_name}-tidb-client-secret -ojsonpath='{.data.tls\.crt}' | base64 --decode > client-tls.crt
kubectl get secret -n ${namespace} ${cluster_name}-tidb-client-secret -ojsonpath='{.data.tls\.key}' | base64 --decode > client-tls.key
kubectl get secret -n ${namespace} ${cluster_name}-tidb-client-secret -ojsonpath='{.data.ca\.crt}' | base64 --decode > client-ca.crt
mysql --comments -uroot -p -P 4000 -h ${tidb_host} --ssl-cert=client-tls.crt --ssl-key=client-tls.key --ssl-ca=client-ca.crt
最后请参考官网文档来验证是否正确开启了 TLS。
如果不使用 Client 证书,可以运行以下命令:
kubectl get secret -n ${namespace} ${cluster_name}-tidb-client-secret -ojsonpath='{.data.ca\.crt}' | base64 --decode > client-ca.crt
mysql --comments -uroot -p -P 4000 -h ${tidb_host} --ssl-ca=client-ca.crt
故障排查
X.509 证书存储在 Kubernetes Secret 中。可以使用类似下面的命令查看这些 Secret:
kubectl -n ${namespace} get secret
这些 Secret 会被挂载到容器内。可以通过查看 Pod 描述中的 Volumes 部分来确认挂载的卷信息:
kubectl -n ${namespace} describe pod ${podname}
要在容器内部检查这些 Secret 挂载情况,可以运行以下命令:
kubectl exec -n ${cluster_name} --stdin=true --tty=true ${cluster_name}-tidb-0 -c tidb -- /bin/sh
在容器内查看 TLS 目录的内容:
sh-5.1# ls -l /var/lib/*tls
/var/lib/tidb-server-tls:
total 0
lrwxrwxrwx. 1 root root 13 Sep 25 12:23 ca.crt -> ..data/ca.crt
lrwxrwxrwx. 1 root root 14 Sep 25 12:23 tls.crt -> ..data/tls.crt
lrwxrwxrwx. 1 root root 14 Sep 25 12:23 tls.key -> ..data/tls.key
/var/lib/tidb-tls:
total 0
lrwxrwxrwx. 1 root root 13 Sep 25 12:23 ca.crt -> ..data/ca.crt
lrwxrwxrwx. 1 root root 14 Sep 25 12:23 tls.crt -> ..data/tls.crt
lrwxrwxrwx. 1 root root 14 Sep 25 12:23 tls.key -> ..data/tls.key
检查 TiDB 容器日志以确认 TLS 已启用,示例命令及输出如下:
kubectl -n ${cluster_name} logs ${cluster_name}-tidb-0 -c tidb
[2025/09/25 12:23:19.739 +00:00] [INFO] [server.go:291] ["mysql protocol server secure connection is enabled"] ["client verification enabled"=true]
重新加载证书
重新加载证书的方式取决于证书的生成方式:
- 如果使用
cfssl手动生成证书和密钥文件,必须手动更新对应的 Secret。 - 如果使用
cert-manager生成证书和密钥文件,Secret 在颁发新证书时会自动更新。
要让 TiDB 使用新的证书,需要运行 ALTER INSTANCE RELOAD TLS。
可以执行下面语句查看状态变量 Ssl_server_not_before 和 Ssl_server_not_after 来检查证书的有效期。
SHOW GLOBAL STATUS LIKE 'Ssl\_server\_not\_%';
+-----------------------+--------------------------+
| Variable_name | Value |
+-----------------------+--------------------------+
| Ssl_server_not_after | Apr 23 07:59:47 2026 UTC |
| Ssl_server_not_before | Jan 24 07:59:47 2025 UTC |
+-----------------------+--------------------------+
2 rows in set (0.011 sec)