Go Firecracker SDKを使ってFirecrackerをコマンドライン操作するfirectlコマンドを試し、さらにコードも追ってみた #reinvent

firecracker-microvm/firecracker-go-sdkにGo言語用のSDKがあったので確認してみました。そこでSDKを使って作られているfirectlコマンドを見つけたので紹介します。
2018.11.30

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、shoitoです。

先日、re:Invent 2018で発表された軽量な仮想環境Firecrackerについて、前回さわってみました。

Firecrackerをさわって大量のmicroVMを立ち上げてみた #reinvent

今回はfirecracker-microvm/firecracker-go-sdkにGo言語用のSDKがあったので確認しました。その中でSDKを使って作られているfirectlコマンドが紹介されていたので試してみました。

検証環境

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

必須要件

  • Linux 4.14+
  • KVM

セットアップ

Go言語のソースコードをビルドするので、Go言語環境を構築します。
モジュール機能を使ってサクッとビルドしたいので 1.11 以上にしておきます。

$ curl -L -O https://dl.google.com/go/go1.11.2.linux-amd64.tar.gz
$ sudo tar -C /usr/local -xzf go1.11.2.linux-amd64.tar.gz

goコマンドをPATHに設定します。

$ vi ~/.profile

# 以下を追加する
PATH=$PATH:/usr/local/go/bin

$ source ~/.profile
$ go version
go version go1.11.2 linux/amd64

次にSDKのビルド時に必要になる makegcc をインストールします。

sudo apt-get update
sudo apt install make gcc

ビルド環境は整ったので、GitHubからソースコードをcloneして、Makefileに従ってmakeでビルドします。

$ git clone https://github.com/firecracker-microvm/firecracker-go-sdk
$ cd firecracker-go-sdk
$ make
go build
for d in cmd/firectl; do \
    cd $d && go build ; \
done
go: downloading github.com/jessevdk/go-flags v1.4.0

これで cmd/firectl ディレクトリに firectl コマンドのバイナリが作られます。

firectlでFirecrackerのゲストマシンを立ち上げてみる

前回のこちらの記事にまとめたようにFirecrackerをセットアップします。

Firecrackerをさわって大量のmicroVMを立ち上げてみた #reinvent

準備ができたらFirecrackerを立ち上げます。

$ ./firecracker-v0.11.0 --api-sock /tmp/firecracker.sock

問題なく立ち上がったら、別のシェルから、firectlコマンドでゲストマシンを立ち上げてみます。

$ ./cmd/firectl/firectl \
    --firecracker-binary=/home/ubuntu/firecracker-v0.11.0 \
    --firecracker-console=stdio \
    --kernel=/home/ubuntu/hello-vmlinux.bin \
    --root-drive=/home/ubuntu/hello-rootfs.ext4 \
    --tap-device=enp4s0/12:36:38:b9:38:8a \
    --vmm-log-fifo=/tmp/fc-logs.fifo \
    --metrics-fifo=/tmp/fc-metrics

コマンドの実行に成功すると、以下のように上記コマンド実行後、ブートログが出力され、ログインを求められるので root:root でログインします。

INFO[0000] Called startVMM(), setting up a VMM on ./firecracker.sock
INFO[0000] Configured VMM logging to /tmp/fc-logs.fifo, metrics to /tmp/fc-metrics: [PUT /logger][204] putLoggerNoContent
INFO[0000] refreshMachineConfig: [GET /machine-config][200] getMachineConfigOK  &{CPUTemplate:Uninitialized HtEnabled:true MemSizeMib:512 VcpuCount:1}
INFO[0000] PutGuestBootSource: [PUT /boot-source][204] putGuestBootSourceNoContent
INFO[0000] Attaching drive /home/ubuntu/hello-rootfs.ext4, mode rw, slot 1, root true.
INFO[0000] Attached drive /home/ubuntu/hello-rootfs.ext4: [PUT /drives/{drive_id}][204] putGuestDriveByIdNoContent
INFO[0000] startInstance successful: [PUT /actions][204] createSyncActionNoContent
[    0.000000] Linux version 4.14.55-84.37.amzn2.x86_64 (mockbuild@ip-10-0-1-79) (gcc version 7.3.1 20180303 (Red Hat 7.3.1-5) (GCC)) #1 SMP Wed Jul 25 18:47:15 UTC 2018
[    0.000000] Command line: ro console=ttyS0 noapic reboot=k panic=1 pci=off nomodules  root=/dev/vda virtio_mmio.device=4K@0xd0000000:5
[    0.000000] x86/fpu: Supporting XSAVE feature 0x001: 'x87 floating point registers'

...(省略)

Welcome to Alpine Linux 3.8
Kernel 4.14.55-84.37.amzn2.x86_64 on an x86_64 (ttyS0)

localhost login: root
Password:
Welcome to Alpine!

The Alpine Wiki contains a large amount of how-to guides and general
information about administrating Alpine systems.
See <http://wiki.alpinelinux.org>.

You can setup the system with the command: setup-alpine

You may change this message by editing /etc/motd.

login[857]: root login on 'ttyS0'

ここまででfirectlコマンドでゲストマシンを立ち上げて、ゲスト環境が使える状態になりました。

localhost:~# ls /
bin         home        media       root        srv         usr
dev         lib         mnt         run         sys         var
etc         lost+found  proc        sbin        tmp

SDKのコードを見てみる

さて、firectlコマンドが内部で使っている firecracker-microvm/firecracker-go-sdk のコードを見てみます。

ちなみにFirecrackerは前回紹介したようにREST APIが提供されていて、HTTPリクエストを送ることでゲストマシンの操作ができます。
そのREST APIの仕様はSwaggerで定義されています。

https://github.com/firecracker-microvm/firecracker/blob/master/api_server/swagger/firecracker.yaml

一部抜粋してますが、paths配下の/actionsエンドポイントはゲストマシンを立ち上げるときにInstanceStartを送ったパスになります。

swagger: "2.0"
info:
  title: Firecracker API
  description: RESTful public-facing API.
               The API is accessible through HTTP calls on specific URLs carrying JSON modeled data.
               The transport medium is a Unix Domain Socket.
  version: 0.11.0
  termsOfService: ""
  contact:
    email: "compute-capsule@amazon.com"
  license:
    name: "Apache 2.0"
    url: "http://www.apache.org/licenses/LICENSE-2.0.html"

host: "localhost"
basePath: "/"

schemes:
  - http
consumes:
  - application/json
produces:
  - application/json

paths:
  /:
    get:
      summary: Returns general information about an instance.
      operationId: describeInstance
      responses:
        200:
          description: The instance information
          schema:
            $ref: "#/definitions/InstanceInfo"
        default:
          description: Internal Server Error
          schema:
            $ref: "#/definitions/Error"

  /actions:
    put:
      summary: Creates a synchronous action.
      operationId: createSyncAction
      parameters:
      - name: info
        in: body
        required: true
        schema:
          $ref: "#/definitions/InstanceActionInfo"
      responses:
        204:
          description: The update was successful
        400:
          description: The action cannot be executed due to bad input
          schema:
            $ref: "#/definitions/Error"
        default:
          description: Internal Server Error
          schema:
            $ref: "#/definitions/Error"
...(省略)

ここからfirectl.goのmainからREST APIを呼ぶところまで見ていきます。 https://github.com/firecracker-microvm/firecracker-go-sdk/blob/master/cmd/firectl/main.go#L91

func main() {
    var err error
    var opts struct {
        FcBinary           string   `long:"firecracker-binary" description:"Path to firecracker binary"`
        FcConsole          string   `long:"firecracker-console"

firecracker.NewMachine() で新しいゲストマシンを生成します。
https://github.com/firecracker-microvm/firecracker-go-sdk/blob/master/cmd/firectl/main.go#L184

    m, err := firecracker.NewMachine(fcCfg, firecracker.WithLogger(log.NewEntry(logger)))
    if err != nil {
        log.Fatalf("Failed creating machine: %s", err)
    }

firecracker.NewMachine()で何をしているかと追っていくと...
https://github.com/firecracker-microvm/firecracker-go-sdk/blob/master/machine.go#L164

// NewMachine initializes a new Machine instance
func NewMachine(cfg Config, opts ...Opt) (*Machine, error) {
    if err := cfg.Validate(); err != nil {
        return nil, err
    }

    m := &Machine{}

ゲストマシンのインスタンスにclientを生成してセットし...
https://github.com/firecracker-microvm/firecracker-go-sdk/blob/master/machine.go#L186

    if m.client == nil {
        m.client = NewFirecrackerClient(cfg.SocketPath, m.logger, cfg.Debug)
    }

m.StartInstance()が呼ばれ...
https://github.com/firecracker-microvm/firecracker-go-sdk/blob/master/cmd/firectl/main.go#L196

    } else {
        m.StartInstance(vmmCtx)
    }

やっと内部でREST APIを呼ぶclient.CreateSyncAction()が実行されます。
https://github.com/firecracker-microvm/firecracker-go-sdk/blob/master/machine.go#L524

    resp, err := m.client.CreateSyncAction(ctx, &info)
    if err == nil {
        m.logger.Printf("startInstance successful: %s", resp.Error())
    } else {
        m.logger.Printf("Error starting instance: %s", err)
    }

インターフェース定義はここに。
https://github.com/firecracker-microvm/firecracker-go-sdk/blob/master/machine.go#L60

type Firecracker interface {
    ...
    CreateSyncAction(ctx context.Context, info *models.InstanceActionInfo) (*ops.CreateSyncActionNoContent, error)

あとはOperations.CreateSyncAction()が呼ばれ...
https://github.com/firecracker-microvm/firecracker-go-sdk/blob/master/firecracker.go#L132

    return f.client.Operations.CreateSyncAction(params)

最終的に、REST APIで/actions への PUT リクエストが呼ばれます。
https://github.com/firecracker-microvm/firecracker-go-sdk/blob/master/client/operations/operations_client.go#L163

    result, err := a.transport.Submit(&runtime.ClientOperation{
        ID:                 "createSyncAction",
        Method:             "PUT",
        PathPattern:        "/actions",
        ProducesMediaTypes: []string{"application/json"},
        ConsumesMediaTypes: []string{"application/json"},
        Schemes:            []string{"http"},
        Params:             params,
        Reader:             &CreateSyncActionReader{formats: a.formats},
        Context:            params.Context,
        Client:             params.HTTPClient,
    })

さいごに

firectlコマンドを試してみて、実際にSDKをどんな感じで使っているのか1操作だけですがソースコードを追ってみましたがいかがでしょうか?
次はfirecracker-containerdを見てみようと思ってます。