基于kubernetes v1.18.6
概览
kubelet
启动容器的第一步便是拉取镜像,核心源码如下:
kubernetes\pkg\kubelet\kuberuntime\kuberuntime_container.go
func (m *kubeGenericRuntimeManager) startContainer(podSandboxID string, podSandboxConfig *runtimeapi.PodSandboxConfig, spec *startSpec, pod *v1.Pod, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, podIP string, podIPs []string) (string, error) {
container := spec.container
// Step 1: pull the image.
imageRef, msg, err := m.imagePuller.EnsureImageExists(pod, container, pullSecrets, podSandboxConfig)
if err != nil {
s, _ := grpcstatus.FromError(err)
m.recordContainerEvent(pod, container, "", v1.EventTypeWarning, events.FailedToCreateContainer, "Error: %v", s.Message())
return msg, err
}
...
}
接下来我们对m.imagePuller.EnsureImageExists()
函数深入分析
函数入参及返回值解析
入参解析
-
pod *v1.Pod
入参: 当前容器所属pod
实例 -
container *v1.Container
: 容器实例(包含镜像名称等元数据信息) -
pullSecrets []v1.Secret
: 拉取镜像所需的凭据 -
podSandboxConfig *runtimeapi.PodSandboxConfig
:pod
沙箱配置实例(包含主机名、dns、日志目录等)
返回值解析
- 返回值一镜像像的引用(镜像摘要或
ID
,如harbor.wl.io/library/redis:5.0.12
): 返回本地存储中容器的镜像引用(摘要或ID),如果镜像不在本地存储中返回""。 - 返回值二镜像拉取信息:失败/成功及原因
- 返回值三异常:拉取镜像过程中产生的异常(如拉取镜像网络不可达)
设置镜像tag默认值
当容器镜像不存在tag
时,会设置缺省tag
值: latest
func (m *imageManager) EnsureImageExists(pod *v1.Pod, container *v1.Container, pullSecrets []v1.Secret, podSandboxConfig *runtimeapi.PodSandboxConfig) (string, string, error) {
...
// If the image contains no tag or digest, a default tag should be applied.
// 为没有摘要或tag的镜像设置默认tag: latest
image, err := applyDefaultImageTag(container.Image)
if err != nil {
msg := fmt.Sprintf("Failed to apply default image tag %q: %v", container.Image, err)
m.logIt(ref, v1.EventTypeWarning, events.FailedToInspectImage, logPrefix, msg, klog.Warning)
return "", msg, ErrInvalidImageName
}
...
}
例如:下面
spec.containers[0].image
只声明了镜像名称,未声明tag
$ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: nginx-with-no-tag
spec:
containers:
- name: nginx
image: nginx
EOF
查看pod
事件,发现并没有因为未指定镜像tag
而中断
$ kubectl describe pod nginx-with-no-tag
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 119s default-scheduler Successfully assigned default/nginx-with-no-tag to node69
Normal Pulling 118s kubelet, node69 Pulling image "nginx"
Normal Pulled 74s kubelet, node69 Successfully pulled image "nginx"
Normal Created 73s kubelet, node69 Created container nginx
Normal Started 73s kubelet, node69 Started container nginx
查询本地镜像列表
$ docker images|grep nginx
nginx latest ea335eea17ab 44 hours ago 141MB
拉取镜像
1.判断是否需要拉取镜像
以下情况需要拉取镜像:
- 容器的镜像拉取策略为
Always
- 容器的镜像拉取策略为
IfNotPresent
,并且本地没有该镜像
如果需要拉取镜像继续后续流程,如果不需要拉取镜像直接返回,并根据以下场景返回对应信息:
- 容器的镜像拉取策略为
Never
,并且本地存在该镜像,则返回: 镜像引用信息(即镜像名称,如harbor.wl.io/library/redis:5.0.12
) - 容器的镜像拉取策略为
Never
,并且本地不存在该镜像,则返回异常:Container image xxx is not present with pull policy of Never
源码实现
func (m *imageManager) EnsureImageExists(pod *v1.Pod, container *v1.Container, pullSecrets []v1.Secret, podSandboxConfig *runtimeapi.PodSandboxConfig) (string, string, error) {
...
spec := kubecontainer.ImageSpec{Image: image}
imageRef, err := m.imageService.GetImageRef(spec)
if err != nil {
msg := fmt.Sprintf("Failed to inspect image %q: %v", container.Image, err)
m.logIt(ref, v1.EventTypeWarning, events.FailedToInspectImage, logPrefix, msg, klog.Warning)
return "", msg, ErrImageInspect
}
present := imageRef != ""
// 当容器的镜像拉取策略为Never时只用本地的镜像而非从镜像库拉取
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, klog.Info)
return imageRef, "", nil
}
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, klog.Warning)
return "", msg, ErrImageNeverPull
}
...
}
2.判断是否超过镜像拉取时间
--image-pull-progress-deadline
值,默认一分钟。
若在此截止日期前未进行镜像拉取,则镜像拉取将被取消。这个特定于docker
的标志只在容器运行时被设置为docker
时有效
func (m *imageManager) EnsureImageExists(pod *v1.Pod, container *v1.Container, pullSecrets []v1.Secret, podSandboxConfig *runtimeapi.PodSandboxConfig) (string, string, error) {
...
backOffKey := fmt.Sprintf("%s_%s", pod.UID, container.Image)
if m.backOff.IsInBackOffSinceUpdate(backOffKey, m.backOff.Clock.Now()) {
msg := fmt.Sprintf("Back-off pulling image %q", container.Image)
m.logIt(ref, v1.EventTypeNormal, events.BackOffPullImage, logPrefix, msg, klog.Info)
return "", msg, ErrImagePullBackOff
}
...
}
3.拉取镜像操作
调用容器运行时拉取镜像,并开启垃圾回收。
func (m *imageManager) EnsureImageExists(pod *v1.Pod, container *v1.Container, pullSecrets []v1.Secret, podSandboxConfig *runtimeapi.PodSandboxConfig) (string, string, error) {
...
m.logIt(ref, v1.EventTypeNormal, events.PullingImage, logPrefix, fmt.Sprintf("Pulling image %q", container.Image), klog.Info)
pullChan := make(chan pullResult)
m.puller.pullImage(spec, pullSecrets, pullChan, podSandboxConfig)
imagePullResult := <-pullChan
if imagePullResult.err != nil {
m.logIt(ref, v1.EventTypeWarning, events.FailedToPullImage, logPrefix, fmt.Sprintf("Failed to pull image %q: %v", container.Image, imagePullResult.err), klog.Warning)
m.backOff.Next(backOffKey, m.backOff.Clock.Now())
if imagePullResult.err == ErrRegistryUnavailable {
msg := fmt.Sprintf("image pull failed for %s because the registry is unavailable.", container.Image)
return "", msg, imagePullResult.err
}
return "", imagePullResult.err.Error(), ErrImagePull
}
m.logIt(ref, v1.EventTypeNormal, events.PulledImage, logPrefix, fmt.Sprintf("Successfully pulled image %q", container.Image), klog.Info)
m.backOff.GC()
}
网友评论