[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,等这几个进程的关系究竟是啥html
初窥得出的结论是:linux
runc init [args ...]
进程关系模型:git
docker ctr | | V V dockerd -> containerd ---> shim -> runc -> runc init -> process |-- > shim -> runc -> runc init -> process +-- > shim -> runc -> runc init -> process
[root@docker-build-86-050 ~]# ps -aux|grep docker root 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.com root 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-runc root 21238 0.0 0.0 487664 6212 ? Sl 4月20 0:00 docker-containerd-shim 48119c50a0ca8a53967364f75fb709017cc272ae248b78062e0dafaa22108d21 /var/run/docker/libcontainerd/48119c50a0ca8a53967364f75fb709017cc272ae248b78062e0dafaa22108d21 docker-runc
首先dockerd的main函数相信你能找到cmd/dockerd/docker.go
github
其它的先略过,直接进start看一看:docker
err = daemonCli.start(opts)
这函数里咱们先去关注两件事:json
这个New很重要api
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相关介绍看这里app
具体如何建立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
代码中的一句话解释:shim for container lifecycle and reconnection
, 容器生命周期和重连, 因此能够顺着这个思路去看。
先看containerd/linux/runtime.go里的一段代码:
Runtime 的Create方法里有这一行,这里的Runtime对象也是注册到register里面的,能够看init函数,而后containerd进程启动时去加载了这个Runtime
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:
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) }
再看shim与runc的关系,这个比较简单了,直接进入shim service 实现的Create方法便可
sv = shim.New(path)
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子进程。
看docker建立了这么多子进程,而后到了runc咱们期待的本身Dockerfile中的CMD进程就要被建立了,想一想都有点小激动,然而。。。
runc进程启动后会去启动init进程,去建立容器,而后在容器中建立进程,那才是真正咱们须要的进程
关于runc init进程关键看StartInitialization方法(main_unix.go)
ctr 是一个containerd的client,之间经过proto rpc通讯, containerd监听了unix:///run/containerd/containerd.sock。
[root@dev-86-201 ~]# docker-containerd --help NAME: containerd - High performance container daemon USAGE: docker-containerd [global options] command [command options] [arguments...] VERSION: 0.2.0 commit: 0ac3cd1be170d180b2baed755e8f0da547ceb267 COMMANDS: help, h Shows a list of commands or help for one command GLOBAL 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
[root@dev-86-201 ~]# docker-containerd-ctr --help NAME: ctr - High performance container daemon cli USAGE: docker-containerd-ctr [global options] command [command options] [arguments...] VERSION: 0.2.0 commit: 0ac3cd1be170d180b2baed755e8f0da547ceb267 COMMANDS: 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 command GLOBAL 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
比较复杂也比较重要,因此我将单独写一篇相关的介绍 这里
mkdir /mycontainer cd /mycontainer mkdir rootfs docker export $(docker create busybox) | tar -C rootfs -xvf - # 生成容器的配置文件config.json runc spec runc run mycontainerid
默认存在/run/runc目录下,无论是docker engine建立的容器仍是经过runc直接建立的容器都会在/run/runc目录下建立一个以容器名命名的目录,下面有个state.json文件用于存储文件状态
更多问题欢迎关注个人github: https://github.com/fanux