详解 Kubernetes Pod imagePullPolicy

详解 Kubernetes Pod imagePullPolicy

问题背景

Kubernetes 管理下的容器会在什么情况下对容器镜像重新拉取?

概念理解

官方文档:https://v1-12.docs.kubernetes.io/docs/concepts/containers/images/#updating-images

对一个 Pod 来说,spec.containers.imagePullPolicy字段用于管理容器镜像的拉取策略,可选项为IfNotPresentAlways

默认的 imagePullPolicy 为IfNotPresent,image配置如image: nginx:1.12.5,当宿主存在该镜像时,kubelet 会自动跳过镜像拉取的步骤;

如果希望每次容器启动时都从镜像库拉取镜像,可以通过以下方式中的任一一种来配置:

  1. 将 imagePullPolicy 设为 Always;
  2. 不配置 imagePullPolicy, 将容器镜像的版本号设置为:latest,即 images: nginx:latest
  3. 不配置 imagePullPolicy,也不配置容器版本号,如images: nginx

实验测试

测试准备

nginx-deployment.yaml 配置 container 对应的 imagePullPolicy: Always

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  minReadySeconds: 30
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.15.4
        imagePullPolicy: Always
        ports:
        - containerPort: 80

测试方法

  1. 创建好对应的 Deployment, 观察某个 Pod 的事件
  2. 通过某一种手段,不对 Pod 进行操作,只是使得 Pod 里的 Container 退出(模拟 OOM场景);
  3. 观测 Pod 元数据改变情况,观测 docker image 拉取情况。

测试过程

root@kmaster135:/home/chenjiaxi01/yaml/controllers# kubectl get pods -o wide| grep nginx
nginx-deployment-57f495d87b-k6g48   1/1     Running     2          173m   10.244.2.158   dnode137   <none>
nginx-deployment-57f495d87b-rcvlg   1/1     Running     0          173m   10.244.2.156   dnode137   <none>
nginx-deployment-57f495d87b-tnmrq   1/1     Running     0          173m   10.244.1.141   dnode136   <none>

定位到宿主上:

root@dnode137:~# docker ps | grep nginx-deployment-57f495d87b-rcvlg
9947530bc668        nginx@sha256:e8ab8d42e0c34c104ac60b43ba60b19af08e19a0e6d50396bdfd4cef0347ba83   "nginx -g 'daemon ..."   2 hours ago         Up 2 hours                              k8s_nginx_nginx-deployment-57f495d87b-rcvlg_default_40cc107c-ee33-11e9-8ba3-000c290b4cc5_0
6436b335a958        k8s.gcr.io/pause:3.1                                                            "/pause"                 2 hours ago         Up 2 hours                              k8s_POD_nginx-deployment-57f495d87b-rcvlg_default_40cc107c-ee33-11e9-8ba3-000c290b4cc5_0

停掉 Nginx 容器 9947530bc668:

root@dnode137:~# docker stop 9947530bc668
9947530bc668

观察 Pod 情况:

root@kmaster135:/home/chenjiaxi01/yaml/controllers# kubectl describe pods nginx-deployment-57f495d87b-rcvlg
Name:               nginx-deployment-57f495d87b-rcvlg
Namespace:          default
Priority:           0
PriorityClassName:  <none>
Node:               dnode137/192.168.77.137
Start Time:         Sun, 13 Oct 2019 20:32:30 -0700
Labels:             app=nginx
                    pod-template-hash=57f495d87b
Annotations:        <none>
Status:             Running
IP:                 10.244.2.156
Controlled By:      ReplicaSet/nginx-deployment-57f495d87b
Containers:
  nginx:
    Container ID:   docker://29a227a4831cce1f02bd83512dfcc377f703f5663ed5e6409a3db2f0884ba374
    Image:          nginx:1.15.4
    Image ID:       docker-pullable://nginx@sha256:e8ab8d42e0c34c104ac60b43ba60b19af08e19a0e6d50396bdfd4cef0347ba83
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Sun, 13 Oct 2019 23:27:29 -0700
    Last State:     Terminated
      Reason:       Completed
      Exit Code:    0
      Started:      Sun, 13 Oct 2019 20:32:46 -0700
      Finished:     Sun, 13 Oct 2019 23:27:23 -0700
    Ready:          True
    Restart Count:  1
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-zh48z (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
...
Events:
  Type    Reason   Age                 From               Message
  ----    ------   ----                ----               -------
  Normal  Pulling  32s (x2 over 175m)  kubelet, dnode137  pulling image "nginx:1.15.4"
  Normal  Pulled   27s (x2 over 175m)  kubelet, dnode137  Successfully pulled image "nginx:1.15.4"
  Normal  Created  27s (x2 over 175m)  kubelet, dnode137  Created container
  Normal  Started  27s (x2 over 175m)  kubelet, dnode137  Started container

测试结论

  1. Pod Start Time 保持不变,所以可以认为是 Pod 重启,而不是删掉 Pod 重建;
  2. Node 不会发生变化,也就是不会重新调度;
  3. IP 是否会发生变化?停掉 App Container 的时候没有发生变化,如果停掉的是 Infra Container 呢?猜想应该是有可能变化的,因为是重建 Sandbox 时由 CNI 负责的;
  4. Containers 字段里由于容器发生了重建,所以都会有变化;可以看到 State里有本次容器启动的情况,Last State保留了上一次容器运行的基本情况;
  5. Containers Restart Count 从 0 变为 1,表示容器的重启次数,这个数值也会体现在 kubectl get pod 里;
  6. 从 Events 中可以看到,kubelet, dnode137 pulling image "nginx:1.15.4" 重新拉取了容器镜像用于拉起新容器;如果是默认的配置imagePullPolicy: IfNotPresent则该信息为Container image "nginx:1.15.4" already present on machine,如果 image 已经存在的话。

源码分析

在 kublet 的代码里,在主循环SyncPod有相应处理逻辑负责 image 进行拉取:

  1. func SyncPod():pkg/kubelet/kuberuntime/kuberuntime_manager.go:578
// SyncPod syncs the running pod into the desired pod by executing following steps:
//
//  1. Compute sandbox and container changes.
//  2. Kill pod sandbox if necessary.
//  3. Kill any containers that should not be running.
//  4. Create sandbox if necessary.
//  5. Create init containers.
//  6. Create normal containers.
func (m *kubeGenericRuntimeManager) SyncPod(pod *v1.Pod, _ v1.PodStatus, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, backOff *flowcontrol.Backoff) (result kubecontainer.PodSyncResult) {
...
        glog.V(4).Infof("Creating init container %+v in pod %v", container, format.Pod(pod))
        if msg, err := m.startContainer(podSandboxID, podSandboxConfig, container, pod, podStatus, pullSecrets, podIP, kubecontainer.ContainerTypeInit); err != nil {
            startContainerResult.Fail(err, msg)
            utilruntime.HandleError(fmt.Errorf("init container start failed: %v: %s", err, msg))
            return
        }
...
}
  1. func StartContainer: pkg/kubelet/kuberuntime/kuberuntime_container.go:89
// startContainer starts a container and returns a message indicates why it is failed on error.
// It starts the container through the following steps:
// * pull the image
// * create the container
// * start the container
// * run the post start lifecycle hooks (if applicable)
func (m *kubeGenericRuntimeManager) startContainer(podSandboxID string, podSandboxConfig *runtimeapi.PodSandboxConfig, container *v1.Container, pod *v1.Pod, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, podIP string, containerType kubecontainer.ContainerType) (string, error) {
    // Step 1: pull the image.
    imageRef, msg, err := m.imagePuller.EnsureImageExists(pod, container, pullSecrets)
    if err != nil {
        m.recordContainerEvent(pod, container, "", v1.EventTypeWarning, events.FailedToCreateContainer, "Error: %v", grpc.ErrorDesc(err))
        return msg, err
    }
    ...
}
  1. type ImageManager interface:pkg/kubelet/images/types.go:50
// ImageManager provides an interface to manage the lifecycle of images.
// Implementations of this interface are expected to deal with pulling (downloading),
// managing, and deleting container images.
// Implementations are expected to abstract the underlying runtimes.
// Implementations are expected to be thread safe.
type ImageManager interface {
    // EnsureImageExists ensures that image specified in `container` exists.
    EnsureImageExists(pod *v1.Pod, container *v1.Container, pullSecrets []v1.Secret) (string, string, error)

    // TODO(ronl): consolidating image managing and deleting operation in this interface
}
  1. func EnsureImageExist:pkg/kubelet/images/image_manager.go:86
// EnsureImageExists pulls the image for the specified pod and container, and returns
// (imageRef, error message, error).
func (m *imageManager) EnsureImageExists(pod *v1.Pod, container *v1.Container, pullSecrets []v1.Secret) (string, string, error) {
    ...
    present := imageRef != ""
    if !shouldPullImage(container, present) {
        if present {
            msg := fmt.Sprintf("Container image %q already present on machine", container.Image)
            m.logIt(ref, v1.EventTypeNormal, events.PulledImage, logPrefix, msg, glog.Info)
            return imageRef, "", nil
        } else {
            msg := fmt.Sprintf("Container image %q is not present with pull policy of Never", container.Image)
            m.logIt(ref, v1.EventTypeWarning, events.ErrImageNeverPullPolicy, logPrefix, msg, glog.Warning)
            return "", msg, ErrImageNeverPull
        }
    }
    ...
}
  1. func shouldPullImage:pkg/kubelet/images/image_manager.go:62
// shouldPullImage returns whether we should pull an image according to
// the presence and pull policy of the image.
func shouldPullImage(container *v1.Container, imagePresent bool) bool {
    if container.ImagePullPolicy == v1.PullNever {
        return false
    }

    if container.ImagePullPolicy == v1.PullAlways ||
        (container.ImagePullPolicy == v1.PullIfNotPresent && (!imagePresent)) {
        return true
    }

    return false
}

新的问题: 当容器配置形如image: nginx:latest时,imagePullPolicy默认配置为Always,管理默认配置的代码在哪里?

pkg/apis/core/v1/defaults.go:77:

func SetDefaults_Container(obj *v1.Container) {
    if obj.ImagePullPolicy == "" {
        // Ignore error and assume it has been validated elsewhere
        _, tag, _, _ := parsers.ParseImageName(obj.Image)

        // Check image tag
        if tag == "latest" {
            obj.ImagePullPolicy = v1.PullAlways
        } else {
            obj.ImagePullPolicy = v1.PullIfNotPresent
        }
    }
    if obj.TerminationMessagePath == "" {
        obj.TerminationMessagePath = v1.TerminationMessagePathDefault
    }
    if obj.TerminationMessagePolicy == "" {
        obj.TerminationMessagePolicy = v1.TerminationMessageReadFile
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容