2018-02-05 16:05:59 -05:00
package daemon // import "github.com/docker/docker/daemon"
2014-07-31 16:24:54 -04:00
2015-10-12 19:34:03 -04:00
import (
2017-03-30 16:52:40 -04:00
"context"
2015-10-12 19:34:03 -04:00
"fmt"
"runtime"
"syscall"
2015-11-02 18:25:26 -05:00
"time"
2015-10-12 19:34:03 -04:00
2017-03-30 23:01:41 -04:00
containerpkg "github.com/docker/docker/container"
2018-01-11 14:53:06 -05:00
"github.com/docker/docker/errdefs"
Windows: Experimental: Allow containerd for runtime
Signed-off-by: John Howard <jhoward@microsoft.com>
This is the first step in refactoring moby (dockerd) to use containerd on Windows.
Similar to the current model in Linux, this adds the option to enable it for runtime.
It does not switch the graphdriver to containerd snapshotters.
- Refactors libcontainerd to a series of subpackages so that either a
"local" containerd (1) or a "remote" (2) containerd can be loaded as opposed
to conditional compile as "local" for Windows and "remote" for Linux.
- Updates libcontainerd such that Windows has an option to allow the use of a
"remote" containerd. Here, it communicates over a named pipe using GRPC.
This is currently guarded behind the experimental flag, an environment variable,
and the providing of a pipename to connect to containerd.
- Infrastructure pieces such as under pkg/system to have helper functions for
determining whether containerd is being used.
(1) "local" containerd is what the daemon on Windows has used since inception.
It's not really containerd at all - it's simply local invocation of HCS APIs
directly in-process from the daemon through the Microsoft/hcsshim library.
(2) "remote" containerd is what docker on Linux uses for it's runtime. It means
that there is a separate containerd service running, and docker communicates over
GRPC to it.
To try this out, you will need to start with something like the following:
Window 1:
containerd --log-level debug
Window 2:
$env:DOCKER_WINDOWS_CONTAINERD=1
dockerd --experimental -D --containerd \\.\pipe\containerd-containerd
You will need the following binary from github.com/containerd/containerd in your path:
- containerd.exe
You will need the following binaries from github.com/Microsoft/hcsshim in your path:
- runhcs.exe
- containerd-shim-runhcs-v1.exe
For LCOW, it will require and initrd.img and kernel in `C:\Program Files\Linux Containers`.
This is no different to the current requirements. However, you may need updated binaries,
particularly initrd.img built from Microsoft/opengcs as (at the time of writing), Linuxkit
binaries are somewhat out of date.
Note that containerd and hcsshim for HCS v2 APIs do not yet support all the required
functionality needed for docker. This will come in time - this is a baby (although large)
step to migrating Docker on Windows to containerd.
Note that the HCS v2 APIs are only called on RS5+ builds. RS1..RS4 will still use
HCS v1 APIs as the v2 APIs were not fully developed enough on these builds to be usable.
This abstraction is done in HCSShim. (Referring specifically to runtime)
Note the LCOW graphdriver still uses HCS v1 APIs regardless.
Note also that this does not migrate docker to use containerd snapshotters
rather than graphdrivers. This needs to be done in conjunction with Linux also
doing the same switch.
2019-01-08 17:30:52 -05:00
libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
2021-07-09 18:11:57 -04:00
"github.com/moby/sys/signal"
2017-07-19 10:20:13 -04:00
"github.com/pkg/errors"
2017-07-26 17:42:13 -04:00
"github.com/sirupsen/logrus"
2015-10-12 19:34:03 -04:00
)
2014-07-31 16:24:54 -04:00
2016-03-04 15:41:06 -05:00
type errNoSuchProcess struct {
pid int
2022-05-01 18:28:17 -04:00
signal syscall . Signal
2016-03-04 15:41:06 -05:00
}
func ( e errNoSuchProcess ) Error ( ) string {
2022-05-02 08:06:37 -04:00
return fmt . Sprintf ( "cannot kill process (pid=%d) with signal %d: no such process" , e . pid , e . signal )
2016-03-04 15:41:06 -05:00
}
2017-07-19 10:20:13 -04:00
func ( errNoSuchProcess ) NotFound ( ) { }
2016-03-17 13:15:32 -04:00
// ContainerKill sends signal to the container
2022-05-01 19:00:09 -04:00
// If no signal is given, then Kill with SIGKILL and wait
2014-07-31 16:24:54 -04:00
// for the container to exit.
// If a signal is given, then just send it to the container and return.
2022-05-01 19:00:09 -04:00
func ( daemon * Daemon ) ContainerKill ( name , stopSignal string ) error {
var (
err error
sig = syscall . SIGKILL
)
if stopSignal != "" {
sig , err = signal . ParseSignal ( stopSignal )
if err != nil {
return errdefs . InvalidParameter ( err )
}
if ! signal . ValidSignalForPlatform ( sig ) {
return errdefs . InvalidParameter ( errors . Errorf ( "the %s daemon does not support signal %d" , runtime . GOOS , sig ) )
}
}
2015-12-11 12:39:28 -05:00
container , err := daemon . GetContainer ( name )
2014-12-16 18:06:35 -05:00
if err != nil {
2015-03-25 03:44:12 -04:00
return err
2014-12-16 18:06:35 -05:00
}
2022-05-01 19:00:09 -04:00
if sig == syscall . SIGKILL {
// perform regular Kill (SIGKILL + wait())
2015-11-11 20:19:39 -05:00
return daemon . Kill ( container )
2014-07-31 16:24:54 -04:00
}
2022-05-01 18:28:17 -04:00
return daemon . killWithSignal ( container , sig )
2014-07-31 16:24:54 -04:00
}
2015-11-02 18:25:26 -05:00
// killWithSignal sends the container the given signal. This wrapper for the
// host specific kill command prepares the container before attempting
// to send the signal. An error is returned if the container is paused
// or not running, or if there is a problem returned from the
// underlying kill command.
2022-05-01 18:28:17 -04:00
func ( daemon * Daemon ) killWithSignal ( container * containerpkg . Container , stopSignal syscall . Signal ) error {
2022-05-01 18:05:21 -04:00
logrus . Debugf ( "Sending kill signal %d to container %s" , stopSignal , container . ID )
2015-11-02 18:25:26 -05:00
container . Lock ( )
defer container . Unlock ( )
if ! container . Running {
2017-07-19 10:20:13 -04:00
return errNotRunning ( container . ID )
2015-11-02 18:25:26 -05:00
}
2017-07-09 09:34:14 -04:00
var unpause bool
2022-05-01 18:05:21 -04:00
if container . Config . StopSignal != "" && stopSignal != syscall . SIGKILL {
2016-10-24 14:10:14 -04:00
containerStopSignal , err := signal . ParseSignal ( container . Config . StopSignal )
if err != nil {
return err
}
2022-05-01 18:05:21 -04:00
if containerStopSignal == stopSignal {
2016-10-24 14:10:14 -04:00
container . ExitOnNext ( )
2017-07-09 09:34:14 -04:00
unpause = container . Paused
2016-10-24 14:10:14 -04:00
}
} else {
container . ExitOnNext ( )
2017-07-09 09:34:14 -04:00
unpause = container . Paused
2016-10-24 14:10:14 -04:00
}
2015-11-02 18:25:26 -05:00
2016-03-18 14:50:19 -04:00
if ! daemon . IsShuttingDown ( ) {
container . HasBeenManuallyStopped = true
2017-11-01 02:15:02 -04:00
container . CheckpointTo ( daemon . containersReplica )
2016-03-18 14:50:19 -04:00
}
2015-11-02 18:25:26 -05:00
// if the container is currently restarting we do not need to send the signal
2016-07-21 06:03:37 -04:00
// to the process. Telling the monitor that it should exit on its next event
2015-11-02 18:25:26 -05:00
// loop is enough
if container . Restarting {
return nil
}
2022-05-01 18:05:21 -04:00
err := daemon . containerd . SignalProcess ( context . Background ( ) , container . ID , libcontainerdtypes . InitProcessName , stopSignal )
if err != nil {
2017-12-15 10:00:15 -05:00
if errdefs . IsNotFound ( err ) {
2017-07-09 09:34:14 -04:00
unpause = false
2017-12-15 10:00:15 -05:00
logrus . WithError ( err ) . WithField ( "container" , container . ID ) . WithField ( "action" , "kill" ) . Debug ( "container kill failed because of 'container not found' or 'no such process'" )
2020-08-11 16:13:00 -04:00
go func ( ) {
// We need to clean up this container but it is possible there is a case where we hit here before the exit event is processed
// but after it was fired off.
// So let's wait the container's stop timeout amount of time to see if the event is eventually processed.
// Doing this has the side effect that if no event was ever going to come we are waiting a a longer period of time uneccessarily.
// But this prevents race conditions in processing the container.
ctx , cancel := context . WithTimeout ( context . TODO ( ) , time . Duration ( container . StopTimeout ( ) ) * time . Second )
defer cancel ( )
s := <- container . Wait ( ctx , containerpkg . WaitConditionNotRunning )
if s . Err ( ) != nil {
daemon . handleContainerExit ( container , nil )
}
} ( )
2016-04-07 10:05:29 -04:00
} else {
2017-12-15 10:00:15 -05:00
return errors . Wrapf ( err , "Cannot kill container %s" , container . ID )
2016-04-07 10:05:29 -04:00
}
2015-11-02 18:25:26 -05:00
}
2017-07-09 09:34:14 -04:00
if unpause {
// above kill signal will be sent once resume is finished
2017-09-22 09:52:41 -04:00
if err := daemon . containerd . Resume ( context . Background ( ) , container . ID ) ; err != nil {
2018-07-11 09:51:51 -04:00
logrus . Warnf ( "Cannot unpause container %s: %s" , container . ID , err )
2017-07-09 09:34:14 -04:00
}
}
2016-01-07 17:14:05 -05:00
attributes := map [ string ] string {
2022-05-01 18:05:21 -04:00
"signal" : fmt . Sprintf ( "%d" , stopSignal ) ,
2016-01-07 17:14:05 -05:00
}
daemon . LogContainerEventWithAttributes ( container , "kill" , attributes )
2015-11-02 18:25:26 -05:00
return nil
}
// Kill forcefully terminates a container.
2017-03-30 23:01:41 -04:00
func ( daemon * Daemon ) Kill ( container * containerpkg . Container ) error {
2015-11-02 18:25:26 -05:00
if ! container . IsRunning ( ) {
2017-07-19 10:20:13 -04:00
return errNotRunning ( container . ID )
2015-11-02 18:25:26 -05:00
}
// 1. Send SIGKILL
2022-05-01 18:28:17 -04:00
if err := daemon . killPossiblyDeadProcess ( container , syscall . SIGKILL ) ; err != nil {
2020-10-24 13:56:21 -04:00
// kill failed, check if process is no longer running.
2022-05-02 08:06:37 -04:00
if errors . As ( err , & errNoSuchProcess { } ) {
2016-03-04 15:41:06 -05:00
return nil
}
2020-10-24 13:56:21 -04:00
}
2015-11-02 18:25:26 -05:00
2020-10-24 13:56:21 -04:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , 10 * time . Second )
defer cancel ( )
2017-03-30 16:52:40 -04:00
2020-10-24 13:56:21 -04:00
status := <- container . Wait ( ctx , containerpkg . WaitConditionNotRunning )
if status . Err ( ) == nil {
return nil
2015-11-02 18:25:26 -05:00
}
2020-10-24 13:56:21 -04:00
logrus . WithError ( status . Err ( ) ) . WithField ( "container" , container . ID ) . Error ( "Container failed to exit within 10 seconds of kill - trying direct SIGKILL" )
2015-11-02 18:25:26 -05:00
if err := killProcessDirectly ( container ) ; err != nil {
2022-05-02 08:06:37 -04:00
if errors . As ( err , & errNoSuchProcess { } ) {
2016-03-04 15:41:06 -05:00
return nil
}
2015-11-02 18:25:26 -05:00
return err
}
2020-10-24 13:56:21 -04:00
// wait for container to exit one last time, if it doesn't then kill didnt work, so return error
ctx2 , cancel2 := context . WithTimeout ( context . Background ( ) , 2 * time . Second )
defer cancel2 ( )
2017-03-30 16:52:40 -04:00
2020-10-24 13:56:21 -04:00
if status := <- container . Wait ( ctx2 , containerpkg . WaitConditionNotRunning ) ; status . Err ( ) != nil {
return errors . New ( "tried to kill container, but did not receive an exit event" )
}
2015-11-02 18:25:26 -05:00
return nil
}
2015-12-13 11:00:39 -05:00
// killPossibleDeadProcess is a wrapper around killSig() suppressing "no such process" error.
2022-05-01 18:28:17 -04:00
func ( daemon * Daemon ) killPossiblyDeadProcess ( container * containerpkg . Container , sig syscall . Signal ) error {
2015-11-02 18:25:26 -05:00
err := daemon . killWithSignal ( container , sig )
2017-12-15 10:00:15 -05:00
if errdefs . IsNotFound ( err ) {
2022-05-01 18:28:17 -04:00
err = errNoSuchProcess { container . GetPID ( ) , sig }
logrus . Debug ( err )
return err
2015-11-02 18:25:26 -05:00
}
return err
}