`--rm` is a client side flag which caused lots of problems:
1. if client lost connection to daemon, including client crash or be
killed, there's no way to clean garbage container.
2. if docker stop a `--rm` container, this container won't be
autoremoved.
3. if docker daemon restart, container is also left over.
4. bug: `docker run --rm busybox fakecmd` will exit without cleanup.
In a word, client side `--rm` flag isn't sufficient for garbage
collection. Move the `--rm` flag to daemon will be more reasonable.
What this commit do is:
1. implement a `--rm` on daemon side, adding one flag `AutoRemove` into
HostConfig.
2. Allow `run --rm -d`, no conflicting `--rm` and `-d` any more,
auto-remove can work on detach mode.
3. `docker restart` a `--rm` container will succeed, the container won't
be autoremoved.
This commit will help a lot for daemon to do garbage collection for
temporary containers.
Signed-off-by: Zhang Wei <zhangwei555@huawei.com>
| ... | ... |
@@ -25,7 +25,6 @@ import ( |
| 25 | 25 |
) |
| 26 | 26 |
|
| 27 | 27 |
type runOptions struct {
|
| 28 |
- autoRemove bool |
|
| 29 | 28 |
detach bool |
| 30 | 29 |
sigProxy bool |
| 31 | 30 |
name string |
| ... | ... |
@@ -55,7 +54,6 @@ func NewRunCommand(dockerCli *client.DockerCli) *cobra.Command {
|
| 55 | 55 |
flags.SetInterspersed(false) |
| 56 | 56 |
|
| 57 | 57 |
// These are flags not stored in Config/HostConfig |
| 58 |
- flags.BoolVar(&opts.autoRemove, "rm", false, "Automatically remove the container when it exits") |
|
| 59 | 58 |
flags.BoolVarP(&opts.detach, "detach", "d", false, "Run container in background and print container ID") |
| 60 | 59 |
flags.BoolVar(&opts.sigProxy, "sig-proxy", true, "Proxy received signals to the process") |
| 61 | 60 |
flags.StringVar(&opts.name, "name", "", "Assign a name to the container") |
| ... | ... |
@@ -87,7 +85,6 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions, |
| 87 | 87 |
flAttach *opttypes.ListOpts |
| 88 | 88 |
ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d")
|
| 89 | 89 |
ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
|
| 90 |
- ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d")
|
|
| 91 | 90 |
) |
| 92 | 91 |
|
| 93 | 92 |
config, hostConfig, networkingConfig, err := runconfigopts.Parse(flags, copts) |
| ... | ... |
@@ -127,9 +124,6 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions, |
| 127 | 127 |
return ErrConflictAttachDetach |
| 128 | 128 |
} |
| 129 | 129 |
} |
| 130 |
- if opts.autoRemove {
|
|
| 131 |
- return ErrConflictDetachAutoRemove |
|
| 132 |
- } |
|
| 133 | 130 |
|
| 134 | 131 |
config.AttachStdin = false |
| 135 | 132 |
config.AttachStdout = false |
| ... | ... |
@@ -172,7 +166,7 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions, |
| 172 | 172 |
fmt.Fprintf(stdout, "%s\n", createResponse.ID) |
| 173 | 173 |
}() |
| 174 | 174 |
} |
| 175 |
- if opts.autoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) {
|
|
| 175 |
+ if hostConfig.AutoRemove && !hostConfig.RestartPolicy.IsNone() {
|
|
| 176 | 176 |
return ErrConflictRestartPolicyAndAutoRemove |
| 177 | 177 |
} |
| 178 | 178 |
attach := config.AttachStdin || config.AttachStdout || config.AttachStderr |
| ... | ... |
@@ -225,16 +219,6 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions, |
| 225 | 225 |
}) |
| 226 | 226 |
} |
| 227 | 227 |
|
| 228 |
- if opts.autoRemove {
|
|
| 229 |
- defer func() {
|
|
| 230 |
- // Explicitly not sharing the context as it could be "Done" (by calling cancelFun) |
|
| 231 |
- // and thus the container would not be removed. |
|
| 232 |
- if err := removeContainer(dockerCli, context.Background(), createResponse.ID, true, false, true); err != nil {
|
|
| 233 |
- fmt.Fprintf(stderr, "%v\n", err) |
|
| 234 |
- } |
|
| 235 |
- }() |
|
| 236 |
- } |
|
| 237 |
- |
|
| 238 | 228 |
//start the container |
| 239 | 229 |
if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
|
| 240 | 230 |
// If we have holdHijackedConnection, we should notify |
| ... | ... |
@@ -272,30 +256,17 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions, |
| 272 | 272 |
var status int |
| 273 | 273 |
|
| 274 | 274 |
// Attached mode |
| 275 |
- if opts.autoRemove {
|
|
| 276 |
- // Autoremove: wait for the container to finish, retrieve |
|
| 277 |
- // the exit code and remove the container |
|
| 278 |
- if status, err = client.ContainerWait(ctx, createResponse.ID); err != nil {
|
|
| 279 |
- return runStartContainerErr(err) |
|
| 280 |
- } |
|
| 281 |
- if _, status, err = getExitCode(dockerCli, ctx, createResponse.ID); err != nil {
|
|
| 282 |
- return err |
|
| 283 |
- } |
|
| 284 |
- } else {
|
|
| 285 |
- // No Autoremove: Simply retrieve the exit code |
|
| 286 |
- if !config.Tty && hostConfig.RestartPolicy.IsNone() {
|
|
| 287 |
- // In non-TTY mode, we can't detach, so we must wait for container exit |
|
| 288 |
- if status, err = client.ContainerWait(ctx, createResponse.ID); err != nil {
|
|
| 289 |
- return err |
|
| 290 |
- } |
|
| 291 |
- } else {
|
|
| 292 |
- // In TTY mode, there is a race: if the process dies too slowly, the state could |
|
| 293 |
- // be updated after the getExitCode call and result in the wrong exit code being reported |
|
| 294 |
- if _, status, err = getExitCode(dockerCli, ctx, createResponse.ID); err != nil {
|
|
| 295 |
- return err |
|
| 296 |
- } |
|
| 297 |
- } |
|
| 275 |
+ if !config.Tty {
|
|
| 276 |
+ // In non-TTY mode, we can't detach, so we must wait for container exit |
|
| 277 |
+ client.ContainerWait(context.Background(), createResponse.ID) |
|
| 298 | 278 |
} |
| 279 |
+ |
|
| 280 |
+ // In TTY mode, there is a race: if the process dies too slowly, the state could |
|
| 281 |
+ // be updated after the getExitCode call and result in the wrong exit code being reported |
|
| 282 |
+ if _, status, err = dockerCli.GetExitCode(ctx, createResponse.ID); err != nil {
|
|
| 283 |
+ return fmt.Errorf("tty: status: %d; error: %v;", status, err)
|
|
| 284 |
+ } |
|
| 285 |
+ |
|
| 299 | 286 |
if status != 0 {
|
| 300 | 287 |
return cli.StatusError{StatusCode: status}
|
| 301 | 288 |
} |
| ... | ... |
@@ -238,6 +238,10 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *containertypes.HostCon |
| 238 | 238 |
return nil, nil |
| 239 | 239 |
} |
| 240 | 240 |
|
| 241 |
+ if hostConfig.AutoRemove && !hostConfig.RestartPolicy.IsNone() {
|
|
| 242 |
+ return nil, fmt.Errorf("Can't create 'AutoRemove' container with restart policy")
|
|
| 243 |
+ } |
|
| 244 |
+ |
|
| 241 | 245 |
for port := range hostConfig.PortBindings {
|
| 242 | 246 |
_, portStr := nat.SplitProtoPort(string(port)) |
| 243 | 247 |
if _, err := nat.ParsePort(portStr); err != nil {
|
| ... | ... |
@@ -147,6 +147,7 @@ func (daemon *Daemon) restore() error {
|
| 147 | 147 |
} |
| 148 | 148 |
|
| 149 | 149 |
var migrateLegacyLinks bool |
| 150 |
+ removeContainers := make(map[string]*container.Container) |
|
| 150 | 151 |
restartContainers := make(map[*container.Container]chan struct{})
|
| 151 | 152 |
activeSandboxes := make(map[string]interface{})
|
| 152 | 153 |
for _, c := range containers {
|
| ... | ... |
@@ -194,10 +195,14 @@ func (daemon *Daemon) restore() error {
|
| 194 | 194 |
} |
| 195 | 195 |
// fixme: only if not running |
| 196 | 196 |
// get list of containers we need to restart |
| 197 |
- if daemon.configStore.AutoRestart && !c.IsRunning() && !c.IsPaused() && c.ShouldRestart() {
|
|
| 198 |
- mapLock.Lock() |
|
| 199 |
- restartContainers[c] = make(chan struct{})
|
|
| 200 |
- mapLock.Unlock() |
|
| 197 |
+ if !c.IsRunning() && !c.IsPaused() {
|
|
| 198 |
+ if daemon.configStore.AutoRestart && c.ShouldRestart() {
|
|
| 199 |
+ mapLock.Lock() |
|
| 200 |
+ restartContainers[c] = make(chan struct{})
|
|
| 201 |
+ mapLock.Unlock() |
|
| 202 |
+ } else if c.HostConfig != nil && c.HostConfig.AutoRemove {
|
|
| 203 |
+ removeContainers[c.ID] = c |
|
| 204 |
+ } |
|
| 201 | 205 |
} |
| 202 | 206 |
|
| 203 | 207 |
if c.RemovalInProgress {
|
| ... | ... |
@@ -283,6 +288,18 @@ func (daemon *Daemon) restore() error {
|
| 283 | 283 |
} |
| 284 | 284 |
group.Wait() |
| 285 | 285 |
|
| 286 |
+ removeGroup := sync.WaitGroup{}
|
|
| 287 |
+ for id := range removeContainers {
|
|
| 288 |
+ removeGroup.Add(1) |
|
| 289 |
+ go func(cid string) {
|
|
| 290 |
+ defer removeGroup.Done() |
|
| 291 |
+ if err := daemon.ContainerRm(cid, &types.ContainerRmConfig{ForceRemove: true, RemoveVolume: true}); err != nil {
|
|
| 292 |
+ logrus.Errorf("Failed to remove container %s: %s", cid, err)
|
|
| 293 |
+ } |
|
| 294 |
+ }(id) |
|
| 295 |
+ } |
|
| 296 |
+ removeGroup.Wait() |
|
| 297 |
+ |
|
| 286 | 298 |
// any containers that were started above would already have had this done, |
| 287 | 299 |
// however we need to now prepare the mountpoints for the rest of the containers as well. |
| 288 | 300 |
// This shouldn't cause any issue running on the containers that already had this run. |
| ... | ... |
@@ -295,7 +312,11 @@ func (daemon *Daemon) restore() error {
|
| 295 | 295 |
// has a volume and the volume dirver is not available. |
| 296 | 296 |
if _, ok := restartContainers[c]; ok {
|
| 297 | 297 |
continue |
| 298 |
+ } else if _, ok := removeContainers[c.ID]; ok {
|
|
| 299 |
+ // container is automatically removed, skip it. |
|
| 300 |
+ continue |
|
| 298 | 301 |
} |
| 302 |
+ |
|
| 299 | 303 |
group.Add(1) |
| 300 | 304 |
go func(c *container.Container) {
|
| 301 | 305 |
defer group.Done() |
| ... | ... |
@@ -3,6 +3,7 @@ package daemon |
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
|
| 6 |
+ "github.com/Sirupsen/logrus" |
|
| 6 | 7 |
"github.com/docker/docker/container" |
| 7 | 8 |
) |
| 8 | 9 |
|
| ... | ... |
@@ -35,11 +36,25 @@ func (daemon *Daemon) containerRestart(container *container.Container, seconds i |
| 35 | 35 |
defer daemon.Unmount(container) |
| 36 | 36 |
} |
| 37 | 37 |
|
| 38 |
- if err := daemon.containerStop(container, seconds); err != nil {
|
|
| 38 |
+ // set AutoRemove flag to false before stop so the container won't be |
|
| 39 |
+ // removed during restart process |
|
| 40 |
+ autoRemove := container.HostConfig.AutoRemove |
|
| 41 |
+ |
|
| 42 |
+ container.HostConfig.AutoRemove = false |
|
| 43 |
+ err := daemon.containerStop(container, seconds) |
|
| 44 |
+ // restore AutoRemove irrespective of whether the stop worked or not |
|
| 45 |
+ container.HostConfig.AutoRemove = autoRemove |
|
| 46 |
+ // containerStop will write HostConfig to disk, we shall restore AutoRemove |
|
| 47 |
+ // in disk too |
|
| 48 |
+ if toDiskErr := container.ToDiskLocking(); toDiskErr != nil {
|
|
| 49 |
+ logrus.Errorf("Write container to disk error: %v", toDiskErr)
|
|
| 50 |
+ } |
|
| 51 |
+ |
|
| 52 |
+ if err != nil {
|
|
| 39 | 53 |
return err |
| 40 | 54 |
} |
| 41 | 55 |
|
| 42 |
- if err := daemon.containerStart(container); err != nil {
|
|
| 56 |
+ if err = daemon.containerStart(container); err != nil {
|
|
| 43 | 57 |
return err |
| 44 | 58 |
} |
| 45 | 59 |
|
| ... | ... |
@@ -14,6 +14,7 @@ import ( |
| 14 | 14 |
"github.com/docker/docker/errors" |
| 15 | 15 |
"github.com/docker/docker/libcontainerd" |
| 16 | 16 |
"github.com/docker/docker/runconfig" |
| 17 |
+ "github.com/docker/engine-api/types" |
|
| 17 | 18 |
containertypes "github.com/docker/engine-api/types/container" |
| 18 | 19 |
) |
| 19 | 20 |
|
| ... | ... |
@@ -197,4 +198,10 @@ func (daemon *Daemon) Cleanup(container *container.Container) {
|
| 197 | 197 |
} |
| 198 | 198 |
} |
| 199 | 199 |
container.CancelAttachContext() |
| 200 |
+ |
|
| 201 |
+ // if containers AutoRemove flag is set, remove it after clean up |
|
| 202 |
+ if container.HostConfig.AutoRemove {
|
|
| 203 |
+ // If containers lock is not released, goroutine will guarantee no block |
|
| 204 |
+ go daemon.ContainerRm(container.ID, &types.ContainerRmConfig{ForceRemove: true, RemoveVolume: true})
|
|
| 205 |
+ } |
|
| 200 | 206 |
} |
| ... | ... |
@@ -2742,3 +2742,28 @@ func (s *DockerDaemonSuite) TestRunWithRuntimeFromCommandLine(c *check.C) {
|
| 2742 | 2742 |
out, err = s.d.Cmd("run", "--rm", "--runtime=runc", "busybox", "ls")
|
| 2743 | 2743 |
c.Assert(err, check.IsNil, check.Commentf(out)) |
| 2744 | 2744 |
} |
| 2745 |
+ |
|
| 2746 |
+func (s *DockerDaemonSuite) TestDaemonRestartWithAutoRemoveContainer(c *check.C) {
|
|
| 2747 |
+ err := s.d.StartWithBusybox() |
|
| 2748 |
+ c.Assert(err, checker.IsNil) |
|
| 2749 |
+ |
|
| 2750 |
+ // top1 will exist after daemon restarts |
|
| 2751 |
+ out, err := s.d.Cmd("run", "-d", "--name", "top1", "busybox:latest", "top")
|
|
| 2752 |
+ c.Assert(err, checker.IsNil, check.Commentf("run top1: %v", out))
|
|
| 2753 |
+ // top2 will be removed after daemon restarts |
|
| 2754 |
+ out, err = s.d.Cmd("run", "-d", "--rm", "--name", "top2", "busybox:latest", "top")
|
|
| 2755 |
+ c.Assert(err, checker.IsNil, check.Commentf("run top2: %v", out))
|
|
| 2756 |
+ |
|
| 2757 |
+ out, err = s.d.Cmd("ps")
|
|
| 2758 |
+ c.Assert(out, checker.Contains, "top1", check.Commentf("top1 should be running"))
|
|
| 2759 |
+ c.Assert(out, checker.Contains, "top2", check.Commentf("top2 should be running"))
|
|
| 2760 |
+ |
|
| 2761 |
+ // now restart daemon gracefully |
|
| 2762 |
+ err = s.d.Restart() |
|
| 2763 |
+ c.Assert(err, checker.IsNil) |
|
| 2764 |
+ |
|
| 2765 |
+ out, err = s.d.Cmd("ps", "-a")
|
|
| 2766 |
+ c.Assert(err, checker.IsNil, check.Commentf("out: %v", out))
|
|
| 2767 |
+ c.Assert(out, checker.Contains, "top1", check.Commentf("top1 should exist after daemon restarts"))
|
|
| 2768 |
+ c.Assert(out, checker.Not(checker.Contains), "top2", check.Commentf("top2 should be removed after daemon restarts"))
|
|
| 2769 |
+} |
| ... | ... |
@@ -240,3 +240,15 @@ func (s *DockerSuite) TestRestartContainerwithRestartPolicy(c *check.C) {
|
| 240 | 240 |
dockerCmd(c, "start", id1) |
| 241 | 241 |
dockerCmd(c, "start", id2) |
| 242 | 242 |
} |
| 243 |
+ |
|
| 244 |
+func (s *DockerSuite) TestRestartAutoRmoveContainer(c *check.C) {
|
|
| 245 |
+ out, _ := runSleepingContainer(c, "--rm") |
|
| 246 |
+ |
|
| 247 |
+ id := strings.TrimSpace(string(out)) |
|
| 248 |
+ dockerCmd(c, "restart", id) |
|
| 249 |
+ err := waitInspect(id, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 15*time.Second)
|
|
| 250 |
+ c.Assert(err, checker.IsNil) |
|
| 251 |
+ |
|
| 252 |
+ out, _ = dockerCmd(c, "ps") |
|
| 253 |
+ c.Assert(out, checker.Contains, id[:12], check.Commentf("container should be restarted instead of removed: %v", out))
|
|
| 254 |
+} |
| ... | ... |
@@ -4498,6 +4498,15 @@ func (s *DockerSuite) TestRunAddHostInHostMode(c *check.C) {
|
| 4498 | 4498 |
c.Assert(out, checker.Contains, expectedOutput, check.Commentf("Expected '%s', but got %q", expectedOutput, out))
|
| 4499 | 4499 |
} |
| 4500 | 4500 |
|
| 4501 |
+func (s *DockerSuite) TestRunRmAndWait(c *check.C) {
|
|
| 4502 |
+ dockerCmd(c, "run", "--name=test", "--rm", "-d", "busybox", "sh", "-c", "sleep 3;exit 2") |
|
| 4503 |
+ |
|
| 4504 |
+ out, code, err := dockerCmdWithError("wait", "test")
|
|
| 4505 |
+ c.Assert(err, checker.IsNil, check.Commentf("out: %s; exit code: %d", out, code))
|
|
| 4506 |
+ c.Assert(out, checker.Equals, "2\n", check.Commentf("exit code: %d", code))
|
|
| 4507 |
+ c.Assert(code, checker.Equals, 0) |
|
| 4508 |
+} |
|
| 4509 |
+ |
|
| 4501 | 4510 |
// Test case for #23498 |
| 4502 | 4511 |
func (s *DockerSuite) TestRunUnsetEntrypoint(c *check.C) {
|
| 4503 | 4512 |
testRequires(c, DaemonIsLinux) |
| ... | ... |
@@ -103,6 +103,7 @@ type ContainerOptions struct {
|
| 103 | 103 |
flHealthTimeout time.Duration |
| 104 | 104 |
flHealthRetries int |
| 105 | 105 |
flRuntime string |
| 106 |
+ flAutoRemove *bool |
|
| 106 | 107 |
|
| 107 | 108 |
Image string |
| 108 | 109 |
Args []string |
| ... | ... |
@@ -163,6 +164,7 @@ func AddFlags(flags *pflag.FlagSet) *ContainerOptions {
|
| 163 | 163 |
flags.Var(copts.flUlimits, "ulimit", "Ulimit options") |
| 164 | 164 |
flags.StringVarP(&copts.flUser, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])") |
| 165 | 165 |
flags.StringVarP(&copts.flWorkingDir, "workdir", "w", "", "Working directory inside the container") |
| 166 |
+ flags.BoolVarP(&copts.flAutoRemove, "rm", false, "Automatically remove the container when it exits") |
|
| 166 | 167 |
|
| 167 | 168 |
// Security |
| 168 | 169 |
flags.Var(&copts.flCapAdd, "cap-add", "Add Linux capabilities") |
| ... | ... |
@@ -553,6 +555,7 @@ func Parse(flags *pflag.FlagSet, copts *ContainerOptions) (*container.Config, *c |
| 553 | 553 |
Binds: binds, |
| 554 | 554 |
ContainerIDFile: copts.flContainerIDFile, |
| 555 | 555 |
OomScoreAdj: copts.flOomScoreAdj, |
| 556 |
+ AutoRemove: copts.flAutoRemove, |
|
| 556 | 557 |
Privileged: copts.flPrivileged, |
| 557 | 558 |
PortBindings: portBindings, |
| 558 | 559 |
Links: copts.flLinks.GetAll(), |