Windows11でApache Airflowを起動するまで

恥ずかしながら最近になって知ったワークフローエンジン Apache Airflow。
AWSのマネージドサービスとしての提供もされるなどそれなりの注目度のOSS製品ということで、どんなものか見てみたくなりました。

※あらかじめ申し上げておきますが、ここに示す手順は「参考URL」に示した記事に示された内容を一部端折りながらもほぼそのままにつなぎ合わせたものになっています。こうした記事を執筆してくださった皆さま、ありがとうございます。そしてオリジナリティのない、個人用備忘録みたいな投稿でごめんなさい。。

# 構成

今回はこの Airflow v2.3.2 を次のような構成で動かしてみます：

* ホストマシン：Surface Go 3
* ホストOS：Windows 11 Home 
* 仮想化基盤：Windows Subsystem for Linux 2（WSL2）
* ゲストOS：Alpine Linux Minimal root filesystem v3.16.0 x86_64
* コンテナ基盤：Docker v20.10.16
* オーケストレーションツール：docker-compose v1.29.2

ホストOSを動かすマシンについては本当はCPUもメモリも強めがよいのだろうなと思いますが、無い袖は振れないわけで今回はSurface Go 3で挑戦です。

Airflowの公式ガイドでは docker-compose を利用して構築する方法は本番利用に耐えるものではないと警告されています。
今回の構築もあくまでもお勉強が目的です。
Dockerとdocker-composeのおかげで、Windows OS、それもリソースの限られたマシンで動作確認をすることができます。

# 参考URL

構築にあたって次のWebページを参考にさせていただきました：

* https://zenn.dev/ttani/articles/wsl2-docker-setup
* https://zenn.dev/ignorant/articles/wsl2_alpine_docker
* https://news.line.me/articles/oa-ascii/1516b3178859
* https://atmarkit.itmedia.co.jp/flinux/rensai/linuxtips/310delvcon.html
* https://text.superbrothers.dev/200328-how-to-avoid-pid-1-problem-in-kubernetes/
* https://airflow.apache.org/docs/apache-airflow/stable/start/docker.html

# WSL2のインストール

まずは一番の土台となるWSL2のインストールから。

「Windowsの機能の有効化または無効化」で「Linux用Windowsサブシステム」と「仮想マシンプラットフォーム」に☑をして「OK」。
途中OS再起動を求められるので従います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50571/ab1885e2-df26-04dd-cec3-46acc80b3aaa.png)

続いてMicrosoft社のWebサイトでLinuxカーネル更新プログラムをダウンロードし、インストールします。

[ダウンロード元ページはこちら](https://docs.microsoft.com/ja-jp/windows/wsl/install-manual#step-4---download-the-linux-kernel-update-package)。
これはPowerShellで `wsl --update` するのとと同じことだろうと考えています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50571/598c5357-b06d-1617-f0b8-43d49a7977ac.png)

更新プログラムのインストールが終わったらPowerShellを起動。
次のコマンドを実行します：

```ps
wsl --set-default-version 2
wsl --status
```

WSLで仮想マシンを作成するときのデフォルトのバージョンとして `2` を指定。
WSLのインストールはもちろん上記の設定が正しく行われているかを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50571/b1426a76-618a-bea3-3f58-1a04e2df772a.png)

いい感じ。
これでWSL2のインストールは完了です。

# Alpine Linuxのインストール

続いてWSL2の上にAlpine Linuxを導入します。
このディストリビューションはとにかく軽量なのがうれしいポイント。
このあと構築を進めていくと結局まあまあのサイズになってしまうのですが、それでもなるべく小さなサイズに収めたい。
目的はLinuxの上にDockerを導入しそのDockerでAirflowを動かすこと。その目的に寄与しないものは何も要りません。
──前置きはこのへんで。

Alpine Linuxの[ダウンロードページ](https://alpinelinux.org/downloads/)からMinimal root filesystem（Mini Root Filesystem）エディションの x86_64アーキテクチャ向けイメージ `alpine-minirootfs-3.16.0-x86_64.tar.gz` をダウンロードします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50571/aa30af8c-3632-e53c-94e5-b0760b134116.png)

仮想マシンイメージが配置されるフォルダを用意（今回は `.\devel\wsl\alpine` ）し、ダウンロードしたイメージをWSLにインポートします：

```ps
wsl --import alpine .\devel\wsl\alpine .\Downloads\alpine-minirootfs-3.16.0-x86_64.tar.gz
wsl -l -v
wsl --status
```

`wsl --status` の結果を見ると「既定の配布」（デフォルトのディストリビューション）に `alpine` と表示されています：

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50571/51bd1bf7-991c-5276-2820-2d46ab4390e8.png)

Alpine Linuxを起動してみます：

```ps
	PS C:\Users\mizuk> wsl -l -v
	  NAME      STATE           VERSION
	* alpine    Stopped         2
	PS C:\Users\mizuk> wsl
	TABLET-07LV9VBK:/mnt/c/Users/mizuk# ls -la devel/wsl/alpine/
	total 78848
	drwxrwxrwx    1 root     root          4096 Jul  5 11:38 .
	drwxrwxrwx    1 root     root          4096 Jul  5 11:37 ..
	-rwxrwxrwx    1 root     root      80740352 Jul  5 11:44 ext4.vhdx
	TABLET-07LV9VBK:/mnt/c/Users/mizuk# exit
	PS C:\Users\mizuk> wsl -l -v
	  NAME      STATE           VERSION
	* alpine    Running         2
	PS C:\Users\mizuk> wsl -t alpine
	PS C:\Users\mizuk> wsl -l -v
	  NAME      STATE           VERSION
	* alpine    Stopped         2
```

# Dockerのインストール

Dockerをインストールします。
`wsl` コマンドでAlpine Linuxを再度起動＆ログインしたあと、
まずは前提として必要なパッケージや設定を施していきます。今回は作業用のLinuxユーザーとして `penguin` を追加して `sudo` できるようにしています：

```sh
apk add --no-cache tzdata sudo
install -Dm 644 /usr/share/zoneinfo/Asia/Tokyo /etc/zoneinfo/Asia/Tokyo
export TZ='Asia/Tokyo'
"export TZ='$TZ'" >> /etc/profile.d/timezone.sh
apk del tzdata
echo '%wheel ALL=(ALL) ALL' > /etc/sudoers.d/wheel
apk add --no-cache bash bash-completion
adduser penguin -s /bin/bash
adduser penguin wheel
```

いよいよDockerをインストールします。ついでにDockerをサービス（デーモン）として起動させるためOpenRCもインストールします（`default=penguin` のところは作業用ユーザー名に合わせて変えてください）：

```sh
apk add --no-cache docker openrc
addgroup penguin docker
cat <<EOF > /etc/wsl.conf
[user]
default=penguin

[interop]
appendWindowsPath=false

[boot]
command = "/usr/bin/env -i /usr/bin/unshare --pid --mount-proc --fork --propagation private -- sh -c 'exec /sbin/init'"
EOF
exit
wsl -t alpine
wsl
whoami
```

最後にAlpine Linuxを再起動してログインユーザー名を確認しています。

# OpenRCによるサービス化（デーモン化）

DockerやOpenRCのインストールが済んだので、Alpine Linuxを起動するたびに自動でDockerエンジンが起動するように設定します。

まずはOpenRCでDockerを起動できることを確認し：

```sh
sudo su -
rc-status
rc-service docker start
rc-status
```

しかるのち自動起動の設定を行います：

```sh
rc-update add docker default
rc-status
rc-update show default
```

念には念を入れてという感じですが、余計なプロセスにリソースを食われないように、不要なTTYを停止します：

```sh
vi /etc/inittab
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50571/05f0e095-e53d-1af3-bcb5-a5b017b9bfd5.png)

viを `:wd` で抜け編集を終えたら、Alpine Linuxからログアウトし再起動します。

# PID1問題対策

きちんと理解していないのですが、DockerにはPID 1問題というものがあり、Dockerで起動したプロセスをシグナルで制御できないリスク（プロセス上でうごくコマンドにより制御できたりできなかったりする）があるので、これにあらかじめ対処します。

```ps
sudo su -
echo '%wheel ALL=(ALL) NOPASSWD: /usr/bin/nsenter' >> /etc/sudoers.d/wheel
vi /etc/profile.d/wsl-init.sh
```

`wsl-init.sh` には以下の内容を書き込みます：

```
#!/bin/bash

# Get PID of /sbin/init
sleep 1
pid="$(ps -o pid,args | awk '$2 ~ /\/sbin\/init/ {print $1}')"

# Run WSL service script
if [ "$pid" -ne 1 ]; then
  # Export ENV variables
  if [ "$USER" != "root" ]; then
    [ -f "$HOME/.openrc.env" ] && rm "$HOME/.openrc.env"
    export > "$HOME/.openrc.env"
  fi

  echo "Entering /sbin/init PID: $pid"
  exec sudo /usr/bin/nsenter -p -m -t "${pid}" -- su - "$USER"
fi

# Import ENV variables
if [ -f "$HOME/.openrc.env" ]; then
  set -a
  source "$HOME/.openrc.env"
  set +a
  rm "$HOME/.openrc.env"
fi
```

viを `:wq` で抜けて編集を終えたらまたAlpine Linuxを再起動します。
そして次のようにしてコンテナの起動が問題なくできることを確認します：

```sh
docker container run --rm hello-world
docker image rm hello-world
```

この時点でAlpine Linuxの仮想マシンイメージは302MBほど。。
Dockerを導入する前は二桁だった気がするのですが、導入にともないガツンとサイズが増しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50571/aec1ea0e-802e-4725-d3e6-2e896afe0d95.png)

# docker-composeのインストール

Airflowを稼動させる方法としてローカル、コンテナ、Kubernetes、SaaSなど複数の選択肢がありますが、今回はもちろんコンテナを利用します。
Airflowは `worker`、`schedular`、`webserver`などの複数のコンポーネントからなるので、それらを正しい構成・順番で起動していく必要があります。

そのためにdocker-composeをインストールします：

```sh
sudo su -
apk add --no-cache docker-compose
exit
```

これでまた70MBほど増量です。。

# Airflowのインストール

ようやく主役の登場です。
公式サイトから [docker-compose.yaml](https://airflow.apache.org/docs/apache-airflow/stable/docker-compose.yaml) をダウンロードします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50571/ad67ec31-c512-ac62-45ea-9123a6c39701.png)

ダウンロードしたファイルを適用な場所（今回は `C:\Users\mizuk\devel|wsl`）に置いたら、Alpine Linuxで `docker-compose up` コマンドを実行します：

```sh
docker-compose -f /mnt/c/Users/mizuk/devel/wsl/docker-compose.yaml.txt up
```

イメージのダウンロード（pull）とコンテナ起動が順番に行われていきます：

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50571/b2af816a-fe16-e4ec-8a82-30499f650cce.png)

AirflowやCeleryのASCIIアートが表示されたりして：

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50571/d6ca5248-549b-0495-3645-891aa174b444.png)

最後に、HTTPリクエストのログが定期的に出力されるようになったら起動したサインです：

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50571/7894b78c-c61e-3280-8a1e-02ab15b595bc.png)

Webブラウザで `http://localhost:8080` にアクセスするとAirflowのWeb UIが表示されます（IDとパスワードは YMLファイルに記述されています）：

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50571/e3856ee0-9d92-39c3-50cb-a2f8c6444094.png)

ログインすると、サンプルのワークフローを閲覧したり実行したり出来ます：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50571/687c9ab5-d3e2-0096-c52a-c0f1c7cb0ee9.png)

おっかなびっくり実行してみた図：

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50571/a6e7ea8e-b2c3-a098-91f2-9739574c0fef.png)

以上で、Windows11でApache Airflowを起動するまでが完了です。
一連の作業が終わった時点でAlpine Linuxの仮想マシンイメージは 2.4GBを超えていました。。。