最近在将项目上使用的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的访问,将来会有更细粒度的权限限制,在生产上大规模使用的时候,将来会很方便的满足更细粒度权限需求。