Kubernetes mirror pod & static pod

最近在将项目上使用的Kubernetes从1.5.3升级到了1.6.2,性能能正如官方描述的那样的,提升了不少,整个升级过程还算顺利。我们的kubernetes组件启动模式是这样的,kubelet是systemd的方式启动,交由操作系统的systemd进程来管理,kube-apiserver、kube-proxy、kube-controller-manager、kube-scheduler都是以static pod启动,交由kubelet进程管理。也就是说在kubernetes master节点也会启动一个kubelet进程。关于服务发现,我们使用的是CoreDNS,直接以一个Deployment的方式启动,并对应启动一个service,kubelet的启动参数--cluster_dns指定该service的IP即可,具体的操作可以查看CoreDNS官方blog.

Static Pod

Static pod 是由kubelet直接管理的,k8s api server并不会感知到static pod的存在,当然也不会和任何一个replication controller关联上,完全是由kubelet进程来监管,并在它崩溃的时候负责重启。Kubelet会通过api server为每一个static pod创建一个对应的mirror pod,如此以来我就可以可以通过kubectl命令或者在k8s dashboard 上查看到kube-apiserver等组件对应的pod,并且可以通过kubectl logs 命令直接查看到static pod的日志信息。但是api server无法管理mirror pod及对应的static pod,如删除操作。static pod的描述文件和正常pod完全一样,可以是yaml或者json格式,同样也可以指定namespace,kubelet会在指定的namespace下创建对应的mirror pod。

我们遇到的问题

我们从1.5升级到1.6之后,整个集群看起来都很健康,所有的业务组件都能正常运行,也就是说k8s组件相关的static pod都已经运行起来了,但是在master上执行kubectl get pod -n kube-system,并没有看到有对应的mirror pod(在static pod的yml文件中有指定namespace为kube-system)。查看任意一个node的kubelet日志发现如下错误:

May  3 11:09:52 node01 kubelet: E0507 11:09:52.411269   25439 kubelet.go:1535] Failed creating a mirror pod for "kube-apiserver-10.202.xxx.xxx_kube-system(2251328d5e4f61790ba8de3c943fa56f)": pods "kube-apiserver-10.202.xxx.xxx" is forbidden: a mirror pod may not reference secrets

由日志看出创建mirror pod的动作被禁止了。更奇怪的是在升级之前(kubernetes版本为1.5.3)并没有这个报错,所有的mirror pod都能正常创建,而且升级之后我们的所有static pod 描述文件没有任何修改,以kube-apiserver为例:

---
apiVersion: v1
kind: Pod
metadata:
  name: kube-apiserver
  namespace: kube-system
  labels:
    k8s-app: kube-apiserver
spec:
  containers:
    -
      command:
       - /usr/bin/run.sh
       - " --admission-control=NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota"
       - "--runtime-config=api/v1 --allow-privileged=true --etcd-servers=etcd_server_list"
       - "--kubelet-https=true --secure-port=6443 --bind-address=xx.xx.xx.xx"
       - "--client-ca-file=/opt/k8s/cert/ca.crt --tls-private-key-file=/opt/k8s/cert/server.key"
       - "--service-account-key-file=/opt/k8s/cert/server.key"
       - "--tls-cert-file=/opt/k8s/cert/server.crt --service-cluster-ip-range=10.200.0.0/16"
       - "--logtostderr=true --cors-allowed-origins='.*'"
       - "--enable-swagger-ui=true --anonymous-auth=false"

      image: "MY_SERVER:5000/kube-apiserver:1.6.2"
      imagePullPolicy: IfNotPresent
      name: kube-apiserver
      initialDelaySeconds: 15
      timeoutSeconds: 15
      ports:
        -
          containerPort: 6443
          hostPort: 6443
          name: https
        -
          containerPort: 8080
          hostPort: 8080
          name: http

      volumeMounts:
        -
          mountPath: /opt/k8s
          name: sslcert
          readOnly: true
  imagePullSecrets:
      - name: image-secret
  hostNetwork: true
  volumes:
    -
      hostPath:
        path: /opt/k8s
      name: sslcert

查看官方文档关于static pod的描述,并没有找到相关的解决方案,到github上按照错误关键词搜索,也没有找到问题的解决方法,但是有看到,从kubernetes最新版开始,需要对api server的访问进行更细粒度的权限控制,如kubelet只能watch相关的events,上报自己节点的状态信息等,但是无法通过一些serviceaccount或者secrets认证后post信息到apiserver。

我们的集群配置了x509的双向认证并且也开启了一些admission,kubelet如果要post数据到apiserver也会和其他任何外部进程一样,也会经过admission鉴权。整个集群是正常工作的,证明了kubelet所携带的证书都是正确的,可以和api server正常通信,但是日志中还是一直报forbidden: a mirror pod may not reference secrets。也就说在admission的某个步骤出现的认证错误,创建mirror pod的动作被禁止了。没办法,直接看代码吧,查看了admission相关的代码发现,1.6.2的代码如下:

~/github/kubernetes/plugin/pkg/admission/serviceaccount/admission.go

func (s *serviceAccount) Admit(a admission.Attributes) (err error) {
    if a.GetResource().GroupResource() != api.Resource("pods") {
        return nil
    }
    obj := a.GetObject()
    if obj == nil {
        return nil
    }
    pod, ok := obj.(*api.Pod)
    if !ok {
        return nil
    }

    // Don't modify the spec of mirror pods.
    // That makes the kubelet very angry and confused, and it immediately deletes the pod (because the spec doesn't match)
    // That said, don't allow mirror pods to reference ServiceAccounts or SecretVolumeSources either
    //关键代码
    if _, isMirrorPod := pod.Annotations[kubelet.ConfigMirrorAnnotationKey]; isMirrorPod {
        //检测serviceAccountName
        if len(pod.Spec.ServiceAccountName) != 0 {
            return admission.NewForbidden(a, fmt.Errorf("a mirror pod may not reference service accounts"))
        }
        //对secrets相关的检测
        hasSecrets := false
        podutil.VisitPodSecretNames(pod, func(name string) bool {
            hasSecrets = true
            return false
        })
        if hasSecrets {
            return admission.NewForbidden(a, fmt.Errorf("a mirror pod may not reference secrets"))
        }
        return nil
    }
    ...省略若干行...
}

~/github/kubernetes/pkg/api/v1/pod/util.go

func VisitPodSecretNames(pod *v1.Pod, visitor func(string) bool) bool {
    //对pod描述文件中的ImagePullSecrets检查
    for _, reference := range pod.Spec.ImagePullSecrets {
        if !visitor(reference.Name) {
            return false
        }
    }       
    ///
    for i := range pod.Spec.InitContainers {
        if !visitContainerSecretNames(&pod.Spec.InitContainers[i], visitor) {
            return false
        }
    }
    for i := range pod.Spec.Containers {
        if !visitContainerSecretNames(&pod.Spec.Containers[i], visitor) {
            return false
        }
    }
    ...省略若干行...
}

1.5的代码

~/github/kubernetes/plugin/pkg/admission/serviceaccount/admission.go

func (s *serviceAccount) Admit(a admission.Attributes) (err error) {
    if a.GetResource().GroupResource() != api.Resource("pods") {
        return nil
    }
    obj := a.GetObject()
    if obj == nil {
        return nil
    }
    pod, ok := obj.(*api.Pod)
    if !ok {
        return nil
    }

    // Don't modify the spec of mirror pods.
    // That makes the kubelet very angry and confused, and it immediately deletes the pod (because the spec doesn't match)
    // That said, don't allow mirror pods to reference ServiceAccounts or SecretVolumeSources either
    //关键代码
    if _, isMirrorPod := pod.Annotations[kubelet.ConfigMirrorAnnotationKey]; isMirrorPod {
        //serviceAccount检测
        if len(pod.Spec.ServiceAccountName) != 0 {
            return admission.NewForbidden(a, fmt.Errorf("a mirror pod may not reference service accounts"))
        }
        for _, volume := range pod.Spec.Volumes {
            if volume.VolumeSource.Secret != nil {
                return admission.NewForbidden(a, fmt.Errorf("a mirror pod may not reference secrets"))
            }
        }
        return nil
    }
    ...省略若干行...
}

在创建mirror pod的时候首先会检查在static pod 文件中有没有指定serviceAccount,如果指定,直接返回并打印“禁止”的错误信息。也就是说我们无法通过指定serviceAccount来认证并提交创建请求到api server。没关系,我们的文件里面也没指定serviceAccount。继续向下看代码,如果static pod文件中有指定ImagePullSecrets,也会返回函数并且打印“禁止”的错误信息。因为kubelet在创建mirror pod的时候,如果本地没有指定的docker image,也会执行pull镜像的操作,好吧,我们刚好中招,由于我们的docker registry 开启了用户验证,pull image的时候必须得经过帐号密码验证才可以,所以在我们的文件中指定了ImagePullSecrets

对比1.5的代码并没有对ImagePullSecrets进行监测,所以在升级kubernetes之前,mirror pod可以正常创建。

解决方法

去掉static pod文件中的ImagePullSecrets。我们的kubernetes集群是经过Ansible playbook自动创建的,在相关的playbook中增加一个task,在所有的节点将所需要的kubernetes组件的镜像提前pull下来,这样在kubelet创建mirror pod的时候就不需要重新pull镜像了,也自然不需要什么ImagePullSecrets了。

总结

Kubernets的文档比较滞后,在使用kubernetes这类的开源软件的时候,除了通读官方文档之外,源码才是王道。kubernetes release节奏比较快,社区也很健康,开发组很注重社区提交的新特性,并且会将比较好的特性很快的提上开发日程或者merage相关的pr,所以个人觉得文档滞后点也情有可原。此外,对kubernetes api server的访问,将来会有更细粒度的权限限制,在生产上大规模使用的时候,将来会很方便的满足更细粒度权限需求。

发表评论

电子邮件地址不会被公开。 必填项已用*标注