[docker] pythonスクリプトを入れたイメージを作成して、引数で実行内容を分岐させる
2021/08追記: .pyファイルに引数を渡せるようにもした。
やりたいこと
単純にpython
スクリプトを実行したい。なおかつ複数の処理を選択的に実行したい。
pythonの実行というのは、python <script_name>.py
を行いたい、ということ。
複数の処理を選択的に実行というのは、引数などで分岐を発生させたいということ。
ローカルの環境をこれ以上汚したくないというのも理由の1つ。pyenv
やpipenv
でもいいのだが、Docker
で集約した方がわかりやすいかと思って今後はこの形にしていきたい。
Docker
はこれはこれでいつの間にかストレージ容量を食っていたりと問題があるにはあるのだが。それは置いておくとしよう。
方針
dockerのイメージを作っておいて、必要に応じてコンテナを新規起動して、処理を行ったあと、クリーンにするためコンテナを削除する、という流れで行うことにした。
コンテナは都度作成して走らせるため、コマンドの入力が面倒。ミスを減らすためにもコンテナ起動用のシェルスクリプトを作成しておくことにした。
ついでにイメージビルドもシェルスクリプトを書いておくことにした。ビルドに使ったコマンド・オプションを忘れてしまうので。 ビルドにはそんなにオプションないんだけど使用頻度が低めなので念の為。
処理の分岐はコンテナ起動用のシェルスクリプトに引数(というかpython
のスクリプトファイルのパス)を与えて行うことにした。
実装
pythonファイル自体はなんてこともないので、省略。print('Hello')
で十分。
いや、本当はいくつかのスクリプトのファイルに分けていて、それぞれのif __name__ == "__main__":
の中に処理が1つずつ入っている。
テスト環境なら、それぞれのpython
ファイルでprint(__file__)
しておけばいいだろう。
またsh
ファイルは実行権限の付与を忘れずに。chmod +x <script_name>.sh
など。
Dockerfile
Dockerfile
は以下のようにした。モジュールのインストールはrequirements.txt
から。VSCode
のリモートコンテナのDockerfile
を参考にした。
FROM python:alpine
COPY requirements.txt /tmp/pip-tmp/
RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
&& rm -rf /tmp/pip-tmp
COPY src/ /etc/mysrc/
WORKDIR /etc/mysrc/
ENV APP_LOGLEVEL=INFO
処理を行うpython
ファイルは、/etc/mysrc/
内に格納しておく。そして、/etc/mysrc/
を作業用ディレクトリにしておいて、python
で使用するロギング用に環境変数も設定しておく。イメージの実行でDEBUG
ログは不要なため。
build.sh
イメージをビルドするためのスクリプト。
オプション付与でイメージを削除する機能も付けたら長くなってしまった。
ここでのメインは、build
関数。
#! /bin/bash
# カレントディレクトリでdockerイメージをビルド。
VERSION=1.0
IMAGE_NAME=A_IMAGE_NAME
# -dオプションでコンテナ削除機能があると便利。
FLAG_D=0
while getopts d OPT
do
case $OPT in
d) FLAG_D=1
;;
esac
done
function build () {
echo "イメージをビルドします"
docker build -t ${IMAGE_NAME}:${VERSION} .
}
function delete_image () {
echo "イメージを削除します"
docker rmi ${IMAGE_NAME}:${VERSION}
}
if [ $FLAG_D -eq 1 ]; then
delete_image
else
build
fi
処理でしていることはものすごい単純で、docker build ...
をするだけ。これ1行でも実用可能。イメージさえ作れればいい。
このスクリプトを単純に実行するだけで、dockerイメージの作成は完了。
run.sh
2021/08追記: pyファイルに引数が必要な場合は以下のケースだとうまくいかないので、後述のようにちょっと変える必要がある。後述の方法だと、引数不要の場合もカバーできるので、以前の方法は不要になるが、一応記述は残しておく。
pyファイルに引数を与える必要のない場合
コンテナを実行するためのスクリプト。build.sh
よりは内容がある。
#! /bin/bash
# 以前の手法。後述の方法の方がいい。
if [ $# -ne 1 ]; then
echo "引数が1つ必要です。" 1>&2
echo "引数は、src/ディレクトリのpyファイルを指定します。" 1>&2
exit 1
fi
# srcのなかに該当ファイルがないときは何もしないで終わり。
# あれば、それを引数として、コンテナをrunする。
# src/はつけてもなくてもいい。
# 実装しようと思えば、一旦ビルドし直してリトライもできる。
if [ -f ./src/${1} ] || [ -f ./${1} ]; then
name=`basename ${1}`
docker run --rm ad-python-retriever:1.0 python ${name}
else
echo "指定されたファイルがsrcディレクトリにありません。" 1>&2
exit 2
fi
exit 0
引数(python
のスクリプトファイルの相対パスかファイル名)を1つ指定して、該当ファイルを引数として、python
コマンドをコンテナ上で行う。
入力補完の都合上、src/
から始まる相対パスでも、ファイル名単体でも動作するようにしている。コンテナは実行後は消去してしまって構わないので、--rm
オプションを付けている。
pyファイルの引数を与えもできる方法
こちらの方法の方がいいだろう。単純にbashの位置パラメータをスライスしているだけ。2つ目以降が存在しない場合は空文字になると思うので、引数が1つだけでも問題ない。
#! /bin/bash
# こちらの方がいい。
if [ $# -le 0 ]; then
echo "引数が1つ以上必要です。" 1>&2
echo "1つ目の引数は、src/ディレクトリのpyファイルを指定します。" 1>&2
echo "2つ目以降の引数は、1つ目のファイルの引数として渡されます。" 1>&2
exit 1
fi
# srcのなかに該当ファイルがないときは何もしないで終わり。
# あれば、それを引数として、コンテナをrunする。
# src/はつけてもなくてもいい。
if [ -f ./src/${1} ] || [ -f ./${1} ]; then
name=`basename ${1}`
docker run --rm ad-python-retriever:1.0 python ${name} ${@:2:${#}-1}
else
echo "指定されたファイルがsrcディレクトリにありません。" 1>&2
exit 2
fi
exit 0
変更点は、引数チェックの条件と、python実行時に${@:2:${#}-1}
を渡している点。
{parameter:offset:length}
はparameter
をoffset
の位置からlength
の長さだけ取り出してくれる記法。参考: Bash / Shell Parameter Expansion — DevDocs
また、run.sh
を書き換えた場合はイメージのリビルドを忘れずに。
使い方
簡単。今までやってきたことで、イメージのビルド
、コンテナの起動(と自動削除)
、イメージの削除
の3点が行える。
# まずはスクリプトのあるディレクトリへ移動
$ cd /path/to/myapp
# イメージのビルド
$ ./build.sh
# pythonファイルの実行(コンテナの起動・自動削除)
$ ./run.sh hello.py
# イメージの削除
$ ./build.sh -d
シンプルなpython
ファイルを環境を気にせず実行できる。やったね。
おわり
シェルスクリプト側で今回は処理を分岐させたが、pythonスクリプト側で分岐させることも簡単だ。argparse
やsys.argv
などを使えばできる。
getopts
を使ってみたかったので使ったみたが、オプション少ないなら使う必要はなかった。ともあれ大体の使い方はわかった。
イメージのビルドと削除は同じスクリプトで行う必要もなかった気がする…が、イメージ名やバージョンの管理は同一スクリプトの方が楽なので実装してみた方のがいいだろう。 ==> 使ってみると、同一スクリプトは楽だった。
これでどんなダメなスクリプトでもとりあえず作って実行してみることができる。GUIありソフトはやはり難しいが、簡単なツールはどんどん作れると思う。
以上です。
関連書籍(初心者向け)