Lima で k3s 環境を構築し BuildKit でアプリをビルドして実行

[Lima](https://github.com/lima-vm/lima) で [k3s](https://github.com/k3s-io/k3s) を使った Kubernetes 環境を構築し、サンプルアプリケーションをビルドして実行します。

## 1. k3s の環境構築

まずは、https://github.com/lima-vm/lima/blob/master/examples/k3s.yaml を参考に、k3s を実行するための Lima の構成ファイルを次のようにしてみました。

コンテナは k3s が管理するので ```containerd``` の設定を ```false``` にして、あとは k3s をインストールするだけで良さそうです。

なお、```~/temp/lima_k3s``` で作業を行うように書き込み可でマウントしています。

```yaml:lima_k3s.yaml 
images:
...省略

mounts:
- location: "~/temp/lima_k3s"
  writable: true

containerd:
  system: false
  user: false

provision:
- mode: system
  script: |
    #!/bin/sh
    curl -sfL https://get.k3s.io | sh -

probes:
- script: |
    #!/bin/bash
    set -eux -o pipefail
    if ! timeout 30s bash -c "until test -f /etc/rancher/k3s/k3s.yaml; do sleep 3; done"; then
            echo >&2 "k3s is not running yet"
            exit 1
    fi
```

このファイルで Lima を実行し、シェルへ入っておきます。

##### 実行例

```sh
% cd ~/temp/lima_k3s
% limactl start --tty=false lima_k3s.yaml
・・・

% limactl shell lima_k3s
$ 
```

以降の作業は基本的に lima_k3s シェルで実施します。

## 2. コンテナイメージのビルド

上記で構築した環境では ```nerdctl``` がインストールされないので、コンテナイメージをビルドする手段が無さそうです。

そこで、ここでは [BuildKit](https://github.com/moby/buildkit) でコンテナイメージをビルドします。

### 2-1. BuildKit インストール

今回は BuildKit をダウンロードして手動でインストールしました。
解凍すると ./bin が作られるので、その中のコマンド群を /usr/local/bin へ配置しています。

##### BuildKit インストール例

```sh
$ wget https://github.com/moby/buildkit/releases/download/v0.11.6/buildkit-v0.11.6.linux-arm64.tar.gz
$ tar zxf buildkit-v0.11.6.linux-arm64.tar.gz
$ sudo cp ./bin/* /usr/local/bin/
$ rm -fr ./bin
```

### 2-2. サンプルアプリケーション

次のような Rust アプリケーションをビルドする事にします。

ファイル一式は ```~/temp/lima_k3s/sampleapp``` へ配置しているものとします。

```toml:Cargo.toml
[package]
name = "sampleapp"
version = "0.1.0"
edition = "2021"

[dependencies]
gotham = "0.7"

[profile.release]
lto = true
```

```rust:src/main.rs
use gotham::state::State;
use gotham::prelude::FromState;
use gotham::hyper::Uri;

use std::env;

fn sample(state: State) -> (State, String) {
    let uri = Uri::borrow_from(&state);
    println!("path={}", uri);

    let res = format!("ok:{}", uri);

    (state, res)
}

fn main() {
    let port = env::var("APP_PORT").unwrap_or("3000".to_owned());
    let addr = format!("0.0.0.0:{}", port);

    println!("listening: {}", addr);

    gotham::start(addr, || Ok(sample)).unwrap();
}
```

```dockerfile:Dockerfile
FROM rust:1.69 as builder

WORKDIR /app

COPY . .

RUN cargo install --path .

FROM debian:bullseye-slim

WORKDIR /app

RUN apt-get update && rm -rf /var/lib/apt/lists/*

COPY --from=builder /usr/local/cargo/bin/sampleapp .

CMD ["./sampleapp"]
```

Dockerfile は https://hub.docker.com/_/rust の内容を参考にしましたが、これだと ```cargo install``` 実行時に依存パッケージの処理が毎回行われるので、あまり効率的では無さそうでした。

### 2-3. ビルド

```buildkitd``` を実行しておき、```buildctl``` を使ってコンテナイメージをビルドします。

今回は k3s へコンテナイメージを直接インポートする事にしたので、コンテナイメージを tar ファイル（```sampleapp.tar```）へ出力しています。

##### buildkitd 実行例
```sh
$ sudo buildkitd &
```

##### ビルド例
```sh
$ sudo buildctl build --frontend dockerfile.v0 \
  --local context=./sampleapp --local dockerfile=./sampleapp \
  --output type=oci,name=sampleapp > sampleapp.tar
```

##### buildkitd 終了例
```sh
$ sudo killall buildkitd
```

## 3. 実行

まずは、sampleapp.tar を ```k3s ctr``` でインポートしておきます。

##### コンテナイメージのインポート
```sh
$ sudo k3s ctr images import sampleapp.tar
```

実行用の Deployment + Service の構成ファイルを用意します。

インポートしたコンテナイメージを使うので ```imagePullPolicy: Never``` としています。

```yaml:sampleapp.yaml
kind: Service
apiVersion: v1
metadata:
  name: sampleapp
  labels:
    app: sampleapp
spec:
  selector:
    app: sampleapp
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 3000
  type: LoadBalancer

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sampleapp
  labels:
    app: sampleapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sampleapp
  template:
    metadata:
      labels:
        app: sampleapp
    spec:
      containers:
      - name: sampleapp
        image: sampleapp
        ports:
        - containerPort: 3000
        imagePullPolicy: Never
```

```kubectl apply``` を実行してリソースを作成します。

##### リソース作成
```sh
$ sudo kubectl apply -f sampleapp.yaml

service/sampleapp created
deployment.apps/sampleapp created
```

次のようになりました。

##### Pod 確認
```sh
$ sudo kubectl get pods

NAME                         READY   STATUS    RESTARTS   AGE
sampleapp-6786f87bd7-gs6sq   1/1     Running   0          6s
```
##### Service 確認
```sh
$ sudo kubectl get svc

NAME         TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)          AGE
kubernetes   ClusterIP      10.43.0.1      <none>         443/TCP          122m
sampleapp    LoadBalancer   10.43.198.42   192.168.5.15   8080:30201/TCP   10s
```

最後にホスト OS（macOS）からアクセスしてみます。

##### 動作確認1
```sh
% curl http://localhost:8080/   
ok:/
```

##### 動作確認2
```sh
% curl http://localhost:8080/ab
ok:/ab
```