Остановите работающий Docker-контейнер, отправив SIGTERM

У меня очень простое приложение Go, прослушивающее порт 8080

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(200)
    w.Header().Set("Content-Type", "text-plain")
    w.Write([]byte("Hello World!"))
})
log.Fatal(http.ListenAndServe(":8080", http.DefaultServeMux))

Я устанавливаю его в контейнер Docker и запускаю так:

FROM golang:alpine
ADD . /go/src/github.com/myuser/myapp
RUN go install github.com/myuser/myapp
ENTRYPOINT ["/go/bin/myapp"]
EXPOSE 8080

Затем я запускаю контейнер, используяdocker run:

docker run --publish 8080:8080 first-app

Я ожидаю, что, как и большинство программ, я могу отправить SIGTERM запущенному процессуdocker run и это заставит контейнер перестать работать. Я заметил, что отправка SIGTERM не имеет никакого эффекта, и вместо этого мне нужно использовать такую ​​команду, какdocker kill или жеdocker stop.

Это намеренное поведение? Я спросилна форумах и на IRC так и не получил ответа.

 johnharris8530 мая 2016 г., 01:16
Я не думаю, что OP говорит об отправке SIGTERM на PID1 в контейнере. Я думаю, он говорит об отправке SIGTERM на запуск pidof на хосте?
 slugonamission30 мая 2016 г., 01:25
@JHarris - я тоже так думаю; Я просто не знал, что посылкаdocker run сигнал на самом деле сделал и принимал удар в темноте. Я только что посмотрел, и кажется, чтоdocker run процесс пропускает все сигналы по умолчанию (искать это заsig-proxy).
 Kevin Burke30 мая 2016 г., 04:51
@JHarris Это верно, отправка SIGTERM в пид докера, запущенного на хосте.
 slugonamission30 мая 2016 г., 00:53
Честно говоря, я не уверен на 100%, хотя, похоже, я вспоминаю, что отправка SIGTERM процессу PID 1 на самом деле не убивает его, как вы ожидаете, потому что вам нужно явно перехватить сигнал и действовать на это (см. обоснование немой инициации для болееgithub.com/Yelp/dumb-init). Попробуйте запустить процесс черезsh (укажите точку входа в виде строки, не используя синтаксис массива) или используйтеdumb-init и посмотрим, поможет ли это.

Ответы на вопрос(2)

SIGTERM размножаетсяdocker run По умолчанию это команда для демона Docker, но она не вступит в силу, если вы специально не обработаете сигнал в основном процессе, выполняемом Docker.

Первый процесс, который выrun в контейнере будет PID 1 в контексте этого контейнера. Это рассматривается как особый процесс в ядре Linux. Будетне отправить сигнал, если в процессе не установлен обработчик для этого сигнала. Работа PID 1 также направляет сигналы на другие дочерние процессы.

docker run и другие команды являются клиентами PI дляУдаленный PI размещенный на демоне докера. Демон docker работает как отдельный процесс и является родительским для команд, которые вы запускаете в контексте контейнера. Это означает, что нет прямой отправки сигналов междуrun и демон, в стандартной манере Unix.

docker run а такжеdocker attach команда есть--sig-proxy флаг, который по умолчанию передает сигнал кtrue, Вы можете отключить это, если хотите.

docker exec не прокси сигналы.

ВDockerfileбудьте осторожны при использовании формы exec при указанииCMD а такжеENTRYPOINT по умолчанию такsh не становится процессом PID 1 (Кевин Берк):

CMD ["executable", "param1", "param2"]
Пример обработки сигналов Go

Используя пример кода Go здесь:https://gobyexample.com/signals

Запустите обычный процесс, который не обрабатывает сигналы, и демон Go, который перехватывает сигналы и помещает их в фоновый режим. я используюsleep поскольку это легко и не обрабатывает сигналы "демона".

$ docker run busybox sleep 6000 &
$ docker run gosignal &

Сps инструмент, который имеет «древовидное» представление, вы можете увидеть два разных дерева процессов. Один дляdocker run процесс подsshd, Другой для фактических контейнерных процессов, подdocker daemon.

$ pstree -p
init(1)-+-VBoxService(1287)
        |-docker(1356)---docker-containe(1369)-+-docker-containe(1511)---gitlab-ci-multi(1520)
        |                                      |-docker-containe(4069)---sleep(4078)
        |                                      `-docker-containe(4638)---main(4649)
        `-sshd(1307)---sshd(1565)---sshd(1567)---sh(1568)-+-docker(4060)
                                                          |-docker(4632)
                                                          `-pstree(4671)

Детали процессов хоста докера:

$ ps -ef | grep "docker r\|sleep\|main"
docker    4060  1568  0 02:57 pts/0    00:00:00 docker run busybox sleep 6000
root      4078  4069  0 02:58 ?        00:00:00 sleep 6000
docker    4632  1568  0 03:10 pts/0    00:00:00 docker run gosignal
root      4649  4638  0 03:10 ?        00:00:00 /main
Убийство

Я не могу убитьdocker run busybox sleep команда:

$ kill 4060
$ ps -ef | grep 4060
docker    4060  1568  0 02:57 pts/0    00:00:00 docker run busybox sleep 6000

Я могу убитьdocker run gosignal команда с обработчиком ловушек:

$ kill 4632
$ 
terminated
exiting

[2]+  Done                       docker run gosignal
Сигналы черезdocker exec

Если яdocker exec новыйsleep процесс в уже запущенномsleep контейнер, я могу отправить Ctrl-C и прерватьdocker exec само по себе, но это не переходит к фактическому процессу:

$ docker exec 30b6652cfc04 sleep 600
^C
$ docker exec 30b6652cfc04 ps -ef
PID   USER     TIME   COMMND
    1 root       0:00 sleep 6000   <- original
   97 root       0:00 sleep 600    <- execed still running
  102 root       0:00 ps -ef
TL; DR

Любой процесс, который выrun с докером должен обрабатывать сигналы сам.

1) Если вы укажете строку для точки входа, например:

ENTRYPOINT /go/bin/myapp

Докер запускает скрипт с/bin/sh -c 'command', Этот промежуточный скрипт получает SIGTERM, но не отправляет его запущенному серверному приложению.

Чтобы избежать промежуточного слоя, укажите вашу точку входа в виде массива строк.

ENTRYPOINT ["/go/bin/myapp"]

2) Я собрал приложение, которое пытался запустить, используя следующую строку:

docker build -t first-app .

Это помечает контейнер с именем first-app. К сожалению, когда я попытался перестроить / перезапустить контейнер, я запустил:

docker build .

Который не переписал тег, поэтому мои изменения не были применены.

После того, как я сделал обе эти вещи, я смог убить процесс с помощью ctrl + c и остановить работающий контейнер.

 Matt31 мая 2016 г., 06:05
Я не думал о форме оболочки, а не о форме exec в Dockerfile! Поскольку люди проголосовали против другого, я добавил примечание об этом.

Ваш ответ на вопрос