先来简单回顾上一篇博客《kubernetes/k8s CRI 分析-容器运行时接口分析》的内容。

上篇博文先对 CRI 做了介绍,然后对 kubelet CRI 相关源码包括 kubelet 组件 CRI 相关启动参数分析、CRI 相关 interface/struct 分析、CRI 相关初始化分析 3 个部分进行了分析,没有看的小伙伴,可以点击上面的链接去看一下。

把上一篇博客分析到的CRI架构图再贴出来一遍。

本篇博文将对kubelet调用CRI创建pod做分析。

kubelet中CRI相关的源码分析

kubelet的CRI源码分析包括如下几部分:

(1)kubelet CRI相关启动参数分析;

(2)kubelet CRI相关interface/struct分析;

(3)kubelet CRI初始化分析;

(4)kubelet调用CRI创建pod分析;

(5)kubelet调用CRI删除pod分析。

上篇博文先对前三部分做了分析,本篇博文将对kubelet调用CRI创建pod做分析。

基于tag v1.17.4

https://github.com/kubernetes/kubernetes/releases/tag/v1.17.4

4.kubelet调用CRI创建pod分析

kubelet CRI创建pod调用流程

下面以kubelet dockershim创建pod调用流程为例做一下分析。

kubelet通过调用dockershim来创建并启动容器,而dockershim则调用docker来创建并启动容器,并调用CNI来构建pod网络。

图1:kubelet dockershim创建pod调用流程图示

dockershim属于kubelet内置CRI shim,其余remote CRI shim的创建pod调用流程其实与dockershim调用基本一致,只不过是调用了不同的容器引擎来操作容器,但一样由CRI shim调用CNI来构建pod网络。

下面开始详细的源码分析。

直接看到kubeGenericRuntimeManagerSyncPod方法,调用CRI创建pod的逻辑将在该方法里触发发起。

从该方法代码也可以看出,kubelet创建一个pod的逻辑为:

(1)先创建并启动pod sandbox容器,并构建好pod网络;

(2)创建并启动ephemeral containers;

(3)创建并启动init containers;

(4)最后创建并启动normal containers(即普通业务容器)。

这里对调用m.createPodSandbox来创建pod sandbox进行分析,m.startContainer等调用分析可以参照该分析自行进行分析,调用流程几乎一致。

  1. // pkg/kubelet/kuberuntime/kuberuntime_manager.go
  2. // SyncPod syncs the running pod into the desired pod by executing following steps:
  3. //
  4. // 1. Compute sandbox and container changes.
  5. // 2. Kill pod sandbox if necessary.
  6. // 3. Kill any containers that should not be running.
  7. // 4. Create sandbox if necessary.
  8. // 5. Create ephemeral containers.
  9. // 6. Create init containers.
  10. // 7. Create normal containers.
  11. func (m *kubeGenericRuntimeManager) SyncPod(pod *v1.Pod, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, backOff *flowcontrol.Backoff) (result kubecontainer.PodSyncResult) {
  12. ...
  13. // Step 4: Create a sandbox for the pod if necessary.
  14. podSandboxID := podContainerChanges.SandboxID
  15. if podContainerChanges.CreateSandbox {
  16. var msg string
  17. var err error
  18. klog.V(4).Infof("Creating sandbox for pod %q", format.Pod(pod))
  19. createSandboxResult := kubecontainer.NewSyncResult(kubecontainer.CreatePodSandbox, format.Pod(pod))
  20. result.AddSyncResult(createSandboxResult)
  21. podSandboxID, msg, err = m.createPodSandbox(pod, podContainerChanges.Attempt)
  22. ...
  23. }

4.1 m.createPodSandbox

m.createPodSandbox方法主要是调用m.runtimeService.RunPodSandbox

runtimeService即RemoteRuntimeService,实现了CRI shim客户端-容器运行时接口RuntimeService interface,持有与CRI shim容器运行时服务端通信的客户端。所以调用m.runtimeService.RunPodSandbox,实际上等于调用了CRI shim服务端的RunPodSandbox方法,来进行pod sandbox的创建。

  1. // pkg/kubelet/kuberuntime/kuberuntime_sandbox.go
  2. // createPodSandbox creates a pod sandbox and returns (podSandBoxID, message, error).
  3. func (m *kubeGenericRuntimeManager) createPodSandbox(pod *v1.Pod, attempt uint32) (string, string, error) {
  4. podSandboxConfig, err := m.generatePodSandboxConfig(pod, attempt)
  5. if err != nil {
  6. message := fmt.Sprintf("GeneratePodSandboxConfig for pod %q failed: %v", format.Pod(pod), err)
  7. klog.Error(message)
  8. return "", message, err
  9. }
  10. // Create pod logs directory
  11. err = m.osInterface.MkdirAll(podSandboxConfig.LogDirectory, 0755)
  12. if err != nil {
  13. message := fmt.Sprintf("Create pod log directory for pod %q failed: %v", format.Pod(pod), err)
  14. klog.Errorf(message)
  15. return "", message, err
  16. }
  17. runtimeHandler := ""
  18. if utilfeature.DefaultFeatureGate.Enabled(features.RuntimeClass) && m.runtimeClassManager != nil {
  19. runtimeHandler, err = m.runtimeClassManager.LookupRuntimeHandler(pod.Spec.RuntimeClassName)
  20. if err != nil {
  21. message := fmt.Sprintf("CreatePodSandbox for pod %q failed: %v", format.Pod(pod), err)
  22. return "", message, err
  23. }
  24. if runtimeHandler != "" {
  25. klog.V(2).Infof("Running pod %s with RuntimeHandler %q", format.Pod(pod), runtimeHandler)
  26. }
  27. }
  28. podSandBoxID, err := m.runtimeService.RunPodSandbox(podSandboxConfig, runtimeHandler)
  29. if err != nil {
  30. message := fmt.Sprintf("CreatePodSandbox for pod %q failed: %v", format.Pod(pod), err)
  31. klog.Error(message)
  32. return "", message, err
  33. }
  34. return podSandBoxID, "", nil
  35. }
m.runtimeService.RunPodSandbox

m.runtimeService.RunPodSandbox方法,会调用r.runtimeClient.RunPodSandbox,即利用CRI shim客户端,调用CRI shim服务端来进行pod sandbox的创建。

分析到这里,kubelet中的CRI相关调用就分析完毕了,接下来将会进入到CRI shim(以kubelet内置CRI shim-dockershim为例)里进行创建pod sandbox的分析。

  1. // pkg/kubelet/remote/remote_runtime.go
  2. // RunPodSandbox creates and starts a pod-level sandbox. Runtimes should ensure
  3. // the sandbox is in ready state.
  4. func (r *RemoteRuntimeService) RunPodSandbox(config *runtimeapi.PodSandboxConfig, runtimeHandler string) (string, error) {
  5. // Use 2 times longer timeout for sandbox operation (4 mins by default)
  6. // TODO: Make the pod sandbox timeout configurable.
  7. ctx, cancel := getContextWithTimeout(r.timeout * 2)
  8. defer cancel()
  9. resp, err := r.runtimeClient.RunPodSandbox(ctx, &runtimeapi.RunPodSandboxRequest{
  10. Config: config,
  11. RuntimeHandler: runtimeHandler,
  12. })
  13. if err != nil {
  14. klog.Errorf("RunPodSandbox from runtime service failed: %v", err)
  15. return "", err
  16. }
  17. if resp.PodSandboxId == "" {
  18. errorMessage := fmt.Sprintf("PodSandboxId is not set for sandbox %q", config.GetMetadata())
  19. klog.Errorf("RunPodSandbox failed: %s", errorMessage)
  20. return "", errors.New(errorMessage)
  21. }
  22. return resp.PodSandboxId, nil
  23. }

4.2 r.runtimeClient.RunPodSandbox

接下来将会以dockershim为例,进入到CRI shim来进行创建pod sandbox的分析。

前面kubelet调用r.runtimeClient.RunPodSandbox,会进入到dockershim下面的RunPodSandbox方法。

创建pod sandbox主要有5个步骤:

(1)调用docker,拉取pod sandbox的镜像;

(2)调用docker,创建pod sandbox容器;

(3)创建pod sandbox的Checkpoint;

(4)调用docker,启动pod sandbox容器;

(5)调用CNI,给pod sandbox构建网络。

  1. // pkg/kubelet/dockershim/docker_sandbox.go
  2. // RunPodSandbox creates and starts a pod-level sandbox. Runtimes should ensure
  3. // the sandbox is in ready state.
  4. // For docker, PodSandbox is implemented by a container holding the network
  5. // namespace for the pod.
  6. // Note: docker doesn't use LogDirectory (yet).
  7. func (ds *dockerService) RunPodSandbox(ctx context.Context, r *runtimeapi.RunPodSandboxRequest) (*runtimeapi.RunPodSandboxResponse, error) {
  8. config := r.GetConfig()
  9. // Step 1: Pull the image for the sandbox.
  10. image := defaultSandboxImage
  11. podSandboxImage := ds.podSandboxImage
  12. if len(podSandboxImage) != 0 {
  13. image = podSandboxImage
  14. }
  15. // NOTE: To use a custom sandbox image in a private repository, users need to configure the nodes with credentials properly.
  16. // see: http://kubernetes.io/docs/user-guide/images/#configuring-nodes-to-authenticate-to-a-private-repository
  17. // Only pull sandbox image when it's not present - v1.PullIfNotPresent.
  18. if err := ensureSandboxImageExists(ds.client, image); err != nil {
  19. return nil, err
  20. }
  21. // Step 2: Create the sandbox container.
  22. if r.GetRuntimeHandler() != "" && r.GetRuntimeHandler() != runtimeName {
  23. return nil, fmt.Errorf("RuntimeHandler %q not supported", r.GetRuntimeHandler())
  24. }
  25. createConfig, err := ds.makeSandboxDockerConfig(config, image)
  26. if err != nil {
  27. return nil, fmt.Errorf("failed to make sandbox docker config for pod %q: %v", config.Metadata.Name, err)
  28. }
  29. createResp, err := ds.client.CreateContainer(*createConfig)
  30. if err != nil {
  31. createResp, err = recoverFromCreationConflictIfNeeded(ds.client, *createConfig, err)
  32. }
  33. if err != nil || createResp == nil {
  34. return nil, fmt.Errorf("failed to create a sandbox for pod %q: %v", config.Metadata.Name, err)
  35. }
  36. resp := &runtimeapi.RunPodSandboxResponse{PodSandboxId: createResp.ID}
  37. ds.setNetworkReady(createResp.ID, false)
  38. defer func(e *error) {
  39. // Set networking ready depending on the error return of
  40. // the parent function
  41. if *e == nil {
  42. ds.setNetworkReady(createResp.ID, true)
  43. }
  44. }(&err)
  45. // Step 3: Create Sandbox Checkpoint.
  46. if err = ds.checkpointManager.CreateCheckpoint(createResp.ID, constructPodSandboxCheckpoint(config)); err != nil {
  47. return nil, err
  48. }
  49. // Step 4: Start the sandbox container.
  50. // Assume kubelet's garbage collector would remove the sandbox later, if
  51. // startContainer failed.
  52. err = ds.client.StartContainer(createResp.ID)
  53. if err != nil {
  54. return nil, fmt.Errorf("failed to start sandbox container for pod %q: %v", config.Metadata.Name, err)
  55. }
  56. // Rewrite resolv.conf file generated by docker.
  57. // NOTE: cluster dns settings aren't passed anymore to docker api in all cases,
  58. // not only for pods with host network: the resolver conf will be overwritten
  59. // after sandbox creation to override docker's behaviour. This resolv.conf
  60. // file is shared by all containers of the same pod, and needs to be modified
  61. // only once per pod.
  62. if dnsConfig := config.GetDnsConfig(); dnsConfig != nil {
  63. containerInfo, err := ds.client.InspectContainer(createResp.ID)
  64. if err != nil {
  65. return nil, fmt.Errorf("failed to inspect sandbox container for pod %q: %v", config.Metadata.Name, err)
  66. }
  67. if err := rewriteResolvFile(containerInfo.ResolvConfPath, dnsConfig.Servers, dnsConfig.Searches, dnsConfig.Options); err != nil {
  68. return nil, fmt.Errorf("rewrite resolv.conf failed for pod %q: %v", config.Metadata.Name, err)
  69. }
  70. }
  71. // Do not invoke network plugins if in hostNetwork mode.
  72. if config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetNetwork() == runtimeapi.NamespaceMode_NODE {
  73. return resp, nil
  74. }
  75. // Step 5: Setup networking for the sandbox.
  76. // All pod networking is setup by a CNI plugin discovered at startup time.
  77. // This plugin assigns the pod ip, sets up routes inside the sandbox,
  78. // creates interfaces etc. In theory, its jurisdiction ends with pod
  79. // sandbox networking, but it might insert iptables rules or open ports
  80. // on the host as well, to satisfy parts of the pod spec that aren't
  81. // recognized by the CNI standard yet.
  82. cID := kubecontainer.BuildContainerID(runtimeName, createResp.ID)
  83. networkOptions := make(map[string]string)
  84. if dnsConfig := config.GetDnsConfig(); dnsConfig != nil {
  85. // Build DNS options.
  86. dnsOption, err := json.Marshal(dnsConfig)
  87. if err != nil {
  88. return nil, fmt.Errorf("failed to marshal dns config for pod %q: %v", config.Metadata.Name, err)
  89. }
  90. networkOptions["dns"] = string(dnsOption)
  91. }
  92. err = ds.network.SetUpPod(config.GetMetadata().Namespace, config.GetMetadata().Name, cID, config.Annotations, networkOptions)
  93. if err != nil {
  94. errList := []error{fmt.Errorf("failed to set up sandbox container %q network for pod %q: %v", createResp.ID, config.Metadata.Name, err)}
  95. // Ensure network resources are cleaned up even if the plugin
  96. // succeeded but an error happened between that success and here.
  97. err = ds.network.TearDownPod(config.GetMetadata().Namespace, config.GetMetadata().Name, cID)
  98. if err != nil {
  99. errList = append(errList, fmt.Errorf("failed to clean up sandbox container %q network for pod %q: %v", createResp.ID, config.Metadata.Name, err))
  100. }
  101. err = ds.client.StopContainer(createResp.ID, defaultSandboxGracePeriod)
  102. if err != nil {
  103. errList = append(errList, fmt.Errorf("failed to stop sandbox container %q for pod %q: %v", createResp.ID, config.Metadata.Name, err))
  104. }
  105. return resp, utilerrors.NewAggregate(errList)
  106. }
  107. return resp, nil
  108. }

接下来以ds.client.CreateContainer调用为例,分析下dockershim是如何调用docker的。

ds.client.CreateContainer

主要是调用d.client.ContainerCreate

  1. // pkg/kubelet/dockershim/libdocker/kube_docker_client.go
  2. func (d *kubeDockerClient) CreateContainer(opts dockertypes.ContainerCreateConfig) (*dockercontainer.ContainerCreateCreatedBody, error) {
  3. ctx, cancel := d.getTimeoutContext()
  4. defer cancel()
  5. // we provide an explicit default shm size as to not depend on docker daemon.
  6. // TODO: evaluate exposing this as a knob in the API
  7. if opts.HostConfig != nil && opts.HostConfig.ShmSize <= 0 {
  8. opts.HostConfig.ShmSize = defaultShmSize
  9. }
  10. createResp, err := d.client.ContainerCreate(ctx, opts.Config, opts.HostConfig, opts.NetworkingConfig, opts.Name)
  11. if ctxErr := contextError(ctx); ctxErr != nil {
  12. return nil, ctxErr
  13. }
  14. if err != nil {
  15. return nil, err
  16. }
  17. return &createResp, nil
  18. }
d.client.ContainerCreate

构建请求参数,向docker指定的url发送http请求,创建pod sandbox容器。

  1. // vendor/github.com/docker/docker/client/container_create.go
  2. // ContainerCreate creates a new container based in the given configuration.
  3. // It can be associated with a name, but it's not mandatory.
  4. func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error) {
  5. var response container.ContainerCreateCreatedBody
  6. if err := cli.NewVersionError("1.25", "stop timeout"); config != nil && config.StopTimeout != nil && err != nil {
  7. return response, err
  8. }
  9. // When using API 1.24 and under, the client is responsible for removing the container
  10. if hostConfig != nil && versions.LessThan(cli.ClientVersion(), "1.25") {
  11. hostConfig.AutoRemove = false
  12. }
  13. query := url.Values{}
  14. if containerName != "" {
  15. query.Set("name", containerName)
  16. }
  17. body := configWrapper{
  18. Config: config,
  19. HostConfig: hostConfig,
  20. NetworkingConfig: networkingConfig,
  21. }
  22. serverResp, err := cli.post(ctx, "/containers/create", query, body, nil)
  23. defer ensureReaderClosed(serverResp)
  24. if err != nil {
  25. return response, err
  26. }
  27. err = json.NewDecoder(serverResp.body).Decode(&response)
  28. return response, err
  29. }
  1. // vendor/github.com/docker/docker/client/request.go
  2. // post sends an http request to the docker API using the method POST with a specific Go context.
  3. func (cli *Client) post(ctx context.Context, path string, query url.Values, obj interface{}, headers map[string][]string) (serverResponse, error) {
  4. body, headers, err := encodeBody(obj, headers)
  5. if err != nil {
  6. return serverResponse{}, err
  7. }
  8. return cli.sendRequest(ctx, "POST", path, query, body, headers)
  9. }

总结

CRI架构图

在 CRI 之下,包括两种类型的容器运行时的实现:

(1)kubelet内置的 dockershim,实现了 Docker 容器引擎的支持以及 CNI 网络插件(包括 kubenet)的支持。dockershim代码内置于kubelet,被kubelet调用,让dockershim起独立的server来建立CRI shim,向kubelet暴露grpc server;

(2)外部的容器运行时,用来支持 rktcontainerd 等容器引擎的外部容器运行时。

kubelet调用CRI创建pod流程分析

kubelet创建一个pod的逻辑为:

(1)先创建并启动pod sandbox容器,并构建好pod网络;

(2)创建并启动ephemeral containers;

(3)创建并启动init containers;

(4)最后创建并启动normal containers(即普通业务容器)。

kubelet CRI创建pod调用流程

下面以kubelet dockershim创建pod调用流程为例做一下分析。

kubelet通过调用dockershim来创建并启动容器,而dockershim则调用docker来创建并启动容器,并调用CNI来构建pod网络。

图1:kubelet dockershim创建pod调用流程图示

dockershim属于kubelet内置CRI shim,其余remote CRI shim的创建pod调用流程其实与dockershim调用基本一致,只不过是调用了不同的容器引擎来操作容器,但一样由CRI shim调用CNI来构建pod网络。

本篇博文将对kubelet调用CRI创建pod做了分析,下一篇博客将对kubelet中CRI相关的源码分析最后一个部分进行分析,也就是kubelet调用CRI删除pod分析,敬请期待。

关联博客:《kubernetes/k8s CSI分析-容器存储接口分析》

《kubernetes/k8s CRI 分析-容器运行时接口分析》

kubernetes/k8s CRI分析-kubelet创建pod分析的更多相关文章

  1. kubernetes/k8s CRI分析-kubelet删除pod分析

    关联博客<kubernetes/k8s CRI 分析-容器运行时接口分析> <kubernetes/k8s CRI分析-kubelet创建pod分析> 之前的博文先对 CRI ...

  2. 12.深入k8s:kubelet创建pod流程源码分析

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 源码版本是1.19 在上一篇中,我们知道在kubelet中,工作核心就是围绕着整个syn ...

  3. kubernetes/k8s CRI分析-容器运行时接口分析

    关联博客:kubernetes/k8s CSI分析-容器存储接口分析 概述 kubernetes的设计初衷是支持可插拔架构,从而利于扩展kubernetes的功能.在此架构思想下,kubernetes ...

  4. Kubernetes K8S之通过yaml文件创建Pod与Pod常用字段详解

    YAML语法规范:在kubernetes k8s中如何通过yaml文件创建pod,以及pod常用字段详解 YAML 语法规范 K8S 里所有的资源或者配置都可以用 yaml 或 Json 定义.YAM ...

  5. k8s组件通信或者创建pod生命周期

    Kubernetes 多组件之间的通信原理: apiserver 负责 etcd 存储的所有操作,且只有 apiserver 才直接操作 etcd 集群 apiserver 对内(集群中的其他组件)和 ...

  6. kubelet源码分析——启动Pod

    前文说到Kubelet启动时,调用到kubelet.Run方法,里面最核心的就是调用到kubelet.syncLoop.它是一个循环,这个循环里面有若干个检查和同步操作,其中一个是地在监听Pod的增删 ...

  7. kubelet源码分析——关闭Pod

    上一篇说到kublet如何启动一个pod,本篇讲述如何关闭一个Pod,引用一段来自官方文档介绍pod的生命周期的话 你使用 kubectl 工具手动删除某个特定的 Pod,而该 Pod 的体面终止限期 ...

  8. Kubernetes K8S之资源控制器Daemonset详解

    Kubernetes的资源控制器Daemonset详解与示例 主机配置规划 服务器名称(hostname) 系统版本 配置 内网IP 外网IP(模拟) k8s-master CentOS7.7 2C/ ...

  9. Kubernetes K8S之存储ConfigMap详解

    K8S之存储ConfigMap概述与说明,并详解常用ConfigMap示例 主机配置规划 服务器名称(hostname) 系统版本 配置 内网IP 外网IP(模拟) k8s-master CentOS ...

随机推荐

  1. go语言结构体内存对齐

    cpu要想从内存读取数据,需要通过地址总线,把地址传输给内存,内存准备好数据,输出到数据总线,交给cpu,如果地址总线只有8根,那这个地址就只有8位可以表示[0,255]256个地址,因为表示不了更多 ...

  2. 精尽Spring Boot源码分析 - Jar 包的启动实现

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  3. ASP.Net Core Configuration 理解与源码分析

    Configuration 在ASP.NET Core开发过程中起着很重要的作用,这篇博客主要是理解configuration的来源,以及各种不同类型的configuration source是如何被 ...

  4. Linux中su和sudo的用法

    su -#su - oldboy //当执行这个命令的时候表示切换到oldboy用户,并且重新读取用户环境相关配置文件,具体的来说就是执行下用户家目录下.bash_profile和.bashrc文件, ...

  5. oracle行转列实现

    1.新建测试表 create table TEST_TABLE( T1 VARCHAR2(10),--姓名 T2 VARCHAR2(10),--科目 T3 VARCHAR2(10)--成绩 ) 2.插 ...

  6. Datahub 0.8.5发布! 通用的元数据搜索和发现工具

    近期Datahub 发布了最新的版本0.8.5,作为LinkedIn开源的通用的元数据搜索和发现工具.Datahub近一年来有了巨大的发展,也成为了很多公司进行元数据管理的调研方向并进行使用的选择. ...

  7. 解决Windows Server 2012 在VMware ESXi中经常自动断网问题

    最近一些开发人员反映他们使用的 Windows server2012 R2 虚拟机过段时间就远程连接不上了,ping也不通(已关闭防火墙),我们登录ESXi发现,Windows Server 的网络图 ...

  8. 23 shell 进程替换

    0.shell进程替换的用法 1.使用进程替换的必要性 2.进程替换的本质 进程替换和命令替换非常相似.命令替换是把一个命令的输出结果赋值给另一个变量,例如dir_files=`ls -l`或date ...

  9. mysql 深度解析auto-increment自增列"Duliplicate key"问题

    转载自:https://cloud.tencent.com/developer/article/1367681 问题描述 近期,线上有个重要Mysql客户的表在从5.6升级到5.7后master上插入 ...

  10. IDEA+Hadoop2.10.1+Zookeeper3.4.10+Hbase 2.3.5 操作JavaAPI

    在此之前要配置好三节点的hadoop集群,zookeeper集群,并启动它们,然后再配置好HBase环境 本文只是HBase2.3.5API操作作相应说明,如果前面环境还没有配置好,可以翻看我之前的博 ...