Airflow Docker ImageのEntrypointの挙動


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のページ見ればすぐわかったねってお話でした。