如何为Kubernetes Operator指标端点动态加载证书?
问题描述
我正在尝试为Kubernetes Operator的指标端点配置HTTPS,相关代码如下:
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, Metrics: server.Options{ SecureServing: true, BindAddress: metricsAddr, ExtraHandlers: map[string]http.Handler{ fmt.Sprintf("/%s/metrics", GetNamespace()): metrics.GetCustomMetricsHandlerInstance(), }, FilterProvider: func(c *rest.Config, httpClient *http.Client) (server.Filter, error) {}, CertDir: "", CertName: "", KeyName: "", TLSOpts: []func(*tls.Config){}, }, // ... other configurations })
启动时如果未提供证书,服务器会自动生成自签名证书。我的核心需求是:在目标Secret创建完成后,从该Secret中加载更新后的证书,但Operator启动时无法访问到这个Secret。想了解是否可以通过TLSOpts或其他推荐方法实现后续证书动态加载,以及是否需要重载HTTPS服务器?
解决方案
不需要重载HTTPS服务器,利用TLSOpts配置tls.Config的动态证书回调即可实现需求,具体步骤如下:
1. 实现从Secret加载证书的函数
编写一个函数,通过Operator的客户端从指定Secret中读取TLS证书和密钥:
import ( "context" "crypto/tls" "fmt" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" ) func loadMetricsCertFromSecret(secretName, namespace string) (*tls.Certificate, error) { client := ctrl.GetConfigOrDie() k8sClient, err := ctrl.NewClient(client, ctrl.Options{}) if err != nil { return nil, err } secret := &corev1.Secret{} err = k8sClient.Get(context.TODO(), types.NamespacedName{ Name: secretName, Namespace: namespace, }, secret) if err != nil { return nil, err } certPEM, exists := secret.Data["tls.crt"] if !exists { return nil, fmt.Errorf("secret %s/%s missing tls.crt field", namespace, secretName) } keyPEM, exists := secret.Data["tls.key"] if !exists { return nil, fmt.Errorf("secret %s/%s missing tls.key field", namespace, secretName) } return tls.X509KeyPair(certPEM, keyPEM) }
2. 通过TLSOpts配置动态证书回调
在TLSOpts中修改tls.Config,设置GetCertificate回调函数——每次TLS握手时都会触发该函数,尝试从Secret加载最新证书;如果Secret未就绪,则回退到自签名证书:
// 生成自签名证书作为启动兜底,可复用controller-runtime的自签生成逻辑 func generateSelfSignedCert() (*tls.Certificate, error) { // 此处可直接复制controller-runtime/pkg/metrics/server/server.go中的自签证书生成代码 // 以下为简化示例,实际需替换为完整的自签生成逻辑 cert, err := tls.X509KeyPair([]byte("dummy-cert"), []byte("dummy-key")) if err != nil { return nil, err } return &cert, nil } // 配置Manager的Metrics选项 mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, Metrics: server.Options{ SecureServing: true, BindAddress: metricsAddr, ExtraHandlers: map[string]http.Handler{ fmt.Sprintf("/%s/metrics", GetNamespace()): metrics.GetCustomMetricsHandlerInstance(), }, FilterProvider: func(c *rest.Config, httpClient *http.Client) (server.Filter, error) { return nil, nil }, CertDir: "", CertName: "", KeyName: "", TLSOpts: []func(*tls.Config){ func(cfg *tls.Config) { // 初始化时加载自签证书作为兜底 fallbackCert, err := generateSelfSignedCert() if err == nil { cfg.Certificates = []tls.Certificate{*fallbackCert} } // 设置动态加载证书的回调 cfg.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { // 尝试从目标Secret加载证书 cert, err := loadMetricsCertFromSecret("metrics-tls-secret", GetNamespace()) if err != nil { // Secret未就绪时返回自签证书 if fallbackCert != nil { return fallbackCert, nil } return nil, err } return cert, nil } }, }, }, // ... other configurations })
3. 配置RBAC权限
确保Operator的ServiceAccount拥有读取目标Secret的权限,添加对应的Role和RoleBinding:
apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: your-operator-namespace name: metrics-cert-reader rules: - apiGroups: [""] resources: ["secrets"] resourceNames: ["metrics-tls-secret"] verbs: ["get", "watch", "list"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: namespace: your-operator-namespace name: metrics-cert-reader-binding subjects: - kind: ServiceAccount name: your-operator-sa namespace: your-operator-namespace roleRef: kind: Role name: metrics-cert-reader apiGroup: rbac.authorization.k8s.io
关键说明
GetCertificate回调会在每次TLS握手时触发,因此Secret创建或更新后,下一次请求会自动加载最新证书,无需重启Operator。- 启动时先使用自签证书兜底,保证指标端点能正常启动,待Secret就绪后自动切换到正式证书。
内容的提问来源于stack exchange,提问作者nonus25
相关产品推荐
相关产品推荐

