【Airflow on Kubernetes】DockerイメージのbuildとPodのdeployの仕組みについて

## 概要
Airflowでは、Kubernetes用のDockerイメージの作成スクリプトと、Podのdeploy用のスクリプトが用意されている。
**GitHUB:** https://github.com/apache/airflow

これらのスクリプトが実際にどのような処理を行っているのかを調べた。

## Version
- [airflow-1.10.3](https://github.com/apache/airflow/releases/tag/1.10.3)

## 目次
- [【Airflow on Kubernetes】目次](https://qiita.com/Esfahan/items/cd6b3dc0f724b84361d2)

## 関連記事
- [Kubernetesハンズオン目次](https://qiita.com/Esfahan/items/4fcf51622fed20dffb88)

## おおまかな処理の流れ
処理の流れを大きく分けると、以下の２つに分けられる。

1. Dockerイメージをbuildする
    1. [scripts/ci/kubernetes/docker/build.sh](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/docker/build.sh)
1. Podをdeployをする
    1. [scripts/ci/kubernetes/kube/deploy.sh](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/kube/deploy.sh)

以降で、それぞれの詳細な処理について追っていく。

## Dockerイメージをbuildする
Dockerイメージをbuildするための、[build.sh](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/docker/build.sh)の処理内容を追う。

実際に実行させる場合は、以下の様にする。

```shell-sesison
$ sudo ./scripts/ci/kubernetes/docker/build.sh
```

### build.shを実行してairflowのDockerイメージを作成する
[build.sh#L45](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/docker/build.sh#L45)で[compile.sh](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/docker/compile.sh)が実行される。

```bash:scripts/ci/kubernetes/docker/build.sh#L45
docker run -it --rm -v ${AIRFLOW_ROOT}:/airflow \
    -w /airflow ${PYTHON_DOCKER_IMAGE} ./scripts/ci/kubernetes/docker/compile.sh
```

### build.shの中でcompile.shが実行される
[compile.sh#L32](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/docker/compile.sh#L32)でsetup.pyが実行されると、ホストOSに`~/airflow/dist/`というディレクトリが生成され、その下に`apache-airflow-2.0.0.dev0.tar.gz`というファイルが生成される。
これが、airflowのソースコードとなるものである。

```bash:scripts/ci/kubernetes/docker/compile.sh#L32
# apache-airflow-2.0.0.dev0.tar.gzが生成される
python setup.py compile_assets sdist -q
```

compile.shでairflowのtarファイルが作成されると、[build.sh#L50](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/docker/build.sh#L50)で、先程生成された`apache-airflow-2.0.0.dev0.tar.gz`が、`scripts/ci/kubernetes/docker/airflow.tar.gz`にリネームしてコピーされる。

```bash:scripts/ci/kubernetes/docker/build.sh#L50
cp $AIRFLOW_ROOT/dist/*.tar.gz ${DIRNAME}/airflow.tar.gz
```

### build.shの中でairflowのDockerイメージをbuildする
[build.sh#L51](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/docker/build.sh#L51)で、`docker build`が実行される。

```sh:build.sh#L51
cd $DIRNAME && docker build --pull $DIRNAME --tag=${IMAGE}:${TAG}
```

このときに、この[Dockerfile](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/docker/Dockerfile)が読み込まれる。

#### Dockerfileの処理
[Dockerfile#L45](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/docker/Dockerfile#L45)で、先程生成されたホストOSの`scripts/ci/kubernetes/docker/airflow.tar.gz`がコンテナ内にCOPYされる。

```Dockerfile:Dockerfile#L45
# コンテナ内にCOPY
COPY airflow.tar.gz /tmp/airflow.tar.gz
```

### Dockerfileの中でairflow-test-env-init.shが実行される
次に[Dockerfile#L48](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/docker/Dockerfile#L48)で、[airflow-test-env-init.sh](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/docker/airflow-test-env-init.sh)が実行される。

```Dockerfile:Dockerfile#L48
COPY airflow-test-env-init.sh /tmp/airflow-test-env-init.sh
```

すると、[airflow-test-env-init.sh#L23](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/docker/airflow-test-env-init.sh#L23)で、コンテナ内の`/usr/local/lib/python3.6/site-packages/airflow/example_dags/`を、PersistentVolumeのmount先である`/root/airflow/dags/`にコピーする。

```sh:airflow-test-env-init.sh#L23
# example_dags/とcontrib/example_dags/をPersistentVolumeにコピー
cd /usr/local/lib/python3.6/site-packages/airflow && \
cp -R example_dags/* /root/airflow/dags/ && \
cp -R contrib/example_dags/example_kubernetes_*.py /root/airflow/dags/ && \
cp -a contrib/example_dags/libs /root/airflow/dags/ && \
```

build.shの処理はこれで完了。

## Deploy
Podをdeployするための、[deploy.sh](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/kube/deploy.sh)の処理内容を追う。

実際に実行させる場合は、以下の様にする。
`-d`オプションで、dags_folderの追加方法を`persistent_mode`か`git_mode`のどちらかから選ぶ。

```
  usage: ./scripts/ci/kubernetes/kube/deploy.sh options
  OPTIONS:
    -d Use PersistentVolume or GitSync for dags_folder. Available options are "persistent_mode" or "git_mode"
```

実行

```shell-sesison
$ sudo ./scripts/ci/kubernetes/kube/deploy.sh -d {persistent_mode,git_mode}
```

### deploy.shを実行
[deploy.sh](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/kube/deploy.sh)を実行して、Podをdeployする。

実行されると、[scripts/ci/kubernetes/kube/templates](https://github.com/apache/airflow/tree/1.10.3/scripts/ci/kubernetes/kube/templates)以下のmanifestのテンプレートファイルの必要箇所が置換されて、`scripts/ci/kubernetes/kube/build`の下に生成される。
生成されたmanifestファイルを使って`kubectl apply`が実行される。

```shell-session
$ ls -1 scripts/ci/kubernetes/kube/templates/
airflow.template.yaml
configmaps.template.yaml
init_git_sync.template.yaml
```

### InitContainer
[ci/kubernetes/kube/templates/airflow.template.yaml](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/kube/templates/airflow.template.yaml)を元にして生成された、scripts/ci/kubernetes/kube/build/airflow.yamlをapplyすると、[airflow.yaml#L44](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/kube/templates/airflow.template.yaml#L44)で定義されているInitContainerが実行され、deployの際にInitContainerでも[airflow-test-env-init.sh](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/docker/airflow-test-env-init.sh)が実行される。

ここで、コンテナ内の`/usr/local/lib/python3.6/site-packages/airflow/example_dags/`が、PodのPersistentVolumeのmount先である`/root/airflow/dags/`にコピーされる。

[airflow.template.yaml#L44](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/kube/templates/airflow.template.yaml#L44)

```yaml:airflow.template.yaml#一部抜粋
    spec:
      initContainers:
      - name: "init"
        image: {{AIRFLOW_IMAGE}}:{{AIRFLOW_TAG}}
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: airflow-configmap
          mountPath: /root/airflow/airflow.cfg
          subPath: airflow.cfg
        - name: {{INIT_DAGS_VOLUME_NAME}}
          mountPath: /root/airflow/dags
        - name: test-volume
          mountPath: /root/test_volume
        env:
        - name: SQL_ALCHEMY_CONN
          valueFrom:
            secretKeyRef:
              name: airflow-secrets
              key: sql_alchemy_conn
        command:
          - "bash"
        args:
          - "-cx"
          - "./tmp/airflow-test-env-init.sh"
```

deploy.shの処理はこれで完了。

### dagsの確認
Pod内を確認。/root/airflow/dags/だけでなく、/usr/local/lib/python3.6/site-packages/airflow/example_dags/にもファイルがあることがわかる。


Pod内に入って確認してみる。

```sh
$ sudo kubectl exec -it airflow-xxxxxxxxxx-xxxxx /bin/bash
```

Pod内の/usr/local/lib/python3.6/site-packages/airflow/example_dags/を確認。

```shell-session
root@airflow-xxxxxxxxxx-xxxxx:/# ls -1 /usr/local/lib/python3.6/site-packages/airflow/example_dags/
__init__.py
__pycache__
docker_copy_data.py
example_bash_operator.py
example_branch_operator.py
example_branch_python_dop_operator_3.py
example_docker_operator.py
example_http_operator.py
example_latest_only.py
example_latest_only_with_trigger.py
example_passing_params_via_test_command.py
example_pig_operator.py
example_python_operator.py
example_short_circuit_operator.py
example_skip_dag.py
example_subdag_operator.py
example_trigger_controller_dag.py
example_trigger_target_dag.py
example_xcom.py
subdags
test_utils.py
tutorial.py
```

Pod内の/root/airflow/dags/以下を確認。

```shell-session
root@airflow-xxxxxxxxxx-xxxxx:/# ls -1 /root/airflow/dags/
__init__.py
__pycache__
docker_copy_data.py
example_bash_operator.py
example_branch_operator.py
example_branch_python_dop_operator_3.py
example_docker_operator.py
example_http_operator.py
example_kubernetes_executor.py
example_kubernetes_executor_config.py
example_kubernetes_operator.py
example_latest_only.py
example_latest_only_with_trigger.py
example_passing_params_via_test_command.py
example_pig_operator.py
example_python_operator.py
example_short_circuit_operator.py
example_skip_dag.py
example_subdag_operator.py
example_trigger_controller_dag.py
example_trigger_target_dag.py
example_xcom.py
libs
subdags
test_utils.py
tutorial.py
```

どちらにもdagファイルがあるが、/root/airflow/dags/のほうがファイルが多い。
これは[airflow-test-env-init.sh](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/docker/airflow-test-env-init.sh)を見ると分かるが、`example_dags/`だけでなく、`contrib/example_dags/`以下のファイルもコピーされているためである。

```sh:airflow-test-env-init.sh#L23
cp -R example_dags/* /root/airflow/dags/ && \
cp -R contrib/example_dags/example_kubernetes_*.py /root/airflow/dags/ && \
cp -a contrib/example_dags/libs /root/airflow/dags/ && \
```

## Podのlogを確認する方法
Pod名とコンテナ名を指定する。

```console
$ sudo kubectl logs -f pod/airflow--xxxxxxxxxx-xxxxx -c {webserver,scheduler}
```

## Hackする
### 同期されるdagファイルを変更する場合
[airflow-test-env-init.sh#L23](https://github.com/apache/airflow/blob/1.10.3/scripts/ci/kubernetes/docker/airflow-test-env-init.sh#L23)
の以下の箇所を変更することで、同期されるdagファイルを変更することができる。

```sh:airflow-test-env-init.sh#L23
cp -R example_dags/* /root/airflow/dags/ && \
cp -R contrib/example_dags/example_kubernetes_*.py /root/airflow/dags/ && \
cp -a contrib/example_dags/libs /root/airflow/dags/ && \
```

### PersistentVolumeのmount先を変更する方法
以下２つのvolumeの定義を変更する。

- **airflowのファイル群のmount先**
    - scripts/ci/kubernetes/kube/volumes.yaml
- **postgresのデータのmount先**
    - scripts/ci/kubernetes/kube/postgres.yaml

### メタデータDBをデフォルトのPostgreSQLからMySQLに変更する
[【Airflow on Kubernetes】メタデータDBをデフォルトのPostgreSQLからMySQLに変更する](https://qiita.com/Esfahan/items/68da6ba314aca2c21f73)

## ログの確認方法
deploy時に問題が起きた際は、以下のように各種コンテナのログ確認する。

```console
$ sudo kubectl logs -f airflow-xxxxxxxxxx-xxxxx init
$ sudo kubectl logs -f airflow-xxxxxxxxxx-xxxxx webserver
$ sudo kubectl logs -f airflow-xxxxxxxxxx-xxxxx scheduler
```

#### 参考
- [【KubernetesのPersistentVolume】その１- 外部ストレージをPodにmountする](https://qiita.com/Esfahan/items/7ad7695ea78c7630239a)
- [【KubernetesのPersistentVolume】その２- Dynamic Provisionerを使ってNFSサーバーをPodにmountする](https://qiita.com/Esfahan/items/68e2d97545091cb6d0ac)