1、问题描述

通过K8S集群部署应用时,时常会遇到pod的状态是ImagePullBackOffErrImagePull,查看pod的事件可以看到类似如下提示:

Warning  Failed     65s (x4 over 2m40s)  kubelet            Failed to pull image "tharbor.timesgroup.cn/chenhq/nginx-web1:v3": rpc error: code = Unknown desc = Error response from daemon: unauthorized: unauthorized to access repository: monitor/node-exporter, action: pull: unauthorized to access repository: monitor/node-exporter, action: pull

2、问题分析

出现这种情况说明是pod去harbor拉取镜像的时候没有认证通过。首先,我们确认在本地直接拉取镜像是否正常。最好去到pod所在节点执行:

docker pull tharbor.timesgroup.cn/chenhq/nginx-web1:v3

如果能正常拉取说明镜像没问题,本地认证也正常。如果也提示认证问题,则要先在本地节点执行harbor的登陆操作:

docker login tharbor.timesgroup.cn

根据提示输入harbor的账号和密码(注意,每个k8s节点都应该预先做好这个登录),然后节点就会保存了这个harbor的登录信息。我们可以在下面文件找到:

# cat ~/.docker/config.json 
{
        "auths": {
                "tharbor.timesgroup.cn": {
                        "auth": "YWRtaW46MTIzNDU2"
                }
        },
        "HttpHeaders": {
                "User-Agent": "Docker-Client/19.03.15 (linux)"
        }

本地拉取镜像正常后,有可能pod就能正常拉取镜像了。但是并不是一定的,我碰到过很多次本地正常但是pod依然不能拉取镜像的情况。

3、解决办法

一开始我的解决办法是很笨的,直接在每个节点先通过本地拉取的方式把镜像拉到本地,然后pod的镜像拉取的imagePullPolicy设置为IfNotPresent。因为本地已经有了所以不会重新拉取,也就跳过了这个问题。
这个方法确实比较蠢,所以我又找了另外一个办法。在pod里面设置imagePullSecrets。例如以下pod的yaml文件:

imagePullSecrets:
- name: registry-harbor
containers:
- name: node-exporter
  image: tharbor.timesgroup.cn/chenhq/nginx-web1:v3
  imagePullPolicy: IfNotPresent

这里用到的secret:registry-harbor 需要提前创建好,可使用以下命令创建:

kubectl create secret docker-registry registry-harbor --namespace=monitor \
--docker-server=tharbor.timesgroup.cn --docker-username=admin \
    --docker-password=123456 

注意:secret是分namespace的,要跟pod所在的命名空间一致。

这样也能解决,但是太麻烦了,不是么?每个pod都得设置这个,如果pod很多,那大概率要疯了。

4、更优的解决

既然pod能设置imagePullSecrets,那在namespace层面上能否设置imagePullSecrets呢?答案是不可以的,namespace并没有这个参数。

我们先来聊下pod是如何进行集群认证的。我们知道,K8S集群正常是需要认证通过才可以访问的,而集群内部应用认证用到的账号称为serviceaccout,即我们常说的sa账号。

默认情况下,k8s会给每一个namespace创建一个名字为default的sa账号,如下

#  kubectl get sa -A|grep default
default                default                              1         38d
kube-public            default                              1         38d
kube-system            default                              1         38d
kubernetes-dashboard   default                              1         37d

而pod在创建时,如果没有明确指定,则会默认使用这个default账号

# kubectl get pod web-server-74f7d596b5-thwtb -o jsonpath='{.spec.serviceAccountName}'
default

pod使用该sa账号的认证信息去访问api等服务。其中,也包括了镜像访问的服务。是的,sa可以设置imagePullSecrets

kubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "registry-harbor"}]}' -n monitor

上面我们给monitor这个命名空间下的default账号加上了imagePullSecrets参数,当然,其中的secret:registry-harbor 也是需要提前创建好的。

这样设置完,后续这个命名空间下的所有pod去拉取镜像的时候都会使用这个认证信息。这可比在pod设置方便多了。

如果你觉得在namespace层面设置还是很麻烦,想在集群层面设置好? 抱歉,暂时我还没想到办法,如果你知道了告诉我。