Airflow公式のDocker Imageを使って、Airflowクラスターを構築しようとした際に、Docker Entrypointの挙動に少しハマったので記事にします。
遭遇した事象
Airflowでは、クラスタとバックエンドDBの接続後にdb initコマンドを叩いてスキーマを作る必要があります。 これはDBとの接続設定さえされていれば、SchedulerやWebserver、どのコンポーネントから叩いても良いです。
airflow db init
また、デフォルトのAuth設定ではWebserverにアクセスするよりも前に、ユーザをパスワードとともに作っておく必要があります。
# create an admin user
airflow users create \
--username admin \
--firstname Peter \
--lastname Parker \
--role Admin \
--email spiderman@superhero.org
そのため、クラスタ立ち上げ時にこの2つの初期化コマンドを実行したいと思って、docker-compose.ymlに以下のようにコマンドを書いてました。
services:
airflow-scheduler:
image: apache/airflow
...
command: >
sh -c '
airflow db init &&
airflow users create \
--username admin \
--firstname Peter \
--lastname Parker \
--role Admin \
--email spiderman@superhero.org
'
しかし上のymlだと以下のようなエラーになります。
airflow command error: argument GROUP_OR_COMMAND: invalid choice: 'sh' (choose from 'celery',
'cheat-sheet', 'config', 'connections', 'dags', 'db', 'info', 'jobs', 'kerberos',
'kubernetes', 'plugins', 'pools', 'providers', 'roles', 'rotate-fernet-key', 'scheduler',
'standalone', 'sync-perm', 'tasks', 'triggerer', 'users', 'variables', 'version',
'webserver'), see help above.
usage: airflow [-h] GROUP_OR_COMMAND ...
...
原因
エラーを見るに、どうやらcommandに渡した文字列がairflowコマンドの引数として渡っているようです。
これには心当たりがあって、そもそもAirflow公式が提供しているdocker-compose.ymlを見ると、
...
services:
airflow-webserver:
...
command: webserver
airflow-scheduler:
...
command: scheduler
airflow-worker:
...
command: celery worker
...
とあり、「なんでairflowコマンド省略できるん?」と前から疑問でした。
というわけで、airflow imageのDockerfileを見ると、
ENTRYPOINT ["/usr/bin/dumb-init", "--", "/entrypoint"]
とENTRYPOINTが定義してあり、コンテナ立ち上げ時に/entrypointが実行されるであろうことが見て取れます。
ENTRYPOINTとCMDの両方が定義されている場合、CMDはENTRYPOINTに引数として渡されるので、docker-compose.ymlでcommandとして定義した文字列が/entrypointに引数として渡っていることが判明しました。
(ENTRYPOINTとCMDについてはこちらの記事が参考になりました。 DockerfileのCMDとENTRYPOINTを読み解く(2/3) — CMD命令とENTRYPOINT命令の基礎 #docker #dockerfile)
じゃあ次はということで、/entrypointの中身を見てみると、
exec "airflow" "${@}"
と/entrypointに対する引数が全てairflowコマンドに渡されていることがわかります。
また、第1引数に’bash’もしくは’python’が指定された際には、/bin/bashかpythonコマンドにたいして引数が渡されるようになっています。
AIRFLOW_COMMAND="${1:-}"
function exec_to_bash_or_python_command_if_specified() {
# If one of the commands: 'bash', 'python' is used, either run appropriate
# command with exec
if [[ ${AIRFLOW_COMMAND} == "bash" ]]; then
shift
exec "/bin/bash" "${@}"
elif [[ ${AIRFLOW_COMMAND} == "python" ]]; then
shift
exec "python" "${@}"
fi
}
exec_to_bash_or_python_command_if_specified "${@}"
また、第1引数に’airflow’が指定された際は、shiftして第2引数以降をairflowコマンドに渡すようになっています。
AIRFLOW_COMMAND="${1:-}"
# Remove "airflow" if it is specified as airflow command
# This way both command types work the same way:
#
# docker run IMAGE airflow webserver
# docker run IMAGE webserver
#
if [[ ${AIRFLOW_COMMAND} == "airflow" ]]; then
AIRFLOW_COMMAND="${2:-}"
shift
fi
exec "airflow" "${@}"
そのため、コンテナ起動時にコマンドとしてairflow webserverで指定しようが、単にwebserverのみで指定しようが同じ動きをするようになってます。
解決策
commandに’sh -c’を指定していると、sh -cごとairflowコマンドの引数として渡されてしまうので、‘bash -c’を指定するようにしたら解決しました。
services:
airflow-scheduler:
image: apache/airflow
...
command: >
bash -c '
airflow db init &&
airflow users create \
--username admin \
--firstname Peter \
--lastname Parker \
--role Admin \
--email spiderman@superhero.org
'
おわりに
公式のEntrypointのページ見ればすぐわかったねってお話でした。