美文网首页
Kubelet的边角料功能笔记

Kubelet的边角料功能笔记

作者: Teddy_b | 来源:发表于2023-07-13 15:15 被阅读0次

kubelet的功能复杂,本文记录下它的边角功能--kubelet中的认证和授权

会包括以下几个方面的问题:

  • kubelet作为服务端堆外暴露的10250端口怎么认证和授权的

  • kubelet的只读端口是怎么工作的

  • kubelet作为客户端怎么访问kube-apiserver的

  • kubelet访问kube-apiserver的证书到期后怎么自动更新的

启动参数

kubelet的启动参数很多,通常的启动配置:

/usr/bin/kubelet
--node-ip=1.1.1.1
--hostname-override=1.1.1.1
--address=0.0.0.0
--anonymous-auth=true
--authentication-token-webhook=true
--authorization-mode=Webhook
--bootstrap-kubeconfig=/etc/kubernetes/bootstrap.kubeconfig
--client-ca-file=/etc/kubernetes/ssl/ca.pem
--network-plugin=cni
--cgroup-driver=systemd
--cert-dir=/etc/kubernetes/ssl
--cluster-dns=10.254.0.2
--cluster-domain=cluster.local
--cni-conf-dir=/etc/cni/net.d
--eviction-soft=imagefs.available<12%,memory.available<512Mi,nodefs.available<12%,nodefs.inodesFree<10%
--eviction-soft-grace-period=imagefs.available=3m,memory.available=1m,nodefs.available=3m,nodefs.inodesFree=1m
--eviction-hard=imagefs.available<10%,memory.available<256Mi,nodefs.available<10%,nodefs.inodesFree<5%
--eviction-max-pod-grace-period=30
--image-gc-high-threshold=80
--image-gc-low-threshold=70
--image-pull-progress-deadline=30s
--kube-reserved=cpu=500m,memory=512Mi,ephemeral-storage=1Gi
--kubeconfig=/etc/kubernetes/kubelet.kubeconfig
--resolv-conf=/etc/kubernetes/resolv.conf
--max-pods=200
--minimum-image-ttl-duration=720h0m0s
--node-labels=node.kubernetes.io/k8s-node=true
--pod-infra-container-image=myharbor.com:80/library/pause-amd64:3.1
--port=10250
--read-only-port=0
--root-dir=/data/k8s/kubelet
--rotate-certificates
--rotate-server-certificates
--system-reserved=cpu=500m,memory=1257Mi,ephemeral-storage=1Gi
--fail-swap-on=false
--logtostderr=false
--log-dir=/data/logs/kubernetes/
--v=4

kubelet服务器端证书

在启动参数中可以看到没有常见的HTTPS服务器配置证书的参数

--tls-cert-file=path_to_pem \
--tls-private-key-file=path_to_key \

这是因为kubelet实现了服务端证书的自动刷新。我们先来看下服务端证书是怎么自动刷新的:

func InitializeTLS(kf *options.KubeletFlags, kc *kubeletconfiginternal.KubeletConfiguration) (*server.TLSOptions, error) {
        // 是否配置了启动参数 --rotate-server-certificates、--tls-cert-file、--tls-private-key-file
        // 我们一般是会配置服务端证书自动刷新的
    if !kc.ServerTLSBootstrap && kc.TLSCertFile == "" && kc.TLSPrivateKeyFile == "" {
        ...
    }

    ...

    minTLSVersion, err := cliflag.TLSVersion(kc.TLSMinVersion)
    ...

    tlsOptions := &server.TLSOptions{
        Config: &tls.Config{
            MinVersion:   minTLSVersion,
            CipherSuites: tlsCipherSuites,
        },
        CertFile: kc.TLSCertFile,
        KeyFile:  kc.TLSPrivateKeyFile,
    }

      // 是否配置了启动参数 --client-ca-file=/etc/kubernetes/ssl/ca.pem
      // 配置一个CA证书是为了收到请求时来验证请求证书
    if len(kc.Authentication.X509.ClientCAFile) > 0 {
        clientCAs, err := certutil.NewPool(kc.Authentication.X509.ClientCAFile)
        ...
        // Specify allowed CAs for client certificates
        tlsOptions.Config.ClientCAs = clientCAs
        // Populate PeerCertificates in requests, but don't reject connections without verified certificates
        tlsOptions.Config.ClientAuth = tls.RequestClientCert
    }

    return tlsOptions, nil
}

可见我们如果开启了服务端证书的自动更新,就不需要提供证书对了,那HTTPS服务器的证书是从哪里获取的呢?

// 是否配置了启动参数 --rotate-server-certificates;而且RotateKubeletServerCertificate这个特性再1.12版本就beta了,默认开启了
if kubeCfg.ServerTLSBootstrap && kubeDeps.TLSOptions != nil && utilfeature.DefaultFeatureGate.Enabled(features.RotateKubeletServerCertificate) {
                // 先创建一个证书Manager
        klet.serverCertificateManager, err = kubeletcertificate.NewKubeletServerCertificateManager(klet.kubeClient, kubeCfg, klet.nodeName, klet.getLastObservedNodeAddresses, certDirectory)
        
              // 由于上面TLSOptions中没有指定证书对的,因此我们需要指定GetCertificate方法,用于启动HTTPS服务器的时候获取证书对
              // 可以看到这里返回的正式对就是从 证书Manager 中获取相关证书
        kubeDeps.TLSOptions.Config.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
            cert := klet.serverCertificateManager.Current()
            if cert == nil {
                return nil, fmt.Errorf("no serving certificate available for the kubelet")
            }
            return cert, nil
        }
    }

证书Maager 的Current()方法就是简单的校验证书的有效性,证书有效即返回,相当于我们shell命令获取证书有效期openssl x509 -in example.crt -noout -enddate

func (m *manager) Current() *tls.Certificate {
    m.certAccessLock.RLock()
    defer m.certAccessLock.RUnlock()
    if m.cert != nil && m.cert.Leaf != nil && m.now().After(m.cert.Leaf.NotAfter) {
        m.logf("%s: Current certificate is expired", m.name)
        return nil
    }
    return m.cert
}

那么证书Maager 中的 cert 是啥时候生成的呢?

在kubelet的启动代码中有如下的代码,启动证书Manager

func (kl *Kubelet) initializeModules() error {
    ...
    // Start the certificate manager if it was enabled.
    if kl.serverCertificateManager != nil {
        kl.serverCertificateManager.Start()
    }
    ...
    return nil
}

那启动证书Manager它会干啥呢,它就是主要负责生成证书的,通过检测证书的有效期,向kube-apiserver发送CSR请求,等待kube-apiserver Approve请求后,将证书写入到文件系统、更新到缓存

func (m *manager) Start() {
    ...

    templateChanged := make(chan struct{})
    go wait.Until(func() {
                
                // BackOff方式刷新证书
        if err := wait.ExponentialBackoff(backoff, m.rotateCerts); err != nil {
            utilruntime.HandleError(fmt.Errorf("%s: Reached backoff limit, still unable to rotate certs: %v", m.name, err))
            wait.PollInfinite(32*time.Second, m.rotateCerts)
        }
    }, time.Second, m.stopCh)

    ...
}

func (m *manager) rotateCerts() (bool, error) {
    // 生成证书请求文件
    template, csrPEM, keyPEM, privateKey, err := m.generateCSR()
    ...

    // kube-apiserver的请求客户端
    clientSet, err := m.getClientset()
    ...
        
         // 构建 CertificateSigningRequest资源, 请求kube-apiserver创建资源
    reqName, reqUID, err := csr.RequestCertificate(clientSet, csrPEM, "", m.signerName, m.requestedCertificateLifetime, usages, privateKey)
    ...
    
        // 等待kube-apiserver Approve证书请求
    crtPEM, err := csr.WaitForCertificate(ctx, clientSet, reqName, reqUID)
    ...
        
         // 将证书和KEY 写入到文件系统 /etc/kubernetes/ssl/kubelet-server-2022-03-11-15-01-21.pem
         // 创建文件 /etc/kubernetes/ssl/kubelet-server-current.pem 链接到上面的pem文件
         // 其中目录 /etc/kubernetes/ssl 是通过 --cert-dir 启动参数指定的
    cert, err := m.certStore.Update(crtPEM, keyPEM)
    
        ... 

        // 将证书更新到缓存中
    if old := m.updateCached(cert); old != nil && m.certificateRotation != nil {
        m.certificateRotation.Observe(m.now().Sub(old.Leaf.NotBefore).Seconds())
    }

    return true, nil
}

kubelet服务端启动

在启动参数中我们提供了服务端的监听地址为0.0.0.0:10250;以及是否需要启用只读端口,read-only-port=0表示不启用

--address=0.0.0.0
--port=10250
--read-only-port=0

然后再kubelet启动的时候会判断我们是否需要启动HTTPS服务器以及是否启动只读端口监听

func startKubelet(k kubelet.Bootstrap, podCfg *config.PodConfig, kubeCfg *kubeletconfiginternal.KubeletConfiguration, kubeDeps *kubelet.Dependencies, enableServer bool) {
    ...
    // start the kubelet server
    if enableServer {
        go k.ListenAndServe(kubeCfg, kubeDeps.TLSOptions, kubeDeps.Auth, kubeDeps.TracerProvider)
    }
    if kubeCfg.ReadOnlyPort > 0 {
        go k.ListenAndServeReadOnly(netutils.ParseIPSloppy(kubeCfg.Address), uint(kubeCfg.ReadOnlyPort))
    }
    ...
}

启动HTTPS服务器

使用的TLS配置就是之前初始化出来的tlsOptions,虽然没有指定证书和私钥,但是指定了GetCertificate 方法用于获取证书,而获取到的证书就是证书Manager 更新出来的

func ListenAndServeKubeletServer(
    host HostInterface,
    resourceAnalyzer stats.ResourceAnalyzer,
    kubeCfg *kubeletconfiginternal.KubeletConfiguration,
    tlsOptions *TLSOptions,
    auth AuthInterface,
    tp oteltrace.TracerProvider) {

    address := netutils.ParseIPSloppy(kubeCfg.Address)
    port := uint(kubeCfg.Port)
    klog.InfoS("Starting to listen", "address", address, "port", port)
    handler := NewServer(host, resourceAnalyzer, auth, tp, kubeCfg)
    s := &http.Server{
        Addr:           net.JoinHostPort(address.String(), strconv.FormatUint(uint64(port), 10)),
        Handler:        &handler,
        IdleTimeout:    90 * time.Second, // matches http.DefaultTransport keep-alive timeout
        ReadTimeout:    4 * 60 * time.Minute,
        WriteTimeout:   4 * 60 * time.Minute,
        MaxHeaderBytes: 1 << 20,
    }

    if tlsOptions != nil {
        s.TLSConfig = tlsOptions.Config
        // Passing empty strings as the cert and key files means no
        // cert/keys are specified and GetCertificate in the TLSConfig
        // should be called instead.
        if err := s.ListenAndServeTLS(tlsOptions.CertFile, tlsOptions.KeyFile); err != nil {
            klog.ErrorS(err, "Failed to listen and serve")
            os.Exit(1)
        }
    } else if err := s.ListenAndServe(); err != nil {
        klog.ErrorS(err, "Failed to listen and serve")
        os.Exit(1)
    }
}
启动只读服务器
func ListenAndServeKubeletReadOnlyServer(
    host HostInterface,
    resourceAnalyzer stats.ResourceAnalyzer,
    address net.IP,
    port uint) {
    klog.InfoS("Starting to listen read-only", "address", address, "port", port)
    // TODO: https://github.com/kubernetes/kubernetes/issues/109829 tracer should use WithPublicEndpoint
    s := NewServer(host, resourceAnalyzer, nil, oteltrace.NewNoopTracerProvider(), nil)

    server := &http.Server{
        Addr:           net.JoinHostPort(address.String(), strconv.FormatUint(uint64(port), 10)),
        Handler:        &s,
        IdleTimeout:    90 * time.Second, // matches http.DefaultTransport keep-alive timeout
        ReadTimeout:    4 * 60 * time.Minute,
        WriteTimeout:   4 * 60 * time.Minute,
        MaxHeaderBytes: 1 << 20,
    }

    if err := server.ListenAndServe(); err != nil {
        os.Exit(1)
    }
}

只读服务器和HTTPS服务器的区别在于,提供的是HTTP服务,然后指定了auth=nil 即不启用认证和授权;

HTTPS服务器的认证和授权

一般情况下,由于只读服务器提供的是HTTP服务,并且未开启认证,所以通常情况下处于安全考虑,我们会关闭只读端口;那HTTPS服务器是怎么认证和授权的呢?

func BuildAuth(nodeName types.NodeName, client clientset.Interface, config kubeletconfig.KubeletConfiguration) (server.AuthInterface, func(<-chan struct{}), error) {
    // Get clients, if provided
    var (
        tokenClient authenticationclient.AuthenticationV1Interface
        sarClient   authorizationclient.AuthorizationV1Interface
    )
        // 使用的是kube-apiserver的认证和授权API
    if client != nil && !reflect.ValueOf(client).IsNil() {
        tokenClient = client.AuthenticationV1()
        sarClient = client.AuthorizationV1()
    }

         // 构建认证规则
    authenticator, runAuthenticatorCAReload, err := BuildAuthn(tokenClient, config.Authentication)
    ...

    attributes := server.NewNodeAuthorizerAttributesGetter(nodeName)

         // 构建授权规则
    authorizer, err := BuildAuthz(sarClient, config.Authorization)
        ...

    return server.NewKubeletAuth(authenticator, attributes, authorizer), runAuthenticatorCAReload, nil
}

可以看到认证和授权主要依赖kube-apiserver的认证和授权API,具体的认证和授权有哪些方式呢?

认证
func BuildAuthn(client authenticationclient.AuthenticationV1Interface, authn kubeletconfig.KubeletAuthentication) (authenticator.Request, func(<-chan struct{}), error) {
    var dynamicCAContentFromFile *dynamiccertificates.DynamicFileCAContent
    var err error
        // 启动参数 --client-ca-file=/etc/kubernetes/ssl/ca.pem;第一种认证方式是通过证书访问,证书是通过这个CA证书签发的
    if len(authn.X509.ClientCAFile) > 0 {
        dynamicCAContentFromFile, err = dynamiccertificates.NewDynamicCAContentFromFile("client-ca-bundle", authn.X509.ClientCAFile)
        ...
    }

    authenticatorConfig := authenticatorfactory.DelegatingAuthenticatorConfig{
                // 启动参数--anonymous-auth=true,启用匿名用户,即所有请求都能够通过认证
        Anonymous:                          authn.Anonymous.Enabled,
        CacheTTL:                           authn.Webhook.CacheTTL.Duration,
        ClientCertificateCAContentProvider: dynamicCAContentFromFile,
    }

          // 启动参数--authentication-token-webhook=true,通过kube-apiserver请求TokenAccessReview资源,验证Token是否合法
    if authn.Webhook.Enabled {
        ...
        authenticatorConfig.WebhookRetryBackoff = genericoptions.DefaultAuthWebhookRetryBackoff()
        authenticatorConfig.TokenAccessReviewClient = client
    }

    authenticator, _, err := authenticatorConfig.New()
    ...

    return authenticator, ..., err
}

可以看到kubelet支持三种认证方式:

  • 携带证书访问,证书是通过--client-ca-file签发的,那么证书种的CN 和 O 会作为用户和组信息去授权

  • 携带Token访问,kubelet会像kube-apiserver发起TokenAccessReview请求,验证Token的合法性;当然为了避免每次都要去请求kube-apiserver,kubelet会对Token做缓存

  • 啥也不带,匿名用户访问

授权
func BuildAuthz(client authorizationclient.AuthorizationV1Interface, authz kubeletconfig.KubeletAuthorization) (authorizer.Authorizer, error) {

        // 启动参数 --authorization-mode=Webhook
    switch authz.Mode {
    ...
    case kubeletconfig.KubeletAuthorizationModeWebhook:
        ...
                // Webhook 授权模式,请求kube-apiserver的SubjectAccessReview资源,查看用户是否具有权限
        authorizerConfig := authorizerfactory.DelegatingAuthorizerConfig{
            SubjectAccessReviewClient: client,
            AllowCacheTTL:             authz.Webhook.CacheAuthorizedTTL.Duration,
            DenyCacheTTL:              authz.Webhook.CacheUnauthorizedTTL.Duration,
            WebhookRetryBackoff:       genericoptions.DefaultAuthWebhookRetryBackoff(),
        }
        return authorizerConfig.New()
    ...
    }
}

可以看到kubelet种授权主要是通过向kube-apiserver创建SubjectAccessReview资源,该资源会把请求方法、请求资源路径、用户信息、组信息打包到一起发送给kube-apiserver,由kube-apiserver判定是否有权限

认证和授权都看完了,这就意味着我们可以向请求kube-apiserver一样的请求kubelet的HTTPS服务端,仅限于证书和Token这两种方式,因为kubelet没有支持其它的kube-apiserver种的认证和授权方式。

kubelet服务端暴露的端点

认证和授权看完后,我们再看下kubelet暴露了那些HTTP端点

func NewServer(
    host HostInterface,
    resourceAnalyzer stats.ResourceAnalyzer,
    auth AuthInterface,
    tp oteltrace.TracerProvider,
    kubeCfg *kubeletconfiginternal.KubeletConfiguration) Server {

    server := Server{
        host:                 host,
        resourceAnalyzer:     resourceAnalyzer,
        auth:                 auth,
        restfulCont:          &filteringContainer{Container: restful.NewContainer()},
        metricsBuckets:       sets.NewString(),
        metricsMethodBuckets: sets.NewString("OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT"),
    }
        // 服务端是否需要启用认证和授权,只读端口由于指定了auth=nil,所以不会认证和授权;但HTTPS服务端会启用
    if auth != nil {
        server.InstallAuthFilter()
    }
    ...
    server.InstallDefaultHandlers()
    ...
    return server
}

func (s *Server) InstallDefaultHandlers() {
    s.addMetricsBucketMatcher("healthz")

         // 端点 /healthz  检查kubelet健康状况
    healthz.InstallHandler(s.restfulCont,
        healthz.PingHealthz,
        healthz.LogHealthz,
        healthz.NamedCheck("syncloop", s.syncLoopHealthCheck),
    )

    ...
       // 端点 /pods   获取kubelet所在节点上的所有POD
    s.addMetricsBucketMatcher("pods")
    ws := new(restful.WebService)
    ws.
        Path("/pods").
        Produces(restful.MIME_JSON)
    ws.Route(ws.GET("").
        To(s.getPods).
        Operation("getPods"))
    s.restfulCont.Add(ws)

        // 端点 /stats/summary  获取kubelet所在节点CPU、内存、POD信息
    s.addMetricsBucketMatcher("stats")
    s.restfulCont.Add(stats.CreateHandlers(statsPath, s.host, s.resourceAnalyzer))

        // 端点 /metrics  获取指标数据
    s.addMetricsBucketMatcher("metrics")
    s.addMetricsBucketMatcher("metrics/cadvisor")
    s.addMetricsBucketMatcher("metrics/probes")
    s.addMetricsBucketMatcher("metrics/resource")
    s.restfulCont.Handle(metricsPath, legacyregistry.Handler())

    ...
         ...
}

可以看到,kubelet作为HTTPS服务端所提供的端口很少,都是些功能性的API

  • /healthz:检测健康状态,会检查kubelet的日志有没有正常在刷(2min)、kubelet的主功能syncLoop有没有在正常执行(5min)

    • /healthz/log: 单独检查日志是否正常在刷的健康状态
    • /healthz/syncloop : 也是单独检查syncloop的健康状态
  • /pods :获取节点的POD,通过kube-apiserver指定 spec.nodeName=当前节点获取

  • /stats/summary: 获取kubelet所在节点CPU、内存、POD信息,通过cadvisor获取

  • /metrics: 获取指标数据,通过Prometheus获取

结合认证和和授权,我们调用这些端点试下,这里使用的token就是我们请求kube-apiserver所使用的token:

curl -k -H 'Authorization: Bearer <token>'  https://1.1.1.1:10250/healthz

> ok

curl -k -H 'Authorization: Bearer <token>'  https://1.1.1.1:10250/pods

> xxxxxx一大串

kubelet访问kube-apiserver

上面介绍的kubelet的这些功能都有个前提条件是,kubelet能够正常访问kube-apiserver,那kubelet是怎么访问kube-apiserver的呢?

func buildKubeletClientConfig(ctx context.Context, s *options.KubeletServer, tp oteltrace.TracerProvider, nodeName types.NodeName) (*restclient.Config, func(), error) {
        // 启动参数--rotate-certificates,指定是否需要自动刷新访问kube-apiserver的证书
    if s.RotateCertificates {
        ...
                // 启动参数--kubeconfig=/etc/kubernetes/kubelet.kubeconfig,指定了kubelet访问kube-apiserver的kubeconfig文件
                // 启动参数--bootstrap-kubeconfig=/etc/kubernetes/bootstrap.kubeconfig, 指定了证书到期后通过这个bootstrap文件去刷新证书
        certConfig, clientConfig, err := bootstrap.LoadClientConfig(s.KubeConfig, s.BootstrapKubeconfig, s.CertDirectory)
        ...

                 // 也是创建证书Manager,跟服务端的证书刷新是同一个原理
        clientCertificateManager, err := buildClientCertificateManager(certConfig, clientConfig, s.CertDirectory, nodeName)
        ...
        clientCertificateManager.Start()

        return transportConfig, onHeartbeatFailure, nil
    }
    ...
}

func LoadClientConfig(kubeconfigPath, bootstrapPath, certDir string) (certConfig, userConfig *restclient.Config, err error) {
    ...
    store, err := certificate.NewFileStore("kubelet-client", certDir, certDir, "", "")
    ...
          // --kubeconfig=/etc/kubernetes/kubelet.kubeconfig指向的kubeconfig文件是否存在,已经文件里的证书是否还有效
    ok, err := isClientConfigStillValid(kubeconfigPath)
    ...
    // 证书仍然有效,则继续使用这个证书
    if ok {
        clientConfig, err := loadRESTClientConfig(kubeconfigPath)
        ...
    }

        // 证书已经失效了,使用bootstrap去请求新的证书文件
    bootstrapClientConfig, err := loadRESTClientConfig(bootstrapPath)
    ...
    ...
}

可以看到kubelet访问kube-apiserver也是依赖kubeconfig文件,如果我们指定了一个证书仍然有效的kubeconfig文件,那么将会直接使用这个kubeconfig文件去访问kube-apiserver;

如果我们没有指定或者指定的kubeconfig文件已经到期了,那么在启用了客户端证书自动刷新的前提下,会通过我们指定bootstrap文件去获取有效的证书,然后将证书保存到kubeconfig文件中;

bootstrap获取证书

那么bootstrap是怎么获取证书的呢?一般情况下,我们的bootstrap文件长这样

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: base64_data
    server: https://1.1.1.1:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: system:bootstrap:911963
  name: default
current-context: default
kind: Config
preferences: {}
users:
- name: system:bootstrap:911963
  user:
    token: 911963.7c55ce1710186e66

而kube-apiserver也是支持bootstrap token这种认证方式的,在kube-apiserver的代码中可以找到它的踪迹

// kube-apiserver的启动参数 --enable-bootstrap-token-auth
if authenticatorConfig.BootstrapToken {
        authenticatorConfig.BootstrapTokenAuthenticator = bootstrap.NewTokenAuthenticator(
            versionedInformer.Core().V1().Secrets().Lister().Secrets(metav1.NamespaceSystem),
        )
}

// kube-apiserver的启动参数 --enable-bootstrap-token-auth,上述也设置了BootstrapTokenAuthenticator ;因此这里会添加一个认证器
if config.BootstrapToken && config.BootstrapTokenAuthenticator != nil {
        tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, config.BootstrapTokenAuthenticator))
}

// 这个认证器只是简单的包装了一下BootstrapTokenAuthenticator ,还得看BootstrapTokenAuthenticator 是怎么认证token的
func (a *audAgnosticTokenAuthenticator) AuthenticateToken(ctx context.Context, tok string) (*Response, bool, error) {
    return authenticate(ctx, a.implicit, func() (*Response, bool, error) {
        return a.delegate.AuthenticateToken(ctx, tok)
    })
}

func (t *TokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
        // 根据token=911963.7c55ce1710186e66,找到用小数点划分的两部分
    tokenID, tokenSecret, err := bootstraptokenutil.ParseToken(token)
    ...
        // 在kube-system命名空间下存在对应的Secret,类型必须是 bootstrap.kubernetes.io/token
        // bootstrap-token-911963                           bootstrap.kubernetes.io/token         6
    secretName := "bootstrap-token-" + tokenID
    secret, err := t.lister.Get(secretName)
    ...

    ...
        // 这个Secret中记录了 7c55ce1710186e66 这一串数据,token中的数据需要和这个一致才能通过认证
    ts := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenSecretKey)
    if subtle.ConstantTimeCompare([]byte(ts), []byte(tokenSecret)) != 1 {
        ...
    }
        // 同样这个Secret中也记录了过期时间,需要在有效期内;过期时间置空表示永不过期
    if bootstrapsecretutil.HasExpired(secret, time.Now()) {
        ...
    }
        // 认证完成后,找到这个token对应的用户和组信息,默认的组为:system:bootstrappers
    groups, err := bootstrapsecretutil.GetGroups(secret)
    ...

       // 这里再后面授权的时候主要就是依靠组信息,用户信息不重要
    return &authenticator.Response{
        User: &user.DefaultInfo{
            Name:   bootstrapapi.BootstrapUserPrefix + string(id),
            Groups: groups,
        },
    }, true, nil
}

// 最后通过RBAC授权的时候,集群中一般会存在这么个 clusterrolebinding;它关联了用户组system:bootstrappers的权限
  roleRef:
    apiGroup: rbac.authorization.k8s.io
    kind: ClusterRole
    name: system:node-bootstrapper
  subjects:
  - apiGroup: rbac.authorization.k8s.io
    kind: Group
    name: system:bootstrappers

// 而这个组的权限对应的clusterrole,可以看到就是CSR请求的创建、查询、WATCH,所以kubelet才能够通过bootstrap token申请证书
rules:
- apiGroups:
  - certificates.k8s.io
  resources:
  - certificatesigningrequests
  verbs:
  - create
  - get
  - list
  - watch

可以看到bootstrap token申请证书主要依赖kube-apiserver的bootstrap token认证,以及所属组system:bootstrappers默认就有CSR证书请求的权限

客户端证书怎么刷新

前面已经了解过了,证书的刷新仍然是由证书Manager完成,刷新过程和服务端证书的刷新是一致的

总结

通过这个文章,大致摸清楚了这些小问题:

  • kubelet作为服务端堆外暴露的10250端口怎么认证和授权的

  • kubelet的只读端口是怎么工作的

  • kubelet作为客户端怎么访问kube-apiserver的

  • kubelet访问kube-apiserver的证书到期后怎么自动更新的

相关文章

网友评论

      本文标题:Kubelet的边角料功能笔记

      本文链接:https://www.haomeiwen.com/subject/bmdpudtx.html