0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pod sandbox とは何か

0
Posted at

はじめに

Pod の起動順序では、コンテナの起動前に Pod sandbox やネットワークなどの準備が行われることを説明しました。Pod sandbox の役割は、名前だけでは分かりにくいと思います。この記事では、その目的と役割、containerd による実装、再作成時の挙動を整理します。

対象コンポーネントとバージョン

コンポーネント バージョン リポジトリ 本記事での役割
Kubernetes v1.37.1 kubernetes/kubernetes CRI の Go インターフェース定義、kubelet による PodSandboxConfig の組み立て・sandbox 再作成判定、pause バイナリのソース
containerd v2.4.1 / v2.3.4 containerd/containerd v2.4.1 はソース確認用、v2.3.4 は kind ノード内で実地検証したバージョン
kind v0.33.0 kubernetes-sigs/kind 検証環境。ノードイメージは kindest/node:v1.37.0

TL;DR

  • Pod sandbox は、Pod 内のコンテナが共有する実行環境です。kubelet は CRI の RunPodSandbox を通じて作成を依頼します。
  • containerd の Linux 向け podsandbox 実装では、pause コンテナを使います。VM を使うランタイムなどでは実装が異なります。
  • 通常の Pod では network / IPC namespace を共有します。PID namespace はコンテナごとに分かれますが、shareProcessNamespace やホストの namespace を使う設定によって変わります。
  • pause は待機とシグナル処理を行う小さなプログラムです。アプリケーションコンテナは、pause の PID に対応する /proc/<pid>/ns/... を使って共有 namespace に参加します。
  • sandbox が利用できなくなると、kubelet は再作成を判断します。アプリケーションコンテナも再作成され、Pod IP が変わる場合があります。

Pod sandbox の概要

Pod sandbox は、同じ Pod のコンテナが共有する実行環境です。例えば、アプリケーションと sidecar が同じ IP アドレスを使い、localhost で通信できるように、コンテナを起動する前に共有するネットワークなどを用意します。アプリケーションコンテナが終了・再起動しても、その共有環境を維持できるようにする役割があります。

kubelet は CRI の RunPodSandbox でランタイムに作成を依頼します。kubelet の役割や、コンテナランタイム・CNI・OCI Runtime との関係は、kubelet の機能一覧で整理しています。

この記事で扱う containerd の Linux 向け実装では、主に次の処理を行います。

  • Pod 用の network namespace を用意し、CNI を通じてネットワーク接続や IP アドレスを設定する(hostNetwork を使う場合はホストのネットワークを使う)
  • pause コンテナを起動し、コンテナ間で共有する namespace を維持する
  • 後から起動するアプリケーションコンテナが、その共有環境に参加できるようにする

Pod sandbox の実現方法はランタイムによって異なり、pause コンテナを使う方式のほか、VM を使う方式もあります。以降では、共有する環境の指定方法、containerd による実装、sandbox が再作成される条件を順に確認します。

containerd の Linux 向け podsandbox 実装で、ホストの namespace を使わず、shareProcessNamespace を有効にしていない場合の構成です。A・B・C は別々の PID namespace を示します。線は network / IPC namespace の共有を示し、プロセスの親子関係を表すものではありません。

第1章: なぜ複数コンテナでネットワークなどを共有できるのか

Pod の定義には「同じ network namespace を共有する複数コンテナのグループ」という説明がよく出てきます。この章では、Linux コンテナを対象に、共有の指定を CRI の仕様から確認します。

1.1 Linux namespace の共有という発想

Linux のコンテナは、プロセスに network/PID/IPC/UTS/mount などの namespace を割り当てることで分離を実現しています。Pod 内のアプリケーションと sidecar が同じ IP アドレスを使い、localhost で通信できるのは、同じ network namespace を共有するためです。

CRI はこれを NamespaceMode という列挙型で表現しています。

NamespaceMode 共有の範囲
POD 同じ Pod 内で共有する
CONTAINER コンテナごとに分離する
NODE ノードの namespace を使う
TARGET target_id で指定したコンテナの namespace を使う

(参考: api.proto#L397-L417)

1.2 NamespaceOption: sandbox とコンテナがどの namespace を使うか

NamespaceMode を実際に namespace の種類ごとに指定するのが NamespaceOption です。

message NamespaceOption {
    // Network namespace for this container/sandbox.
    NamespaceMode network = 1;
    // PID namespace for this container/sandbox.
    // Note: The CRI default is POD, but the v1.PodSpec default is CONTAINER.
    // The kubelet's runtime manager will set this to CONTAINER explicitly for v1 pods.
    NamespaceMode pid = 2;
    // IPC namespace for this container/sandbox.
    NamespaceMode ipc = 3;
    // Target Container ID for NamespaceMode of TARGET.
    string target_id = 4;
    UserNamespace userns_options = 5;
}

(参考: api.proto#L433-L457)

CRI では PID namespace のデフォルトも POD ですが、kubelet は通常の Pod に対して CONTAINER を明示します。そのため、ホストの namespace を使う設定がない通常の Pod では、network / IPC は共有し、PID はコンテナごとに分かれます。shareProcessNamespace: true を指定すると、他のコンテナのプロセスも同じ PID namespace 内で確認できます。

1.3 kubelet は Pod spec から NamespaceOption をどう組み立てるか

この NamespaceOption は、kubelet の NamespacesForPod が Pod の HostNetwork/HostPID/HostIPC/ShareProcessNamespace から組み立てます。

// pkg/kubelet/kuberuntime/util/util.go
func NetworkNamespaceForPod(pod *v1.Pod) runtimeapi.NamespaceMode {
	if pod != nil && pod.Spec.HostNetwork {
		return runtimeapi.NamespaceMode_NODE
	}
	return runtimeapi.NamespaceMode_POD
}

func PidNamespaceForPod(pod *v1.Pod) runtimeapi.NamespaceMode {
	if pod != nil {
		if pod.Spec.HostPID {
			return runtimeapi.NamespaceMode_NODE
		}
		if pod.Spec.ShareProcessNamespace != nil && *pod.Spec.ShareProcessNamespace {
			return runtimeapi.NamespaceMode_POD
		}
	}
	// Note that PID does not default to the zero value for v1.Pod
	return runtimeapi.NamespaceMode_CONTAINER
}

func NamespacesForPod(ctx context.Context, pod *v1.Pod, runtimeHelper kubecontainer.RuntimeHelper, rcManager RuntimeHandlerResolver) (*runtimeapi.NamespaceOption, error) {
	...
	return &runtimeapi.NamespaceOption{
		Ipc:           IpcNamespaceForPod(pod),
		Network:       NetworkNamespaceForPod(pod),
		Pid:           PidNamespaceForPod(pod),
		UsernsOptions: userNs,
	}, nil
}

(参考: util.go#L74-L133)

Pod spec のフィールド 対応する namespace デフォルト true にした場合
hostNetwork network POD (Pod 内で共有) NODE (ホストの network namespace を直接使う)
hostIPC ipc POD NODE
hostPID pid CONTAINER (コンテナごとに別) NODE
shareProcessNamespace pid CONTAINER POD (Pod 内で共有)

この NamespaceOption は、generatePodSandboxConfig の中で PodSandboxConfig.Linux.SecurityContext.NamespaceOptions としてセットされ、sandbox 作成時に CRI 経由でランタイムに渡されます。

// pkg/kubelet/kuberuntime/kuberuntime_sandbox.go
namespaceOptions, err := runtimeutil.NamespacesForPod(ctx, pod, m.runtimeHelper, m.runtimeClassManager)
...
lc.SecurityContext.NamespaceOptions = namespaceOptions

(参考: kuberuntime_sandbox.go#L202-L206)

第2章: containerd は sandbox をどう実装するか

CRI の Pod sandbox と、その実装で使う pause コンテナは区別します。この章では、Linux 上で動く containerd の podsandbox 実装を確認します。

2.1 CRI の PodSandboxManager インターフェース

The CRI is a plugin interface which enables the kubelet to use a wide variety of container runtimes, without having a need to recompile the cluster components.

(参考: Container Runtime Interface (CRI))

CRI は sandbox の作成・停止・削除・状態取得などを gRPC API として定義しています。kubelet 側では、これらの操作を次の Go インターフェースを通じて呼び出します。

// staging/src/k8s.io/cri-api/pkg/apis/services.go
type PodSandboxManager interface {
	// RunPodSandbox creates and starts a pod-level sandbox. Runtimes should ensure
	// the sandbox is in ready state.
	RunPodSandbox(ctx context.Context, config *runtimeapi.PodSandboxConfig, runtimeHandler string) (string, error)
	// StopPodSandbox stops the sandbox. If there are any running containers in the
	// sandbox, they should be force terminated.
	StopPodSandbox(ctx context.Context, podSandboxID string) error
	// RemovePodSandbox removes the sandbox. If there are running containers in the
	// sandbox, they should be forcibly removed.
	RemovePodSandbox(ctx context.Context, podSandboxID string) error
	// PodSandboxStatus returns the Status of the PodSandbox.
	PodSandboxStatus(ctx context.Context, podSandboxID string, verbose bool) (*runtimeapi.PodSandboxStatusResponse, error)
	// ListPodSandbox returns a list of Sandbox.
	ListPodSandbox(ctx context.Context, filter *runtimeapi.PodSandboxFilter) ([]*runtimeapi.PodSandbox, error)
	...
}

(参考: services.go#L67-L95)

kubelet はこのインターフェースの実装(containerd や CRI-O 等)に対して RunPodSandbox を呼び出すだけで、sandbox を実際にどう実現するかはランタイム側の裁量に委ねられています。containerd では、これは別の sandbox.Controller インターフェースとして差し替え可能な拡張点になっており、デフォルトの podsandbox モードとは別に、VM ベースの隔離(Kata Containers 等)を使う shim sandboxer を実装として差し込むこともできます。

cri uses containerd internal to create and start a special pause container (the sandbox container) and put that container inside the pod's cgroups and namespace

(参考: containerd CRI architecture)

2.2 デフォルト実装: pause イメージを使った最小限のコンテナ

containerd の Sandbox API の説明では、pause コンテナが共有 namespace を維持し、アプリケーションコンテナが起動時にその namespace に参加する構成が紹介されています。

(参考: containerd Sandbox API)

デフォルトの sandbox イメージは registry.k8s.io/pause です。

// internal/cri/config/config.go
// DefaultSandboxImage is the default image to use for sandboxes when empty or
// for default configurations.
DefaultSandboxImage = "registry.k8s.io/pause:3.10.2"

(参考: config.go#L73-L75)

sandbox を作成する RunPodSandbox の実装は、(1) sandbox ID の予約、(2) (hostNetwork でなければ) network namespace の新規作成、(3) CNI によるネットワーク設定、(4) pause イメージが無ければ pull、(5) 実際に pause コンテナを作成・起動、という順に進みます。

// internal/cri/server/sandbox_run.go
func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandboxRequest) (_ *runtime.RunPodSandboxResponse, retErr error) {
	...
}
// internal/cri/server/sandbox_run.go
sandbox.NetNS, err = netns.NewNetNS(netnsMountDir)

(参考: sandbox_run.go#L54、sandbox_run.go#L209)

CRI 層で pause イメージを事前取得するのは、多くのランタイム実装が pause コンテナを使うためです。コメントには、この処理が Sandbox API の抽象化とは整合しない面があり、将来変更される可能性も記されています。

(参考: sandbox_run.go#L285-L304)

pause コンテナの OCI spec では、ホストの namespace を使う場合に、その種類の namespace を新規作成する設定を外します。network namespace は、ホストを使わない場合も新規作成のデフォルトに任せず、先に用意した nsPath を指定します。

// internal/cri/server/podsandbox/sandbox_run_linux.go
if nsOptions.GetNetwork() == runtime.NamespaceMode_NODE {
	specOpts = append(specOpts, customopts.WithoutNamespace(runtimespec.NetworkNamespace))
	specOpts = append(specOpts, customopts.WithoutNamespace(runtimespec.UTSNamespace))
} else {
	specOpts = append(specOpts, oci.WithLinuxNamespace(
		runtimespec.LinuxNamespace{
			Type: runtimespec.NetworkNamespace,
			Path: nsPath,
		}))
}
if nsOptions.GetPid() == runtime.NamespaceMode_NODE {
	specOpts = append(specOpts, customopts.WithoutNamespace(runtimespec.PIDNamespace))
}
if nsOptions.GetIpc() == runtime.NamespaceMode_NODE {
	specOpts = append(specOpts, customopts.WithoutNamespace(runtimespec.IPCNamespace))
}

(参考: sandbox_run_linux.go#L76-L91)

2.3 pause バイナリの中身

containerd は pause イメージを取得し、コンテナとして起動します。イメージ内で動く pause バイナリのソースは Kubernetes 本体にあります。

// build/pause/linux/pause.c
static void sigdown(int signo) {
  psignal(signo, "Shutting down, got signal");
  exit(0);
}

static void sigreap(int signo) {
  while (waitpid(-1, NULL, WNOHANG) > 0)
    ;
}

int main(int argc, char **argv) {
  ...
  if (sigaction(SIGINT, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0)
    return 1;
  if (sigaction(SIGTERM, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0)
    return 2;
  if (sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap,
                                             .sa_flags = SA_NOCLDSTOP},
                NULL) < 0)
    return 3;

  for (;;)
    pause();
  ...
}

(参考: pause.c#L32-L66)

SIGINT / SIGTERM を受け取ると終了し、SIGCHLD を受け取ると waitpid で終了した子プロセスを回収します。それ以外の間は pause() で待機します。アプリケーションの起動やヘルスチェックは行いません。

子プロセスの回収は、共有 PID namespace の PID 1 として孤児プロセスを引き受ける場合などに関わります。PID namespace をコンテナごとに分けた通常の Pod で、pause が各コンテナ内のすべてのゾンビプロセスを回収するわけではありません。

2.4 アプリケーションコンテナは pause の namespace にどう参加するか

sandbox (pause コンテナ) 自体は、CNI で設定したネットワーク namespace のパスを直接参照します。一方で、アプリケーションコンテナは、sandbox の PID に対応する /proc/<pid>/ns/net を参照して同じ namespace に参加します。

// internal/cri/opts/spec_opts.go
const (
	netNSFormat = "/proc/%v/ns/net"
	ipcNSFormat = "/proc/%v/ns/ipc"
	utsNSFormat = "/proc/%v/ns/uts"
	pidNSFormat = "/proc/%v/ns/pid"
)

func WithPodNamespaces(config *runtime.LinuxContainerSecurityContext, sandboxPid uint32, targetPid uint32, uids, gids []runtimespec.LinuxIDMapping) oci.SpecOpts {
	namespaces := config.GetNamespaceOptions()
	opts := []oci.SpecOpts{
		oci.WithLinuxNamespace(runtimespec.LinuxNamespace{Type: runtimespec.NetworkNamespace, Path: GetNetworkNamespace(sandboxPid)}),
		oci.WithLinuxNamespace(runtimespec.LinuxNamespace{Type: runtimespec.IPCNamespace, Path: GetIPCNamespace(sandboxPid)}),
		oci.WithLinuxNamespace(runtimespec.LinuxNamespace{Type: runtimespec.UTSNamespace, Path: GetUTSNamespace(sandboxPid)}),
	}
	if namespaces.GetPid() != runtime.NamespaceMode_CONTAINER {
		opts = append(opts, oci.WithLinuxNamespace(runtimespec.LinuxNamespace{Type: runtimespec.PIDNamespace, Path: GetPIDNamespace(targetPid)}))
	}
	...
}

(参考: spec_opts.go#L348-L411)

sandboxPid は CreateContainer の実装の中で、sandbox の状態取得 (SandboxStatus) から取り出されます。

// internal/cri/server/container_create.go
sandboxPid = cstatus.Pid

(参考: container_create.go#L68-L76、WithPodNamespaces 呼び出しは container_create.go#L925)

PID namespace は、CONTAINER の場合は共有先のパスを追加せず、コンテナごとの namespace を使います。POD の場合は sandbox、TARGET の場合は指定した別コンテナの PID namespace に参加します。NODE ではホストの PID namespace を使います。

2.5 kind で実際に確認する

2コンテナの Pod を使い、pause と各コンテナの namespace を比較します。以下の YAML を sandbox-demo-pod.yaml として保存します。

シェル例は Bash で実行し、ホスト側で jq を使います。出力は2026年10月22日の検証記録です。PID・sandbox ID・IP アドレスは実行ごとに変わります。ノード内の containerd は v2.3.4 で、ソース確認に使った v2.4.1 とは異なります。

apiVersion: v1
kind: Pod
metadata:
  name: sandbox-demo
spec:
  containers:
    - name: c1
      image: nginx:1.27
    - name: c2
      image: busybox:1.36
      command: ["sh", "-c", "sleep 3600"]
kind create cluster --name sandbox-review --image kindest/node:v1.37.0 --kubeconfig /tmp/sandbox-review-kubeconfig
export KUBECONFIG=/tmp/sandbox-review-kubeconfig
kubectl apply -f sandbox-demo-pod.yaml
kubectl wait --for=condition=Ready pod/sandbox-demo --timeout=90s

kind のノードコンテナの中には crictl/ctr が同梱されているので、docker exec 経由でそのまま使えます。まず sandbox の NamespaceOption を見ます。

PODID=$(docker exec sandbox-review-control-plane crictl pods --name sandbox-demo --namespace default --state Ready -q)
docker exec sandbox-review-control-plane crictl inspectp "$PODID"
"security_context": {
  "namespace_options": {
    "pid": 1,
    "userns_options": { "mode": 2 }
  },
  ...
}

この出力では network / ipc が省略されています。どちらも proto3 のゼロ値である POD = 0 に対応し、"pid": 1 は CONTAINER を示します。1.2節で説明した通り、kubelet が PID namespace に CONTAINER を明示していることを確認できます。

次に、sandbox (pause) と各コンテナのプロセスの namespace を /proc/<pid>/ns で直接比較します。ここで使う PID は kind のノードコンテナ内から見た値です。sandbox の PID は crictl inspectp の info.pid、各コンテナの PID は crictl inspect の info.pid で取得できます。

SANDBOX_PID=$(docker exec sandbox-review-control-plane crictl inspectp "$PODID" | jq '.info.pid')
CIDS=$(docker exec sandbox-review-control-plane crictl ps --pod "$PODID" -q)
for cid in $CIDS; do
  docker exec sandbox-review-control-plane crictl inspect "$cid" | jq -r '"\(.status.metadata.name) pid=\(.info.pid)"'
done
c2 pid=1793
c1 pid=1730

(SANDBOX_PID は 1448)

CONTAINER_PIDS=()
for cid in $CIDS; do
  CONTAINER_PIDS+=("$(docker exec sandbox-review-control-plane crictl inspect "$cid" | jq -r '.info.pid')")
done
docker exec sandbox-review-control-plane sh -c '
for pid do
  echo "pid=$pid net=$(readlink /proc/$pid/ns/net) pid_ns=$(readlink /proc/$pid/ns/pid) ipc=$(readlink /proc/$pid/ns/ipc)"
done' sh "$SANDBOX_PID" "${CONTAINER_PIDS[@]}"
pid=1448 net=net:[4026532360] pid_ns=pid:[4026532682] ipc=ipc:[4026532681]
pid=1793 net=net:[4026532360] pid_ns=pid:[4026532700] ipc=ipc:[4026532681]
pid=1730 net=net:[4026532360] pid_ns=pid:[4026532697] ipc=ipc:[4026532681]

(pid=1448 が pause、1730 が nginx、1793 が busybox の sh)

net/ipc の namespace inode 番号 (4026532360/4026532681) は3プロセスとも完全に一致している一方、pid_ns だけは3プロセスともすべて異なります。2.4節の実装で確認した、network / IPC は共有し、PID はコンテナごとに分ける動作と一致します。

プロセスツリーを見ると、3つのプロセスは親子関係ではなく、同じ containerd-shim の子として並んでいます。

$ docker exec sandbox-review-control-plane ps -o pid,ppid,comm -p 1448,1730,1793
    PID    PPID COMMAND
   1448    1361 pause
   1730    1361 nginx
   1793    1361 sh

この例では、pause はアプリケーションプロセスの直接の親ではありません。namespace の共有と、ホストから見たプロセスの親子関係は分けて確認します。pause の実行バイナリは /pause、実行ユーザーの UID は 65535、この時点の RSS は 384 KiB でした。

$ docker exec sandbox-review-control-plane ps -o uid,pid,rss,args -p 1448
  UID     PID   RSS COMMAND
65535    1448   384 /pause

第3章でも同じクラスタと Pod を使います。

第3章: sandbox のライフサイクルと再作成

sandbox は一度作られたら Pod が削除されるまでずっと同じもの、というわけではありません。この章では、sandbox がいつ作られ、どういう条件で作り直されるかを見ます。

3.1 sandbox 作成を呼び出す箇所

kubelet が sandbox を新規作成する処理は createPodSandbox が担います。

// pkg/kubelet/kuberuntime/kuberuntime_sandbox.go
func (m *kubeGenericRuntimeManager) createPodSandbox(ctx context.Context, pod *v1.Pod, attempt uint32) (string, string, error) {
	podSandboxConfig, err := m.generatePodSandboxConfig(ctx, pod, attempt)
	...
	podSandBoxID, err := m.runtimeService.RunPodSandbox(ctx, podSandboxConfig, runtimeHandler)
	...
	return podSandBoxID, "", nil
}

(参考: kuberuntime_sandbox.go#L42-L75)

3.2 PodSandboxChanged: 既存の sandbox を使い回せるかの判定

sandbox を新規作成する必要があるかは PodSandboxChanged が判定します。5つの条件を上から順に評価し、いずれかに該当すれば true (新しい sandbox が必要) を返します。以下のコードは、ログ出力などを省略した抜粋です。

初回作成も含む判定フローです。true は新しい sandbox が必要という判定で、作成処理自体は呼び出し元で行います。最後の IP 判定は、以下のコードの条件に対応しています。

// pkg/kubelet/kuberuntime/util/util.go
func PodSandboxChanged(pod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, uint32, string, string) {
	if len(podStatus.SandboxStatuses) == 0 {
		return true, 0, "", kubetypes.PodSandboxNotReadyMsgNoPodSandbox
	}

	readySandboxCount := 0
	for _, s := range podStatus.SandboxStatuses {
		if s.State == runtimeapi.PodSandboxState_SANDBOX_READY {
			readySandboxCount++
		}
	}

	sandboxStatus := podStatus.SandboxStatuses[0]
	if readySandboxCount > 1 {
		return true, sandboxStatus.Metadata.Attempt + 1, sandboxStatus.Id, kubetypes.PodSandboxNotReadyMsgMultipleSandboxes
	}
	if sandboxStatus.State != runtimeapi.PodSandboxState_SANDBOX_READY {
		return true, sandboxStatus.Metadata.Attempt + 1, sandboxStatus.Id, kubetypes.PodSandboxNotReadyMsgSandboxNotReady
	}

	if sandboxStatus.GetLinux().GetNamespaces().GetOptions().GetNetwork() != NetworkNamespaceForPod(pod) {
		return true, sandboxStatus.Metadata.Attempt + 1, "", kubetypes.PodSandboxNotReadyMsgNetworkNamespaceMode
	}

	if !kubecontainer.IsHostNetworkPod(pod) && sandboxStatus.Network != nil && sandboxStatus.Network.Ip == "" {
		return true, sandboxStatus.Metadata.Attempt + 1, sandboxStatus.Id, kubetypes.PodSandboxNotReadyMsgNoIPAddress
	}

	return false, sandboxStatus.Metadata.Attempt, sandboxStatus.Id, ""
}

(参考: util.go#L31-L70)

条件 意味
sandbox が1つも無い 初回作成前、または既存の sandbox が失われている
READY な sandbox が複数ある 複数の sandbox が利用可能と報告されているため、再作成して状態を整える
最新の sandbox が READY でない ランタイムが sandbox を利用可能と報告していない。pause の終了などが考えられる
network namespace のモードが異なる Pod が要求するモードと、ランタイムが報告する既存 sandbox のモードが一致しない
hostNetwork ではなく、Network 情報はあるが IP アドレスが空 Pod 用の IP アドレスが報告されていないため、ネットワーク設定を確認する

この関数は、sandbox の現在の状態から再作成の要否を判断します。判定理由はログと戻り値に残しますが、pause が終了した原因やネットワーク障害の原因まで特定するものではありません。呼び出し元は computePodActions で、戻り値は podContainerChanges.CreateSandbox として SyncPod に渡され、true であれば古いコンテナを Kill した上で createPodSandbox が呼ばれます。

(参考: kuberuntime_manager.go#L1278-L1321)

3.3 sandbox が失われるケース

公式ドキュメントは、Pod が Eviction されないままノードが再起動する場合と、VM による隔離を使うランタイムで sandbox の VM が再起動する場合を挙げています。

(参考: Pod Lifecycle)

どちらも sandbox とネットワーク設定を再び準備する必要がありますが、pause コンテナを使うかどうかはランタイムの実装によって異なります。

3.4 PodReadyToStartContainers Condition との関係

PodReadyToStartContainers は、コンテナを起動するための sandbox やネットワークなどの準備状態を表す Condition です。pause の起動だけを表すものではなく、hostNetwork や VM ベースのランタイムにも適用されます。

sandbox が失われ、ランタイムから利用可能な sandbox が報告されなくなると、この Condition は False になります。再作成後、必要な準備が整うと再び True になります。kubelet は GeneratePodReadyToStartContainersCondition の中で PodSandboxChanged を呼び出し、再作成が必要なら False、不要なら True として Condition を計算します。API への反映は非同期なので、すべての途中状態が観測できるとは限りません。起動準備の流れは、Pod の起動順序でも説明しています。

(参考: Pod Lifecycle、generate.go#L271-L291)

3.5 kind で実際に確認する

第2章で作成した検証用クラスタで、pause プロセスを停止し、sandbox の再作成を確認します。以下のシェル例は Bash で実行します。

PODID_BEFORE=$(docker exec sandbox-review-control-plane crictl pods --name sandbox-demo --namespace default --state Ready -q)
SANDBOX_PID=$(docker exec sandbox-review-control-plane crictl inspectp "$PODID_BEFORE" | jq -r '.info.pid')
kubectl get pod sandbox-demo -o wide
docker exec sandbox-review-control-plane kill -9 "$SANDBOX_PID"
$ docker exec sandbox-review-control-plane crictl pods --name sandbox-demo --namespace default
POD ID              CREATED             STATE               NAME                NAMESPACE           ATTEMPT             RUNTIME
83d2bb8fa2f45       36 seconds ago      Ready               sandbox-demo        default             0                   (default)

停止直後の取得では、まだ Ready と表示されました。状態の反映には時間差があります。その後、kubelet が利用できない sandbox を検出し、再作成を行いました。再作成後の一覧では、古い sandbox が NotReady になっていることを確認できます。

$ kubectl get events --field-selector involvedObject.name=sandbox-demo --sort-by=.lastTimestamp
...
SandboxChanged   Pod sandbox changed, it will be killed and re-created.
Killing          Stopping container c1
Killing          Stopping container c2

この Event は、SyncPod が新しい sandbox を作成すると判断し、podContainerChanges.SandboxID に既存の sandbox ID が入っている場合に記録されます。初回作成では記録されません。

// pkg/kubelet/kuberuntime/kuberuntime_manager.go
m.recorder.WithLogger(logger).Eventf(ref, v1.EventTypeNormal, events.SandboxChanged, "Pod sandbox changed, it will be killed and re-created.")

(参考: kuberuntime_manager.go#L1594)

再作成後の状態を見ると、新しい sandbox ID・新しい Pod IP で、両方のコンテナが再起動していることが分かります。

$ docker exec sandbox-review-control-plane crictl pods --name sandbox-demo --namespace default
POD ID              CREATED                  STATE               NAME                NAMESPACE           ATTEMPT             RUNTIME
dec647060111e       Less than a second ago   Ready               sandbox-demo        default             1                   (default)
83d2bb8fa2f45       About a minute ago       NotReady            sandbox-demo        default             0                   (default)

$ kubectl get pod sandbox-demo -o wide
NAME           READY   STATUS    RESTARTS     AGE   IP           NODE                           NOMINATED NODE   READINESS GATES
sandbox-demo   2/2     Running   2 (0s ago)   76s   10.244.0.6   sandbox-review-control-plane   <none>           <none>

ATTEMPT が 0 から 1 に上がった新しい sandbox が Ready になり、Pod の IP アドレスも 10.244.0.3 から 10.244.0.6 に変わりました。この検証では Pod の UID は変わらず、sandbox の再作成に伴って両コンテナの restartCount がそれぞれ 0 から 1 になりました。Pod IP が変わるかどうかはネットワークの実装や割り当て状況にもよるため、再作成のたびに変わるとは限りません。

kind delete cluster --name sandbox-review

おわりに

Pod sandbox は普段意識することが少ないと思いますが、Pod を理解するうえで大事な仕組みなので、記事にまとめました。

参考

参考リンク

本文で登場する順に並べています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?