containerd 原始碼分析:建立 container(三)

lubanseven發表於2024-06-04

文接 containerd 原始碼分析:建立 container(二)

1.2.2.2 啟動 task

上節介紹了建立 task,task 建立之後將返回 response 給 ctr。接著,ctr 呼叫 task.Start 啟動容器。

// containerd/client/task.go
func (t *task) Start(ctx context.Context) error {
	r, err := t.client.TaskService().Start(ctx, &tasks.StartRequest{
		ContainerID: t.id,
	})
	if err != nil {
		...
	}
	t.pid = r.Pid
	return nil
}

// containerd/api/services/tasks/v1/tasks_grpc.pb.go
func (c *tasksClient) Start(ctx context.Context, in *StartRequest, opts ...grpc.CallOption) (*StartResponse, error) {
	out := new(StartResponse)
	err := c.cc.Invoke(ctx, "/containerd.services.tasks.v1.Tasks/Start", in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

ctr 呼叫 contaienrd/containerd.services.tasks.v1.Tasks/Start 介面建立 task。進入 containerd 檢視提供該服務的外掛:

// containerd/plugins/services/tasks/service.go
func (s *service) Start(ctx context.Context, r *api.StartRequest) (*api.StartResponse, error) {
	return s.local.Start(ctx, r)
}

// containerd/plugins/services/tasks/local.go
func (l *local) Start(ctx context.Context, r *api.StartRequest, _ ...grpc.CallOption) (*api.StartResponse, error) {
	t, err := l.getTask(ctx, r.ContainerID)
	if err != nil {
		return nil, err
	}
	p := runtime.Process(t)
	if r.ExecID != "" {
		if p, err = t.Process(ctx, r.ExecID); err != nil {
			return nil, errdefs.ToGRPC(err)
		}
	}
	// 啟動 task: shimTask.Start
	if err := p.Start(ctx); err != nil {
		return nil, errdefs.ToGRPC(err)
	}
	state, err := p.State(ctx)
	if err != nil {
		return nil, errdefs.ToGRPC(err)
	}
	return &api.StartResponse{
		Pid: state.Pid,
	}, nil
}

// containerd/core/runtime/v2/shim.go
func (s *shimTask) Start(ctx context.Context) error {
	_, err := s.task.Start(ctx, &task.StartRequest{
		ID: s.ID(),
	})
	if err != nil {
		return errdefs.FromGRPC(err)
	}
	return nil
}

// containerd/api/runtime/task/v2/shim_ttrpc.pb.go
func (c *taskClient) Start(ctx context.Context, req *StartRequest) (*StartResponse, error) {
	var resp StartResponse
	if err := c.client.Call(ctx, "containerd.task.v2.Task", "Start", req, &resp); err != nil {
		return nil, err
	}
	return &resp, nil
}

經過 containerd 各個外掛的層層呼叫,最終走到 containerd.task.v2.Task.Start ttrpc 服務。提供 containerd.task.v2.Task.Start 服務的是 containerd-shim-runc-v2

// containerd/cmd/containerd-shim-runc-v2/task/service.go
// Start a process
func (s *service) Start(ctx context.Context, r *taskAPI.StartRequest) (*taskAPI.StartResponse, error) {
	// 根據 task 的 StartRequest 獲得 container 的 metadata
	container, err := s.getContainer(r.ID)
	if err != nil {
		return nil, err
	}

	...
	p, err := container.Start(ctx, r)
	if err != nil {
		handleStarted(container, p)
		return nil, errdefs.ToGRPC(err)
	}
	...
}

呼叫 Container.Start 啟動容器程序:

// containerd/cmd/containerd-shim-runc-v2/runc/container.go
// Start a container process
func (c *Container) Start(ctx context.Context, r *task.StartRequest) (process.Process, error) {
	p, err := c.Process(r.ExecID)
	if err != nil {
		return nil, err
	}
	if err := p.Start(ctx); err != nil {
		return p, err
	}
	...
}

Container.Start 呼叫 Process.Start 啟動容器程序。啟動容器後 runc init 將退出,將容器的主程序交由 runc init 的父程序 shim:

# ps -ef | grep 138915
root      138915       1  0 15:52 ?        00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace default -id nginx1 -address /run/containerd/containerd.sock
root      138934  138915  0 15:52 ?        00:00:00 nginx: master process nginx -g daemon off;

透過這樣的處理,容器程序就和 containerd 沒關係了,容器不再受 containerd 的影響,僅和它的 shim 有關係,被 shim 管理,這也是為什麼要引入 shim 的原因。

1.3 containerd

從上述 containerd 建立 container 的分析可以看出,containerd 中外掛之間的呼叫是分層的。contianerd 架構如下:

image

containerd 建立 container 的示意圖如下:

image

ctr 建立的 container 的互動流程圖如下:

image

2. 小結

containerd 原始碼分析系列文章介紹了 contianerd 是如何建立 container 的,完整了從 kubernetes 到容器建立這一條線。



相關文章