Skip to content

Commit 0c8cfb1

Browse files
committed
Move pod sandbox recovery to podsandbox/ package
Signed-off-by: Maksym Pavlenko <[email protected]>
1 parent 91d9f5c commit 0c8cfb1

4 files changed

Lines changed: 194 additions & 124 deletions

File tree

pkg/cri/sbserver/events.go

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -418,28 +418,11 @@ func handleContainerExit(ctx context.Context, e *eventtypes.TaskExit, cntr conta
418418

419419
// handleSandboxExit handles TaskExit event for sandbox.
420420
func handleSandboxExit(ctx context.Context, e *eventtypes.TaskExit, sb sandboxstore.Sandbox, c *criService) error {
421-
// TODO: Move pause container cleanup to podsandbox/ package.
422-
if sb.Container != nil {
423-
// No stream attached to sandbox container.
424-
task, err := sb.Container.Task(ctx, nil)
425-
if err != nil {
426-
if !errdefs.IsNotFound(err) {
427-
return fmt.Errorf("failed to load task for sandbox: %w", err)
428-
}
429-
} else {
430-
// TODO(random-liu): [P1] This may block the loop, we may want to spawn a worker
431-
if _, err = task.Delete(ctx, containerd.WithProcessKill); err != nil {
432-
if !errdefs.IsNotFound(err) {
433-
return fmt.Errorf("failed to stop sandbox: %w", err)
434-
}
435-
// Move on to make sure container status is updated.
436-
}
437-
}
438-
}
439-
440421
if err := sb.Status.Update(func(status sandboxstore.Status) (sandboxstore.Status, error) {
441422
status.State = sandboxstore.StateNotReady
442423
status.Pid = 0
424+
status.ExitStatus = e.ExitStatus
425+
status.ExitedAt = e.ExitedAt.AsTime()
443426
return status, nil
444427
}); err != nil {
445428
return fmt.Errorf("failed to update sandbox state: %w", err)

pkg/cri/sbserver/helpers.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ import (
3838
criconfig "github.com/containerd/containerd/pkg/cri/config"
3939
containerstore "github.com/containerd/containerd/pkg/cri/store/container"
4040
imagestore "github.com/containerd/containerd/pkg/cri/store/image"
41-
sandboxstore "github.com/containerd/containerd/pkg/cri/store/sandbox"
4241
runtimeoptions "github.com/containerd/containerd/pkg/runtimeoptions/v1"
4342
"github.com/containerd/containerd/plugin"
4443
"github.com/containerd/containerd/reference/docker"
@@ -84,12 +83,12 @@ const (
8483
containerKindSandbox = "sandbox"
8584
// containerKindContainer is a label value indicating container is application container
8685
containerKindContainer = "container"
86+
8787
// imageLabelKey is the label key indicating the image is managed by cri plugin.
8888
imageLabelKey = criContainerdPrefix + ".image"
8989
// imageLabelValue is the label value indicating the image is managed by cri plugin.
9090
imageLabelValue = "managed"
91-
// sandboxMetadataExtension is an extension name that identify metadata of sandbox in CreateContainerRequest
92-
sandboxMetadataExtension = criContainerdPrefix + ".sandbox.metadata"
91+
9392
// containerMetadataExtension is an extension name that identify metadata of container in CreateContainerRequest
9493
containerMetadataExtension = criContainerdPrefix + ".container.metadata"
9594

@@ -409,13 +408,6 @@ func unknownContainerStatus() containerstore.Status {
409408
}
410409
}
411410

412-
// unknownSandboxStatus returns the default sandbox status when its status is unknown.
413-
func unknownSandboxStatus() sandboxstore.Status {
414-
return sandboxstore.Status{
415-
State: sandboxstore.StateUnknown,
416-
}
417-
}
418-
419411
// getPassthroughAnnotations filters requested pod annotations by comparing
420412
// against permitted annotations for the given runtime.
421413
func getPassthroughAnnotations(podAnnotations map[string]string,
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package podsandbox
18+
19+
import (
20+
"context"
21+
"fmt"
22+
goruntime "runtime"
23+
"time"
24+
25+
"github.com/containerd/containerd/pkg/netns"
26+
"github.com/containerd/typeurl/v2"
27+
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
28+
29+
"github.com/containerd/containerd"
30+
"github.com/containerd/containerd/errdefs"
31+
"github.com/containerd/containerd/log"
32+
sandboxstore "github.com/containerd/containerd/pkg/cri/store/sandbox"
33+
ctrdutil "github.com/containerd/containerd/pkg/cri/util"
34+
)
35+
36+
// loadContainerTimeout is the default timeout for loading a container/sandbox.
37+
// One container/sandbox hangs (e.g. containerd#2438) should not affect other
38+
// containers/sandboxes.
39+
// Most CRI container/sandbox related operations are per container, the ones
40+
// which handle multiple containers at a time are:
41+
// * ListPodSandboxes: Don't talk with containerd services.
42+
// * ListContainers: Don't talk with containerd services.
43+
// * ListContainerStats: Not in critical code path, a default timeout will
44+
// be applied at CRI level.
45+
// * Recovery logic: We should set a time for each container/sandbox recovery.
46+
// * Event monitor: We should set a timeout for each container/sandbox event handling.
47+
const loadContainerTimeout = 10 * time.Second
48+
49+
func (c *Controller) RecoverContainer(ctx context.Context, cntr containerd.Container) (sandboxstore.Sandbox, error) {
50+
ctx, cancel := context.WithTimeout(ctx, loadContainerTimeout)
51+
defer cancel()
52+
var sandbox sandboxstore.Sandbox
53+
// Load sandbox metadata.
54+
exts, err := cntr.Extensions(ctx)
55+
if err != nil {
56+
return sandbox, fmt.Errorf("failed to get sandbox container extensions: %w", err)
57+
}
58+
ext, ok := exts[sandboxMetadataExtension]
59+
if !ok {
60+
return sandbox, fmt.Errorf("metadata extension %q not found", sandboxMetadataExtension)
61+
}
62+
data, err := typeurl.UnmarshalAny(ext)
63+
if err != nil {
64+
return sandbox, fmt.Errorf("failed to unmarshal metadata extension %q: %w", ext, err)
65+
}
66+
meta := data.(*sandboxstore.Metadata)
67+
68+
s, err := func() (sandboxstore.Status, error) {
69+
status := sandboxstore.Status{
70+
State: sandboxstore.StateUnknown,
71+
}
72+
// Load sandbox created timestamp.
73+
info, err := cntr.Info(ctx)
74+
if err != nil {
75+
return status, fmt.Errorf("failed to get sandbox container info: %w", err)
76+
}
77+
status.CreatedAt = info.CreatedAt
78+
79+
// Load sandbox state.
80+
t, err := cntr.Task(ctx, nil)
81+
if err != nil && !errdefs.IsNotFound(err) {
82+
return status, fmt.Errorf("failed to load task: %w", err)
83+
}
84+
var taskStatus containerd.Status
85+
var notFound bool
86+
if errdefs.IsNotFound(err) {
87+
// Task is not found.
88+
notFound = true
89+
} else {
90+
// Task is found. Get task status.
91+
taskStatus, err = t.Status(ctx)
92+
if err != nil {
93+
// It's still possible that task is deleted during this window.
94+
if !errdefs.IsNotFound(err) {
95+
return status, fmt.Errorf("failed to get task status: %w", err)
96+
}
97+
notFound = true
98+
}
99+
}
100+
if notFound {
101+
// Task does not exist, set sandbox state as NOTREADY.
102+
status.State = sandboxstore.StateNotReady
103+
} else {
104+
if taskStatus.Status == containerd.Running {
105+
// Wait for the task for sandbox monitor.
106+
// wait is a long running background request, no timeout needed.
107+
exitCh, err := t.Wait(ctrdutil.NamespacedContext())
108+
if err != nil {
109+
if !errdefs.IsNotFound(err) {
110+
return status, fmt.Errorf("failed to wait for task: %w", err)
111+
}
112+
status.State = sandboxstore.StateNotReady
113+
} else {
114+
// Task is running, set sandbox state as READY.
115+
status.State = sandboxstore.StateReady
116+
status.Pid = t.Pid()
117+
118+
go func() {
119+
c.waitSandboxExit(context.Background(), meta.ID, exitCh)
120+
}()
121+
}
122+
} else {
123+
// Task is not running. Delete the task and set sandbox state as NOTREADY.
124+
if _, err := t.Delete(ctx, containerd.WithProcessKill); err != nil && !errdefs.IsNotFound(err) {
125+
return status, fmt.Errorf("failed to delete task: %w", err)
126+
}
127+
status.State = sandboxstore.StateNotReady
128+
}
129+
}
130+
return status, nil
131+
}()
132+
if err != nil {
133+
log.G(ctx).WithError(err).Errorf("Failed to load sandbox status for %q", cntr.ID())
134+
}
135+
136+
sandbox = sandboxstore.NewSandbox(*meta, s)
137+
sandbox.Container = cntr
138+
139+
// Load network namespace.
140+
sandbox.NetNS = getNetNS(meta)
141+
142+
// It doesn't matter whether task is running or not. If it is running, sandbox
143+
// status will be `READY`; if it is not running, sandbox status will be `NOT_READY`,
144+
// kubelet will stop the sandbox which will properly cleanup everything.
145+
return sandbox, nil
146+
}
147+
148+
func getNetNS(meta *sandboxstore.Metadata) *netns.NetNS {
149+
// Don't need to load netns for host network sandbox.
150+
if hostNetwork(meta.Config) {
151+
return nil
152+
}
153+
return netns.LoadNetNS(meta.NetNSPath)
154+
}
155+
156+
// hostNetwork handles checking if host networking was requested.
157+
// TODO: Copy pasted from sbserver to handle container sandbox events in podsandbox/ package, needs refactoring.
158+
func hostNetwork(config *runtime.PodSandboxConfig) bool {
159+
var hostNet bool
160+
switch goruntime.GOOS {
161+
case "windows":
162+
// Windows HostProcess pods can only run on the host network
163+
hostNet = config.GetWindows().GetSecurityContext().GetHostProcess()
164+
case "darwin":
165+
// No CNI on Darwin yet.
166+
hostNet = true
167+
default:
168+
// Even on other platforms, the logic containerd uses is to check if NamespaceMode == NODE.
169+
// So this handles Linux, as well as any other platforms not governed by the cases above
170+
// that have special quirks.
171+
hostNet = config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetNetwork() == runtime.NamespaceMode_NODE
172+
}
173+
return hostNet
174+
}

pkg/cri/sbserver/restart.go

Lines changed: 16 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/containerd/containerd/log"
3232
criconfig "github.com/containerd/containerd/pkg/cri/config"
3333
"github.com/containerd/containerd/pkg/cri/sbserver/podsandbox"
34+
"github.com/containerd/containerd/pkg/netns"
3435
"github.com/containerd/containerd/platforms"
3536
"github.com/containerd/typeurl/v2"
3637
"golang.org/x/sync/errgroup"
@@ -40,7 +41,6 @@ import (
4041
containerstore "github.com/containerd/containerd/pkg/cri/store/container"
4142
sandboxstore "github.com/containerd/containerd/pkg/cri/store/sandbox"
4243
ctrdutil "github.com/containerd/containerd/pkg/cri/util"
43-
"github.com/containerd/containerd/pkg/netns"
4444
)
4545

4646
// NOTE: The recovery logic has following assumption: when the cri plugin is down:
@@ -60,11 +60,21 @@ func (c *criService) recover(ctx context.Context) error {
6060
return fmt.Errorf("failed to list sandbox containers: %w", err)
6161
}
6262

63+
podSandboxController, ok := c.sandboxControllers[criconfig.ModePodSandbox]
64+
if !ok {
65+
log.G(ctx).Fatal("unable to restore pod sandboxes, no controller found")
66+
}
67+
68+
podSandboxLoader, ok := podSandboxController.(podSandboxRecover)
69+
if !ok {
70+
log.G(ctx).Fatal("pod sandbox controller doesn't support recovery")
71+
}
72+
6373
eg, ctx2 := errgroup.WithContext(ctx)
6474
for _, sandbox := range sandboxes {
6575
sandbox := sandbox
6676
eg.Go(func() error {
67-
sb, err := c.loadSandbox(ctx2, sandbox)
77+
sb, err := podSandboxLoader.RecoverContainer(ctx2, sandbox)
6878
if err != nil {
6979
log.G(ctx2).WithError(err).Errorf("Failed to load sandbox %q", sandbox.ID())
7080
return nil
@@ -388,99 +398,10 @@ func (c *criService) loadContainer(ctx context.Context, cntr containerd.Containe
388398
return containerstore.NewContainer(*meta, opts...)
389399
}
390400

391-
// loadSandbox loads sandbox from containerd.
392-
func (c *criService) loadSandbox(ctx context.Context, cntr containerd.Container) (sandboxstore.Sandbox, error) {
393-
ctx, cancel := context.WithTimeout(ctx, loadContainerTimeout)
394-
defer cancel()
395-
var sandbox sandboxstore.Sandbox
396-
// Load sandbox metadata.
397-
exts, err := cntr.Extensions(ctx)
398-
if err != nil {
399-
return sandbox, fmt.Errorf("failed to get sandbox container extensions: %w", err)
400-
}
401-
ext, ok := exts[sandboxMetadataExtension]
402-
if !ok {
403-
return sandbox, fmt.Errorf("metadata extension %q not found", sandboxMetadataExtension)
404-
}
405-
data, err := typeurl.UnmarshalAny(ext)
406-
if err != nil {
407-
return sandbox, fmt.Errorf("failed to unmarshal metadata extension %q: %w", ext, err)
408-
}
409-
meta := data.(*sandboxstore.Metadata)
410-
411-
s, err := func() (sandboxstore.Status, error) {
412-
status := unknownSandboxStatus()
413-
// Load sandbox created timestamp.
414-
info, err := cntr.Info(ctx)
415-
if err != nil {
416-
return status, fmt.Errorf("failed to get sandbox container info: %w", err)
417-
}
418-
status.CreatedAt = info.CreatedAt
419-
420-
// Load sandbox state.
421-
t, err := cntr.Task(ctx, nil)
422-
if err != nil && !errdefs.IsNotFound(err) {
423-
return status, fmt.Errorf("failed to load task: %w", err)
424-
}
425-
var taskStatus containerd.Status
426-
var notFound bool
427-
if errdefs.IsNotFound(err) {
428-
// Task is not found.
429-
notFound = true
430-
} else {
431-
// Task is found. Get task status.
432-
taskStatus, err = t.Status(ctx)
433-
if err != nil {
434-
// It's still possible that task is deleted during this window.
435-
if !errdefs.IsNotFound(err) {
436-
return status, fmt.Errorf("failed to get task status: %w", err)
437-
}
438-
notFound = true
439-
}
440-
}
441-
if notFound {
442-
// Task does not exist, set sandbox state as NOTREADY.
443-
status.State = sandboxstore.StateNotReady
444-
} else {
445-
if taskStatus.Status == containerd.Running {
446-
// Wait for the task for sandbox monitor.
447-
// wait is a long running background request, no timeout needed.
448-
exitCh, err := t.Wait(ctrdutil.NamespacedContext())
449-
if err != nil {
450-
if !errdefs.IsNotFound(err) {
451-
return status, fmt.Errorf("failed to wait for task: %w", err)
452-
}
453-
status.State = sandboxstore.StateNotReady
454-
} else {
455-
// Task is running, set sandbox state as READY.
456-
status.State = sandboxstore.StateReady
457-
status.Pid = t.Pid()
458-
c.eventMonitor.startSandboxExitMonitor(context.Background(), meta.ID, status.Pid, exitCh)
459-
}
460-
} else {
461-
// Task is not running. Delete the task and set sandbox state as NOTREADY.
462-
if _, err := t.Delete(ctx, containerd.WithProcessKill); err != nil && !errdefs.IsNotFound(err) {
463-
return status, fmt.Errorf("failed to delete task: %w", err)
464-
}
465-
status.State = sandboxstore.StateNotReady
466-
}
467-
}
468-
return status, nil
469-
}()
470-
if err != nil {
471-
log.G(ctx).WithError(err).Errorf("Failed to load sandbox status for %q", cntr.ID())
472-
}
473-
474-
sandbox = sandboxstore.NewSandbox(*meta, s)
475-
sandbox.Container = cntr
476-
477-
// Load network namespace.
478-
sandbox.NetNS = getNetNS(meta)
479-
480-
// It doesn't matter whether task is running or not. If it is running, sandbox
481-
// status will be `READY`; if it is not running, sandbox status will be `NOT_READY`,
482-
// kubelet will stop the sandbox which will properly cleanup everything.
483-
return sandbox, nil
401+
// podSandboxRecover is an additional interface implemented by podsandbox/ controller to handle
402+
// Pod sandbox containers recovery.
403+
type podSandboxRecover interface {
404+
RecoverContainer(ctx context.Context, cntr containerd.Container) (sandboxstore.Sandbox, error)
484405
}
485406

486407
func getNetNS(meta *sandboxstore.Metadata) *netns.NetNS {

0 commit comments

Comments
 (0)