专栏首页sealyunDocker架构分析

Docker架构分析

Docker架构分析

[root@docker-build-86-050 ~]# ls /usr/bin |grep docker
docker
docker-compose
docker-containerd
docker-containerd-ctr
docker-containerd-shim
dockerd
docker-proxy
docker-runc    

大家一定很困惑 dockerd, containerd, ctr,shim, runc,等这几个进程的关系到底是啥

初窥得出的结论是:

  • docker是cli没啥可说的
  • dockerd是docker engine守护进程,dockerd启动时会启动containerd子进程。
  • dockerd与containerd通过rpc进行通信(待验证,可能是通过ctr)
  • ctr是containerd的cli
  • containerd通过shim操作runc,runc真正控制容器生命周期
  • 启动一个容器就会启动一个shim进程
  • shim直接调用runc的包函数,shim与containerd之前通过rpc通信
  • 真正用户想启动的进程由runc的init进程启动,即runc init [args ...]

进程关系模型:

123456

docker ctr | | V Vdockerd -> containerd ---> shim -> runc -> runc init -> process |-- > shim -> runc -> runc init -> process +-- > shim -> runc -> runc init -> process

1234

[root@docker-build-86-050 ~]# ps -aux|grep dockerroot 3925 0.0 0.1 2936996 74020 ? Ssl 3月06 68:14 /usr/bin/dockerd --storage-driver=aufs -H 0.0.0.0:2375 --label ip=10.1.86.50 -H unix:///var/run/docker.sock --insecure-registry 192.168.86.106 --insecure-registry 10.1.86.51 --insecure-registry dev.reg.iflytek.comroot 3939 0.0 0.0 1881796 27096 ? Ssl 3月06 9:10 docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --shim docker-containerd-shim --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --runtime docker-runcroot 21238 0.0 0.0 487664 6212 ? Sl 4月20 0:00 docker-containerd-shim 48119c50a0ca8a53967364f75fb709017cc272ae248b78062e0dafaa22108d21 /var/run/docker/libcontainerd/48119c50a0ca8a53967364f75fb709017cc272ae248b78062e0dafaa22108d21 docker-runc

dockerd 与 containerd 之间的基情

首先dockerd的main函数相信你能找到cmd/dockerd/docker.go

其它的先略过,直接进start看一看:

1

err = daemonCli.start(opts)

这函数里我们先去关注两件事:

  1. 创建了多个Hosts,这是给client去连接的,dockerd启动时用-H参数指定,可以是多个,如指定一个tcp 指定一个unix sock( -H unix:///var/run/docker.sock)
  2. 创建了containerd子进程

这个New很重要

1

containerdRemote, err := libcontainerd.New(cli.getLibcontainerdRoot(), cli.getPlatformRemoteOptions()...)

进去看看:

...    err := r.runContainerdDaemon(); ...    conn, err := grpc.Dial(r.rpcAddr, dialOpts...)    if err != nil {        return nil, fmt.Errorf("error connecting to containerd: %v", err)    }    r.rpcConn = conn    r.apiClient = containerd.NewAPIClient(conn)...

启动了一个containerd进程,并与之建立连接。通过protobuf进行rpc通信, grpc相关介绍看这里

具体如何创建containerd进程的可以进入runContainerDaemon里细看

cmd := exec.Command(containerdBinary, args...)    // redirect containerd logs to docker logs    cmd.Stdout = os.Stdout    cmd.Stderr = os.Stderr    cmd.SysProcAttr = setSysProcAttr(true)    cmd.Env = nil    // clear the NOTIFY_SOCKET from the env when starting containerd    for _, e := range os.Environ() {        if !strings.HasPrefix(e, "NOTIFY_SOCKET") {            cmd.Env = append(cmd.Env, e)        }    }    if err := cmd.Start(); err != nil {        return err    }

看不明白的话,去标准库里恶补一下cmd怎么用。 cmd.Start()异步创建进程,创建完直接返回

所以创建一个协程等待子进程退出

go func() {        cmd.Wait()        close(r.daemonWaitCh)    }() // Reap our child when needed

docker-containerd-shim是何方神圣 与containerd和runc又有什么关系?

代码中的一句话解释:shim for container lifecycle and reconnection, 容器生命周期和重连, 所以可以顺着这个思路去看。

先看containerd/linux/runtime.go里的一段代码: Runtime 的Create方法里有这一行,这里的Runtime对象也是注册到register里面的,可以看init函数,然后containerd进程启动时去加载了这个Runtime

1

s, err := newShim(path, r.remote)

缩减版内容:

func newShim(path string, remote bool) (shim.ShimClient, error) {    l, err := sys.CreateUnixSocket(socket) //创建了一个UnixSocket    cmd := exec.Command("containerd-shim")    f, err := l.(*net.UnixListener).File()    cmd.ExtraFiles = append(cmd.ExtraFiles, f) //留意一下这个,非常非常重要,不知道这个原理可能就看不懂shim里面的代码了    if err := reaper.Default.Start(cmd); err != nil { //启动了一个shim进程    }    return connectShim(socket) // 这里返回了与shim进程通信的客户端}

再去看看shim的代码: shim进程启动干的最主要的一件事就是启动一个grpc server:

1

if err := serve(server, "shim.sock"); err != nil {

进去一探究竟:

func serve(server *grpc.Server, path string) error {    l, err := net.FileListener(os.NewFile(3, "socket"))    logrus.WithField("socket", path).Debug("serving api on unix socket")    go func() {        if err := server.Serve(l); err != nil &&        }    }()}

我曾经因为这个os.NewFile(3, "socket")看了半天看不懂,为啥是3?联系cmd.ExtraFiles = append(cmd.ExtraFiles, f) 创建shim进程时的这句,问题解决了。

这个3的文件描述符,就是containerd用于创建UnixSocket的文件,这样containerd的client刚好与这边启动的 grpc server连接上了,可以远程调用其接口了:

type ContainerServiceClient interface {    Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateResponse, error)    Start(ctx context.Context, in *StartRequest, opts ...grpc.CallOption) (*google_protobuf.Empty, error)    Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*DeleteResponse, error)    Info(ctx context.Context, in *InfoRequest, opts ...grpc.CallOption) (*containerd_v1_types1.Container, error)    List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error)    Kill(ctx context.Context, in *KillRequest, opts ...grpc.CallOption) (*google_protobuf.Empty, error)    Events(ctx context.Context, in *EventsRequest, opts ...grpc.CallOption) (ContainerService_EventsClient, error)    Exec(ctx context.Context, in *ExecRequest, opts ...grpc.CallOption) (*ExecResponse, error)    Pty(ctx context.Context, in *PtyRequest, opts ...grpc.CallOption) (*google_protobuf.Empty, error)    CloseStdin(ctx context.Context, in *CloseStdinRequest, opts ...grpc.CallOption) (*google_protobuf.Empty, error)}

containerd与shim通信模型介绍

再看shim与runc的关系,这个比较简单了,直接进入shim service 实现的Create方法即可

1

sv = shim.New(path)

123456

func (s *Service) Create(ctx context.Context, r *shimapi.CreateRequest) (*shimapi.CreateResponse, error) { process, err := newInitProcess(ctx, s.path, r) return &shimapi.CreateResponse{ Pid: uint32(pid), }, nil}

进入到newInitProcess里面:

func newInitProcess(context context.Context, path string, r *shimapi.CreateRequest) (*initProcess, error) {    runtime := &runc.Runc{        Command:      r.Runtime,        Log:          filepath.Join(path, "log.json"),        LogFormat:    runc.JSON,        PdeathSignal: syscall.SIGKILL,    }    p := &initProcess{        id:     r.ID,        bundle: r.Bundle,        runc:   runtime,    }      if err := p.runc.Create(context, r.ID, r.Bundle, opts); err != nil {        return nil, err    }    return p, nil}

可以看到,在这里调用了runc的API去真正执行创建容器的操作。其本质是调用了runc create --bundle [bundle] [containerid] 命令,在此不多作介绍了

shim进程与runc进程之间

上文可知,shim进程创建runc子进程。

runc 与 容器内第一个进程 init进程

看docker创建了这么多子进程,然后到了runc我们期待的自己Dockerfile中的CMD进程就要被创建了,想想都有点小激动,然而。。。

runc进程启动后会去启动init进程,去创建容器,然后在容器中创建进程,那才是真正我们需要的进程

关于runc init进程关键看StartInitialization方法(main_unix.go)

docker-containerd-ctr 与 docker-containerd

ctr 是一个containerd的client,之间通过proto rpc通信, containerd监听了unix:///run/containerd/containerd.sock。

1 2 3 4 5 6 7 8 9101112131415161718192021222324252627

[root@dev-86-201 ~]# docker-containerd --helpNAME: containerd - High performance container daemonUSAGE: docker-containerd [global options] command [command options] [arguments...]VERSION: 0.2.0 commit: 0ac3cd1be170d180b2baed755e8f0da547ceb267COMMANDS: help, h Shows a list of commands or help for one commandGLOBAL OPTIONS: --debug enable debug output in the logs --state-dir "/run/containerd" runtime state directory --metrics-interval "5m0s" interval for flushing metrics to the store --listen, -l "unix:///run/containerd/containerd.sock" proto://address on which the GRPC API will listen --runtime, -r "runc" name or path of the OCI compliant runtime to use when executing containers --runtime-args [--runtime-args option --runtime-args option] specify additional runtime args --shim "containerd-shim" Name or path of shim --pprof-address http address to listen for pprof events --start-timeout "15s" timeout duration for waiting on a container to start before it is killed --retain-count "500" number of past events to keep in the event log --graphite-address Address of graphite server --help, -h show help --version, -v print the version

1 2 3 4 5 6 7 8 9101112131415161718192021222324

[root@dev-86-201 ~]# docker-containerd-ctr --helpNAME: ctr - High performance container daemon cliUSAGE: docker-containerd-ctr [global options] command [command options] [arguments...]VERSION: 0.2.0 commit: 0ac3cd1be170d180b2baed755e8f0da547ceb267COMMANDS: checkpoints list all checkpoints containers interact with running containers events receive events from the containerd daemon state get a raw dump of the containerd state version return the daemon version help, h Shows a list of commands or help for one commandGLOBAL OPTIONS: --debug enable debug output in the logs --address "unix:///run/containerd/containerd.sock" proto://address of GRPC API --conn-timeout "1s" GRPC connection timeout --help, -h show help --version, -v print the version

runc 架构破析

比较复杂也比较重要,所以我将单独写一篇相关的介绍 这里

使用runc直接创建容器

1234567

mkdir /mycontainercd /mycontainermkdir rootfsdocker export $(docker create busybox) | tar -C rootfs -xvf -# 生成容器的配置文件config.jsonrunc specrunc run mycontainerid

容器状态文件

默认存在/run/runc目录下,不管是docker engine创建的容器还是通过runc直接创建的容器都会在/run/runc目录下创建一个以容器名命名的目录,下面有个state.json文件用于存储文件状态

本文分享自微信公众号 - sealyun(gh_f33fe7b0c869),作者:fanux

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-03-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • containerd与kubernetes集成

    由于docker嵌入了太多自身内容,为了减轻容器负担。此次选用containerd作为kubernetes的容器实现方案。本文将带大家讲述如何搭建一个集成了co...

    sealyun
  • 全网最简单的k8s User JWT token管理器

    kubernetes server account的token很容易获取,但是User的token非常麻烦,本文给出一个极简的User token生成方式,让用...

    sealyun
  • 关于overlay2存储驱动的磁盘配额问题

    你需要一个高版本的内核推荐4.9以上,我们用的是4.14,如果使用低内核可能你一些FROM别的基础镜像就跑不了,如用overlay2在centos系统上跑FRO...

    sealyun
  • Iterator 、Generator

    JS里原有的表示”集合“的数据结构,主要是Array和Object,ES6又添加了Map和Set。我们可以任意组合和设计数据的结构,那么就需要一个机制,可处理所...

    用户3258338
  • 深入理解 Linux Cgroup 系列(一):基本概念

    Cgroup 是 Linux kernel 的一项功能:它是在一个系统中运行的层级制进程组,你可对其进行资源分配(如 CPU 时间、系统内存、网络带宽或者这些资...

    米开朗基杨
  • python实现验证码生成显示

    py3study
  • 技术解析 | 线下门店消费场景中的感知和互动

    随着技术的快速发展和人们生活水平的不断提升,传统的零售模式已经难以满足消费者的需求,而且传统的运营模式需要进行重构。京东提出了无界零售的概念,对于前端门店用户体...

    京东技术
  • SAP最佳业务实践:MM–不交货与库存调拨(135)-1业务概览

    用途 由 MRP 自动或由采购员手动将请求的物料从一个工厂转储到另一个工厂(在同的一公司里)。 优点 不含交货的过程的简易转储处理:生成了较少的凭证 ...

    SAP最佳业务实践
  • 带你搞懂Java泛型

    定义泛型方法:修饰符和返回值类型中间要有<参数类型>,泛型方法可定义在任意位置。 注意 定义在泛型类中的泛型方法,传入的参数类型可以与泛型类传入的类型不同,泛...

    longzeqiu
  • Dynamic Network Surgery for Efficient DNNs

    NIPS 2016 http://arxiv.org/abs/1608.04493

    用户1148525

扫码关注云+社区

领取腾讯云代金券