Skip to content

Commit f609e64

Browse files
authored
fix: revert signal pass through handling (#96)
* Revert "fix: add a 1m timeout to signal shutdown (#92)" This reverts commit 0d37a62. * Revert "fix: pass through signals to inner container (#83)" This reverts commit c07d2c2.
1 parent 9b6f446 commit f609e64

File tree

11 files changed

+44
-322
lines changed

11 files changed

+44
-322
lines changed

README.md

-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ The environment variables can be used to configure various aspects of the inner
2727
| `CODER_CPUS` | Dictates the number of CPUs to allocate the inner container. It is recommended to set this using the Kubernetes [Downward API](https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/#use-container-fields-as-values-for-environment-variables). | false |
2828
| `CODER_MEMORY` | Dictates the max memory (in bytes) to allocate the inner container. It is recommended to set this using the Kubernetes [Downward API](https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/#use-container-fields-as-values-for-environment-variables). | false |
2929
| `CODER_DISABLE_IDMAPPED_MOUNT` | Disables idmapped mounts in sysbox. For more information, see the [Sysbox Documentation](https://github.com/nestybox/sysbox/blob/master/docs/user-guide/configuration.md#disabling-id-mapped-mounts-on-sysbox). | false |
30-
| `CODER_SHUTDOWN_TIMEOUT` | Configure a custom shutdown timeout to wait for the boostrap command to exit. Defaults to 1 minute. | false |
3130

3231
## Coder Template
3332

cli/clitest/cli.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func New(t *testing.T, cmd string, args ...string) (context.Context, *cobra.Comm
5656
ctx = ctx(t, fs, execer, mnt, client)
5757
)
5858

59-
root := cli.Root(nil)
59+
root := cli.Root()
6060
// This is the one thing that isn't really mocked for the tests.
6161
// I cringe at the thought of introducing yet another mock so
6262
// let's avoid it for now.

cli/docker.go

+29-90
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@ import (
77
"io"
88
"net/url"
99
"os"
10-
"os/exec"
1110
"path"
1211
"path/filepath"
1312
"sort"
1413
"strconv"
1514
"strings"
16-
"time"
1715

1816
dockertypes "github.com/docker/docker/api/types"
1917
"github.com/docker/docker/api/types/container"
@@ -40,10 +38,6 @@ const (
4038
// EnvBoxContainerName is the name of the inner user container.
4139
EnvBoxPullImageSecretEnvVar = "CODER_IMAGE_PULL_SECRET" //nolint:gosec
4240
EnvBoxContainerName = "CODER_CVM_CONTAINER_NAME"
43-
// We define a custom exit code to distinguish from the generic '1' when envbox exits due to a shutdown timeout.
44-
// Docker claims exit codes 125-127 so we start at 150 to
45-
// ensure we don't collide.
46-
ExitCodeShutdownTimeout = 150
4741
)
4842

4943
const (
@@ -82,9 +76,8 @@ const (
8276
// with UID/GID 1000 will be mapped to `UserNamespaceOffset` + 1000
8377
// on the host. Changing this value will result in improper mappings
8478
// on existing containers.
85-
UserNamespaceOffset = 100000
86-
devDir = "/dev"
87-
defaultShutdownTimeout = time.Minute
79+
UserNamespaceOffset = 100000
80+
devDir = "/dev"
8881
)
8982

9083
var (
@@ -108,7 +101,6 @@ var (
108101
EnvDockerConfig = "CODER_DOCKER_CONFIG"
109102
EnvDebug = "CODER_DEBUG"
110103
EnvDisableIDMappedMount = "CODER_DISABLE_IDMAPPED_MOUNT"
111-
EnvShutdownTimeout = "CODER_SHUTDOWN_TIMEOUT"
112104
)
113105

114106
var envboxPrivateMounts = map[string]struct{}{
@@ -146,15 +138,14 @@ type flags struct {
146138
cpus int
147139
memory int
148140
disableIDMappedMount bool
149-
shutdownTimeout time.Duration
150141

151142
// Test flags.
152143
noStartupLogs bool
153144
debug bool
154145
ethlink string
155146
}
156147

157-
func dockerCmd(ch chan func() error) *cobra.Command {
148+
func dockerCmd() *cobra.Command {
158149
var flags flags
159150

160151
cmd := &cobra.Command{
@@ -295,7 +286,7 @@ func dockerCmd(ch chan func() error) *cobra.Command {
295286
return xerrors.Errorf("wait for dockerd: %w", err)
296287
}
297288

298-
err = runDockerCVM(ctx, log, client, blog, ch, flags)
289+
err = runDockerCVM(ctx, log, client, blog, flags)
299290
if err != nil {
300291
// It's possible we failed because we ran out of disk while
301292
// pulling the image. We should restart the daemon and use
@@ -324,7 +315,7 @@ func dockerCmd(ch chan func() error) *cobra.Command {
324315
}()
325316

326317
log.Debug(ctx, "reattempting container creation")
327-
err = runDockerCVM(ctx, log, client, blog, ch, flags)
318+
err = runDockerCVM(ctx, log, client, blog, flags)
328319
}
329320
if err != nil {
330321
blog.Errorf("Failed to run envbox: %v", err)
@@ -358,7 +349,6 @@ func dockerCmd(ch chan func() error) *cobra.Command {
358349
cliflag.IntVarP(cmd.Flags(), &flags.cpus, "cpus", "", EnvCPUs, 0, "Number of CPUs to allocate inner container. e.g. 2")
359350
cliflag.IntVarP(cmd.Flags(), &flags.memory, "memory", "", EnvMemory, 0, "Max memory to allocate to the inner container in bytes.")
360351
cliflag.BoolVarP(cmd.Flags(), &flags.disableIDMappedMount, "disable-idmapped-mount", "", EnvDisableIDMappedMount, false, "Disable idmapped mounts in sysbox. Note that you may need an alternative (e.g. shiftfs).")
361-
cliflag.DurationVarP(cmd.Flags(), &flags.shutdownTimeout, "shutdown-timeout", "", EnvShutdownTimeout, defaultShutdownTimeout, "Duration after which envbox will be forcefully terminated.")
362352

363353
// Test flags.
364354
cliflag.BoolVarP(cmd.Flags(), &flags.noStartupLogs, "no-startup-log", "", "", false, "Do not log startup logs. Useful for testing.")
@@ -368,7 +358,7 @@ func dockerCmd(ch chan func() error) *cobra.Command {
368358
return cmd
369359
}
370360

371-
func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.DockerClient, blog buildlog.Logger, shutdownCh chan func() error, flags flags) error {
361+
func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.DockerClient, blog buildlog.Logger, flags flags) error {
372362
fs := xunix.GetFS(ctx)
373363

374364
// Set our OOM score to something really unfavorable to avoid getting killed
@@ -688,87 +678,36 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker
688678
}
689679

690680
blog.Info("Envbox startup complete!")
691-
if flags.boostrapScript == "" {
692-
return nil
693-
}
694-
695-
bootstrapExec, err := client.ContainerExecCreate(ctx, containerID, dockertypes.ExecConfig{
696-
User: imgMeta.UID,
697-
Cmd: []string{"/bin/sh", "-s"},
698-
Env: []string{fmt.Sprintf("BINARY_DIR=%s", bootDir)},
699-
AttachStdin: true,
700-
AttachStdout: true,
701-
AttachStderr: true,
702-
Detach: true,
703-
})
704-
if err != nil {
705-
return xerrors.Errorf("create exec: %w", err)
706-
}
707-
708-
resp, err := client.ContainerExecAttach(ctx, bootstrapExec.ID, dockertypes.ExecStartCheck{})
709-
if err != nil {
710-
return xerrors.Errorf("attach exec: %w", err)
711-
}
712681

713-
_, err = io.Copy(resp.Conn, strings.NewReader(flags.boostrapScript))
714-
if err != nil {
715-
return xerrors.Errorf("copy stdin: %w", err)
716-
}
717-
err = resp.CloseWrite()
718-
if err != nil {
719-
return xerrors.Errorf("close write: %w", err)
720-
}
721-
722-
go func() {
723-
defer resp.Close()
724-
rd := io.LimitReader(resp.Reader, 1<<10)
725-
_, err := io.Copy(blog, rd)
726-
if err != nil {
727-
log.Error(ctx, "copy bootstrap output", slog.Error(err))
728-
}
729-
}()
730-
731-
// We can't just call ExecInspect because there's a race where the cmd
732-
// hasn't been assigned a PID yet.
733-
bootstrapPID, err := dockerutil.GetExecPID(ctx, client, bootstrapExec.ID)
682+
// The bootstrap script doesn't return since it execs the agent
683+
// meaning that it can get pretty noisy if we were to log by default.
684+
// In order to allow users to discern issues getting the bootstrap script
685+
// to complete successfully we pipe the output to stdout if
686+
// CODER_DEBUG=true.
687+
debugWriter := io.Discard
688+
if flags.debug {
689+
debugWriter = os.Stdout
690+
}
691+
// Bootstrap the container if a script has been provided.
692+
blog.Infof("Bootstrapping workspace...")
693+
err = dockerutil.BootstrapContainer(ctx, client, dockerutil.BootstrapConfig{
694+
ContainerID: containerID,
695+
User: imgMeta.UID,
696+
Script: flags.boostrapScript,
697+
// We set this because the default behavior is to download the agent
698+
// to /tmp/coder.XXXX. This causes a race to happen where we finish
699+
// downloading the binary but before we can execute systemd remounts
700+
// /tmp.
701+
Env: []string{fmt.Sprintf("BINARY_DIR=%s", bootDir)},
702+
StdOutErr: debugWriter,
703+
})
734704
if err != nil {
735-
return xerrors.Errorf("exec inspect: %w", err)
705+
return xerrors.Errorf("boostrap container: %w", err)
736706
}
737707

738-
shutdownCh <- killBootstrapCmd(ctx, log, bootstrapPID, bootstrapExec.ID, client, flags.shutdownTimeout)
739-
740708
return nil
741709
}
742710

743-
// KillBootstrapCmd is the command we run when we receive a signal
744-
// to kill the envbox container.
745-
func killBootstrapCmd(ctx context.Context, log slog.Logger, pid int, execID string, client dockerutil.DockerClient, timeout time.Duration) func() error {
746-
return func() error {
747-
log.Debug(ctx, "killing container",
748-
slog.F("bootstrap_pid", pid),
749-
slog.F("timeout", timeout.String()),
750-
)
751-
752-
ctx, cancel := context.WithTimeout(ctx, timeout)
753-
defer cancel()
754-
// The PID returned is the PID _outside_ the container...
755-
//nolint:gosec
756-
out, err := exec.CommandContext(ctx, "kill", "-TERM", strconv.Itoa(pid)).CombinedOutput()
757-
if err != nil {
758-
return xerrors.Errorf("kill bootstrap process (%s): %w", out, err)
759-
}
760-
761-
log.Debug(ctx, "sent kill signal waiting for process to exit")
762-
err = dockerutil.WaitForExit(ctx, client, execID)
763-
if err != nil {
764-
return xerrors.Errorf("wait for exit: %w", err)
765-
}
766-
767-
log.Debug(ctx, "bootstrap process successfully exited")
768-
return nil
769-
}
770-
}
771-
772711
//nolint:revive
773712
func dockerdArgs(link, cidr string, isNoSpace bool) ([]string, error) {
774713
// We need to adjust the MTU for the host otherwise packets will fail delivery.

cli/root.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"github.com/spf13/cobra"
55
)
66

7-
func Root(ch chan func() error) *cobra.Command {
7+
func Root() *cobra.Command {
88
cmd := &cobra.Command{
99
Use: "envbox",
1010
SilenceErrors: true,
@@ -15,6 +15,6 @@ func Root(ch chan func() error) *cobra.Command {
1515
},
1616
}
1717

18-
cmd.AddCommand(dockerCmd(ch))
18+
cmd.AddCommand(dockerCmd())
1919
return cmd
2020
}

cmd/envbox/main.go

+3-34
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,20 @@
11
package main
22

33
import (
4-
"context"
54
"fmt"
65
"os"
7-
"os/signal"
86
"runtime"
9-
"syscall"
107

11-
"golang.org/x/xerrors"
12-
13-
"cdr.dev/slog"
14-
"cdr.dev/slog/sloggers/slogjson"
158
"github.com/coder/envbox/cli"
169
)
1710

1811
func main() {
19-
ch := make(chan func() error, 1)
20-
sigs := make(chan os.Signal, 1)
21-
signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT, syscall.SIGWINCH)
22-
go func() {
23-
ctx := context.Background()
24-
log := slog.Make(slogjson.Sink(os.Stderr))
25-
log.Info(ctx, "waiting for signal")
26-
<-sigs
27-
log.Info(ctx, "got signal")
28-
select {
29-
case fn := <-ch:
30-
log.Info(ctx, "running shutdown function")
31-
err := fn()
32-
if err != nil {
33-
log.Error(ctx, "shutdown function failed", slog.Error(err))
34-
if xerrors.Is(err, context.DeadlineExceeded) {
35-
os.Exit(cli.ExitCodeShutdownTimeout)
36-
}
37-
os.Exit(1)
38-
}
39-
default:
40-
log.Info(ctx, "no shutdown function")
41-
}
42-
log.Info(ctx, "exiting")
43-
os.Exit(0)
44-
}()
45-
_, err := cli.Root(ch).ExecuteC()
12+
_, err := cli.Root().ExecuteC()
4613
if err != nil {
4714
_, _ = fmt.Fprintln(os.Stderr, err.Error())
4815
os.Exit(1)
4916
}
17+
18+
// We exit the main thread while keepin all the other procs goin strong.
5019
runtime.Goexit()
5120
}

dockerutil/container.go

+2-9
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ func BootstrapContainer(ctx context.Context, client DockerClient, conf Bootstrap
113113

114114
var err error
115115
for r, n := retry.New(time.Second, time.Second*2), 0; r.Wait(ctx) && n < 10; n++ {
116-
var out io.Reader
116+
var out []byte
117117
out, err = ExecContainer(ctx, client, ExecConfig{
118118
ContainerID: conf.ContainerID,
119119
User: conf.User,
@@ -122,16 +122,9 @@ func BootstrapContainer(ctx context.Context, client DockerClient, conf Bootstrap
122122
Stdin: strings.NewReader(conf.Script),
123123
Env: conf.Env,
124124
StdOutErr: conf.StdOutErr,
125-
Detach: conf.Detach,
126125
})
127126
if err != nil {
128-
output, rerr := io.ReadAll(out)
129-
if rerr != nil {
130-
err = xerrors.Errorf("read all: %w", err)
131-
continue
132-
}
133-
134-
err = xerrors.Errorf("boostrap container (%s): %w", output, err)
127+
err = xerrors.Errorf("boostrap container (%s): %w", out, err)
135128
continue
136129
}
137130
break

dockerutil/dockerfake/client.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,7 @@ func (m MockClient) ContainerExecCreate(ctx context.Context, name string, config
162162

163163
func (m MockClient) ContainerExecInspect(ctx context.Context, id string) (dockertypes.ContainerExecInspect, error) {
164164
if m.ContainerExecInspectFn == nil {
165-
return dockertypes.ContainerExecInspect{
166-
Pid: 1,
167-
}, nil
165+
return dockertypes.ContainerExecInspect{}, nil
168166
}
169167

170168
return m.ContainerExecInspectFn(ctx, id)

0 commit comments

Comments
 (0)