一、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 start
和ctr 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,
})
}
}