>  기사  >  운영 및 유지보수  >  Docker 컨테이너에서 신호를 잡는 방법을 알고 있나요?

Docker 컨테이너에서 신호를 잡는 방법을 알고 있나요?

王林
王林앞으로
2021-02-22 10:29:052424검색

Docker 컨테이너에서 신호를 잡는 방법을 알고 있나요?

실행 중인 컨테이너를 중지하려면 docker stop 명령을 사용해야 한다고 생각합니다. 때로는 컨테이너를 강제로 닫거나 컨테이너의 프로세스에 신호를 전달하기 위해 docker kill 명령을 사용할 수도 있습니다.

사실 우리가 수행하는 작업은 본질적으로 호스트에서 컨테이너로 신호를 전송하여 호스트와 컨테이너에 있는 프로그램 간의 상호 작용입니다. 예를 들어 컨테이너의 애플리케이션에 다시 로드 신호를 보내면 컨테이너의 애플리케이션은 해당 핸들러를 실행하여 신호를 받은 후 구성 파일을 다시 로드하는 작업을 완료합니다.

신호(linux)

신호는 프로세스 간 통신의 한 형태입니다. 신호는 특정 이벤트가 발생했음을 프로세스에 알리기 위해 커널이 프로세스에 보내는 메시지입니다. 신호가 프로세스에 전송되면 프로세스는 즉시 현재 실행 흐름을 중단하고 신호 처리기 실행을 시작합니다(특정 시간에 신호가 처리된다고 말하는 것은 정확하지 않습니다). 이 신호에 대해 핸들러가 지정되지 않으면 기본 핸들러가 실행됩니다.
프로세스는 관심 있는 신호에 대한 핸들러를 등록해야 합니다. 예를 들어 프로그램이 정상적으로 종료되도록(종료 요청을 받은 후 리소스를 정리하기 위해) 일반적으로 프로그램은 SIGTERM 신호를 처리합니다. SIGTERM 신호와 달리 SIGKILL 신호는 프로세스를 강제로 종료합니다. 따라서 우리 애플리케이션은 프로그램을 정상적으로 종료하기 위해 SIGTERM 신호를 캡처하고 처리하는 디렉터리를 구현해야 합니다. 실패하면 사용자는 최후의 수단으로 SIGKILL 신호를 사용해야 합니다. SIGTERM 및 SIGKILL 외에도 사용자 정의 동작을 특별히 지원하는 SIGUSR1과 같은 신호가 있습니다. 다음 코드는 nodejs에서 신호에 대한 핸들러를 등록하는 방법을 간단히 설명합니다.

process.on('SIGTERM', function() {
  console.log('shutting down...');
});

신호에 대한 자세한 내용은 작성자가 "linux kill 명령" 기사에서 언급했으며 여기서는 반복하지 않습니다.

컨테이너의 신호

Docker의 stop 및 kill 명령은 컨테이너에 신호를 보내는 데 사용됩니다. 컨테이너의 프로세스 1번만 신호를 받을 수 있다는 점에 유의하세요. 이는 매우 중요합니다!
stop 명령은 먼저 SIGTERM 신호를 보내고 애플리케이션이 정상적으로 종료될 때까지 기다립니다. 애플리케이션이 종료되지 않은 것으로 확인되면(사용자가 대기 시간을 지정할 수 있음) 또 다른 SIGKILL 신호를 보내 프로그램을 강제로 종료합니다.
kill 명령은 기본적으로 SIGKILL 신호를 보냅니다. 물론 -s 옵션을 통해 모든 신호를 지정할 수 있습니다.

아래에서는 nodejs 애플리케이션을 사용하여 컨테이너의 신호 작업 프로세스를 보여줍니다. 다음 콘텐츠로 app.js 파일을 만듭니다.

'use strict';

var http = require('http');

var server = http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
}).listen(3000, '0.0.0.0');

console.log('server started');

var signals = {
  'SIGINT': 2,
  'SIGTERM': 15
};

function shutdown(signal, value) {
  server.close(function () {
    console.log('server stopped by ' + signal);
    process.exit(128 + value);
  });
}

Object.keys(signals).forEach(function (signal) {
  process.on(signal, function () {
    shutdown(signal, signals[signal]);
  });
});

이 앱은 SIGINT 및 SIGTERM 신호에 등록된 핸들러를 사용하여 포트 3000에서 수신 대기하는 http 서버입니다. 다음으로 컨테이너에서 프로그램을 다양한 방식으로 실행할 때 신호를 처리하는 방법을 소개합니다.

애플리케이션은 컨테이너에서 프로세스 1번 역할을 합니다.

Dockerfile 파일을 생성하고 위 애플리케이션을 이미지에 패키징합니다.

FROM iojs:onbuild
COPY ./app.js ./app.js
COPY ./package.json ./package.json
EXPOSE 3000ENTRYPOINT ["node", "app"]

ENTRYPOINT 명령어 작성에 주의하세요. 이렇게 작성하면 노드가 컨테이너의 1번 프로세스를 실행합니다.

다음으로 이미지 생성:

$ docker build --no-cache -t signal-app -f Dockerfile .

그런 다음 컨테이너를 시작하고 애플리케이션을 실행합니다.

请注意 ENTRYPOINT 指令的写法,这种写法会让 node 在容器中以 1 号进程的身份运行。
接下来创建镜像:
$ docker build --no-cache -t signal-app -f Dockerfile .
然后启动容器运行应用程序:
$ docker run -it --rm -p 3000:3000 --name="my-app" signal-app
此时 node 应用在容器中的进程号为 1:

이때 컨테이너에 있는 노드 애플리케이션의 프로세스 번호는 1입니다.

Docker 컨테이너에서 신호를 잡는 방법을 알고 있나요?

이제 프로그램을 종료합니다.

$ docker container kill --signal="SIGTERM" my-app

이 시점에서 애플리케이션은 예상대로 종료됩니다.

Docker 컨테이너에서 신호를 잡는 방법을 알고 있나요?

애플리케이션은 컨테이너의 1번 프로세스가 아닙니다.

다음을 시작하는 스크립트 파일 app1.sh를 생성합니다.

#!/usr/bin/env bash
node app

그런 다음 Dockerfile1 파일을 생성합니다. 내용은 다음과 같습니다.

FROM iojs:onbuild
COPY ./app.js ./app.js
COPY ./app1.sh ./app1.sh
COPY ./package.json ./package.json
RUN chmod +x ./app1.sh
EXPOSE 3000
ENTRYPOINT ["./app1.sh"]

다음으로 이미지를 생성합니다.

$ docker build --no-cache -t signal-app1 -f Dockerfile1 .

그런 다음 컨테이너를 시작하여 애플리케이션을 실행합니다.

$ docker run -it --rm -p 3000:3000 --name="my-app1" signal-app1

이때, 컨테이너에 있는 노드 애플리케이션의 프로세스 번호가 더 이상 1이 아닙니다:

Docker 컨테이너에서 신호를 잡는 방법을 알고 있나요?

现在给 my-app1 发送 SIGTERM 信号试试,已经无法退出程序了!在这个场景中,应用程序由 bash 脚本启动,bash 作为容器中的 1 号进程收到了 SIGTERM  信号,但是它没有做出任何的响应动作。
我们可以通过:

$ docker container stop my-app1
# or
$ docker container kill --signal="SIGKILL" my-app1

退出应用,它们最终都是向容器中的 1 号进程发送了 SIGKILL 信号。很显然这不是我们期望的,我们希望程序能够收到 SIGTERM  信号优雅的退出。

在脚本中捕获信号

创建另外一个启动应用程序的脚本文件 app2.sh,内容如下:

#!/usr/bin/env bash
set -x

pid=0

# SIGUSR1-handler
my_handler() {
  echo "my_handler"
}

# SIGTERM-handler
term_handler() {
  if [ $pid -ne 0 ]; then
    kill -SIGTERM "$pid"
    wait "$pid"
  fi
  exit 143; # 128 + 15 -- SIGTERM
}
# setup handlers
# on callback, kill the last background process, which is `tail -f /dev/null` and execute the specified handler
trap 'kill ${!}; my_handler' SIGUSR1
trap 'kill ${!}; term_handler' SIGTERM

# run application
node app &
pid="$!"

# wait forever
while true
do
  tail -f /dev/null & wait ${!}
done

这个脚本文件在启动应用程序的同时可以捕获发送给它的 SIGTERM 和 SIGUSR1 信号,并为它们添加了处理程序。其中 SIGTERM 信号的处理程序就是向我们的 node 应用程序发送 SIGTERM 信号。

然后创建 Dockerfile2 文件,内容如下:

FROM iojs:onbuild
COPY ./app.js ./app.js
COPY ./app2.sh ./app2.sh
COPY ./package.json ./package.json
RUN chmod +x ./app2.sh
EXPOSE 3000
ENTRYPOINT ["./app2.sh"]

接下来创建镜像:

$ docker build --no-cache -t signal-app2 -f Dockerfile2 .

然后启动容器运行应用程序:

$ docker run -it --rm -p 3000:3000 --name="my-app2" signal-app2

此时 node 应用在容器中的进程号也不是 1,但是它却可以接收到 SIGTERM 信号并优雅的退出了:

Docker 컨테이너에서 신호를 잡는 방법을 알고 있나요?

结论

容器中的 1 号进程是非常重要的,如果它不能正确的处理相关的信号,那么应用程序退出的方式几乎总是被强制杀死而不是优雅的退出。究竟谁是 1 号进程则主要由 EntryPoint, CMD, RUN 等指令的写法决定,所以这些指令的使用是很有讲究的。

相关推荐:docker入门教程

위 내용은 Docker 컨테이너에서 신호를 잡는 방법을 알고 있나요?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 cnblogs.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제