Skaffold + MicroK8s で快適なコンテナ開発環境を構築する（WSL/Ubuntu編）

クラウドエンジニアの @aoyagi9936 です。
`Mac` での構築方法は同時に公開した記事を参照してください。

https://qiita.com/aoyagi9936/items/47ac6f1c70ba5c8977f8

開発環境の構築はエンジニアにとって最初の登竜門ですが、コンテナ環境は特に Docker Desktop の有料化やローカルでの Kubernetes の実行などのハードルもあり、経験豊富なエンジニアにとっても一筋縄にはいかない難しさがあるように感じます。

今回は `Windows(WSL)` `Mac` `Linux(Ubuntu)` ユーザーが同一の開発体験を得られることを目標にコンテナ開発環境の構築方法をご紹介します。
また、

- `Kubernetes` を学びたいがどうして良いかわからない
- `本番環境に近いコンテナ実行環境` をローカルで実現したい
- `CI/CD` を回して高速にコンテナアプリケーションを開発したい

という方も参考にしてみてください。

# Skaffold とは

https://skaffold.dev/

Google が開発しオープンソースで提供している、ローカルで Kubernetes ネイティブなアプリケーションを開発するためのコマンドラインツールです。
Golang で書かれておりシングルバイナリで動きます。
`Google Cloud Deploy` を用いればローカルだけではなくクラウドでもデリバリーパイプラインを構築できますし、`Kustomize` `Helm` など Kubernetes をより効率的に扱う支援ツールにも対応しているので覚えておいて損はないと思います。

https://cloud.google.com/deploy/docs/using-skaffold?hl=ja#what_is_skaffold

# MicroK8s とは

`MicroK8s` はローカルでも動作する軽量な Kubernetes ディストリビューションです。
他の候補としては `Minikube` や `K3s` 等が挙げられますが、 MicroK8s はエンタープライズサポートや複数ノード対応など本番環境としての運用も可能で、開発や本番で有用なアドオンも充実しているのが特徴です。
詳しくは本家の比較表を参照してください。

[MicroK8s vs K3s vs minikube](https://microk8s.io/compare)

ちなみに私は以前は Minikube を使っていましたが `registry` `hostpath-storage` `linkerd` といった実用的なアドオンに惹かれて乗り換えました。

# Windows (WSL) の準備
※ Ubuntu の方は `Ubuntu に MicroK8s をインストールする` まで飛ばしてください。

## WSL2 のセットアップ
Windows のバージョンによってセットアップ方法は微妙に異なるため詳細は割愛しますが、以下の手順で WSL が使えるようになります。
WSL には現在 `WSL1` と `WSL2` がありますが、より忠実に Linux 環境を再現できる `WSL2` を利用します。[^1]
[^1]:[WSL 1 と WSL 2 の比較 - docs.microsoft.com](https://docs.microsoft.com/ja-jp/windows/wsl/compare-versions)

1. コントロールパネルから `Windowsの機能の有効化または無効化` を表示
1. `Linux用Windowsサブシステム` `仮想マシンプラットフォーム` にチェック
1. Windows を再起動
1. コマンドプロンプトを開き `wsl --set-default-version 2` を実行

**WSL2 requires an update to its kernel component. For information please visit https://aka.ms/wsl2kernel** というエラーが表示される場合は指示に従ってリンク先のページから `x64 マシン用 WSL2 Linux カーネル更新プログラム パッケージ` (wsl_update_x64.msi) をインストールしてください。

## WSL2 で Ubuntu （18.04 or 20.04 or 22.04） を利用する
1. コマンドプロンプトを開き `wsl --install -d Ubuntu-[18.04|20.04|22.04]` を実行
1. `wsl -l -v` を実行し `VERSION` が `2` になっていることを確認
1. 任意のID/PASSでログインし `sudo apt update` `sudo apt -y upgrade` を実行

## WSL での MicroK8s 文字化け対策
[Cascadia Code](https://docs.microsoft.com/ja-jp/windows/terminal/cascadia-code) をインストールします。
ページ内の `Github リリースページ リンク` から zip をダウンロードして展開し、 `.ttf` をダブルクリックして Windows にフォントをインストールします。
WSL ウィンドウを右クリックして `Fontタブ > Font` のリストボックスに先ほどインストールした `Cascadia ...` が表示されるので選択して OK を押してください。
ちなみに私は `Powerline` を愛用しているので `Cascadia Mono PL` を利用しています。

## WSL で systemd を有効にする
[2022年9月より WSL が systemd に正式に対応した](https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl/)ため面倒なハックが不要となりました。
但し、デフォルトでは有効にはならないため以下の手順で有効化させてください。
まずはコマンドプロンプトか PowerShell で WSL を最新にします。

```powershell
wsl --update
wsl --set-default-version 2
```

Ubuntu 上の `etc/wsl.conf` で `systemd` を有効にします。

```shell
echo -e "[boot]\nsystemd=true" | sudo tee /etc/wsl.conf
```

wsl.conf のその他の設定が知りたい方は [Microsoft の公式ドキュメント](https://learn.microsoft.com/ja-jp/windows/wsl/wsl-config#systemd-support)を参照してください。
有効化できたら WSL を再起動してください。

```powershell
wsl --shutdown
wsl
```

以前の記事で紹介したスタートアップスクリプトによる systemd 有効化のハックを使用した方は、スタートアップスクリプトを削除して systemd を有効化してください。
```shell
sudo rm /etc/profile.d/00-wsl2-systemd.sh
echo -e "[boot]\nsystemd=true" | sudo tee /etc/wsl.conf
```

WSL を再起動後にエラーで MicroK8s が起動できない場合は MicroK8s の再インストールが必要ですので以下のコマンドで一旦削除します。
```
sudo snap remove microk8s
```

以前の記事も記録として残しておきます。

~~## WSL で systemd を利用できるようにする~~

~~投稿して一週間も経たず内容が陳腐化するとは思ってもいませんでしたが、何と WSL が `systemd` に正式に対応したそうです。~~

https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl/

~~ですので下記対応は今後は不要となりそうですが未検証ですので記事は残しておきます。~~

~~↓↓↓↓↓~~

~~Ubuntu に MicroK8s をインストールするためにはパッケージマネージャーの `snap` を使用しますが、 snap は `systemd` を利用しており WSL は systemd が利用できない（ `PID 1` で起動しない）という罠にハマります（ハマりました）。~~
~~また、後ほど行うコンテナの Build には `Docker Engine` を利用するためどちらにせよ WSL で sytemd を利用できるようにしておく必要があります。~~
~~最新の `WSL2 + Ubuntu22.04` では標準で systemd を PID1 で起動できるようになったようですが、  `Ubuntu-18.04` `Ubuntu-20.04` では一工夫が必要です。~~

~~まずは `fontconfig` と `daemonize` をインストールします。~~

```shell
apt install -y fontconfig daemonize
```

~~次に `/etc/wsl.conf` に WSL の設定ファイルを追加します。~~
~~`<USER_NAME>` はご自身で作成したユーザー名を追加してください。~~
```conf:/etc/wsl.conf
[automount]
enabled = true
options = "metadata,uid=1000,gid=1000,umask=22,fmask=11,case=off"
mountFsTab = true
crossDistro = true

[network]
generateHosts = false
generateResolvConf = true

[interop]
enabled = true
appendWindowsPath = true

[user]
default = <USER_NAME>
```

~~次に `/etc/profile.d/00-wsl2-systemd.sh` にスタートアップスクリプトを作成します。~~
```shell:/etc/profile.d/00-wsl2-systemd.sh
SYSTEMD_PID=$(ps -ef | grep '/lib/systemd/systemd --system-unit=basic.target$' | grep -v unshare | awk '{print $2}')

if [ -z "$SYSTEMD_PID" ]; then
   sudo /usr/bin/daemonize /usr/bin/unshare --fork --pid --mount-proc /lib/systemd/systemd --system-unit=basic.target
   SYSTEMD_PID=$(ps -ef | grep '/lib/systemd/systemd --system-unit=basic.target$' | grep -v unshare | awk '{print $2}')
fi

if [ -n "$SYSTEMD_PID" ] && [ "$SYSTEMD_PID" != "1" ]; then
    exec sudo /usr/bin/nsenter -t $SYSTEMD_PID -a su - $LOGNAME
fi
```
~~上記は 20.04 の場合ですので 18.04 の場合は daemonize のパスを以下のように変更してください。~~
```
- /usr/bin/daemonize
+ /usr/sbin/daemonize
```

~~最後に localhost への接続をデフォルトのインターフェースに転送することで MicroK8s に到達できるようにします。~~

```shell
echo 'net.ipv4.conf.all.route_localnet = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p /etc/sysctl.conf
```

~~お疲れ様でした。これで WSL2 で MicroK8s を動かす下準備は終了です。~~
~~WSL を再起動したら通常の Ubuntu と同じようにコンテナ開発環境をセットアップしていきます。~~

# Ubuntu に MicroK8s をインストールする

お待たせしました。Ubuntu の方はここから実行してください。
と言っても基本的にインストール方法は公式の通りです。[^2]
[^2]:[MicroK8s - Get started](https://microk8s.io/docs/getting-started)

メモリが少ないPCの場合は次のコマンドで失敗する可能性があるため念の為余計なプロセスは落としておきましょう（そもそも後続の開発に耐えられない可能性があるのでその環境は非推奨ですが）。

```shell
sudo snap install microk8s --classic
# リリースチャンネルを指定する場合
sudo snap install microk8s --classic --channel=1.24
```

Kubernetes の設定ファイルは `.kube` に格納します（既存のものと分ける方法は後述）。
存在しない場合は作成し、存在する場合は既存のものとマージします。
```shell
cd $HOME
mkdir .kube
cd .kube
microk8s config > config​
```

作成したフォルダに権限を付与します。

```shell
sudo usermod -a -G microk8s $USER
sudo chown -f -R $USER ~/.kube
```

既存の環境と分けて設定を管理したい場合は先ほどの手順を　`.kube` ではなく `.microk8s` フォルダで実行し `KUBECONFIG` 環境変数を利用するか `--kubeconfig` フラグを利用してください（後述する `Skaffold` にも `--kubeconfig` は指定できます）。
```shell
export KUBECONFIG=$HOME/.microk8s/config
# または
kubectl --kubeconfig=$HOME/.microk8s/config
```

再起動し以下のコマンドで Microk8s が正常に動作していることを確認します。
（起動には少し時間がかかります）

```shell
microk8s status
```

MicroK8s には多くの便利なアドオンがありますが今回有効にするのは以下の2つです。

```shell
microk8s enable dns
microk8s enable registry
```

Kubernetes に慣れていない方はダッシュボードを使うことで状態をグラフィカルに確認できます。
~~アドオンを有効にするだけではなく追加でポートフォワード等の設定も必要なので脚注をご確認ください。~~[^3]
[^3]:[Addon:dashboard - MicroK8s](https://microk8s.io/docs/addon-dashboard)

ダッシュボードのインストールからアクセスに必要な設定までを一度にやってくれるコマンドがあります（version 1.19+）。

```shell
microk8s dashboard-proxy
```

ダッシュボードの起動後にターミナルに URL と トークンが表示されます。
指示の通りに `https://127.0.0.1:10443` にアクセスするとトークンを入力できますのでコピー＆ペーストしてサインインしてください。

```terminal
Checking if Dashboard is running.
Infer repository core for addon dashboard
Infer repository core for addon metrics-server
Waiting for Dashboard to come up.
Trying to get token from microk8s-dashboard-token
Waiting for secret token (attempt 0)
Dashboard will be available at https://127.0.0.1:10443
Use the following token to login:
<TOKEN>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2767381/7f91afca-7491-0ba6-1dd8-1b5e68d060f8.png)

# Ubuntu に Docker をインストールする
公式の手順に従ってください。
[Install Docker Engine on Ubuntu](https://docs.docker.com/engine/install/ubuntu/)
今回の趣旨は `Skaffold + MicroK8s` による Kubernetes ネイティブな開発環境ですので `Docker Compose` は不要です。

```shell
# GPG キーの設定やリポジトリの更新は公式通りのため省略
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin
```

# Ubuntu に Skaffold をインストールする

最後に Skaffold をセットアップします。
快適な環境までもう一息ですので頑張りましょう。

Skaffold のインストールは公式の通りでも良いですし、 `Homebrew on Linux` でもインストールできます。

[Installing Skaffold - skaffold.dev](https://skaffold.dev/docs/install/)
[Homebrew-on-Linux - docs.brew.sh](https://docs.brew.sh/Homebrew-on-Linux)

インストールができたら MicroK8s の組み込みレジストリを登録します。
以下のコマンドで現在の Kubernetes コンテキスト（接続先クラスタ）で利用するレジストリを登録します。

```shell
skaffold config set insecure-registries localhost:32000
skaffold config set default-repo localhost:32000
```

今までの手順の通りであれば Skaffold に登録されるコンテキスト名は `microk8s` となるはずです。
`~/.skaffold/config` ファイルの中を見ると設定を確認できますが、異なる場合は `kubectl config current-context` コマンドを実行して表示されるコンテキスト名を確認してください。

```yaml:~/.skaffold/config
kubeContexts:
- kube-context: microk8s
  default-repo: localhost:32000
  insecure-registries:
  - localhost:32000
```

最後に `microk8s status` で `registry` アドオンが `enabled` であることを確認しておきましょう。
お疲れ様でした。これで環境は完成です！

# Skaffold を構成してコンテナをローカルの MicroK8s にデプロイする

Dockerfile と Kubernetes のマニフェストファイルを作成し、 Skaffold で Docker Build と MicroK8s へのデプロイの一連の流れを構成します。
テストシナリオとして Postgres データベースを Pod で立ち上げてローカルから接続してみます。
サンプルソースも用意しておいたので `git clone` してください。

https://github.com/aoyagi9936/skaffold-microk8s-example

#### ディレクトリ構成
```shell
.
└── skaffold-microk8s-example/
    ├── skaffold.yaml
    ├── kubernetes-manifests/
    │   └── pod.yaml
    └── src/
        └── infra/
            └── database/
                ├── Dockerfile
                ├── 0_create_database.sql
                └── 1_insert_table.sql
```

#### Docker コンテナ設定ファイル
```Dockerfile:Dockerfile
FROM postgres:14.4-alpine

COPY *.sql /docker-entrypoint-initdb.d/
```

```sql:0_create_database.sql
DROP DATABASE IF EXISTS test_db;
CREATE DATABASE test_db;
```

```sql:1_insert_table.sql
\c test_db;

CREATE TABLE example_tbl (
  id serial PRIMARY KEY,
  name TEXT
);

INSERT INTO example_tbl (name) VALUES ('aoyagi');
```

#### Skaffold 設定ファイル
```yaml:skaffold.yaml
apiVersion: skaffold/v4beta2
kind: Config
build:
  tagPolicy:
    sha256: {}
  local:
    push: true
    useBuildkit: true
  artifacts:
  - image: example-db
    context: src/infra/database
deploy:
  kubeContext: microk8s
profiles:
  - name: local
    portForward:
      - resourceType: pod
        resourceName: my-example-pod
        port: 5432
    manifests:
      rawYaml: [
      "./kubernetes-manifests/pod.yaml"
      ]
```

#### Kubernetes マニフェストファイル
```yaml:pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: my-example-pod
spec:
  containers:
  - name: my-example-pod
    image: example-db
    ports:
      - containerPort: 5432
    env:
      - name: POSTGRES_USER
        value: postgres
      - name: POSTGRES_PASSWORD
        value: postgres
      - name: POSTGRES_DB
        value: test_db 
      - name: TZ
        value: Asia/Tokyo
```

全ての準備が整ったら Skaffold を実行してみましょう。
`skaffold.yaml` のあるパスで以下のコマンドを実行します。

```
skaffold dev -p local
```

コンテナが MicroK8s へデプロイされたらお手元のDB接続ツールで接続してみましょう。

| 接続情報 | 設定値 |
| --- | --- |
| ホスト | localhost |
| ポート | 5432 |
| ユーザー名 | postgres |
| パスワード | postgres |
| データベース名 | test_db |

これだけでは Skaffold の良さが伝わりきらないので **Skaffoldを実行したまま** `1_insert_table.sql` にデータを追加してみましょう。

```sql:1_insert_table.sql
INSERT INTO example_tbl (name) VALUES ('aoyagi');
+ INSERT INTO example_tbl (name) VALUES ('your_name');
```

Skaffold が変更を自動で検知してコンテナを再作成し MicroK8s にデプロイします。
開発中は仕様が頻繁に変わりますが変更の都度環境を再作成してくれることで安全性が高まります。
環境をイミュータブルに作成することの良さはまたどこかでお伝えできればと思いますが、タイトルの目標は達成できましたので本記事はここまでとなります。

本投稿が皆様の快適なコンテナ開発ライフの一助になれば幸いです。