Docker 単純なpythonスクリプトを選択的に実行する

[docker] pythonスクリプトを入れたイメージを作成して、引数で実行内容を分岐させる

2021/08追記: .pyファイルに引数を渡せるようにもした。

やりたいこと

単純にpythonスクリプトを実行したい。なおかつ複数の処理を選択的に実行したい。

pythonの実行というのは、python <script_name>.pyを行いたい、ということ。

複数の処理を選択的に実行というのは、引数などで分岐を発生させたいということ。

ローカルの環境をこれ以上汚したくないというのも理由の1つ。pyenvpipenvでもいいのだが、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}parameteroffsetの位置から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スクリプト側で分岐させることも簡単だ。argparsesys.argvなどを使えばできる。

getoptsを使ってみたかったので使ったみたが、オプション少ないなら使う必要はなかった。ともあれ大体の使い方はわかった。

イメージのビルドと削除は同じスクリプトで行う必要もなかった気がする…が、イメージ名やバージョンの管理は同一スクリプトの方が楽なので実装してみた方のがいいだろう。 ==> 使ってみると、同一スクリプトは楽だった。

これでどんなダメなスクリプトでもとりあえず作って実行してみることができる。GUIありソフトはやはり難しいが、簡単なツールはどんどん作れると思う。

以上です。


関連書籍(初心者向け)

https://amzn.to/38F6378
https://amzn.to/2Q3phx4
https://amzn.to/3eCo0r1
タイトルとURLをコピーしました