0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Dockerコンテナ内のプロセスを終了シグナルで止める + α(:躓いた話とか)

Posted at

はじめに

今回はdockerコンテナとDockerfile内のcmd, entrypointを使ったシェル起動、終了プロセスについて学んだことを書く。
dockerやシェル、バッチ内で動かしているプロセスは、プロセス内のstopやexitみたいなコマンドで正常に終了できるものがある。

Dockerコンテナをentrypoint ["sh"]で起動した際に終了シグナルを受け取る方法と躓いた点を簡単にまとめた。

状況

Dockerコンテナを建てる際、何らかの処理をしてからメインプロセスを実行し、終了シグナルを受けとって終了したかった。

解決策

  1. enptypointでシェルスクリプトを呼び出し、シェルスクリプト側で何らかの処理を行う
  2. シェルスクリプトを呼び出すときは引数を設定できるが、変数展開のためには最初にシェルを呼び出しその引数にスクリプトファイルとそれに渡す引数をセットで渡す
  3. このままだと終了シグナルを受け取るプロセスがentrypointで指定したシェルスクリプトなので、シェルスクリプト内でtrapコマンドを使いシグナルを検出、メインプロセスを安全にkillする
Dockerfile
# base image
ARG BASE_IMAGE
FROM ${BASE_IMAGE}

# 作業ディレクトリの設定
WORKDIR /root/hoge

# 必要なパッケージのインストール
RUN apt-get update && apt-get install -y curl

# メインプロセスの準備

# 実行用のスクリプトをコンテナにコピー
COPY ./execute.sh ./
RUN chmod +x ./execute.sh

# スクリプト側で処理したい値を取得
ARG VAL
ENV VAL=${VAL}

ENTRYPOINT ["/bin/bash", "-c", "./execute.sh ${VAL}"]
execute.sh
#!/bin/bash

# 停止シグナルのハンドル
stop_handler(){
    echo "Terminating process with PID: ${MAIN_PID}..."
    kill -s TERM $MAIN_PID
    wait $MAIN_PID
    echo "Process with PID: ${MAIN_PID} has been terminated."
}

# シグナルハンドリング
trap stop_handler SIGTERM

# 引数を受け取る
VAL=${1}
echo "\$VAL: ${VAL}"

# 何らかの処理

# メインプロセス(ここではjarファイルをjavaで動かす。このmain.jarは終了プロセスを受け取ったとき正常終了する。)を実行
java -jar main.jar &
# メインプロセスのPIDを取得
MAIN_PID=$!
echo "\$MAIN_PID: ${MAIN_PID}"

# 子プロセスの終了を待つ
wait $MAIN_PID

なお、ENTRYPOINTCMDに置き換えても同じ挙動っぽい。

躓いた点

  1. シェルスクリプトに値を渡す方法
    1. シェルを指定し、スクリプトファイルを渡す
    2. スクリプトファイル自体に渡したい引数
  2. 終了シグナルのハンドラーが効かない

1-1:スクリプトファイルを実行
ごにょごにょしてからメインのプロセスを実行したいのでDockerfileからシェルスクリプトを起動する。
このときスクリプトファイルに値を渡したいが補足にも書いている通りexec形式ではシェル環境を利用できず変数が展開できないため、このようにシェルを明示した。

Dockerfile
# NG
ENTRYPOINT ["./execute.sh", "${VAL}"]

# OK
ENTRYPOINT ["/bin/bash", "-c", "./execute.sh ${VAL}"]

1-2: 引数の扱い方
このようにスクリプトファイルに渡したい値を切り離して書いてしまうと"/bin/bash"に対しての第三引数となってしまう。

# NG
ENTRYPOINT ["/bin/bash", "-c", "./execute.sh", "${VAL}"]

2: シグナルがスクリプトファイルに対して届かない問題
前提としてdockerでコンテナに終了シグナルを送るコマンドdocker compose stopは、コンテナのPID:1のプロセスに対して終了シグナル(TERM)を送り、デフォルトでその10秒後に強制終了(KILL)する。

shell形式やシェルを指定したexec形式では、シェルを親プロセスとしてその上にメインプロセスを子プロセスとして動かす。
そのためPID:1が/bin/bash ./execute.sh val_dataのようにシェルになる。

今回正常に終了させたいプロセスはmain.jarだが、これはbash -c execute.shの子プロセス。
スクリプトファイル内ではハンドラーを定義し、スクリプトファイルに対して終了シグナルが届くとその子プロセスであるmain.jarに終了プロセスを送る形にする。

ここで注意なのがENTRYPOINTで指定したシェルとexecute.shのシェバンが一致している必要があること。

補足

shell形式とexec形式

ENTRYPOINTやCMDコマンドには書き方が二種類ある

# shell形式
ENTRYPOINT command arg1 arg2
CMD command arg1 arg2

# exec形式
ENTRYPOINT [ "command", "arg1", "arg2" ]
CMD [ "command", "arg1", "arg2" ]

shell形式ではシェル環境の上で実行される。
シェルの変数を展開して使用したければshell形式か、exec形式で明示的にshell環境を指定する。

# shell形式 シェル環境の上なのでcommandだけでなくエイリアスも使える
ENTRYPOINT command ${val}
CMD command${val}

# exec形式 shell環境を指定
ENTRYPOINT [ "/bin/sh", "-c", "command", "arg1", "arg2" ]
CMD [ "/bin/sh", "-c", "arg1", "arg2" ]

ENTRYPOINTとCMD

ENTRYPOINTCMDはそれぞれ一回ずつしか使えず2回使った場合は最後の行のみ有効。

  • CMDENTRYPOINTが定義されていないときのデフォルトの挙動で、ENTRYPOINTが定義されているときはENTRYPOINTの引数に追加される
    また、コンテナ起動時にコマンドラインから置き換えることができる
  • ENTRYPOINTはコンテナ起動時に必ず実行される挙動を定義しておく

ENTRYPOINTで基本的に変わらなさそうな起動コマンドを定義し、CMDでその引数を柔軟に決めるということらしい。

参考

PID1のプロセス: Docker上のサーバーを強制終了ではなく正常に終了する PID1になるプロセスとは?を理解してみた
ENTRYPOINTでシェル変数の展開: Dockerfile リファレンス#entrypoint
ENTRYPOINTとCMDの使い分け: Docker CMDとENTRYPOINTの使い方

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?