Containerd の詳細な分析 - CRI

Containerd の詳細な分析 - CRI

この記事は、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 ( )
//ホストネットワーク名前空間にない場合は、名前空間を作成しサンドボックスを設定します
//ハンドル。サンドボックス メタデータNetNSPathNetNS は、非ホスト ネットワークの場合のみ空ではありません。
//名前空間。ポッドがホストネットワーク名前空間ある場合は両方とも空あり、
//使用されます。
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」公開アカウントまでご連絡ください。

<<:  2023 年の主要なクラウド コンピューティングとセキュリティのトレンド

>>:  グリーンクラウドコンピューティングは持続可能な未来の夢を推進します

推薦する

Kubernetes と OpenEBS における永続ボリュームと永続ボリュームクレームの理解

[[438582]] [51CTO.com クイック翻訳]概要: Kubernetes のボリューム...

vds6 - 1.87 ドル/オランダ VPS/KVM/1G RAM/1Gbps 帯域幅

vds6は2011年に設立され、米国に登録された会社です(登録番号:#4921432)。PayPal...

ソフト記事プロモーション:どんなタイトルがすぐに注目を集められるのか?

ソフトテキストプロモーションが効果的かどうかを判断する基準は、消費者の注目を集めることができるかどう...

企業のマーケティングプロセスにおける4つの一般的な問題の詳細な説明

インターネット情報化時代において、オンライン マーケティングは社会の変化に適応した結果であり、ネット...

Webmaster.com からの毎日のレポート: JD.com が Yitao 価格比較をブロック、Tencent が武漢に R&D センターを建設

1. JD.comがYitaoの価格比較をブロックする妨害コードを設定8月15日、価格戦争の煙はまだ...

デジタルオーシャンはどうですか? [年] Digitaloceanのオランダデータセンターの簡単なレビュー

DigitalOcean は、AMD シリーズのクラウド サーバーをリリース以来テストしていません。...

周洪義、鄧亜平、李国清の対話:勝者が全てを手に入れるということはない

フォーラムサイト新浪科技報、9月11日正午、2012年中国インターネット会議が本日北京で開催されまし...

中小企業向けブランド構築の迅速化のための実践ガイド

インターネット企業にとって、ブランドの役割は特に重要です。ブランドは無形資産として、企業のビジネス価...

ホスティング - 年間 50 ドル / KVM / メモリ 1g / ハードディスク 25g / トラフィック 1T

Hostigation は、2010 年と 2011 年に非常に人気があった VPS 業者ですが、過...

クラウド コンピューティング時代に入ってから、IT 部門があまりに怠惰であると上司に報告する人が常にいます。

クラウドコンピューティングとは何ですか?投資家を引き付けます。ウォール街の IT エリートたちが真夜...

アリババ平頭、RISC-V高効率プロセッサXuantie C908をリリースし、エンドクラウド統合エコシステムを構築

11月3日、2022 Yunqiカンファレンスで、アリババ平頭閣は新しいRISC-V高効率プロセッサ...

「Tencent Game Manager」を例に、ユーザーの成長についてお話ししましょう。

1. 市場背景市場の状況を考え、分析することで、自社製品の意義や価値を検証し、開発に適した成長ポイン...

カフェのマンスリーカードが引き起こす「バタフライ効果」(前編)

月収10万元の起業の夢を実現するミニプログラム起業支援プラン前回の記事では、ケータリング業界でのマー...

secureragon-9 コンピュータ ルーム / $10 / 64m メモリ / 2CPU / 3g ハード ディスク / 250g フロー / G ポート

Secure Dragon LLC. は 2010 年に設立され、5 ~ 6 年の歴史があります。ワ...