Docker基础之containerd管理的基本流程

一、containerd的main函数

containerd建立Server并提供服务的主要流程分析如下:
1、 创建了一个 App结构体函数对象,代表了containerd
2、 声明了app.Action方法,之后运行daemon()函数,在app.Run(os.Args)中会得到调用
3、 最后执行app.Run(os.Args)

func main() {
	logrus.SetFormatter(&logrus.TextFormatter{TimestampFormat: time.RFC3339Nano})
	app := cli.NewApp()
	app.Name = "containerd"
	if containerd.GitCommit != "" {
		app.Version = fmt.Sprintf("%s commit: %s", containerd.Version, containerd.GitCommit)
	} else {
		app.Version = containerd.Version
	}
	app.Usage = usage
	//声明了Flags
	app.Flags = daemonFlags
	//定义了Before()函数
	app.Before = func(context *cli.Context) error {
		if context.GlobalBool("debug") {
			logrus.SetLevel(logrus.DebugLevel)
			if context.GlobalDuration("metrics-interval") > 0 {
				if err := debugMetrics(context.GlobalDuration("metrics-interval"), context.GlobalString("graphite-address")); err != nil {
					return err
				}
			}
		}
		if p := context.GlobalString("pprof-address"); len(p) > 0 {
			pprof.Enable(p)
		}
		if err := checkLimits(); err != nil {
			return err
		}
		return nil
	}
	//定义了Action()函数,当没有指定subcommands时执行时,启动containerd的daemon进程。开启grpc服务器
	*/
	app.Action = func(context *cli.Context) {
		if err := daemon(context); err != nil {
			logrus.Fatal(err)
		}
	}
	if err := app.Run(os.Args); err != nil {
		logrus.Fatal(err)
	}
}

1、App结构体函数
主要看其run()函数,这里需要注意的是执行containerd和执行ctr命令的区别就是a.Action(context)会直接调用daemon() 函数

// 进入cli应用程序的入口点。解析参数切片和路由到适当的flag/args组合
func (a *App) Run(arguments []string) (err error) {
	...
	...
	//开始解析参数,得到参数集合set
	set := flagSet(a.Name, a.Flags)
	set.SetOutput(ioutil.Discard)
	err = set.Parse(arguments[1:])
	nerr := normalizeFlags(a.Flags, set) //检查参数合法性
	...
	...

	//执行Before()
	if a.Before != nil {
		err = a.Before(context)
		if err != nil {
			fmt.Fprintf(a.Writer, "%v\n\n", err)
			ShowAppHelp(context)
			return err
		}
	}
	//第一次运行`containerd` daemon时,args is:  [],即len(args)=0,执行 `ctr containers` 时,args is: [containers],即len(args)>0
	args := context.Args()
	if args.Present() {
		name := args.First()
		c := a.Command(name)
		if c != nil {
			return c.Run(context)
		}
	}
	//执行Action()
	a.Action(context)
	return nil
}
//检查是否存在任何参数
func (a Args) Present() bool {
	return len(a) != 0
}

2、daemon()函数
daemon()函数的执行流程如下:
1、新建一个Supervisor对象,这个是containerd的核心部件
2、supervisor创建的10个worker的Start(),负责处理创建新容器的task
3、supervisor本身的Start(),消费tasks chan Task
4、启动grpc server端,这里会接收来自docker-daemon的Request

//核心是:
//1. supervisor创建的10个worker的Start(),负责处理创建新容器的task
//2. supervisor本身的Start(),消费tasks chan Task
func daemon(context *cli.Context) error {
	// 创建一个标准的reaper(回收者),避免留下孤立进程。containd daemon在创建容器的时候,会产生新的进程.系统Signal处理,通知系统退出,即kill pragram-pid,当收到信号后,会执行相关清理程序或通知各个子进程做自清理
	s := make(chan os.Signal, 2048)
	signal.Notify(s, syscall.SIGTERM, syscall.SIGINT)
	//新建一个supervisor,这个是containerd的核心部件
	sv, err := supervisor.New(
		context.String("state-dir"),
		context.String("runtime"),
		context.String("shim"),
		context.StringSlice("runtime-args"),
		context.Duration("start-timeout"),
		context.Int("retain-count"))
	if err != nil {
		return err
	}
	//wg为sync.WaitGroup同步goroutine,一个worker里面包含一个supervisor和sync.WaitGroup,这里的wg主要用于实现容器的启动部分
	wg := &sync.WaitGroup{}
	//supervisor 启动10个worker
	for i := 0; i < 10; i++ {
		wg.Add(1)
		w := supervisor.NewWorker(sv, wg)
		go w.Start()
	}
	//启动supervisor
	if err := sv.Start(); err != nil {
		return err
	}
	//根据参数获取监听器
	listenSpec := context.String("listen")
	listenParts := strings.SplitN(listenSpec, "://", 2)
	if len(listenParts) != 2 {
		return fmt.Errorf("bad listen address format %s, expected proto://address", listenSpec)
	}
	//启动grpc server端
	server, err := startServer(listenParts[0], listenParts[1], sv)
	if err != nil {
		return err
	}
	for ss := range s {
		//收到系统Signal信号,退出containerd
		switch ss {
		default:
			logrus.Infof("stopping containerd after receiving %s", ss)
			server.Stop()
			os.Exit(0)
		}
	}
	return nil
}

二、grpc server端的路由

下面我将介绍grpc server端的建立,是如何接收来自于docker-daemon端的请求的。

//proto文件
func startServer(protocol, address string, sv *supervisor.Supervisor) (*grpc.Server, error) {
	// /生成一个socker套接字
	sockets, err := listeners.Init(protocol, address, "", nil)
	if err != nil {
		return nil, err
	}
	if len(sockets) != 1 {
		return nil, fmt.Errorf("incorrect number of listeners")
	}
	l := sockets[0]
	s := grpc.NewServer()
	//注册API
	types.RegisterAPIServer(s, server.NewServer(sv))
	go func() {
		logrus.Debugf("containerd: grpc api on %s", address)
		//将grpc服务器与指定套接字关联起来,从而监听docker-containerd.sock
		if err := s.Serve(l); err != nil {
			logrus.WithField("error", err).Fatal("containerd: serve grpc")
		}
	}()
	return s, nil
}

1、RegisterAPIServer()函数注册API
这里是protoc工具自动生成的代码,声明了grpc server端的路由

func RegisterAPIServer(s *grpc.Server, srv APIServer) {
	// _API_serviceDesc 声明了路由-->handler
	s.RegisterService(&_API_serviceDesc, srv)
}
var _API_serviceDesc = grpc.ServiceDesc{
	ServiceName: "types.API",
	HandlerType: (*APIServer)(nil),
	Methods: []grpc.MethodDesc{
		{
			MethodName: "GetServerVersion",//获取服务端版本
			Handler:    _API_GetServerVersion_Handler,
		},
		{
			MethodName: "CreateContainer", //创建一个容器
			Handler:    _API_CreateContainer_Handler,
		},
		{
			MethodName: "UpdateContainer",//更新容器
			Handler:    _API_UpdateContainer_Handler,
		},
		{
			MethodName: "Signal",//信号量
			Handler:    _API_Signal_Handler,
		},
		{
			MethodName: "UpdateProcess", //更新进程
			Handler:    _API_UpdateProcess_Handler,
		},
		{
			MethodName: "AddProcess",//增加进程
			Handler:    _API_AddProcess_Handler,
		},
		{
			MethodName: "CreateCheckpoint",//创建一个检查点
			Handler:    _API_CreateCheckpoint_Handler,
		},
		{
			MethodName: "DeleteCheckpoint",//删除一个检查点
			Handler:    _API_DeleteCheckpoint_Handler,
		},
		{
			MethodName: "ListCheckpoint",//列出所有断点
			Handler:    _API_ListCheckpoint_Handler,
		},
		{
			MethodName: "State",//状态
			Handler:    _API_State_Handler,
		},
		{
			MethodName: "Stats",
			Handler:    _API_Stats_Handler,
		},
	},
	Streams: []grpc.StreamDesc{
		{
			StreamName:    "Events",
			Handler:       _API_Events_Handler,
			ServerStreams: true,
		},
	},
}

2、Handler函数
之前介绍的docker start {containerID}的路由是/types.API/CreateContainer,那么其对应的Handler就是_API_CreateContainer_Handler函数

func _API_CreateContainer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(CreateContainerRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(APIServer).CreateContainer(ctx, in)
	}
	//对应的是docker start {containerID}命令
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: "/types.API/CreateContainer",
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(APIServer).CreateContainer(ctx, req.(*CreateContainerRequest))
	}
	return interceptor(ctx, in, info, handler)
}
func (s *apiServer) CreateContainer(ctx context.Context, c *types.CreateContainerRequest) (*types.CreateContainerResponse, error) {
	if c.BundlePath == "" {
		return nil, errors.New("empty bundle path")
	}
	//新建一个StartTask e,存放创建容器的request信息,本函数的任务类型是 StartTask
	e := &supervisor.StartTask{}
	e.ID = c.Id
	e.BundlePath = c.BundlePath
	e.Stdin = c.Stdin
	e.Stdout = c.Stdout
	e.Stderr = c.Stderr
	e.Labels = c.Labels
	e.NoPivotRoot = c.NoPivotRoot
	e.Runtime = c.Runtime
	e.RuntimeArgs = c.RuntimeArgs
	e.StartResponse = make(chan supervisor.StartResponse, 1)
	if c.Checkpoint != "" {
		e.CheckpointDir = c.CheckpointDir
		e.Checkpoint = &runtime.Checkpoint{
			Name: c.Checkpoint,
		}
	}
	//把StartTask e发给监控器的主事件循环
	s.sv.SendTask(e)
	if err := <-e.ErrorCh(); err != nil {
		return nil, err
	}
	//StartResponse channel容量为1,如果没有接收到信息,阻塞等待/supervisor/worker.go中func (w *worker) Start()的处理结果
	r := <-e.StartResponse
	apiC, err := createAPIContainer(r.Container, false)
	if err != nil {
		return nil, err
	}
	return &types.CreateContainerResponse{
		Container: apiC,
	}, nil
}

三、Supervisor结构体函数

Supervisor结构体函数是containerd的核心部件。Supervisor是把一个Request转化为一个Task来进行管理,为一个Task调用containerd-shim,从而启动容器。Supervisor结构体有两个核心属性:
1、startTasks chan *startTask ,这是containerd到runc的桥梁,由(w *worker) Start()函数消费
/2、 tasks chan Task ,所有来自于docker-daemon的request都会转化为event存放到这,由 (s *Supervisor) Start()函数消费

// Supervisor描述了一个supervisor容器
type Supervisor struct {
	// stateDir是系统上存放容器运行时状态信息的目录。
	stateDir string
	// 用于执行容器的OCI兼容运行时的名称
	runtime     string
	runtimeArgs []string
	shim        string
	containers  map[string]*containerInfo
	startTasks  chan *startTask //这是containerd到runc的桥梁,由func (w *worker) Start()消费
	// 我们需要一个围绕订阅者映射的锁,因为映射的添加和删除是通过API进行的,所以我们不能真正控制并发性
	subscriberLock sync.RWMutex
	subscribers    map[chan Event]struct{}
	machine        Machine
	tasks          chan Task //所有来自于docker-daemon的request都会转化为event存放到这,由func (s *Supervisor) Start()消费
	monitor        *Monitor
	eventLog       []Event
	eventLock      sync.Mutex
	timeout        time.Duration
}

type startTask struct {
	Container      runtime.Container
	CheckpointPath string
	Stdin          string
	Stdout         string
	Stderr         string
	Err            chan error
	StartResponse  chan StartResponse
}

1、创建一个Supervisor对象

// 新建一个supervisor,任务管理器,处理tasks,记录着并监视着系统中每个container
func New(stateDir string, runtimeName, shimName string, runtimeArgs []string, timeout time.Duration, retainCount int) (*Supervisor, error) {
	startTasks := make(chan *startTask, 10)
	if err := os.MkdirAll(stateDir, 0755); err != nil {
		return nil, err
	}
	//获取宿主机的cpu和memory信息
	machine, err := CollectMachineInformation()
	if err != nil {
		return nil, err
	}
	//monitor用于监视容器中的进程
	monitor, err := NewMonitor()
	if err != nil {
		return nil, err
	}
	s := &Supervisor{
		stateDir:    stateDir,
		containers:  make(map[string]*containerInfo),
		startTasks:  startTasks,
		machine:     machine,
		subscribers: make(map[chan Event]struct{}),
		tasks:       make(chan Task, defaultBufferSize),
		monitor:     monitor,
		runtime:     runtimeName,
		runtimeArgs: runtimeArgs,
		shim:        shimName,
		timeout:     timeout,
	}
	//处理event log
	if err := setupEventLog(s, retainCount); err != nil {
		return nil, err
	}
	//利用monitor处理exit的进程和触发oom的进程
	go s.exitHandler()
	go s.oomHandler()
	if err := s.restore(); err != nil {
		return nil, err
	}
	return s, nil
}

2、Supervisor的SendTask()函数

// 生产者
func (s *Supervisor) SendTask(evt Task) {
	TasksCounter.Inc(1) //任务数+1
	s.tasks <- evt
}

3、Supervisor的Start()函数
StartTask是docker startctr start对应的task类型,是每一个Request都有着自己的task类型,而Supervisor正是据此来做出相应处理。其主要功能是消费tasks chan Task

//Start()是一个非阻塞的调用,运行监视器来监视contianer进程,并运行新的容器。这里的event loop是唯一一个地方可以修改容器和其process的状态
func (s *Supervisor) Start() error {
	logrus.WithFields(logrus.Fields{
		"stateDir":    s.stateDir,
		"runtime":     s.runtime,
		"runtimeArgs": s.runtimeArgs,
		"memory":      s.machine.Memory,
		"cpus":        s.machine.Cpus,
	}).Debug("containerd: supervisor running")
	go func() {
		//消费tasks chan Task
		for i := range s.tasks {
			//新建一个goroutine来遍历s.tasks通道中的所有任务并处理
			s.handleTask(i)
		}
	}()
	return nil
}
func (s *Supervisor) handleTask(i Task) {
	var err error
	//通过任务类型识别调用具体的方法执行函数
	switch t := i.(type) {
	case *AddProcessTask:
		err = s.addProcess(t)
	case *CreateCheckpointTask:
		err = s.createCheckpoint(t) //创建检查点的进一步实现
	case *DeleteCheckpointTask:
		err = s.deleteCheckpoint(t)
	case *StartTask: //`docker start`和`ctr start`对应的task
		err = s.start(t)
	case *DeleteTask:
		err = s.delete(t)
	case *ExitTask:
		err = s.exit(t)
	case *GetContainersTask:
		err = s.getContainers(t)
	case *SignalTask:
		err = s.signal(t)
	case *StatsTask:
		err = s.stats(t)
	case *UpdateTask:
		err = s.updateContainer(t)
	case *UpdateProcessTask:
		err = s.updateProcess(t)
	case *OOMTask:
		err = s.oom(t)
	default:
		err = ErrUnknownTask
	}
	if err != errDeferredResponse {
		i.ErrorCh() <- err
		close(i.ErrorCh())
	}
}

4、处理docker start和ctr start对应的task

func (s *Supervisor) start(t *StartTask) error {
	start := time.Now()
	rt := s.runtime
	rtArgs := s.runtimeArgs
	if t.Runtime != "" {
		rt = t.Runtime
		rtArgs = t.RuntimeArgs
	}
	//创建一个runtime.container
	container, err := runtime.New(runtime.ContainerOpts{
		Root:        s.stateDir,
		ID:          t.ID,
		Bundle:      t.BundlePath,
		Runtime:     rt,
		RuntimeArgs: rtArgs,
		Shim:        s.shim,
		Labels:      t.Labels,
		NoPivotRoot: t.NoPivotRoot,
		Timeout:     s.timeout,
	})
	if err != nil {
		return err
	}
	//记录到Supervisor.containers[]中,之后容器数量+1
	s.containers[t.ID] = &containerInfo{
		container: container,
	}
	ContainersCounter.Inc(1)
	//把runtime.container函数封装到startTask结构体函数中
	task := &startTask{
		Err:           t.ErrorCh(),
		Container:     container,
		StartResponse: t.StartResponse,
		Stdin:         t.Stdin,
		Stdout:        t.Stdout,
		Stderr:        t.Stderr,
	}
	if t.Checkpoint != nil {
		task.CheckpointPath = filepath.Join(t.CheckpointDir, t.Checkpoint.Name)
	}
	//把task发给Supervisor的startTasks  chan *startTask
	s.startTasks <- task
	ContainerCreateTimer.UpdateSince(start)
	return errDeferredResponse
}

四、Supervisor的worker

type worker struct {
	wg *sync.WaitGroup
	s  *Supervisor
}
// 返回一个新的初始化的worker
func NewWorker(s *Supervisor, wg *sync.WaitGroup) Worker {
	return &worker{
		s:  s,
		wg: wg,
	}
}

1、worker的Start()函数
worker的Start()函数负责调用containerd-shim,监控容器的进程,把结果回传给StartResponse chan StartResponse。worker的Start()函数的执行的两个关键步骤为:
1、 Container.Start(),会通过containerd-shim调用runc create {containerID}命令
2、process.Start()中会调用runc start {containerID}命令启动容器中的进程

// Start运行一个负责启动新容器
func (w *worker) Start() {
	defer w.wg.Done()
	//消费startTasks  chan *startTask
	for t := range w.s.startTasks {
		started := time.Now()
		//通过containerd-shim调用`runc create {containerID}`命令
		process, err := t.Container.Start(t.CheckpointPath, runtime.NewStdio(t.Stdin, t.Stdout, t.Stderr))
		if err != nil {
			logrus.WithFields(logrus.Fields{
				"error": err,
				"id":    t.Container.ID(),
			}).Error("containerd: start container")
			t.Err <- err
			//启动失败,创建DeleteTask,并放入tasks中
			evt := &DeleteTask{
				ID:      t.Container.ID(),
				NoEvent: true,
				Process: process,
			}
			w.s.SendTask(evt)
			continue
		}
		//把该容器加入oom监控列表中
		if err := w.s.monitor.MonitorOOM(t.Container); err != nil && err != runtime.ErrContainerExited {
			if process.State() != runtime.Stopped {
				logrus.WithField("error", err).Error("containerd: notify OOM events")
			}
		}
		//监控容器的进程
		if err := w.s.monitorProcess(process); err != nil {
			logrus.WithField("error", err).Error("containerd: add process to monitor")
			t.Err <- err
			evt := &DeleteTask{
				ID:      t.Container.ID(),
				NoEvent: true,
				Process: process,
			}
			w.s.SendTask(evt)
			continue
		}
		//process.Start()中会调用`runc start {containerID}`命令启动容器
		if err := process.Start(); err != nil {
			logrus.WithField("error", err).Error("containerd: start init process")
			t.Err <- err
			evt := &DeleteTask{
				ID:      t.Container.ID(),
				NoEvent: true,
				Process: process,
			}
			w.s.SendTask(evt)
			continue
		}
		ContainerStartTimer.UpdateSince(started)
		t.Err <- nil
		//结果回传给StartResponse  chan StartResponse和/api/grpc/server/server.go中的等待对应上
		t.StartResponse <- StartResponse{
			Container: t.Container,
		}
		w.s.notifySubscribers(Event{
			Timestamp: time.Now(),
			ID:        t.Container.ID(),
			Type:      StateStart,
		})
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

绝域时空

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值