0. 前言
Kubernetes:kubelet 原始碼分析之建立 pod 流程 介紹了 kubelet
建立 pod 的流程,其中介紹了 kubelet
呼叫 runtime cri 介面建立 pod。containerd 原始碼分析:啟動註冊流程 介紹了 containerd
作為一種行業標準的高階執行時的啟動註冊流程。那麼,kubelet
是怎麼和 containerd
互動的呢? 本文會帶著這個問題分析 kubelet
和 containerd
的互動。
1. kubelet 和 containerd 互動
1.1 kubelet
如 Kubernetes:kubelet 原始碼分析之建立 pod 流程 分析,kubelet
呼叫 runtime cri 介面 /runtime.v1.RuntimeService/RunPodSandbox
建立 pod:
// kubernetes/vendor/k8s.io/cri-api/pkg/apis/runtime/v1/api.pb.go
func (c *runtimeServiceClient) RunPodSandbox(ctx context.Context, in *RunPodSandboxRequest, opts ...grpc.CallOption) (*RunPodSandboxResponse, error) {
out := new(RunPodSandboxResponse)
err := c.cc.Invoke(ctx, "/runtime.v1.RuntimeService/RunPodSandbox", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
kubelet
是 runtime cri 介面呼叫的客戶端,那麼容器執行時作為服務端是怎麼提供服務的呢?
1.2 kubelet 和 containerd 互動流程
在介紹容器執行時提供的服務之前先看下 cri 架構圖。
從圖中可以看出,containerd
的 CRI 外掛提供 image service
和 runtime service
,負責對接 kubelet
runtime cri 的介面呼叫,並將呼叫轉發給 containerd
。
繼續,檢視 containerd
的處理流程。
1.3 containerd
1.3.1 CRI Plugin
根據 cri 架構圖
, 從 CRI
外掛入手檢視 id 為 io.containerd.grpc.v1.cri
的 CRI
外掛。
// containerd/plugins/cri/cri.go
func initCRIService(ic *plugin.InitContext) (interface{}, error) {
...
// Get runtime service.
criRuntimePlugin, err := ic.GetByID(plugins.CRIServicePlugin, "runtime")
if err != nil {
return nil, fmt.Errorf("unable to load CRI runtime service plugin dependency: %w", err)
}
// Get image service.
criImagePlugin, err := ic.GetByID(plugins.CRIServicePlugin, "images")
if err != nil {
return nil, fmt.Errorf("unable to load CRI image service plugin dependency: %w", err)
}
...
service := &criGRPCServer{
RuntimeServiceServer: rs,
ImageServiceServer: is,
Closer: s, // TODO: Where is close run?
initializer: s,
}
if config.DisableTCPService {
return service, nil
}
return criGRPCServerWithTCP{service}, nil
}
外掛返回的是 criGRPCServerWithTCP
物件。其中,包括 criGRPCServer
物件。criGRPCServer
物件實現了 grpcService
介面,將呼叫介面的 Register
註冊物件到 grpc server。
// containerd/plugins/cri/cri.go
// Register registers all required services onto a specific grpc server.
// This is used by containerd cri plugin.
func (c *criGRPCServer) Register(s *grpc.Server) error {
return c.register(s)
}
func (c *criGRPCServer) register(s *grpc.Server) error {
instrumented := instrument.NewService(c)
runtime.RegisterRuntimeServiceServer(s, instrumented)
runtime.RegisterImageServiceServer(s, instrumented)
return nil
}
在 criGRPCServer.register
中建立 instrumentedService
物件。
type instrumentedService struct {
c criService
}
func NewService(c criService) GRPCServices {
return &instrumentedService{c: c}
}
instrumentedService
包括 criService
物件。實際提供 runtime service
和 image service
的就是 criService
物件。
以註冊 runtime service
為例,檢視 runtime.RegisterRuntimeServiceServer(s, instrumented)
做了什麼。
// containerd/vendor/k8s.io/cri-api/pkg/apis/runtime/v1/api.pb.go
func RegisterRuntimeServiceServer(s *grpc.Server, srv RuntimeServiceServer) {
s.RegisterService(&_RuntimeService_serviceDesc, srv)
}
var _RuntimeService_serviceDesc = grpc.ServiceDesc{
ServiceName: "runtime.v1.RuntimeService",
HandlerType: (*RuntimeServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Version",
Handler: _RuntimeService_Version_Handler,
},
{
MethodName: "RunPodSandbox",
Handler: _RuntimeService_RunPodSandbox_Handler,
},
...
},
...
}
可以看到,註冊 instrumentedService
到 grpc 中,instrumentedService
提供 runtime.v1.RuntimeService
服務,包括 kubelet
呼叫的 RunPodSandbox
方法。
繼續看,instrumentedService
的 RunPodSandbox
做了什麼。
// containerd/internal/cri/instrumented_service.go
func (in *instrumentedService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandboxRequest) (res *runtime.RunPodSandboxResponse, err error) {
...
res, err = in.c.RunPodSandbox(ctrdutil.WithNamespace(ctx), r)
return res, errdefs.ToGRPC(err)
}
instrumentedService
呼叫 criGRPCServer
的 RunPodSandbox
方法,實際執行的是 criGRPCServer
中的 criServer
物件:
// containerd/internal/cri/server/sandbox_run.go
func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandboxRequest) (_ *runtime.RunPodSandboxResponse, retErr error) {
...
if err := c.sandboxService.CreateSandbox(ctx, sandboxInfo, sb.WithOptions(config), sb.WithNetNSPath(sandbox.NetNSPath)); err != nil {
return nil, fmt.Errorf("failed to create sandbox %q: %w", id, err)
}
ctrl, err := c.sandboxService.StartSandbox(ctx, sandbox.Sandboxer, id)
if err != nil {
...
}
...
}
criService.RunPodSandbox
呼叫的是 sandboxService
的 CreateSandbox
和 StartSandbox
方法。
1.3.2 sanbox Plugin
sandboxService
在 cri.initCRIService
中例項化:
// containerd/plugins/cri/cri.go
func initCRIService(ic *plugin.InitContext) (interface{}, error) {
...
sbControllers, err := getSandboxControllers(ic)
if err != nil {
return nil, fmt.Errorf("failed to get sandbox controllers from plugins %v", err)
}
...
options := &server.CRIServiceOptions{
RuntimeService: criRuntimePlugin.(server.RuntimeService),
ImageService: criImagePlugin.(server.ImageService),
StreamingConfig: streamingConfig,
NRI: getNRIAPI(ic),
Client: client,
SandboxControllers: sbControllers,
}
...
s, rs, err := server.NewCRIService(options)
...
service := &criGRPCServer{
RuntimeServiceServer: rs,
ImageServiceServer: is,
Closer: s, // TODO: Where is close run?
initializer: s,
}
}
首先,getSandboxControllers
獲得 sandbox controllers
:
// containerd/plugins/cri/cri.go
func getSandboxControllers(ic *plugin.InitContext) (map[string]sandbox.Controller, error) {
// plugins.SandboxControllerPlugin: "io.containerd.sandbox.controller.v1"
sandboxers, err := ic.GetByType(plugins.SandboxControllerPlugin)
if err != nil {
return nil, err
}
...
return sc, nil
}
sandbox.Controller
是型別為 io.containerd.sandbox.controller.v1
的外掛物件。將該物件作為 options
賦給 criServer
:
// containerd/internal/cri/server/service.go
func NewCRIService(options *CRIServiceOptions) (CRIService, runtime.RuntimeServiceServer, error) {
...
c := &criService{
...
sandboxService: newCriSandboxService(&config, options.SandboxControllers),
}
...
}
func newCriSandboxService(config *criconfig.Config, sandboxers map[string]sandbox.Controller) *criSandboxService {
return &criSandboxService{
sandboxControllers: sandboxers,
config: config,
}
}
criService.sandboxService.CreateSandbox
呼叫的是外掛物件 sanbox controllers
的 CreateSandbox
方法,該方法最終呼叫的是 sandboxClient
的 CreateSandbox
:
func (c *sandboxClient) CreateSandbox(ctx context.Context, in *CreateSandboxRequest, opts ...grpc.CallOption) (*CreateSandboxResponse, error) {
out := new(CreateSandboxResponse)
err := c.cc.Invoke(ctx, "/containerd.runtime.sandbox.v1.Sandbox/CreateSandbox", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
可以看到,在 sandboxClient.CreateSandbox
中呼叫 containerd
提供的 containerd cri 介面 /containerd.runtime.sandbox.v1.Sandbox/CreateSandbox
,該介面用來建立 sandbox,即 pod。
1.4 建立 pod 流程
根據上述分析,這裡畫出 kubelet
到 containerd
的互動流程圖如下:
2. 小結
本文在前文 Kubernetes:kubelet 原始碼分析之建立 pod 流程 和 containerd 原始碼分析:啟動註冊流程 的基礎上,進一步分析從 kubelet
到 containerd
的互動流程,打通了 kubelet
到 containerd
這一步。