You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 21:05:56