Skip to content

Commit 97ece6a

Browse files
committed
enhancement restart policy
Signed-off-by: Ye Sijun <[email protected]>
1 parent 6eae937 commit 97ece6a

File tree

14 files changed

+274
-319
lines changed

14 files changed

+274
-319
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,9 +358,11 @@ Basic flags:
358358
- :whale: :blue_square: `-t, --tty`: Allocate a pseudo-TTY
359359
- :warning: WIP: currently `-t` conflicts with `-d`
360360
- :whale: :blue_square: `-d, --detach`: Run container in background and print container ID
361-
- :whale: `--restart=(no|always)`: Restart policy to apply when a container exits
361+
- :whale: `--restart=(no|always|on-failure|unless-stopped)`: Restart policy to apply when a container exits
362362
- Default: "no"
363-
- :warning: No support for `on-failure` and `unless-stopped`
363+
- always: Always restart the container if it stops.
364+
- on-failure[:max-retries]: Restart only if the container exits with a non-zero exit status. Optionally, limit the number of times attempts to restart the container using the :max-retries option.
365+
- unless-stopped: Always restart the container unless it is stopped.
364366
- :whale: `--rm`: Automatically remove the container when it exits
365367
- :whale: `--pull=(always|missing|never)`: Pull image before running
366368
- Default: "missing"

cmd/nerdctl/info.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,15 +163,15 @@ func prettyPrintInfoNative(w io.Writer, info *native.Info) error {
163163
fmt.Fprintf(w, "Rootless: %v\n", info.Rootless)
164164
fmt.Fprintf(w, "containerd Version: %s (%s)\n", info.Daemon.Version.Version, info.Daemon.Version.Revision)
165165
fmt.Fprintf(w, "containerd UUID: %s\n", info.Daemon.Server.UUID)
166-
var disabledPlugins, enabledPlugins []introspection.Plugin
166+
var disabledPlugins, enabledPlugins []*introspection.Plugin
167167
for _, f := range info.Daemon.Plugins.Plugins {
168168
if f.InitErr == nil {
169169
enabledPlugins = append(enabledPlugins, f)
170170
} else {
171171
disabledPlugins = append(disabledPlugins, f)
172172
}
173173
}
174-
sorter := func(x []introspection.Plugin) func(int, int) bool {
174+
sorter := func(x []*introspection.Plugin) func(int, int) bool {
175175
return func(i, j int) bool {
176176
return x[i].Type+"."+x[j].ID < x[j].Type+"."+x[j].ID
177177
}

cmd/nerdctl/run.go

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ import (
3737
"github.com/containerd/containerd/cmd/ctr/commands/tasks"
3838
"github.com/containerd/containerd/containers"
3939
"github.com/containerd/containerd/oci"
40-
"github.com/containerd/containerd/runtime/restart"
4140
gocni "github.com/containerd/go-cni"
4241
"github.com/containerd/nerdctl/pkg/defaults"
4342
"github.com/containerd/nerdctl/pkg/idgen"
@@ -102,7 +101,7 @@ func setCreateFlags(cmd *cobra.Command) {
102101
cmd.Flags().BoolP("interactive", "i", false, "Keep STDIN open even if not attached")
103102
cmd.Flags().String("restart", "no", `Restart policy to apply when a container exits (implemented values: "no"|"always")`)
104103
cmd.RegisterFlagCompletionFunc("restart", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
105-
return []string{"no", "always"}, cobra.ShellCompDirectiveNoFileComp
104+
return []string{"no", "always", "on-failure", "unless-stopped"}, cobra.ShellCompDirectiveNoFileComp
106105
})
107106
cmd.Flags().Bool("rm", false, "Automatically remove the container when it exits")
108107
cmd.Flags().String("pull", "missing", `Pull image before running ("always"|"missing"|"never")`)
@@ -475,7 +474,7 @@ func createContainer(cmd *cobra.Command, ctx context.Context, client *containerd
475474
if err != nil {
476475
return nil, "", nil, err
477476
}
478-
restartOpts, err := generateRestartOpts(restartValue, logURI)
477+
restartOpts, err := generateRestartOpts(ctx, client, restartValue, logURI)
479478
if err != nil {
480479
return nil, "", nil, err
481480
}
@@ -783,21 +782,6 @@ func withNerdctlOCIHook(cmd *cobra.Command, id, stateDir string) (oci.SpecOpts,
783782
}, nil
784783
}
785784

786-
func generateRestartOpts(restartFlag, logURI string) ([]containerd.NewContainerOpts, error) {
787-
switch restartFlag {
788-
case "", "no":
789-
return nil, nil
790-
case "always":
791-
opts := []containerd.NewContainerOpts{restart.WithStatus(containerd.Running)}
792-
if logURI != "" {
793-
opts = append(opts, restart.WithLogURIString(logURI))
794-
}
795-
return opts, nil
796-
default:
797-
return nil, fmt.Errorf("unsupported restart type %q, supported types are: \"no\", \"always\"", restartFlag)
798-
}
799-
}
800-
801785
func getContainerStateDirPath(cmd *cobra.Command, dataStore, id string) (string, error) {
802786
ns, err := cmd.Flags().GetString("namespace")
803787
if err != nil {

cmd/nerdctl/run_restart.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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 main
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"strconv"
23+
"strings"
24+
25+
"github.com/containerd/containerd"
26+
"github.com/containerd/containerd/runtime/restart"
27+
"github.com/containerd/nerdctl/pkg/strutil"
28+
)
29+
30+
func generateRestartOpts(ctx context.Context, client *containerd.Client, restartFlag, logURI string) ([]containerd.NewContainerOpts, error) {
31+
policySlice := strings.Split(restartFlag, ":")
32+
switch policySlice[0] {
33+
case "", "no":
34+
return nil, nil
35+
}
36+
res, err := client.IntrospectionService().Plugins(ctx, []string{"id==restart"})
37+
if err != nil {
38+
return nil, err
39+
}
40+
if len(res.Plugins) == 0 {
41+
return nil, fmt.Errorf("no restart plugin found")
42+
}
43+
restartPlugin := res.Plugins[0]
44+
capabilities := restartPlugin.Capabilities
45+
if len(capabilities) == 0 {
46+
capabilities = []string{"always"}
47+
}
48+
if !strutil.InStringSlice(capabilities, policySlice[0]) {
49+
return nil, fmt.Errorf("unsupported restart policy %q, supported policies are: %q", policySlice[0], restartPlugin.Capabilities)
50+
}
51+
policy, err := restart.NewPolicy(restartFlag)
52+
if err != nil {
53+
return nil, err
54+
}
55+
opts := []containerd.NewContainerOpts{restart.WithPolicy(policy), restart.WithStatus(containerd.Running)}
56+
if logURI != "" {
57+
opts = append(opts, restart.WithLogURIString(logURI))
58+
}
59+
return opts, nil
60+
}
61+
62+
func updateContainerStoppedLabel(ctx context.Context, container containerd.Container, stopped bool) error {
63+
opt := containerd.WithAdditionalContainerLabels(map[string]string{
64+
restart.ExplicitlyStoppedLabel: strconv.FormatBool(stopped),
65+
})
66+
return container.Update(ctx, containerd.UpdateContainerOpts(opt))
67+
}

cmd/nerdctl/run_restart_linux_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/containerd/nerdctl/pkg/testutil/nettestutil"
2828

2929
"gotest.tools/v3/assert"
30+
"gotest.tools/v3/poll"
3031
)
3132

3233
func TestRunRestart(t *testing.T) {
@@ -87,3 +88,48 @@ func TestRunRestart(t *testing.T) {
8788
base.DumpDaemonLogs(10)
8889
t.Fatalf("the container does not seem to be restarted")
8990
}
91+
92+
func TestRunRestartWithOnFailure(t *testing.T) {
93+
base := testutil.NewBase(t)
94+
if testutil.GetTarget() == testutil.Nerdctl {
95+
testutil.RequireContainerdPlugin(base, "io.containerd.internal.v1", "restart", []string{"on-failure"})
96+
}
97+
tID := testutil.Identifier(t)
98+
defer base.Cmd("rm", "-f", tID).Run()
99+
base.Cmd("run", "-d", "--restart=on-failure:2", "--name", tID, testutil.AlpineImage, "sh", "-c", "exit 1").AssertOK()
100+
101+
check := func(log poll.LogT) poll.Result {
102+
inspect := base.InspectContainer(tID)
103+
if inspect.State != nil && inspect.State.Status == "exited" {
104+
return poll.Success()
105+
}
106+
return poll.Continue("container is not yet exited")
107+
}
108+
poll.WaitOn(t, check, poll.WithDelay(100*time.Microsecond), poll.WithTimeout(60*time.Second))
109+
inspect := base.InspectContainer(tID)
110+
assert.Equal(t, inspect.RestartCount, 2)
111+
}
112+
113+
func TestRunRestartWithUnlessStopped(t *testing.T) {
114+
base := testutil.NewBase(t)
115+
if testutil.GetTarget() == testutil.Nerdctl {
116+
testutil.RequireContainerdPlugin(base, "io.containerd.internal.v1", "restart", []string{"unless-stopped"})
117+
}
118+
tID := testutil.Identifier(t)
119+
defer base.Cmd("rm", "-f", tID).Run()
120+
base.Cmd("run", "-d", "--restart=unless-stopped", "--name", tID, testutil.AlpineImage, "sh", "-c", "exit 1").AssertOK()
121+
122+
check := func(log poll.LogT) poll.Result {
123+
inspect := base.InspectContainer(tID)
124+
if inspect.State != nil && inspect.State.Status == "exited" {
125+
return poll.Success()
126+
}
127+
if inspect.RestartCount == 2 {
128+
base.Cmd("stop", tID).AssertOK()
129+
}
130+
return poll.Continue("container is not yet exited")
131+
}
132+
poll.WaitOn(t, check, poll.WithDelay(100*time.Microsecond), poll.WithTimeout(60*time.Second))
133+
inspect := base.InspectContainer(tID)
134+
assert.Equal(t, inspect.RestartCount, 2)
135+
}

cmd/nerdctl/start.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ func startContainer(ctx context.Context, container containerd.Container) error {
9090
logrus.Warnf("container %s is already running", container.ID())
9191
return nil
9292
}
93+
if err := updateContainerStoppedLabel(ctx, container, false); err != nil {
94+
return err
95+
}
9396
if oldTask, err := container.Task(ctx, nil); err == nil {
9497
if _, err := oldTask.Delete(ctx); err != nil {
9598
logrus.WithError(err).Debug("failed to delete old task")

cmd/nerdctl/stop.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ func stopAction(cmd *cobra.Command, args []string) error {
9090
}
9191

9292
func stopContainer(ctx context.Context, container containerd.Container, timeout time.Duration) error {
93+
if err := updateContainerStoppedLabel(ctx, container, true); err != nil {
94+
return err
95+
}
96+
9397
task, err := container.Task(ctx, cio.Load)
9498
if err != nil {
9599
return err

cmd/nerdctl/volume_rm.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func volumeRmAction(cmd *cobra.Command, args []string) error {
6767
if err != nil {
6868
return err
6969
}
70-
err = json.Unmarshal(n.Container.Spec.Value, &mount)
70+
err = json.Unmarshal(n.Container.Spec.GetValue(), &mount)
7171
if err != nil {
7272
return err
7373
}

go.mod

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ require (
1111
github.com/containerd/containerd v1.6.3
1212
github.com/containerd/continuity v0.3.0
1313
github.com/containerd/go-cni v1.1.5
14-
github.com/containerd/imgcrypt v1.1.4
14+
github.com/containerd/imgcrypt v1.1.5-0.20220421044638-8ba028dca028
1515
github.com/containerd/stargz-snapshotter v0.11.4
1616
github.com/containerd/stargz-snapshotter/estargz v0.11.4
1717
github.com/containerd/stargz-snapshotter/ipfs v0.11.4
18-
github.com/containerd/typeurl v1.0.2
18+
github.com/containerd/typeurl v1.0.3-0.20220422153119-7f6e6d160d67
1919
github.com/containernetworking/cni v1.1.0
2020
github.com/containernetworking/plugins v1.1.1
2121
github.com/cyphar/filepath-securejoin v0.2.3
@@ -24,7 +24,6 @@ require (
2424
github.com/docker/go-connections v0.4.0
2525
github.com/docker/go-units v0.4.0
2626
github.com/fatih/color v1.13.0
27-
github.com/gogo/protobuf v1.3.2
2827
github.com/hashicorp/go-multierror v1.1.1
2928
github.com/ipfs/go-cid v0.1.0
3029
github.com/ipfs/go-ipfs-files v0.1.1
@@ -46,15 +45,23 @@ require (
4645
github.com/vishvananda/netlink v1.2.0-beta
4746
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74
4847
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
49-
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
48+
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
5049
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
51-
golang.org/x/sys v0.0.0-20220405210540-1e041c57c461
50+
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150
5251
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
5352
gopkg.in/yaml.v2 v2.4.0
5453
gotest.tools/v3 v3.2.0
5554
)
5655

57-
require github.com/multiformats/go-multicodec v0.4.1 // indirect
56+
require (
57+
github.com/blang/semver v3.5.1+incompatible // indirect
58+
github.com/container-orchestrated-devices/container-device-interface v0.3.1 // indirect
59+
github.com/gogo/protobuf v1.3.2 // indirect
60+
github.com/multiformats/go-multicodec v0.4.1 // indirect
61+
github.com/opencontainers/runtime-tools v0.0.0-20190417131837-cd1349b7c47e // indirect
62+
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
63+
sigs.k8s.io/yaml v1.3.0 // indirect
64+
)
5865

5966
require (
6067
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
@@ -64,7 +71,7 @@ require (
6471
github.com/cespare/xxhash/v2 v2.1.2 // indirect
6572
github.com/cilium/ebpf v0.7.0 // indirect
6673
github.com/containerd/fifo v1.0.0 // indirect
67-
github.com/containerd/ttrpc v1.1.0 // indirect
74+
github.com/containerd/ttrpc v1.1.1-0.20220420014843-944ef4a40df3 // indirect
6875
github.com/containers/ocicrypt v1.1.3 // indirect
6976
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
7077
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
@@ -75,7 +82,6 @@ require (
7582
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
7683
github.com/docker/go-metrics v0.0.1 // indirect
7784
github.com/godbus/dbus/v5 v5.0.6 // indirect
78-
github.com/gogo/googleapis v1.4.1 // indirect
7985
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
8086
github.com/golang/protobuf v1.5.2 // indirect
8187
github.com/google/go-cmp v0.5.6 // indirect
@@ -123,7 +129,7 @@ require (
123129
github.com/mitchellh/mapstructure v1.4.3 // indirect
124130
github.com/moby/locker v1.0.1 // indirect
125131
github.com/moby/sys/mountinfo v0.6.1 // indirect
126-
github.com/moby/sys/signal v0.6.0 // indirect
132+
github.com/moby/sys/signal v0.7.0 // indirect
127133
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect
128134
github.com/morikuni/aec v1.0.0 // indirect
129135
github.com/mr-tron/base58 v1.2.0 // indirect
@@ -133,7 +139,7 @@ require (
133139
github.com/multiformats/go-multihash v0.0.15 // indirect
134140
github.com/multiformats/go-varint v0.0.6 // indirect
135141
github.com/opencontainers/runc v1.1.1 // indirect
136-
github.com/opencontainers/selinux v1.10.0 // indirect
142+
github.com/opencontainers/selinux v1.10.1 // indirect
137143
github.com/opentracing/opentracing-go v1.2.0 // indirect
138144
github.com/pkg/errors v0.9.1 // indirect
139145
github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e // indirect
@@ -160,11 +166,13 @@ require (
160166
go.uber.org/zap v1.17.0 // indirect
161167
golang.org/x/text v0.3.7 // indirect
162168
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
163-
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350 // indirect
164-
google.golang.org/grpc v1.45.0 // indirect
165-
google.golang.org/protobuf v1.27.1 // indirect
169+
google.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46 // indirect
170+
google.golang.org/grpc v1.46.0 // indirect
171+
google.golang.org/protobuf v1.28.0 // indirect
166172
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
167173
)
168174

169175
// Temporary fork for avoiding importing patent-protected code: https://github.com/hashicorp/golang-lru/issues/73
170176
replace github.com/hashicorp/golang-lru => github.com/ktock/golang-lru v0.5.5-0.20211029085301-ec551be6f75c
177+
178+
replace github.com/containerd/containerd => github.com/containerd/containerd v1.6.1-0.20220428184543-bb8b134a1797

0 commit comments

Comments
 (0)