firecracker のスナップショット機能を試してみる

# 概要

* 今年開催された AWS re:Invent 2022 で発表された Lambda SnapStart なる機能が発表された。
* 機能としては、Microm VM SnapShots を使用したもののようで、その論文を読んでみたのが下記の記事です。
* 今回は何を試していくのかというと、Lambda SnapStart・・・ではなく、firecracker のSnapshotting 機能を試してみようと思います。

https://qiita.com/yoshii0110/items/835b1a5693ba3981c3d9

# firecracker

* firecracker とは、AWS によって開発された OSS の仮想技術で、サーバレスなサービス(AWS Lambda や AWS Fargate など)に利用されています。
* microVM と呼ばれる軽量の仮想マシンで、従来の VM で提供されているワークロードの分離やセキュリティ、コンテナの柔軟性や速度性能を備えています。
* firecracker の主要なコンポーネントは、Linuxカーネル仮想マシン[(KVM)](https://en.wikipedia.org/wiki/Kernel-based_Virtual_Machine)を使用しています。

## firecracker の特徴

* セキュリティ
  * firecracker は、複数レベルの分離と保護を使用し、攻撃面を最小限に抑えるように設計されているようです。

* ハイパフォーマンス
  * microVM 起動までが超高速。

* 低オーバヘッド
  * Firecrackerは、同一インスタンス上で、さまざまな vCPU とメモリ構成を持つ数千のセキュアな VM を実行することができます。

* OSS
  * [OSS](https://github.com/firecracker-microvm/firecracker)として、公開されているので、誰でも PR を出すことができます。

* Process Jail
  * プロセスは、cgroups と seccomp BPF を使用しており、小さく厳密にコントロールされたシステムコールのリストによってアクセスできます。

## ちょっとした歴史の話

* Lambda がローンチした当初は、専用のEC2インスタンスを使用していたようです。
* AWS が Lambda によってユーザに届けたかった価値は、インフラの管理を避け、セキュアなサーバレス体験を提供することにあったため、ユーザに専用の EC2 インスタンスを使用していたのですが、いくつかの管理のためのトレードオフを余儀なくされたと AWS のブログにて語っています。
* その後、AWS Fargate がローンチされたことで、サーバレスの利点をコンテナまで拡張したのですが、改めてサービスの裏側部分における仮想マシンの効率性を再検討していたようです。
* その中で開発されたのが、firecracker なのでしょう。

# 検証してみる
## 検証環境

* EC2インスタンス - i3.metal
* Ubuntu Server 18.04 LTS (HVM), SSD Volume Type - 64 ビット (x86)


## まずは Firecracker を起動してみる

* Firecracker のバイナリを作成した EC2 インスタンス内にダウンロードします。

```sh
> release_url="https://github.com/firecracker-microvm/firecracker/releases"
latest=$(basename $(curl -fsSLI -o /dev/null -w  %{url_effective} ${release_url}/latest))
arch=`uname -m`
curl -L ${release_url}/download/${latest}/firecracker-${latest}-${arch}.tgz \
| tar -xz
```

* バイナリの名前を「firecracker」に変更します。

```sh
> mv release-${latest}-$(uname -m)/firecracker-${latest}-$(uname -m) firecracker
```

* 次に検証用のLinuxカーネルイメージとルートファイルシステムをダウンロードします。

```sh
> curl -fsSL -o hello-vmlinux.bin https://s3.amazonaws.com/spec.ccfc.min/img/hello/kernel/hello-vmlinux.bin
> curl -fsSL -o hello-rootfs.ext4 https://s3.amazonaws.com/spec.ccfc.min/img/hello/fsfiles/hello-rootfs.ext4
```
* KVMのセッティングの確認をします。

```sh
> [ -r /dev/kvm ] && [ -w /dev/kvm ] && echo "OK" || echo "FAIL"
FAIL
```

* FAIL となった場合は、現在のユーザーに権限がないので、権限を付与します。

```sh
> ll /dev/kvm
crw------- 1 root root 10, 232 Dec 19 13:49 /dev/kvm


> sudo setfacl -m u:${USER}:rw /dev/kvm

> ll /dev/kvm
crw-rw----+ 1 root root 10, 232 Dec 19 13:49 /dev/kvm

> [ -r /dev/kvm ] && [ -w /dev/kvm ] && echo "OK" || echo "FAIL"
OK
```


### Firecracker とゲストマシンを起動します

* Firecracker を起動します。

* その前に、Firecracker が API ソケットを作成できるように魔法のおまじない。

```sh
> rm -f /tmp/firecracker.socket
```

```sh
> ./firecracker --api-sock /tmp/firecracker.socket
```

* 起動ができたら、別のシェルを立ち上げ、ゲストカーネルのセッティングを行います。

```sh
> curl --unix-socket /tmp/firecracker.socket -i \
      -X PUT 'http://localhost/boot-source'   \
      -H 'Accept: application/json'           \
      -H 'Content-Type: application/json'     \
      -d "{
            \"kernel_image_path\": \"./hello-vmlinux.bin\",
            \"boot_args\": \"console=ttyS0 reboot=k panic=1 pci=off\"
       }"
HTTP/1.1 204
Server: Firecracker API
Connection: keep-alive
```

* 次にルートファイルシステムを設定します。

```sh
curl --unix-socket /tmp/firecracker.socket -i \
  -X PUT 'http://localhost/drives/rootfs' \
  -H 'Accept: application/json'           \
  -H 'Content-Type: application/json'     \
  -d "{
        \"drive_id\": \"rootfs\",
        \"path_on_host\": \"./hello-rootfs.ext4\",
        \"is_root_device\": true,
        \"is_read_only\": false
   }"
HTTP/1.1 204
Server: Firecracker API
Connection: keep-alive
```

* とりあえず、最低限のゲストマシンの設定はできたので、ゲストマシンを起動します。

```sh
> curl --unix-socket /tmp/firecracker.socket -i \
  -X PUT 'http://localhost/actions'       \
  -H  'Accept: application/json'          \
  -H  'Content-Type: application/json'    \
  -d '{
      "action_type": "InstanceStart"
   }'
HTTP/1.1 204
Server: Firecracker API
Connection: keep-alive
```

* 1つ目のシェルに戻ると、ゲストマシンへのログインを求める状態になります。
* root:root でログインすると、ゲストマシンの Alpine Linux 使用できるようになります。

![スクリーンショット 2022-12-19 23.15.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563635/ca883ed4-8d9d-abbc-8172-4577eb55e94c.png)

* ログイン後の画面が下記になります。

![スクリーンショット 2022-12-19 23.15.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563635/5bbf34e3-37a6-f98d-8c9a-5438feacb958.png)


* また、別のシェルより下記の API を実行するとマシンに関する詳細情報を取得することができます。

```sh
> curl --unix-socket /tmp/firecracker.socket http://localhost/machine-config
{"vcpu_count":1,"mem_size_mib":128,"smt":false,"track_dirty_pages":false}
```


## Firecracker における snapshot 機能を試す

* 本番はここからです。
* Firecracker は、スナップショットを操作するための API を公開しています。
* ただ、まずは、実行中の microVM とその vCPU を一時停止する必要があります。
* 下記の API を実行する一時停止することができます。

```sh
> curl --unix-socket /tmp/firecracker.socket -i \
    -X PATCH 'http://localhost/vm' \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json' \
    -d '{
            "state": "Paused"
    }'
HTTP/1.1 204
Server: Firecracker API
Connection: keep-alive
```

* microVM が一時停止されたので、スナップショットを作成することができます。
* スナップショットの種類として、fullone か diff one が選択できます。


* 完全なスナップショットを作成するには、次の API を使用します。

```sh
> curl --unix-socket /tmp/firecracker.socket -i \
    -X PUT 'http://localhost/snapshot/create' \
    -H  'Accept: application/json' \
    -H  'Content-Type: application/json' \
    -d '{
            "snapshot_type": "Full",
            "snapshot_path": "./snapshot_file",
            "mem_file_path": "./mem_file",
            "version": "1.0.0"
    }'
HTTP/1.1 204
Server: Firecracker API
Connection: keep-alive
```

* また、差分スナップショットの作成 するときは、次の API を使用します。

```sh
curl --unix-socket /tmp/firecracker.socket -i \
    -X PUT 'http://localhost/snapshot/create' \
    -H  'Accept: application/json' \
    -H  'Content-Type: application/json' \
    -d '{
            "snapshot_type": "Diff",
            "snapshot_path": "./snapshot_file",
            "mem_file_path": "./mem_file",
            "version": "1.0.0"
    }'
```

* ここで、一旦作業ディレクトリ上で ls コマンドを実行してみましょう。どうなるでしょう？
* snapshot_file というファイルができてますね。

```sh
> ls -la
total 185056
drwxr-xr-x 6 ubuntu ubuntu      4096 Dec 19 14:22 .
drwxr-xr-x 3 root   root        4096 Dec 19 13:49 ..
-rw-r--r-- 1 ubuntu ubuntu       220 Apr  4  2018 .bash_logout
-rw-r--r-- 1 ubuntu ubuntu      3771 Apr  4  2018 .bashrc
drwx------ 2 ubuntu ubuntu      4096 Dec 19 13:53 .cache
drwx------ 3 ubuntu ubuntu      4096 Dec 19 13:53 .gnupg
-rw-r--r-- 1 ubuntu ubuntu       807 Apr  4  2018 .profile
drwx------ 2 ubuntu ubuntu      4096 Dec 19 13:49 .ssh
-rw-r--r-- 1 ubuntu ubuntu         0 Dec 19 14:02 .sudo_as_admin_successful
-rwxr-xr-x 1 ubuntu ubuntu   2502088 Dec  1 14:41 firecracker
-rw-rw-r-- 1 ubuntu ubuntu  31457280 Dec 19 14:16 hello-rootfs.ext4
-rw-rw-r-- 1 ubuntu ubuntu  21266136 Dec 19 13:59 hello-vmlinux.bin
-rw-rw-r-- 1 ubuntu ubuntu 134217728 Dec 19 14:22 mem_file
drwxrwxr-x 3 ubuntu ubuntu      4096 Dec 19 13:55 release-v1.2.0-x86_64
-rw-rw-r-- 1 ubuntu ubuntu     14352 Dec 19 14:22 snapshot_file
```

* 次に、MicroVM 上で reboot を行い、VM を止めちゃいます。

```sh
> localhost:~# reboot
PID1: Received "reboot" from FIFO...
localhost:~# Starting reboot runlevel
 * Unmounting loop devices
 * Unmounting filesystems
Sending the final term signal
Sending the final kill signal
[ 1401.515176] Unregister pv shared memory for cpu 0
[ 1401.516904] reboot: Restarting system
[ 1401.517932] reboot: machine restart
```

* 次に、先ほど取得したスナップショットの読み込みを行います。
* その前に、API ソケットを削除してから、firecracker を起動し直します。

```sh
> rm -f /tmp/firecracker.socket
> ./firecracker --api-sock /tmp/firecracker.socket
```

* 再度、下記の API より microVM を再開します。

```
curl --unix-socket /tmp/firecracker.socket -i \
>     -X PATCH 'http://localhost/vm' \
>     -H 'Accept: application/json' \
>     -H 'Content-Type: application/json' \
>     -d '{
>             "state": "Resumed"
>     }'
HTTP/1.1 204
Server: Firecracker API
Connection: keep-alive
```

* スナップショットをロードする場合は、次の API を使用し、microVM を構成する前にのみ行います。

```
curl --unix-socket /tmp/firecracker.socket -i \
>     -X PUT 'http://localhost/snapshot/load' \
>     -H  'Accept: application/json' \
>     -H  'Content-Type: application/json' \
>     -d '{
>             "snapshot_path": "./snapshot_file",
>             "mem_backend": {
>                 "backend_path": "./mem_file",
>                 "backend_type": "File"
>             },
>             "enable_diff_snapshots": true,
>             "resume_vm": false
>     }'
HTTP/1.1 204
Server: Firecracker API
Connection: keep-alive
```

* １つ目のシェルを見てみましょう。
* snapshot を load しようとした際に出たログが出力されています。
* まあ、ただ、今回は、ゲストマシン上でガチャガチャしないプレーンなものだったので、あまり意味はありませんが、microVM での スナップショット作成と、読み込みを行う方法は見えました。
* 今後はこれを使ってちょっと遊んでみたいと思います。

```sh
./firecracker --api-sock /tmp/firecracker.socket
2022-12-19T14:44:35.855163957 [anonymous-instance:main:WARN:src/logger/src/lib.rs:36] [DevPreview] Virtual machine snapshots is in development preview.
2022-12-19T14:44:35.855421878 [anonymous-instance:main:WARN:src/vmm/src/persist.rs:72] SMT field not found in snapshot.
2022-12-19T14:44:35.855445224 [anonymous-instance:main:WARN:src/vmm/src/persist.rs:83] CPU template field not found in snapshot.
2022-12-19T14:44:35.855462157 [anonymous-instance:main:WARN:src/vmm/src/persist.rs:94] Boot source information not found in snapshot.
2022-12-19T14:44:35.859464601 [anonymous-instance:main:WARN:src/logger/src/lib.rs:40] [DevPreview] Virtual machine snapshots is in development preview - 'load snapshot' VMM action took 4221 us.
```

* なお、マシンに関する詳細も一応取得しておいた。
```
curl --unix-socket /tmp/firecracker.socket http://localhost/machine-config
{"vcpu_count":1,"mem_size_mib":128,"smt":false,"track_dirty_pages":true}
```


# 参考資料

https://github.com/firecracker-microvm/firecracker

https://aws.amazon.com/jp/blogs/news/firecracker-lightweight-virtualization-for-serverless-computing/