この記事は、DCOS (公開アカウントID: indagate) の許可を得て転載したものです。転載については出典元にお問い合わせください。 著者:ドゥアン・クアンフェン 編集者|zouyee Duan Quanfeng: K8s アーキテクチャに精通し、ランタイムの基礎となる技術的な詳細などに精通したソフトウェア エンジニア。 現在、弊社の既存 K8s クラスターのランタイムは docker から Containerd への切り替えが完了しています。友人の中には、Containerd と RunC の関係、docker と containerd の違い、K8s が Containerd を呼び出してコンテナを作成するプロセス、RunC が最終的にコンテナを作成する方法など、K8s と Containerd の呼び出しチェーンに関係するコンポーネントを理解していない人もいます。この記事では、K8s が Containerd をランタイムとして使用する場合の呼び出しチェーン全体を紹介し、ソースコード レベルで分析します。 次の図は、kubelet とランタイムの階層化アーキテクチャを示しています。 さまざまなランタイムの紹介については、「Containerd の詳細な分析 - ランタイム」を参照してください。 1. ランタイムの概要コンテナ ランタイムとは、コンテナ操作のライフ サイクル全体を管理する機能を意味します。具体的には、コンテナイメージの作成方法、コンテナイメージのフォーマット、コンテナイメージの管理方法、コンテナイメージの配布方法、コンテナの実行方法、作成されたコンテナインスタンスの管理方法などを意味します。 コンテナ ランタイムには OCI 仕様と呼ばれる業界標準があり、これは 2 つの部分に分かれています。 a.コンテナ ランタイム仕様: バンドルを通じてコンテナを実行する方法について説明します。バンドルは、config.json と呼ばれるコンテナ仕様ファイルと rootfs ファイルを含むディレクトリです。 rootfs ファイルには、コンテナ ランタイムに必要なオペレーティング システムのファイルが含まれています。 b.コンテナ イメージ仕様: コンテナ イメージをパッケージ化する方法と、イメージをバンドルに変換する方法を定義します。 この老人はCOVID-19の重症化が疑われている。彼はまだ比較的意識はあったものの、血液ガス分析の結果は非常に悪く、呼吸モニタリングのために集中治療室に入院する必要がありました。一般病棟からICUへの移動はエレベーターで数階上がるだけですが、酸素供給状態が適切でなければリスクは非常に高くなります。妻と娘は老人に会って話をしたいと思っていましたが、二度と会えないのではないかと心配していました。 現在、ランタイムを低レベルランタイムと高レベルランタイムに分割することが一般的です。低レベルランタイムは、runc や kata などのコンテナの作成方法に重点を置いていますが、高レベルには、docker や containerd に代表されるイメージ管理などのより上位レベルの機能が含まれます。 K8s の kubelet はコンテナ ランタイムを呼び出してコンテナを作成しますが、コンテナ ランタイムが多すぎてそれぞれに互換性を持たせることは不可能です。 K8s はコンテナ ランタイムに接続するときに CRI インターフェースを定義し、コンテナ ランタイムはこのインターフェースを実装するだけで使用できます。次の図は、docker と containerd を使用した k8s の呼び出しチェーンを示しています。 containerd を使用する場合、CRI インターフェースは containerd コードに実装されます。 docker を使用する場合、CRI インターフェースは docker-shim (kubernetes/pkg/kubelet/dockershim/docker_service.go) と呼ばれる k8s コードに実装されます。この部分のコードは歴史的な理由により k8s コードに含まれています。当時、Docker はコンテナの事実上の業界標準でした。しかし、CRI サポートを実装するランタイムが増えるにつれて、docker-shim のメンテナンスはコミュニティにとってますます負担になってきました。最新の K8s バージョンでは、この部分のコードは削除されており、mirantis によって一時的にメンテナンスされています。次の図は、プラグインの開発履歴を示しています。 2. Containerd CRI の紹介Containerd は業界標準のコンテナ ランタイムです。これは、イメージの配布と保存、コンテナの操作と監視、基盤となるストレージとネットワークなど、ホストとそのファイルシステム上のコンテナのライフサイクル全体を管理するデーモン プロセスです。 Containerd には、K8s、docker などの複数のクライアントがあります。異なるクライアントのコンテナまたはイメージを分離するために、Containerd には名前空間の概念があります。デフォルトでは、docker の名前空間は moby で、K8s の名前空間は k8s.io です。 Containerd では、コンテナはコンテナのメタデータを表します。 containerd のタスクは、コンテナ オブジェクトを取得し、それをオペレーティング システムで実行できるプロセスに変換するために使用されます。コンテナ内の実行可能なオブジェクトを表します。 Containerd 内の cri モジュールは K8s の CRI インターフェースを実装しているため、K8s の kubelet は containerd を直接使用できます。 CRIインターフェースには、RuntimeServiceとImageServiceが含まれます。 //ランタイムサービスは、リモートコンテナランタイムのパブリックAPIを定義します サービス RuntimeService { // Version は、ランタイム名、ランタイム バージョン、およびランタイム API バージョンを返します。 rpc バージョン( VersionRequest )は( VersionResponse )を返します{ }
// RunPodSandbox はポッドレベルのサンドボックスを作成して起動します。ランタイムは、 //成功するとサンドボックスは準備完了状態になります。 rpc RunPodSandbox ( RunPodSandboxRequest )は( RunPodSandboxResponse )を返します{ } // StopPodSandboxはサンドボックスの一部である実行中のプロセスを停止し、 //サンドボックスに割り当てられたネットワーク リソース( IP アドレスなど)を再利用します。 //サンドボックス内に実行中のコンテナがある場合は、強制的に //終了しました。 //この呼び出しはべき等であり、関連するすべての呼び出しが失敗した場合、エラーを返してはなりません。 //リソースはすでに回収されています。 kubeletはStopPodSandboxを呼び出す // RemovePodSandbox を呼び出す前に少なくとも 1 回。また、 //サンドボックスが不要になったらすぐに、リソースを積極的に回収します。したがって、 //複数の StopPodSandbox 呼び出しが予想されます。 rpc StopPodSandbox ( StopPodSandboxRequest )は( StopPodSandboxResponse )を返します{ } // RemovePodSandbox はサンドボックスを削除します。実行中のコンテナがある場合 //サンドボックス内では、強制的に終了して削除する必要があります。 //この呼び出しはべき等であり、サンドボックスが //すでに削除されています。 rpc RemovePodSandbox ( RemovePodSandboxRequest )は( RemovePodSandboxResponse )を返します{ } // PodSandboxStatus は PodSandbox のステータスを返します。 PodSandboxが //存在する場合、エラーを返します。 rpc PodSandboxStatus ( PodSandboxStatusRequest )は( PodSandboxStatusResponse )を返します{ } // ListPodSandbox は PodSandbox のリストを返します。 rpc ListPodSandbox ( ListPodSandboxRequest )は( ListPodSandboxResponse )を返します{ }
// CreateContainer は指定された PodSandboxに新しいコンテナを作成します rpc CreateContainer ( CreateContainerRequest )は( CreateContainerResponse )を返します{ } // StartContainer はコンテナを起動します。 rpc StartContainer ( StartContainerRequest )は( StartContainerResponse )を返します{ } // StopContainer は、実行中のコンテナを猶予期間(つまり、タイムアウト)で停止します。 //この呼び出しはべき等であり、コンテナが //すでに停止されています。 //猶予期間が経過したら、ランタイムはコンテナを強制終了する必要があります。 //到達しました。 rpc StopContainer ( StopContainerRequest ) returns ( StopContainerResponse ) { } // RemoveContainer はコンテナを削除します。コンテナが実行中の場合、 //コンテナを強制的に削除する必要があります。 //この呼び出しはべき等であり、コンテナが //すでに削除されています。 rpc RemoveContainer ( RemoveContainerRequest )は( RemoveContainerResponse )を返します{ } // ListContainers はフィルターごとにすべてのコンテナーを一覧表示します。 rpc ListContainers ( ListContainersRequest )は( ListContainersResponse )を返します{ } // ContainerStatus はコンテナのステータスを返します。コンテナが //存在する場合、エラーを返します。 rpc ContainerStatus ( ContainerStatusRequest )は( ContainerStatusResponse )を返します{ } // UpdateContainerResources はコンテナの ContainerConfig を同期的に更新します。 //ランタイムが要求されたリソースをトランザクション的に更新できない場合、エラーが返されます。 rpc UpdateContainerResources ( UpdateContainerResourcesRequest )は( UpdateContainerResourcesResponse )を返します{ } // ReopenContainerLog はランタイムに stdout / stderr ログファイルを再度開くように要求します //コンテナ用。これは、ログファイルが作成された後に呼び出されることが多い。 //回転しました。コンテナが実行中でない場合、コンテナランタイムは選択できる //新しいログ ファイルを作成してnil を返すか、エラーを返します。 //エラーが返された場合、新しいコンテナ ログ ファイルを作成してはなりません。 rpc ReopenContainerLog ( ReopenContainerLogRequest )は( ReopenContainerLogResponse )を返します{ }
// ExecSync はコンテナ内のコマンドを同期的に実行します。 rpc ExecSync ( ExecSyncRequest )は( ExecSyncResponse )を返します{ } // Exec は、コンテナー内でコマンドを実行するためのストリーミング エンドポイントを準備します。 rpc Exec ( ExecRequest )は( ExecResponse )を返します{ } // Attach は、実行中のコンテナーに接続するストリーミング エンドポイントを準備します。 rpc Attach ( AttachRequest )は( AttachResponse )を返します{ } // PortForward は、PodSandboxからポートを転送するためのストリーミング エンドポイントを準備します。 rpc PortForward ( PortForwardRequest )は( PortForwardResponse )を返します{ }
// ContainerStats はコンテナの統計を返します。コンテナが //存在する場合、呼び出しはエラーを返します。 rpc ContainerStats ( ContainerStatsRequest )は( ContainerStatsResponse )を返します{ } // ListContainerStats は実行中のすべてのコンテナの統計を返します。 rpc ListContainerStats ( ListContainerStatsRequest )は( ListContainerStatsResponse )を返します{ }
// PodSandboxStats はポッド サンドボックスの統計を返します。ポッドサンドボックスが //存在する場合、呼び出しはエラーを返します。 rpc PodSandboxStats ( PodSandboxStatsRequest )は( PodSandboxStatsResponse )を返します{ } // ListPodSandboxStats は、フィルターに一致するポッド サンドボックスの統計を返します。 rpc ListPodSandboxStats ( ListPodSandboxStatsRequest )は( ListPodSandboxStatsResponse )を返します{ }
// UpdateRuntimeConfig は、指定されたリクエストに基づいてランタイム構成を更新します。 rpc UpdateRuntimeConfig ( UpdateRuntimeConfigRequest )は( UpdateRuntimeConfigResponse )を返します{ }
// Status はランタイムのステータスを返します。 rpc ステータス( StatusRequest )は( StatusResponse )を返します{ } }
// ImageService は、イメージを管理するためのパブリック API を定義します。 サービス ImageService { // ListImages は既存の画像を一覧表示します。 rpc ListImages ( ListImagesRequest )は( ListImagesResponse )を返します{ } // ImageStatus はイメージのステータスを返します。画像が //存在する場合、 ImageStatusResponse.Imageが設定された応答を返します //ゼロ。 rpc ImageStatus ( ImageStatusRequest )は( ImageStatusResponse )を返します{ } // PullImage は認証設定を含むイメージをプルします。 rpc PullImage ( PullImageRequest )は( PullImageResponse )を返します{ } // RemoveImage は画像を削除します。 //この呼び出しはべき等であり、画像が //すでに削除されています。 rpc RemoveImage ( RemoveImageRequest )は( RemoveImageResponse )を返します{ } // ImageFSInfo は、画像を保存するために使用されるファイルシステムの情報を返します。 rpc ImageFsInfo ( ImageFsInfoRequest )は( ImageFsInfoResponse )を返します{ } } kubelet が CRI インターフェースを呼び出して、2 つのビジネス コンテナ A と B を含む Pod を作成するプロセスは次のとおりです。 ①ポッド用のサンドボックスを作成する ②コンテナAを作成する ③コンテナAを起動する ④ コンテナBを作成する ⑤コンテナBを起動する 3. 「Containerd CRI 実装」ランポッドサンドボックスRunPodSandbox のプロセスは次のとおりです。 ① サンドボックスイメージをプルし、containerdで設定する ②ポッド作成時に使用するランタイムを取得します。ポッドを作成するときに、yaml ファイルで指定できます。指定されていない場合は、containerd のデフォルトが使用されます。 (ランタイムはcontainerdで設定されます) ③ ポッドがhostNetworkでない場合は、新しいネット名前空間を追加し、cniプラグインを使用してネットワークを設定します(criServiceは初期化時にコンテナ内のcriで指定されたプラグイン情報を読み込みます) ④ containerdクライアントを呼び出してコンテナを作成する ⑤ 現在のポッドのディレクトリを、rootDir/io.containerd.grpc.v1.cri/sandboxesの下にポッドIDの名前で作成します。 (pkg/cri/cri.go) ⑥ 選択したランタイムに基づいてサンドボックスコンテナのタスクを作成する ⑦ サンドボックスコンテナのタスクを開始し、サンドボックスをデータベースに追加する コードはcontainerd/pkg/cri/server/sanbox_run.goにあります。 // RunPodSandbox はポッドレベルのサンドボックスを作成して起動します。ランタイムは、 //サンドボックスは準備完了状態です。 func ( c * criService ) RunPodSandbox ( ctx コンテキスト.Context , r *ランタイム.RunPodSandboxRequest ) ( _ *ランタイム.RunPodSandboxResponse , retErr エラー) { config : = r.GetConfig ( ) ログ.G ( ctx ) .Debugf ( "サンドボックス設定 %+v" , config )
//サンドボックスの一意の IDと名前を生成し、名前を予約します。 id : = util.GenerateID ( ) メタデータ: = config.GetMetadata ( ) メタデータ== nil の場合{ nil を返します。エラー.New ( "サンドボックス構成にはメタデータが含まれている必要があります" ) } 名前: = makeSandboxName (メタデータ) log .G ( ctx ) .WithField ( "podsandboxid" , id ) .Debugf ( "サンドボックス名 %q の生成された ID" , name )
// cleanupErr は遅延関数内の重要なクリーンアップ操作によって返された最後のエラーを記録します。 // CNI のティアダウンや実行中のサンドボックス タスクの停止など。 //何らかの理由でクリーンアップが完了しなかった場合、 CRIプラグインはサンドボックスを離れます //準備完了状態ではありませんが、 kubeletの syncPod ワークフローの次回実行時にクリーンアップできます。 var cleanupErr エラー
// 同時実行の `RunPodSandbox` リクエストを回避するためにサンドボックス名を予約します。 //同じサンドボックス。 エラーの場合:= c.sandboxNameIndex.Reserve(name, id);エラー != ゼロ { nil を返します。fmt.Errorf("サンドボックス名 %q: %w の予約に失敗しました", name, err) } 遅延関数() { // 関数がエラーを返す場合は名前を解放します。 // cleanupErr != nil の場合、名前は sandbox_remove でクリーンアップされます。 retErr != nil && cleanupErr == nil の場合 { c.sandboxNameIndex.ReleaseByName(名前) } }()
var ( エラー エラー サンドボックス情報 = sb.Sandbox{ID: id} )
ociRuntime、エラー := c.getSandboxRuntime(config, r.GetRuntimeHandler()) err != nil の場合 { nil を返します。fmt.Errorf("サンドボックス %q: %w の OCI ランタイムを取得できません", id, err) }
サンドボックス情報.Runtime.Name = ociRuntime.Type
// ランタイムオプションを取得する ランタイムオプション、エラー:= generateRuntimeOptions(ociRuntime、c.config) err != nil の場合 { nil を返します。fmt.Errorf("サンドボックス ランタイム オプションの生成に失敗しました: %w", err) }
...
// 初期の内部サンドボックス オブジェクトを作成します。 サンドボックス := sandboxstore.NewSandbox( ... )
_ の場合、エラー:= c.client.SandboxStore().Create(ctx, sandboxInfo);エラー != ゼロ { nil を返します。fmt.Errorf("サンドボックス メタデータの保存に失敗しました: %w", err) } ...
// ホスト ネットワークが要求されなかった場合は、ネットワーク名前空間を設定します。 もし!ホストネットワーク(設定) { netStart : =時間.Now ( ) //ホストネットワーク名前空間にない場合は、名前空間を作成してサンドボックスを設定します //ハンドル。サンドボックス メタデータのNetNSPathとNetNS は、非ホスト ネットワークの場合のみ空ではありません。 //名前空間。ポッドがホストネットワーク名前空間にある場合は両方とも空であり、 //使用されます。 var netnsMountDir = "/var/run/netns" c .config .NetNSMountsUnderStateDir {の場合 netnsMountDir =ファイルパス.Join ( c .config .StateDir , "netns" ) } サンドボックス.NetNS 、エラー= netns .NewNetNS ( netnsMountDir ) err != nil の場合{ nilを返します。fmt .Errorf ( "サンドボックス %q: %w のネットワーク名前空間の作成に失敗しました" 、 id 、 err ) } //コンテナの仕様を生成するために使用されるストア内のネットワーク名前空間を更新します サンドボックス.NetNSPath = サンドボックス.NetNS.GetPath() 遅延関数() { // すべてのリソースのクリーンアップが完了した場合にのみネットワーク名前空間を削除します retErr != nil && cleanupErr == nil の場合 { cleanupErr = sandbox.NetNS.Remove() の場合; cleanupErr != nil { log.G(ctx).WithError(cleanupErr).Errorf("サンドボックス %q のネットワーク名前空間 %s を削除できませんでした", sandbox.NetNSPath, id) 戻る } サンドボックス.NetNSPath = "" } }()
エラーの場合:= sandboxInfo.AddExtension(podsandbox.MetadataKey, &sandbox.Metadata);エラー != ゼロ { nil を返します。fmt.Errorf("サンドボックス %q をストアに保存できません: %w", id, err) } // サンドボックスのメタデータをストアに保存する sandboxInfo の場合、err = c.client.SandboxStore().Update(ctx, sandboxInfo, "extensions");エラー != ゼロ { nil を返します。fmt.Errorf("サンドボックス %q の拡張機能を更新できません: %w", id, err) }
// setupPodNetwork 関数の呼び出しの前に、この defer を teardownPodNetwork に定義します。 // これは、setupPodNetworkでは他のリソースとは異なり、エラーが返されてもリソースが割り当てられるためです。 // 作成関数。 遅延関数() { // すべてのリソースのクリーンアップが完了した場合にのみ、ネットワーク名前空間を削除します。 retErr != nil && cleanupErr == nil の場合 { deferCtx、deferCancel := ctrdutil.DeferContext() 延期する deferCancel() // エラーが返された場合はネットワークを破棄します。 cleanupErr が c.teardownPodNetwork(deferCtx, sandbox) の場合; cleanupErr != nil { log.G(ctx).WithError(cleanupErr).Errorf("サンドボックス %q のネットワークを破棄できませんでした", id) }
} }()
// サンドボックス用のネットワークをセットアップします。 // クリアコンテナのような特定の VM ベースのソリューション (Issue containerd/cri-containerd#524) // CRI shim がネットワーク名前空間を照会してチェックしないという前提に依存します。 // IP などのネットワーク状態。 // 将来のランタイム実装では、CRI shim 実装の詳細に依存しないようにする必要があります。 // ただし、この場合、IPをキャッシュすると、 // ポッドのネットワーク名前空間を呼び出して、すべての veth インターフェースの IP を照会します。 // SandboxStatus リクエスト。 エラーの場合:= c.setupPodNetwork(ctx, &sandbox);エラー != ゼロ { nil を返します。fmt.Errorf("サンドボックス %q: %w のネットワークの設定に失敗しました", id, err) } sandboxCreateNetworkTimer.UpdateSince(netStart) }
エラーの場合:= sandboxInfo.AddExtension(podsandbox.MetadataKey, &sandbox.Metadata);エラー != ゼロ { nil を返します。fmt.Errorf("サンドボックス %q をストアに保存できません: %w", id, err) }
コントローラー、エラー:= c.getSandboxController(config, r.GetRuntimeHandler()) err != nil の場合 { nil を返します。fmt.Errorf("サンドボックス コントローラーの取得に失敗しました: %w", err) }
// サンドボックスのメタデータをストアに保存する sandboxInfo の場合、err = c.client.SandboxStore().Update(ctx, sandboxInfo, "extensions");エラー != ゼロ { nil を返します。fmt.Errorf("サンドボックス %q の拡張機能を更新できません: %w", id, err) }
ランタイム開始:= time.Now()
エラーの場合:= controller.Create(ctx, id);エラー != ゼロ { nil を返します。fmt.Errorf("サンドボックス %q の作成に失敗しました: %w", id, err) }
応答、エラー:=コントローラ.Start(ctx, id) err != nil の場合 { sandbox.Container、_ = c.client.LoadContainer(ctx、id) resp != nil && resp.SandboxID == "" && resp.Pid == 0 && resp.CreatedAt == nil && len(resp.Labels) == 0 の場合 { // resp が nil 以外のゼロ値の場合、クリーンアップ中にエラーが発生しました cleanupErr = fmt.Errorf("サンドボックスのクリーンアップに失敗しました") } nil を返します。fmt.Errorf("サンドボックス %q の起動に失敗しました: %w", id, err) }
// TODO: これを削除します。サンドボックス オブジェクトにはコンテナー フィールドがなくなります。 ociRuntime.SandboxMode == string(criconfig.ModePodSandbox) の場合 { コンテナ、エラー:= c.client.LoadContainer(ctx, id) err != nil の場合 { nil を返します。fmt.Errorf("サンドボックスのコンテナ %q の読み込みに失敗しました: %w", id, err) } sandbox.Container = コンテナ }
ラベル:= resp.GetLabels() ラベル == nil { ラベル = マップ[文字列]文字列{} }
sandbox.ProcessLabel = ラベル["selinux_label"]
err := sandbox.Status.Update(func(status sandboxstore.Status) (sandboxstore.Status, error) { // サンドボックス コンテナーを正常に起動した後、ポッド サンドボックスを準備完了として設定します。 status.Pid = 応答Pid ステータス.State = サンドボックスストア.StateReady status.CreatedAt = protobuf.FromTimestamp(resp.CreatedAt) 戻り値: nil });エラー != ゼロ { nil を返します。fmt.Errorf("サンドボックス ステータスの更新に失敗しました: %w", err) }
// INIT 状態のサンドボックスをサンドボックス ストアに追加します。 エラーの場合:= c.sandboxStore.Add(sandbox);エラー != ゼロ { nil を返します。fmt.Errorf("サンドボックス %+v をストアに追加できませんでした: %w", サンドボックス, err) }
// ContainerId と SandboxId の両方が SandboxId に等しい CONTAINER_CREATED イベントを送信します。 // これはsandboxStore.Add()の後に実行する必要があることに注意してください。 // ストアから SandboxStatus を取得し、イベントに含めます。 c.generateAndSendContainerEvent(ctx, id, id, ランタイム.ContainerEventType_CONTAINER_CREATED_EVENT)
// ストアにサンドボックスを追加した後にモニターを起動します。これにより、 // イベント モニターが TaskExit イベントを受信すると、サンドボックスはストア内にあります。 // // サンドボックスがストアに追加される前に、containerd からの TaskOOM が来る場合があります。 // ただし、現時点ではサンドボックス TaskOOM については気にしないので、問題ありません。 関数( ) { resp 、 err : =コントローラ.Wait ( ctrdutil.NamespacedContext ( ) 、 id ) err != nil の場合{ log .G ( ctx ) .WithError ( err ) .Error ( "サンドボックス コントローラーの待機に失敗しました。終了イベントをスキップします" ) 戻る }
e : = &イベントタイプ.TaskExit { コンテナID : id 、 ID : id 、 // Pidは使用されません ピッド: 0 、 ExitStatus : resp .ExitStatus 、 ExitedAt :それぞれExitedAt 、 } c .eventMonitor .backOff .enBackOff ( id , e ) } ( )
// ContainerId が SandboxId に等しい CONTAINER_STARTED イベントを送信します。 c .generateAndSendContainerEvent ( ctx 、 id 、 id 、ランタイム.ContainerEventType_CONTAINER_STARTED_EVENT )
sandboxRuntimeCreateTimer .WithValues (ラベル[ "oci_runtime_type" ] ) .UpdateSince (ランタイム開始)
戻り値&ランタイム.RunPodSandboxResponse { PodSandboxId : id } 、 nil } コンテナの作成CreateContainer は、指定された PodSandbox に新しいコンテナ メタデータを作成します。プロセスは次のとおりです。 ①コンテナのサンドボックス情報を取得する ②コンテナが使用する画像のイメージハンドラを初期化する ③ rootDir/io.containerd.grpc.v1.criディレクトリ内のコンテナIDにちなんで名付けられたディレクトリ ④ サンドボックスから使用するランタイムを取得する ⑤ コンテナのcontainerSpecを作成する ⑥ containerdクライアントを使用してコンテナを作成する ⑦ コンテナ情報を保存する containerd/pkg/cri/server/container_create.go のコードを参照してください。以下は省略したコードです。 func ( c * criService ) CreateContainer ( ctx コンテキスト.Context , r *ランタイム.CreateContainerRequest ) ( _ *ランタイム.CreateContainerResponse , retErr エラー) {
サンドボックス、エラー: = c .sandboxStore .Get ( r .GetPodSandboxId ( ) ) s 、 err : =サンドボックス.Container .Task ( ctx 、 nil ) サンドボックスPid : = s.Pid ( ) イメージ、エラー: = c .localResolve ( config .GetImage ( ) .GetImage ( ) ) err != nil の場合{ nil を返します。エラー.Wrapf ( err , "画像 %q を解決できませんでした" , config .GetImage ( ) .GetImage ( ) ) } containerdImage 、 err : = c .toContainerdImage ( ctx 、 image ) //サンドボックスと同じランタイムを使用してコンテナを実行します。 sandboxInfo 、 err : = sandbox .Container .Info ( ctx ) err != nil の場合{ nil を返します。エラー.Wrapf ( err 、 「サンドボックス %q 情報の取得に失敗しました」 、 sandboxID ) }
//コンテナのルート ディレクトリを作成します。コンテナルートディレクトリ: = c .getContainerRootDir ( id ) err = c .os .MkdirAll ( containerRootDir , 0755 )の場合;エラー!=ゼロ{ nil を返します。エラー.Wrapf ( err 、 「コンテナ ルート ディレクトリ %q の作成に失敗しました」 、 containerRootDir ) } ociRuntime 、エラー: = c .getSandboxRuntime ( sandboxConfig 、 sandbox .Metadata .RuntimeHandler ) err != nil の場合{ nil を返し、エラーをラップします( err 、 「サンドボックス ランタイムの取得に失敗しました」 ) }
spec , err : = c.containerSpec ( id , sandboxID , sandboxPid , sandbox.NetNSPath , containerName , containerdImage.Name ( ) , config , sandboxConfig , & image .ImageSpec .Config 、追加( mounts 、 volumeMounts... ) 、 ociRuntime ) err != nil の場合{ nil を返します。エラー.Wrapf ( err 、 「コンテナ %q 仕様の生成に失敗しました」 、 id ) } opts = append ( opts , containerd .WithSpec ( spec , specOpts... ) , containerd .WithRuntime ( sandboxInfo .Runtime .Name 、 runtimeOptions ) 、 containerd .WithContainerLabels ( containerLabels ) 、 containerd .WithContainerExtension ( containerMetadataExtension 、 & meta ) ) var cntrcontainerd .Container cntr 、 err = c .client .NewContainer ( ctx 、 id 、 opts... )の場合;エラー!=ゼロ{ nil を返し、エラーをラップします( err 、 「containerd コンテナの作成に失敗しました」 ) } //コンテナをコンテナ ストアに追加します。 エラーの場合: = c .containerStore .Add (コンテナ) ;エラー!=ゼロ{ nil を返します。エラー.Wrapf ( err 、 「コンテナ %q をストアに追加できませんでした」 、 id ) } } コンテナの開始StartContainer はコンテナを起動するために使用されます。プロセスは次のとおりです。 ①保存されたコンテナのメタデータを読み取る ② 関連するサンドボックス情報を読む ③コンテナのタスクを作成する ④タスクを開始する コードはcontainerd/pkg/cri/server/container_start.goにあります。部分を省略した後のコードは次のとおりです。 func ( c * criService ) StartContainer ( ctx コンテキスト.Context , r *ランタイム.StartContainerRequest ) ( retRes *ランタイム.StartContainerResponse , retErr エラー) {
cntr , err : = c.containerStore.Get ( r.GetContainerId ( ) ) //サンドボックス ストアからサンドボックス構成を取得します。サンドボックス、 err : = c .sandboxStore .Get ( meta .SandboxID ) ctrInfo 、 err : = container .Info ( ctx ) err != nil の場合{ nil を返し、エラーをラップします( err 、 "コンテナ情報の取得に失敗しました" ) }
taskOpts : = c .taskOpts ( ctrInfo .Runtime .Name ) タスク、エラー: =コンテナ.NewTask ( ctx 、 ioCreation 、 taskOpts... ) err != nil の場合{ nil を返し、エラーをラップします( err 、 「コンテナタスクの作成に失敗しました」 ) } // wait は長時間実行されるバックグラウンド リクエストであり、タイムアウトは必要ありません。 exitCh 、 err : = task.Wait ( ctrdutil.NamespacedContext ( ) ) // containerd タスクを開始します。 err の場合: = task .Start ( ctx ) ;エラー!=ゼロ{ nil を返します。エラー.Wrapf ( err 、 「コンテナタスク %q の開始に失敗しました」 、 id ) } } タスクを作成するコードは次のとおりです。 containerd クライアントの TasksClient を呼び出し、タスクを作成するための要求をサーバーに送信します。 func ( c * container ) NewTask ( ctx context .Context , ioCreate cio .Creator , opts ...NewTaskOpts ) ( _ タスク、エラー エラー) { ...... リクエスト: = &タスク.CreateTaskRequest { コンテナID : c .id , ターミナル: cfg .Terminal 、標準入力: cfg .Stdin 、 標準出力: cfg .Stdout 、 標準エラー出力: cfg .Stderr 、 } ...... レスポンス、エラー: = c .client .TaskService ( ) .Create ( ctx , request ) ...... タスクを開始するためのコードは次のとおりです。 containerd クライアントの TasksClient を呼び出し、タスクを開始する要求をサーバーに送信します。 func ( t * task ) Start ( ctx context .Context ) error { r , err : = t.client.TaskService ( ) . Start ( ctx , & tasks.StartRequest { コンテナID : t .id , } ) err != nil の場合{ t .io != nil の場合{ t .io .キャンセル( ) t .io .閉じる( ) } errdefs.FromGRPC ( err )を返す } t .pid = r .pid ゼロを返す } 4. タスクサービスタスクサービスはタスクプロセスを作成します以下は、タスクを作成し、コンテナ ランタイムに基づいてコンテナを作成する要求を処理する、tasks-service のコードです。 func ( l * local ) Create ( ctx context .Context , r * api .CreateTaskRequest , _ ...grpc .CallOption ) ( * api .CreateTaskResponse 、 error ) { container 、 err : = l .getContainer ( ctx 、 r .ContainerID ) ...... rtime 、 err : = l .getRuntime (コンテナ.Runtime .Name ) err != nil の場合{ nilまたはerr を返す } _ 、 err = rtime .Get ( ctx 、 r .ContainerID ) err != nil && err !=ランタイムの場合.ErrTaskNotExists { nil を返す、 errdefs.ToGRPC ( err ) } エラー== nilの場合{ nil を返します、 errdefs .ToGRPC ( fmt .Errorf ( "タスク %s は既に存在します" 、 r .ContainerID ) ) } c , err : = rtime.Create ( ctx , r.ContainerID , opts )で、コンテナIDを作成します。 ...... 戻り値& api .CreateTaskResponse { コンテナID : r.ContainerID 、 Pid : c.PID ( ) 、 } 、 ランタイムは次のようにコンテナ コードを作成し、shim を起動して、shim に作成要求を送信します。 func ( m * TaskManager ) Create ( ctx context .Context , id string , opts runtime .CreateOpts ) ( _runtime .Task , retErr error ) { ...... shim , err : = m.startShim ( ctx , bundle , id , opts ) です。
t , err : = shim.Create ( ctx , opts )を実行する ..... } startShim は shim 実行可能ファイルを呼び出してサービスを開始します。コードは次のとおりです。 func ( m * TaskManager ) startShim ( ctx context .Context , bundle * Bundle , id string , opts runtime .CreateOpts ) ( * shim , error ) { ...... b : = shimBinary ( ctx 、 bundle 、 opts .Runtime 、 m .containerdAddress 、 m .containerdTTRPCAddress 、 m .events 、 m .tasks ) shim , err : = b.Start ( ctx , topts , func ( ) { ログ.G ( ctx ) .WithField ( "id" , id ) .Info ( "shim が切断されました" ) cleanupAfterDeadShim (コンテキスト.Background ( ) 、 id 、 ns 、 m .tasks 、 m .events 、 b ) m .tasks .Delete ( ctx 、 id ) } ) ...... shim コマンドを実行するために使用される実行可能ファイルは、containerd-shim-<runtime>-<version> です。たとえば、通常使用するランタイム タイプが io.containerd.runc.v2 の場合、使用される実行可能ファイルは containerd-shim-runc-v2 になります。完全なコマンド形式は containerd - shim - runc - v2 -名前空間 xxxx -アドレス xxxx -公開-バイナリ xxxx - id xxxx 開始 func ( b * binary ) Start ( ctx context .Context , opts * types .Any , onClose func ( ) ) ( _ * shim , err error ) { args : = [ ]文字列{ "id" , b .bundle .ID }
args = append ( args , "start" )
cmd 、エラー: = クライアント.コマンド( ctx , b.ランタイム、 b .containerdアドレス、 b .containerdTTRPCアドレス、 b .bundle .Path 、 オプション、 引数... 、 ) ...... 出力、エラー: = cmd.CombinedOutput ( ) err != nil の場合{ nil を返します。エラー.Wrapf ( err , "%s" , out ) } アドレス: =文字列.TrimSpace (文字列(出力) ) conn , err : = client.Connect (アドレス, client.AnonDialer ) err != nil の場合{ nilまたはerr を返す } onCloseWithShimLog : =関数( ) { 閉じる( ) キャンセルシムログ( ) f .閉じる( ) } クライアント: = ttrpc.NewClient ( conn , ttrpc.WithOnClose ( onCloseWithShimLog ) ) 戻り&シム{ バンドル: b .bundle , クライアント:クライアント、 } 、 タスクサービスはタスクプロセスを開始しますタスクを開始するタスク サービス プロセスは次のとおりです。 func ( l * local ) Start ( ctx context .Context , r * api .StartRequest , _ ...grpc .CallOption ) ( * api .StartResponse , error ) { t 、 err : = l .getTask ( ctx 、 r .ContainerID ) err != nil の場合{ nilまたはerr を返す } p : =ランタイム.Process ( t ) r .ExecID != ""の場合{ p の場合、 err = t .Process ( ctx 、 r .ExecID ) ;エラー!=ゼロ{ nil を返す、 errdefs.ToGRPC ( err ) } } err : = p .Start ( ctx )の場合;エラー!=ゼロ{ nil を返す、 errdefs.ToGRPC ( err ) } 状態、エラー: = p.State ( ctx ) err != nil の場合{ nil を返す、 errdefs.ToGRPC ( err ) } 戻り値& api.StartResponse { Pid :状態.Pid 、 } 、ゼロ } コンテナの起動プロセスは、shim サーバーにリクエストを送信することで完了します。 func ( s * shim ) Start ( ctx context .Context ) error { レスポンス、エラー: = s .task .Start ( ctx 、 &タスク.StartRequest { ID : s .ID ( ) 、 } ) err != nil の場合{ errdefs.FromGRPC ( err )を返す } s .taskPid = int (レスポンス.Pid ) ゼロを返す 5. 「Containerd-shim の起動プロセス」 containerd/runtime/v2/shim/shim.go で RunManager ( ctx コンテキスト.Context 、マネージャー Manager 、 opts ...BinaryOpts ) これは、containerd-shim-runc-v2 start のコード エントリです。 ケース「開始」: オプション:= StartOpts{ 住所: addressFlag、 TTRPCアドレス: ttrpcアドレス、 デバッグ: debugFlag、 }
アドレス、err := manager.Start(ctx, id, opts) err != nil の場合 { エラーを返す } _ の場合、err := os.Stdout.WriteString(address);エラー != ゼロ { エラーを返す } ゼロを返す } containerd-shim-runc-v2 開始プロセスは、shim サーバーを起動するために、containerd-shim-runc-v2 -namespace xxxx -id xxxx - address xxxx という名前の別のプロセスを作成します。
func ( manager ) Start ( ctx context .Context , id string , opts shim .StartOpts ) ( _ string , retErr error ) { cmd , err : = newCommand ( ctx , id , opts.Address , opts.TTRPCAddress , opts.Debug ) ... //必要に応じて reexec shim - v2 バイナリが値を使用することを確認します err の場合: = shim .WriteAddress ( "address" , address ) ;エラー!=ゼロ{ ""を返します。エラー } ... エラーの場合: = cmd.Start ( ) ;エラー!=ゼロ{ f .閉じる( ) ""を返します。エラー } ... //開始後に必ず待機してください コマンドを実行してください。待機してください( ) ...
サーバー、 err : = newServer ( ttrpc.WithUnaryServerInterceptor ( unaryInterceptor ) ) err != nil の場合{ return fmt .Errorf ( "サーバーの作成に失敗しました: %w" , err ) }
_ の場合、 srv : =範囲 ttrpcServices { err : = srv の場合.RegisterTTRPC ( server ) ;エラー!=ゼロ{ return fmt .Errorf ( "サービスの登録に失敗しました: %w" , err ) } }
err の場合: = serve ( ctx 、 server 、 signal 、 sd . Shutdown ) ;エラー!=ゼロ{ err !=シャットダウンの場合.ErrShutdown { エラーを返す } } } shim サーバーは、次のインターフェースを提供する ttrpc サービスです。 型 TaskService インターフェース{ 状態(コンテキスト.Context 、 * StateRequest ) ( * StateResponse 、エラー) 作成(コンテキスト.Context 、 * CreateTaskRequest ) ( * CreateTaskResponse 、エラー) 開始(コンテキスト.Context 、 * StartRequest ) ( * StartResponse 、 error ) 削除(コンテキスト.Context 、 * DeleteRequest ) ( * DeleteResponse 、 error ) Pids (コンテキスト.Context 、 * PidsRequest ) ( * PidsResponse 、エラー) 一時停止(コンテキスト.Context 、 * PauseRequest ) ( * emptypb .Empty 、 error ) 再開(コンテキスト.Context 、 * ResumeRequest ) ( * emptypb .Empty 、 error ) チェックポイント(コンテキスト.Context 、 * CheckpointTaskRequest ) ( * emptypb .Empty 、 error ) Kill (コンテキスト.Context 、 * KillRequest ) ( * emptypb .Empty 、 error ) Exec (コンテキスト.Context 、 * ExecProcessRequest ) ( * emptypb .Empty 、 error ) ResizePty (コンテキスト.Context 、 * ResizePtyRequest ) ( * emptypb .Empty 、エラー) CloseIO (コンテキスト.Context 、 * CloseIORequest ) ( * emptypb .Empty 、 error ) 更新( context .Context 、 * UpdateTaskRequest ) ( * emptypb .Empty 、 error ) 待機(コンテキスト.Context 、 * WaitRequest ) ( * WaitResponse 、エラー) 統計(コンテキスト.Context 、 * StatsRequest ) ( * StatsResponse 、エラー) 接続(コンテキスト.Context 、 * ConnectRequest ) ( * ConnectResponse 、 error ) Shutdown ( Context .Context 、 * ShutdownRequest ) ( * emptyPb.Empty 、エラー) } タスクを作成するには、runc create -bundle xxxx xxxxコマンドを実行します。コードを参照してください。 func ( r * runc ) create ( context context .context 、 id 、 bundle string 、 opts * createopts )エラー { args : = [ ] string { "create" 、 "bundle" 、 bundle } ...... cmd : = r .command ( context 、 append ( args 、 id ) ... ) ...... EC 、 err : = monitor.start ( cmd ) ...... } タスクを開始するには、runc start xxxxコマンドが実行されます。コードを参照してください。 func ( r * runc ) start ( context context .context 、 id string ) error { return r .runorerror ( r .command ( context 、 "start" 、 id ) )) } まとめKubelet Sandboxの作成プロセスは、次のように要約されています。 containerdのCRIモジュールは、サンドボックスメタデータを作成および保存します contererdのCRIモジュールは、サンドボックスコンテナを作成して保存します contererdのCRIモジュールは、GRPCを介してタスクサービスを呼び出してタスクを作成します tasks-Serviceモジュールは、containerd-shim-xxxx-xxxx開始プロセスを作成します containerd-shim-xxxx-xxx開始プロセスContainerd-shim-xxxx-xxxxプロセスと出口を作成します contererd-shim-xxxx-xxxxプロセスはShimサーバーを開始し、ttrpcサービスを提供します ⑦タスクサービスモジュールSHIMサーバーの作成インターフェイス、[作成]、およびSHIMサーバーがRUNC CREATEコマンドを実行するコマンドを実行する conterererd CRIモジュールは、GRPC経由でタスクサービスを呼び出すことによりタスクを開始します ⑨タスクサービスモジュールShimServer、Start Task、Shim ServerのStartインターフェイスを呼び出し、Runc Startコマンドを実行します この記事はWeChatの公開アカウント「DCOS」から転載したもので、著者は「DCOS」で、以下のQRコードを通じてフォローできます。 この記事を転載する場合は、「DCOS」公開アカウントまでご連絡ください。 |