@@ -7,11 +7,15 @@ import (
7
7
"io"
8
8
"net/url"
9
9
"os"
10
+ "os/exec"
11
+ "os/signal"
10
12
"path"
11
13
"path/filepath"
12
14
"sort"
13
15
"strconv"
14
16
"strings"
17
+ "syscall"
18
+ "time"
15
19
16
20
"github.com/docker/docker/api/types/container"
17
21
"github.com/google/go-containerregistry/pkg/name"
@@ -157,11 +161,26 @@ func dockerCmd() *cobra.Command {
157
161
Short : "Create a docker-based CVM" ,
158
162
RunE : func (cmd * cobra.Command , args []string ) (err error ) {
159
163
var (
160
- ctx = cmd .Context ()
161
- log = slog .Make (slogjson .Sink (cmd .ErrOrStderr ()), slogkubeterminate .Make ()).Leveled (slog .LevelDebug )
162
- blog buildlog.Logger = buildlog.JSONLogger {Encoder : json .NewEncoder (os .Stderr )}
164
+ ctx , cancel = context . WithCancel ( cmd .Context ()) //nolint
165
+ log = slog .Make (slogjson .Sink (cmd .ErrOrStderr ()), slogkubeterminate .Make ()).Leveled (slog .LevelDebug )
166
+ blog buildlog.Logger = buildlog.JSONLogger {Encoder : json .NewEncoder (os .Stderr )}
163
167
)
164
168
169
+ // We technically leak a context here, but it's impact is negligible.
170
+ signalCtx , signalCancel := context .WithCancel (cmd .Context ())
171
+ sigs := make (chan os.Signal , 1 )
172
+ signal .Notify (sigs , syscall .SIGTERM , syscall .SIGINT , syscall .SIGWINCH )
173
+
174
+ // Spawn a goroutine to wait for a signal.
175
+ go func () {
176
+ defer signalCancel ()
177
+ log .Info (ctx , "waiting for signal" )
178
+ <- sigs
179
+ log .Info (ctx , "got signal, canceling context" )
180
+ }()
181
+
182
+ cmd .SetContext (ctx )
183
+
165
184
if flags .noStartupLogs {
166
185
log = slog .Make (slogjson .Sink (io .Discard ))
167
186
blog = buildlog.NopLogger {}
@@ -210,11 +229,13 @@ func dockerCmd() *cobra.Command {
210
229
case err := <- background .New (ctx , log , "sysbox-mgr" , sysboxArgs ... ).Run ():
211
230
blog .Info (sysboxErrMsg )
212
231
//nolint
213
- log .Fatal (ctx , "sysbox-mgr exited" , slog .Error (err ))
232
+ log .Critical (ctx , "sysbox-mgr exited" , slog .Error (err ))
233
+ panic (err )
214
234
case err := <- background .New (ctx , log , "sysbox-fs" ).Run ():
215
235
blog .Info (sysboxErrMsg )
216
236
//nolint
217
- log .Fatal (ctx , "sysbox-fs exited" , slog .Error (err ))
237
+ log .Critical (ctx , "sysbox-fs exited" , slog .Error (err ))
238
+ panic (err )
218
239
}
219
240
}()
220
241
@@ -316,7 +337,7 @@ func dockerCmd() *cobra.Command {
316
337
)
317
338
}
318
339
319
- err = runDockerCVM (ctx , log , client , blog , flags )
340
+ bootstrapExecID , err : = runDockerCVM (ctx , log , client , blog , flags )
320
341
if err != nil {
321
342
// It's possible we failed because we ran out of disk while
322
343
// pulling the image. We should restart the daemon and use
@@ -345,14 +366,53 @@ func dockerCmd() *cobra.Command {
345
366
}()
346
367
347
368
log .Debug (ctx , "reattempting container creation" )
348
- err = runDockerCVM (ctx , log , client , blog , flags )
369
+ bootstrapExecID , err = runDockerCVM (ctx , log , client , blog , flags )
349
370
}
350
371
if err != nil {
351
372
blog .Errorf ("Failed to run envbox: %v" , err )
352
373
return xerrors .Errorf ("run: %w" , err )
353
374
}
354
375
}
355
376
377
+ go func () {
378
+ defer cancel ()
379
+
380
+ <- signalCtx .Done ()
381
+ log .Debug (ctx , "ctx canceled, forwarding signal to inner container" )
382
+
383
+ time .Sleep (time .Second * 10 )
384
+ if bootstrapExecID == "" {
385
+ log .Debug (ctx , "no bootstrap exec id, skipping" )
386
+ return
387
+ }
388
+
389
+ ctx , cancel := context .WithTimeout (context .Background (), time .Second * 90 )
390
+ defer cancel ()
391
+
392
+ bootstrapPID , err := dockerutil .GetExecPID (ctx , client , bootstrapExecID )
393
+ if err != nil {
394
+ log .Error (ctx , "get exec pid" , slog .Error (err ))
395
+ }
396
+
397
+ log .Debug (ctx , "killing container" , slog .F ("bootstrap_pid" , bootstrapPID ))
398
+
399
+ // The PID returned is the PID _outside_ the container...
400
+ out , err := exec .Command ("kill" , "-TERM" , strconv .Itoa (bootstrapPID )).CombinedOutput ()
401
+ if err != nil {
402
+ log .Error (ctx , "kill bootstrap process" , slog .Error (err ), slog .F ("output" , string (out )))
403
+ return
404
+ }
405
+
406
+ log .Debug (ctx , "sent kill signal waiting for process to exit" )
407
+ err = dockerutil .WaitForExit (ctx , client , bootstrapExecID )
408
+ if err != nil {
409
+ log .Error (ctx , "wait for exit" , slog .Error (err ))
410
+ return
411
+ }
412
+
413
+ log .Debug (ctx , "bootstrap process successfully exited" )
414
+ }()
415
+
356
416
return nil
357
417
},
358
418
}
@@ -390,25 +450,22 @@ func dockerCmd() *cobra.Command {
390
450
return cmd
391
451
}
392
452
393
- func runDockerCVM (ctx context.Context , log slog.Logger , client dockerutil.Client , blog buildlog.Logger , flags flags ) error {
453
+ func runDockerCVM (ctx context.Context , log slog.Logger , client dockerutil.Client , blog buildlog.Logger , flags flags ) ( string , error ) {
394
454
fs := xunix .GetFS (ctx )
395
-
396
- // Set our OOM score to something really unfavorable to avoid getting killed
397
- // in memory-scarce scenarios.
398
455
err := xunix .SetOOMScore (ctx , "self" , "-1000" )
399
456
if err != nil {
400
- return xerrors .Errorf ("set oom score: %w" , err )
457
+ return "" , xerrors .Errorf ("set oom score: %w" , err )
401
458
}
402
459
ref , err := name .ParseReference (flags .innerImage )
403
460
if err != nil {
404
- return xerrors .Errorf ("parse ref: %w" , err )
461
+ return "" , xerrors .Errorf ("parse ref: %w" , err )
405
462
}
406
463
407
464
var dockerAuth dockerutil.AuthConfig
408
465
if flags .imagePullSecret != "" {
409
466
dockerAuth , err = dockerutil .AuthConfigFromString (flags .imagePullSecret , ref .Context ().RegistryStr ())
410
467
if err != nil {
411
- return xerrors .Errorf ("parse auth config: %w" , err )
468
+ return "" , xerrors .Errorf ("parse auth config: %w" , err )
412
469
}
413
470
}
414
471
@@ -417,7 +474,7 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Client
417
474
log .Info (ctx , "detected file" , slog .F ("image" , flags .innerImage ))
418
475
dockerAuth , err = dockerutil .AuthConfigFromPath (flags .dockerConfig , ref .Context ().RegistryStr ())
419
476
if err != nil && ! xerrors .Is (err , os .ErrNotExist ) {
420
- return xerrors .Errorf ("auth config from file: %w" , err )
477
+ return "" , xerrors .Errorf ("auth config from file: %w" , err )
421
478
}
422
479
}
423
480
@@ -430,7 +487,7 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Client
430
487
// Add any user-specified mounts to our mounts list.
431
488
extraMounts , err := parseMounts (flags .containerMounts )
432
489
if err != nil {
433
- return xerrors .Errorf ("read mounts: %w" , err )
490
+ return "" , xerrors .Errorf ("read mounts: %w" , err )
434
491
}
435
492
mounts = append (mounts , extraMounts ... )
436
493
@@ -442,7 +499,7 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Client
442
499
blog .Info ("Creating TUN device" )
443
500
dev , err := xunix .CreateTUNDevice (ctx , OuterTUNPath )
444
501
if err != nil {
445
- return xerrors .Errorf ("creat tun device: %w" , err )
502
+ return "" , xerrors .Errorf ("creat tun device: %w" , err )
446
503
}
447
504
448
505
devices = append (devices , container.DeviceMapping {
@@ -457,7 +514,7 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Client
457
514
blog .Info ("Creating FUSE device" )
458
515
dev , err := xunix .CreateFuseDevice (ctx , OuterFUSEPath )
459
516
if err != nil {
460
- return xerrors .Errorf ("create fuse device: %w" , err )
517
+ return "" , xerrors .Errorf ("create fuse device: %w" , err )
461
518
}
462
519
463
520
devices = append (devices , container.DeviceMapping {
@@ -479,7 +536,7 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Client
479
536
)
480
537
err = fs .Chown (device .PathOnHost , UserNamespaceOffset , UserNamespaceOffset )
481
538
if err != nil {
482
- return xerrors .Errorf ("chown device %q: %w" , device .PathOnHost , err )
539
+ return "" , xerrors .Errorf ("chown device %q: %w" , device .PathOnHost , err )
483
540
}
484
541
}
485
542
@@ -492,27 +549,27 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Client
492
549
ProgressFn : dockerutil .DefaultLogImagePullFn (blog ),
493
550
})
494
551
if err != nil {
495
- return xerrors .Errorf ("pull image: %w" , err )
552
+ return "" , xerrors .Errorf ("pull image: %w" , err )
496
553
}
497
554
498
555
log .Debug (ctx , "remounting /sys" )
499
556
500
557
// After image pull we remount /sys so sysbox can have appropriate perms to create a container.
501
558
err = xunix .MountFS (ctx , "/sys" , "/sys" , "" , "remount" , "rw" )
502
559
if err != nil {
503
- return xerrors .Errorf ("remount /sys: %w" , err )
560
+ return "" , xerrors .Errorf ("remount /sys: %w" , err )
504
561
}
505
562
506
563
if flags .addGPU {
507
564
if flags .hostUsrLibDir == "" {
508
- return xerrors .Errorf ("when using GPUs, %q must be specified" , EnvUsrLibDir )
565
+ return "" , xerrors .Errorf ("when using GPUs, %q must be specified" , EnvUsrLibDir )
509
566
}
510
567
511
568
// Unmount GPU drivers in /proc as it causes issues when creating any
512
569
// container in some cases (even the image metadata container).
513
570
_ , err = xunix .TryUnmountProcGPUDrivers (ctx , log )
514
571
if err != nil {
515
- return xerrors .Errorf ("unmount /proc GPU drivers: %w" , err )
572
+ return "" , xerrors .Errorf ("unmount /proc GPU drivers: %w" , err )
516
573
}
517
574
}
518
575
@@ -528,7 +585,7 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Client
528
585
// with /sbin/init or something simple like 'sleep infinity'.
529
586
imgMeta , err := dockerutil .GetImageMetadata (ctx , log , client , flags .innerImage , flags .innerUsername )
530
587
if err != nil {
531
- return xerrors .Errorf ("get image metadata: %w" , err )
588
+ return "" , xerrors .Errorf ("get image metadata: %w" , err )
532
589
}
533
590
534
591
blog .Infof ("Detected entrypoint user '%s:%s' with home directory %q" , imgMeta .UID , imgMeta .UID , imgMeta .HomeDir )
@@ -543,11 +600,11 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Client
543
600
544
601
uid , err := strconv .ParseInt (imgMeta .UID , 10 , 32 )
545
602
if err != nil {
546
- return xerrors .Errorf ("parse image uid: %w" , err )
603
+ return "" , xerrors .Errorf ("parse image uid: %w" , err )
547
604
}
548
605
gid , err := strconv .ParseInt (imgMeta .GID , 10 , 32 )
549
606
if err != nil {
550
- return xerrors .Errorf ("parse image gid: %w" , err )
607
+ return "" , xerrors .Errorf ("parse image gid: %w" , err )
551
608
}
552
609
553
610
for _ , m := range mounts {
@@ -568,13 +625,13 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Client
568
625
mounter := xunix .Mounter (ctx )
569
626
err := mounter .Mount ("" , m .Source , "" , []string {"remount,rw" })
570
627
if err != nil {
571
- return xerrors .Errorf ("remount: %w" , err )
628
+ return "" , xerrors .Errorf ("remount: %w" , err )
572
629
}
573
630
}
574
631
575
632
err := fs .Chmod (m .Source , 0o2755 )
576
633
if err != nil {
577
- return xerrors .Errorf ("chmod mountpoint %q: %w" , m .Source , err )
634
+ return "" , xerrors .Errorf ("chmod mountpoint %q: %w" , m .Source , err )
578
635
}
579
636
580
637
var (
@@ -601,14 +658,14 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Client
601
658
// user.
602
659
err = fs .Chown (m .Source , shiftedUID , shiftedGID )
603
660
if err != nil {
604
- return xerrors .Errorf ("chown mountpoint %q: %w" , m .Source , err )
661
+ return "" , xerrors .Errorf ("chown mountpoint %q: %w" , m .Source , err )
605
662
}
606
663
}
607
664
608
665
if flags .addGPU {
609
666
devs , binds , err := xunix .GPUs (ctx , log , flags .hostUsrLibDir )
610
667
if err != nil {
611
- return xerrors .Errorf ("find gpus: %w" , err )
668
+ return "" , xerrors .Errorf ("find gpus: %w" , err )
612
669
}
613
670
614
671
for _ , dev := range devs {
@@ -679,22 +736,22 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Client
679
736
MemoryLimit : int64 (flags .memory ),
680
737
})
681
738
if err != nil {
682
- return xerrors .Errorf ("create container: %w" , err )
739
+ return "" , xerrors .Errorf ("create container: %w" , err )
683
740
}
684
741
685
742
blog .Info ("Pruning images to free up disk..." )
686
743
// Prune images to avoid taking up any unnecessary disk from the user.
687
744
_ , err = dockerutil .PruneImages (ctx , client )
688
745
if err != nil {
689
- return xerrors .Errorf ("prune images: %w" , err )
746
+ return "" , xerrors .Errorf ("prune images: %w" , err )
690
747
}
691
748
692
749
// TODO fix iptables when istio detected.
693
750
694
751
blog .Info ("Starting up workspace..." )
695
752
err = client .ContainerStart (ctx , containerID , container.StartOptions {})
696
753
if err != nil {
697
- return xerrors .Errorf ("start container: %w" , err )
754
+ return "" , xerrors .Errorf ("start container: %w" , err )
698
755
}
699
756
700
757
log .Debug (ctx , "creating bootstrap directory" , slog .F ("directory" , imgMeta .HomeDir ))
@@ -714,7 +771,7 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Client
714
771
Args : []string {"-p" , bootDir },
715
772
})
716
773
if err != nil {
717
- return xerrors .Errorf ("make bootstrap dir: %w" , err )
774
+ return "" , xerrors .Errorf ("make bootstrap dir: %w" , err )
718
775
}
719
776
720
777
cpuQuota , err := xunix .ReadCPUQuota (ctx , log )
@@ -736,34 +793,50 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Client
736
793
}
737
794
738
795
blog .Info ("Envbox startup complete!" )
739
-
740
- // The bootstrap script doesn't return since it execs the agent
741
- // meaning that it can get pretty noisy if we were to log by default.
742
- // In order to allow users to discern issues getting the bootstrap script
743
- // to complete successfully we pipe the output to stdout if
744
- // CODER_DEBUG=true.
745
- debugWriter := io .Discard
746
- if flags .debug {
747
- debugWriter = os .Stdout
796
+ if flags .boostrapScript == "" {
797
+ return "" , nil
748
798
}
749
- // Bootstrap the container if a script has been provided.
750
799
blog .Infof ("Bootstrapping workspace..." )
751
- err = dockerutil .BootstrapContainer (ctx , client , dockerutil.BootstrapConfig {
752
- ContainerID : containerID ,
753
- User : imgMeta .UID ,
754
- Script : flags .boostrapScript ,
755
- // We set this because the default behavior is to download the agent
756
- // to /tmp/coder.XXXX. This causes a race to happen where we finish
757
- // downloading the binary but before we can execute systemd remounts
758
- // /tmp.
759
- Env : []string {fmt .Sprintf ("BINARY_DIR=%s" , bootDir )},
760
- StdOutErr : debugWriter ,
800
+
801
+ bootstrapExec , err := client .ContainerExecCreate (ctx , containerID , container.ExecOptions {
802
+ User : imgMeta .UID ,
803
+ Cmd : []string {"/bin/sh" , "-s" },
804
+ Env : []string {fmt .Sprintf ("BINARY_DIR=%s" , bootDir )},
805
+ AttachStdin : true ,
806
+ AttachStdout : true ,
807
+ AttachStderr : true ,
808
+ Detach : true ,
761
809
})
762
810
if err != nil {
763
- return xerrors .Errorf ("boostrap container : %w" , err )
811
+ return "" , xerrors .Errorf ("create exec : %w" , err )
764
812
}
765
813
766
- return nil
814
+ resp , err := client .ContainerExecAttach (ctx , bootstrapExec .ID , container.ExecStartOptions {})
815
+ if err != nil {
816
+ return "" , xerrors .Errorf ("attach exec: %w" , err )
817
+ }
818
+
819
+ _ , err = io .Copy (resp .Conn , strings .NewReader (flags .boostrapScript ))
820
+ if err != nil {
821
+ return "" , xerrors .Errorf ("copy stdin: %w" , err )
822
+ }
823
+ err = resp .CloseWrite ()
824
+ if err != nil {
825
+ return "" , xerrors .Errorf ("close write: %w" , err )
826
+ }
827
+
828
+ go func () {
829
+ defer resp .Close ()
830
+ rd := io .LimitReader (resp .Reader , 1 << 10 )
831
+ _ , err := io .Copy (blog , rd )
832
+ if err != nil {
833
+ log .Error (ctx , "copy bootstrap output" , slog .Error (err ))
834
+ }
835
+ }()
836
+
837
+ // We can't just call ExecInspect because there's a race where the cmd
838
+ // hasn't been assigned a PID yet.
839
+ return bootstrapExec .ID , nil
767
840
}
768
841
769
842
//nolint:revive
0 commit comments