AirflowのタスクログをS3に保存する方法

# はじめに

Airflowのタスクログの保存先は、簡単に S3 や Google Cloud Storage のような外部ストレージに設定することができます。本番環境では、こうした外部ストレージを活用することで、ディスク容量不足による障害を防ぐ予防効果もあります。私はこの理由で、Airflow をクラウド上でデプロイする際に、タスクログを必ず外部ストレージに保存しています。

https://qiita.com/hankehly/items/1f02a34740276d1b8f0f#task-logging

この記事では、公式 [docker-compose.yaml](https://airflow.apache.org/docs/apache-airflow/stable/docker-compose.yaml) で開発環境を立ち上げて、[remote logging](https://airflow.apache.org/docs/apache-airflow/stable/logging-monitoring/logging-tasks.html) 機能で S3 にタスクログを保存するやり方を紹介します！

# 手順

### 環境構築

以下のように、公式 [docker-compose.yaml](https://airflow.apache.org/docs/apache-airflow/stable/docker-compose.yaml) をダウンロードして `docker compose up` で環境を立ち上げます。

```shell
mkdir -p ./dags ./logs ./plugins      # Linux のみ
echo -e "AIRFLOW_UID=$(id -u)" > .env # Linux のみ
wget https://airflow.apache.org/docs/apache-airflow/stable/docker-compose.yaml
docker compose up
```

環境が立ち上がったら `http://localhost:8080` にあるウェブサーバーにでログインします。ユーザー名もパスワードも `airflow` です。問題なければ、以下のような一覧画面が表示されます。
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f323532353537392f39373661336635322d303539662d653034662d313436352d3534323565353862323631612e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2525579/babf12b8-0709-6b8f-48f8-c080b5fa0617.png)

### Airflowの設定

Airflowのタスクログを S3 に保存するために、以下の設定を変更する必要があります。

| 設定項目 | 説明 |
|:-|:-|
| [remote_logging](https://airflow.apache.org/docs/apache-airflow/stable/configurations-ref.html#remote-logging) | (True/False) タスクログを外部ストレージに保存するかしないか |
| [remote_base_log_folder](https://airflow.apache.org/docs/apache-airflow/stable/configurations-ref.html#remote-base-log-folder) | 保存先の S3 URL（`s3://`から始まる必要がある） |
| [remote_log_conn_id](https://airflow.apache.org/docs/apache-airflow/stable/configurations-ref.html#remote-log-conn-id) | 外部ストレージにアクセスするための [Connection ID](https://qiita.com/hankehly/items/1f02a34740276d1b8f0f#connections--hooks) |

Airflowの設定変更は、主に環境変数か `airflow.cfg` の修正で行います。docker-compose を使った開発環境では、環境変数を設定する方が楽です。本番環境では（秘密情報を除いて）変更を加えた `airflow.cfg` をコンテナイメージに同梱させたりします。（どっちを選ぶかぶっちゃけ好みです。）

今回は環境変数で対応するので `docker-compose.yaml` と同じディレクトリーに `.env` ファイルを作り、上記3つの設定項目を指定します。
```shell
touch .env
echo '.env' >> .gitignore
```
```shell:.env
AIRFLOW__LOGGING__REMOTE_LOGGING=True
AIRFLOW__LOGGING__REMOTE_BASE_LOG_FOLDER="s3://examplebucket/airflow_task_logs/"
AIRFLOW__LOGGING__REMOTE_LOG_CONN_ID=aws_default
```

`.env` ファイルに書いた環境変数は `docker compose` 実行時に参照されますが、コンテナの中に適用されないため、`docker-compose.yaml` 側の修正も必要です。

```diff_yaml:docker-compose.yaml
x-airflow-common:
  # ...
  environment:
    # ...
+   AIRFLOW__LOGGING__REMOTE_LOGGING: "${AIRFLOW__LOGGING__REMOTE_LOGGING:-}"
+   AIRFLOW__LOGGING__REMOTE_BASE_LOG_FOLDER: "${AIRFLOW__LOGGING__REMOTE_BASE_LOG_FOLDER:-}"
+   AIRFLOW__LOGGING__REMOTE_LOG_CONN_ID: "${AIRFLOW__LOGGING__REMOTE_LOG_CONN_ID:-}"
```

:::note info
**なぜ `docker-compose.yaml` に直接書かないの？**
開発者によって変わる設定や、開発中にon/offを切り替えたい設定は、`docker-compose.yaml` の外から読み込む形で管理した方がいいです。`.env` ファイルは `.gitignore` に追加しているので、開発者AとBが違う設定を使っていてもコンフリクトが起きません。自分の環境だけ、一部の設定をコメントアウトするような対応も不要になります。
:::

`airflow.cfg` を修正する場合はこうです（今回不要）
```ini:airflow.cfg
[logging]
remote_logging = True
remote_base_log_folder = "s3://examplebucket/airflow_task_logs/"
remote_log_conn_id = aws_default
```

### AWSとの認証

S3にタスクログを送信／取得するためには、以下の権限をウェブサーバー、スケジューラー、ワーカーに割り当てます。

<details>
<summary>送受信に必要な権限（IAMポリシー）</summary>

```json:airflow_s3_logging_policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket"
      ],
      "Resource": "arn:aws:s3:::examplebucket"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject"
      ],
      "Resource": "arn:aws:s3:::examplebucket/*"
    }
  ]
}
```

</details>

ただ、今回は開発環境の話なので、IAMポリシーを使いません。代わりに、ローカル環境の [AWS CLI の名前付きプロファイル](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-profiles.html)で**個人の AWS ユーザーとして認証します**。

これを実現するためには、ホスト環境の `$HOME/.aws` ディレクトリーをコンテナの `/home/airflow/.aws` にマッピングする必要があるため、以下のように[バインドマウント](https://matsuand.github.io/docs.docker.jp.onthefly/storage/bind-mounts)で繋ぎます。そして、環境変数で[空っぽな AWS Connection を定義する](https://airflow.apache.org/docs/apache-airflow-providers-amazon/stable/connections/aws.html#examples)ことで、Airflowで使われるデフォルトの AWS Connectionをホスト環境の `default` AWS プロファイルにマッピングします。

```diff_yaml:docker-compose.yaml
x-airflow-common:
  # ...
  environment:
    # ...
+   AIRFLOW_CONN_AWS_DEFAULT: "aws://"
  volumes:
    # ...
+   - $HOME/.aws/:/home/airflow/.aws/
```

### 動作確認

`docker-compose.yaml` を変更したので `docker compose down` と `docker compose up` を順番に実行して環境を再起動します。

ウェブサーバーにログインして、任意の DAG を実行します。Graph View からいずれかのタスクのログ画面を開くと、最初の行に「Reading remote log from」のようなメッセージが出てくるはずです。今見ているログが S3 上のファイルから読み込まれているわけです！
![ui-task-log.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2525579/5e272e92-2bbf-f4cf-32ca-61c22bf79e41.png)
さらに `AIRFLOW__LOGGING__REMOTE_BASE_LOG_FOLDER` に指定した S3 URL をブラウザーで開くと、タスクログのフォルダー構成が確認できます。
![s3-task-log.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2525579/634277e8-003c-3d60-67a7-5b3ba43203f7.png)

# 終わりに

この記事で、公式 [docker-compose.yaml](https://airflow.apache.org/docs/apache-airflow/stable/docker-compose.yaml) でサクッと開発環境を作り、タスクログの保存先を S3 に変えました。AWS の認証は、開発環境の [AWS CLI の名前付きプロファイル](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-profiles.html)で行い、IAMポリシーの作成は不要でした。（本番環境では上記のIAMポリシーを使ってください。）

Airflowにはまだまだ面白い機能がたくさんのあるので、もっと詳しく知りたい方、以下の記事をおすすめします！

https://qiita.com/hankehly/items/1f02a34740276d1b8f0f