はじめに
11月末、今年もAWS系イベントで最大のイベントre:inventがラスベガスで開催されましたね!
re:invent行った組のキラキラした報告を日本で涙を飲みながら見聞きしていた筆者です。
今回も色々な発表があったようですが、私のココロの琴線に触れたのは**Firecrackerでした。
そんなFirecracker**の紹介と利用手順などを書いていこうと思います。(普通に動かすだけです!)
Firecrackerっていったい…?
一言で言うと?
「すげー速くセキュアなVMが作れるハイパーバイザーOSS」 です。
※FirecrackerのTOPページに書いてあることをまるっと表現しています
(ちなみに和訳するとかんしゃく玉って意味らしいですね。)
なんでAWSが?
どうしてこんなものをAWSが開発していたのかというと、お気付きかもしれないですがサーバレスアーキテクチャの隆盛が大きく寄与しています。
AWSで著名なサーバレス系のサービスとしてLambdaやFargateなどがありますね。
2014年にLambdaが発表された当初、ガチガチにセキュリティを固めて安全性の確保を優先的にしていたようですが、そういった数々のサーバレス系サービスが利用されるにつれて、安全なことはもとより、柔軟性や効率性も無視できない状況となって開発に取り組んだという経緯があるみたいです。
ここがスゴイ!
こんなことが書いてあります。
- 無駄を削ぎ落としたシンプルな構成でセキュア!
- システムコールをKernelレベルの機能で制御していてセキュア!
- 125ms以下でmicroVMが一個立ち上がる!
- 1秒に150個microVM立ち上がる!(!?)
- 1microVM毎に5MiB程度の低オーバーヘッド!
- エンジニア各位には馴染み深いLambdaやFargateでゴリッゴリに使われてきている実績がある!
どんな技術が使われてるの?
こんなことが書いてあります。
- 最初期はcrosvmが使われていた
- 今はもはや全然違う様子(Today, crosvm and Firecracker have divergedって書いてある)
- Firecrackerプロセスはcgroupsとseccomp BPFによって管理されている
- VMを作ったり管理しているのはKVM
- Firecracker processの操作はRESTful APIでできる
- ライセンスはApache License version 2.0
- Rustで書かれている!
どうして今更VM?
コンテナ系技術コミュニティの盛り上がりが目立つ昨今に、なぜVMだのハイパーバイザだの言っているのか、と疑問を持たれた方もいらっしゃるかもしれません。
最近のコンテナ界隈の動向として、コンテナ同士の分離を図ることによりセキュリティ面にフォーカスするという実装が注目を集めています。
そこでコンテナの機能を司るOCI(Open Container Initiative)ランタイムが重要になるのですが、runc,Kata Containers,gVisorなどの既存のOCIランタイムはセキュリティと起動速度がトレードオフな関係にある傾向が強いという課題があります。
Firerackerの強みはその2つを両立できるという点でもあるので、コンテナ系技術との融合を図り、OCIランタイムに組み込んでもらうという目論見も実際にあるようです。
実際に使ってみる
普通に使っていたら芸が無いなあと思い、色々考えてました。
試したこと
あまりリッチなスペックではないマシン上で大量のVMを立ち上げて、 マシンをcrackさせたい。 リソースの使用状況がどうなるか知りたい。
そう思って、まずはしょぼい物理マシンにFirecrackerを入れて大量にVMを立ち上げてやろうかなあと画策したのですが、手頃なマシンが無く断念。
次に、低スペックEC2インスタンスを立ち上げてその上でFirecrackerを入れようかなと思ったのですが、ベアメタルインスタンス以外は仮想化対応してないんですね。なので、こちらも断念。
EC2インスタンスでのトライはこちらの情報を参考にさせて頂きつつ、結構頑張ってなんとかKVM単体は使えるようになったものの、Firecrackerを実行したら
- API socketのOpen
- VMMのCreate
に失敗してコケてしまいました。
-
お使いのLinuxが仮想化対応しているかどうかは下記コマンドで
flags
の行が抽出されるかどうかで確認できます。
egrep -c '(vmx|svm)' /proc/cpuinfo
ということで普通に使う
ちゃんとソースを読んで追っていけば上記環境でも無理やり実行させられるかもしれないですが、このままでは記事にならないので公式のGetting Startedに従って普通にやります。
環境準備
まずは環境を準備しましょう。GitHubにはこんなことが書いてあります。
- EC2インスタンスで試すなら
i3.metal
インスタンスがオススメ - OSはUbuntu 18.04がオススメ
- サポートしているのは64bit環境
- kernelバージョンが4.14以上であることが必要
- 使っているLinux kernelでKVMが使えることが必要
-
/dev/kvm
に対してread/writeの権限が必要
今回は公式オススメのi3.metal
インスタンス(Ubuntu 18.04)で試してみることにします。
- ちなみに、KVM周りの準備が整っているかどうかはこちらで確認してみましょう。
[ -r /dev/kvm ] && [ -w /dev/kvm ] && echo "OK" || echo "FAIL"
-
FAIL
が出たら下記コマンドを実行してread/writeの権限を付与しましょう。
sudo setfacl -m u:${USER}:rw /dev/kvm
Firecrackerのバイナリを取得
GitHubからcurl
でダウンロードしてきます。
curl -LJ -o firecracker https://github.com/firecracker-microvm/firecracker/releases/download/v0.12.0/firecracker-v0.12.0
-
0.12.0
と書いているところは適宜最新バージョンに書き換えてください!
Firecracker実行ファイルに実行権限を付与します。
chmod +x firecracker
Firecrackerを起動
API socketを作成することでFirecrackerが起動されます。
./firecracker --api-sock /tmp/firecracker.socket
ゲストマシンを起動
ここでもう一つ別のターミナル画面を開きます。
公式に検証用のkernelとrootfsを用意してくれていますので、それらをcurl
でダウンロード
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
ゲストマシンにkernelを設定
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"
}'
ゲストマシンにrootfsを設定
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
}'
満を持してゲストマシンを起動!!
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"
}'
するとFirecrackerを起動した方のターミナル画面にこんなログが爆速で流れます。
こんなログ
[ 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: console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda virtio_mmio.device=4K@0xd0000000:5
[ 0.000000] x86/fpu: Supporting XSAVE feature 0x001: 'x87 floating point registers'
[ 0.000000] x86/fpu: Supporting XSAVE feature 0x002: 'SSE registers'
[ 0.000000] x86/fpu: Supporting XSAVE feature 0x004: 'AVX registers'
[ 0.000000] x86/fpu: xstate_offset[2]: 576, xstate_sizes[2]: 256
[ 0.000000] x86/fpu: Enabled xstate features 0x7, context size is 832 bytes, using 'standard' format.
[ 0.000000] e820: BIOS-provided physical RAM map:
[ 0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable
[ 0.000000] BIOS-e820: [mem 0x0000000000100000-0x0000000007ffffff] usable
[ 0.000000] NX (Execute Disable) protection: active
[ 0.000000] DMI not present or invalid.
[ 0.000000] Hypervisor detected: KVM
[ 0.000000] tsc: Using PIT calibration value
[ 0.000000] e820: last_pfn = 0x8000 max_arch_pfn = 0x400000000
[ 0.000000] MTRR: Disabled
[ 0.000000] x86/PAT: MTRRs disabled, skipping PAT initialization too.
[ 0.000000] CPU MTRRs all blank - virtualized system.
[ 0.000000] x86/PAT: Configuration [0-7]: WB WT UC- UC WB WT UC- UC
[ 0.000000] found SMP MP-table at [mem 0x0009fc00-0x0009fc0f] mapped at [ffffffffff200c00]
[ 0.000000] Scanning 1 areas for low memory corruption
[ 0.000000] Using GB pages for direct mapping
[ 0.000000] No NUMA configuration found
[ 0.000000] Faking a node at [mem 0x0000000000000000-0x0000000007ffffff]
[ 0.000000] NODE_DATA(0) allocated [mem 0x07fde000-0x07ffffff]
[ 0.000000] kvm-clock: Using msrs 4b564d01 and 4b564d00
[ 0.000000] kvm-clock: cpu 0, msr 0:7fdc001, primary cpu clock
[ 0.000000] kvm-clock: using sched offset of 2418784953893 cycles
[ 0.000000] clocksource: kvm-clock: mask: 0xffffffffffffffff max_cycles: 0x1cd42e4dffb, max_idle_ns: 881590591483 ns
[ 0.000000] Zone ranges:
[ 0.000000] DMA [mem 0x0000000000001000-0x0000000000ffffff]
[ 0.000000] DMA32 [mem 0x0000000001000000-0x0000000007ffffff]
[ 0.000000] Normal empty
[ 0.000000] Movable zone start for each node
[ 0.000000] Early memory node ranges
[ 0.000000] node 0: [mem 0x0000000000001000-0x000000000009efff]
[ 0.000000] node 0: [mem 0x0000000000100000-0x0000000007ffffff]
[ 0.000000] Initmem setup node 0 [mem 0x0000000000001000-0x0000000007ffffff]
[ 0.000000] Intel MultiProcessor Specification v1.4
[ 0.000000] MPTABLE: OEM ID: FC
[ 0.000000] MPTABLE: Product ID: 000000000000
[ 0.000000] MPTABLE: APIC at: 0xFEE00000
[ 0.000000] Processor #0 (Bootup-CPU)
[ 0.000000] IOAPIC[0]: apic_id 2, version 17, address 0xfec00000, GSI 0-23
[ 0.000000] Processors: 1
[ 0.000000] smpboot: Allowing 1 CPUs, 0 hotplug CPUs
[ 0.000000] PM: Registered nosave memory: [mem 0x00000000-0x00000fff]
[ 0.000000] PM: Registered nosave memory: [mem 0x0009f000-0x000fffff]
[ 0.000000] e820: [mem 0x08000000-0xffffffff] available for PCI devices
[ 0.000000] Booting paravirtualized kernel on KVM
[ 0.000000] clocksource: refined-jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645519600211568 ns
[ 0.000000] random: get_random_bytes called from start_kernel+0x94/0x486 with crng_init=0
[ 0.000000] setup_percpu: NR_CPUS:128 nr_cpumask_bits:128 nr_cpu_ids:1 nr_node_ids:1
[ 0.000000] percpu: Embedded 41 pages/cpu @ffff880007c00000 s128728 r8192 d31016 u2097152
[ 0.000000] KVM setup async PF for cpu 0
[ 0.000000] kvm-stealtime: cpu 0, msr 7c15040
[ 0.000000] PV qspinlock hash table entries: 256 (order: 0, 4096 bytes)
[ 0.000000] Built 1 zonelists, mobility grouping on. Total pages: 32137
[ 0.000000] Policy zone: DMA32
[ 0.000000] Kernel command line: console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda virtio_mmio.device=4K@0xd0000000:5
[ 0.000000] PID hash table entries: 512 (order: 0, 4096 bytes)
[ 0.000000] Memory: 111064K/130680K available (8204K kernel code, 622K rwdata, 1464K rodata, 1268K init, 2820K bss, 19616K reserved, 0K cma-reserved)
[ 0.000000] SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
[ 0.000000] Kernel/User page tables isolation: enabled
[ 0.004000] Hierarchical RCU implementation.
[ 0.004000] RCU restricting CPUs from NR_CPUS=128 to nr_cpu_ids=1.
[ 0.004000] RCU: Adjusting geometry for rcu_fanout_leaf=16, nr_cpu_ids=1
[ 0.004000] NR_IRQS: 4352, nr_irqs: 48, preallocated irqs: 16
[ 0.004000] Console: colour dummy device 80x25
[ 0.004000] console [ttyS0] enabled
[ 0.004000] tsc: Detected 2300.014 MHz processor
[ 0.004000] Calibrating delay loop (skipped) preset value.. 4600.02 BogoMIPS (lpj=9200056)
[ 0.004000] pid_max: default: 32768 minimum: 301
[ 0.004000] Security Framework initialized
[ 0.004000] SELinux: Initializing.
[ 0.004184] Dentry cache hash table entries: 16384 (order: 5, 131072 bytes)
[ 0.005423] Inode-cache hash table entries: 8192 (order: 4, 65536 bytes)
[ 0.006549] Mount-cache hash table entries: 512 (order: 0, 4096 bytes)
[ 0.007643] Mountpoint-cache hash table entries: 512 (order: 0, 4096 bytes)
[ 0.008411] Last level iTLB entries: 4KB 64, 2MB 8, 4MB 8
[ 0.009331] Last level dTLB entries: 4KB 64, 2MB 0, 4MB 0, 1GB 4
[ 0.010341] Spectre V2 : Mitigation: Full generic retpoline
[ 0.012004] Speculative Store Bypass: Vulnerable
[ 0.026199] Freeing SMP alternatives memory: 28K
[ 0.028181] smpboot: Max logical packages: 1
[ 0.029273] x2apic enabled
[ 0.030144] Switched APIC routing to physical x2apic.
[ 0.032542] ..TIMER: vector=0x30 apic1=0 pin1=0 apic2=-1 pin2=-1
[ 0.033478] smpboot: CPU0: Intel(R) Xeon(R) Processor @ 2.30GHz (family: 0x6, model: 0x4f, stepping: 0x1)
[ 0.034983] Performance Events: unsupported p6 CPU model 79 no PMU driver, software events only.
[ 0.036000] Hierarchical SRCU implementation.
[ 0.036000] smp: Bringing up secondary CPUs ...
[ 0.036005] smp: Brought up 1 node, 1 CPU
[ 0.036609] smpboot: Total of 1 processors activated (4600.02 BogoMIPS)
[ 0.037949] devtmpfs: initialized
[ 0.038527] x86/mm: Memory block size: 128MB
[ 0.039341] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645041785100000 ns
[ 0.040008] futex hash table entries: 256 (order: 2, 16384 bytes)
[ 0.041126] NET: Registered protocol family 16
[ 0.041944] cpuidle: using governor ladder
[ 0.042564] cpuidle: using governor menu
[ 0.047084] HugeTLB registered 1.00 GiB page size, pre-allocated 0 pages
[ 0.048007] HugeTLB registered 2.00 MiB page size, pre-allocated 0 pages
[ 0.049173] dmi: Firmware registration failed.
[ 0.049929] NetLabel: Initializing
[ 0.050449] NetLabel: domain hash size = 128
[ 0.051100] NetLabel: protocols = UNLABELED CIPSOv4 CALIPSO
[ 0.051965] NetLabel: unlabeled traffic allowed by default
[ 0.052117] clocksource: Switched to clocksource kvm-clock
[ 0.052972] VFS: Disk quotas dquot_6.6.0
[ 0.053580] VFS: Dquot-cache hash table entries: 512 (order 0, 4096 bytes)
[ 0.056949] NET: Registered protocol family 2
[ 0.057736] TCP established hash table entries: 1024 (order: 1, 8192 bytes)
[ 0.058771] TCP bind hash table entries: 1024 (order: 2, 16384 bytes)
[ 0.059720] TCP: Hash tables configured (established 1024 bind 1024)
[ 0.060704] UDP hash table entries: 256 (order: 1, 8192 bytes)
[ 0.061573] UDP-Lite hash table entries: 256 (order: 1, 8192 bytes)
[ 0.062516] NET: Registered protocol family 1
[ 0.063798] virtio-mmio: Registering device virtio-mmio.0 at 0xd0000000-0xd0000fff, IRQ 5.
[ 0.065078] platform rtc_cmos: registered platform RTC device (no PNP device found)
[ 0.066355] Scanning for low memory corruption every 60 seconds
[ 0.067405] audit: initializing netlink subsys (disabled)
[ 0.068544] Initialise system trusted keyrings
[ 0.069209] Key type blacklist registered
[ 0.069835] audit: type=2000 audit(1545200009.190:1): state=initialized audit_enabled=0 res=1
[ 0.071123] workingset: timestamp_bits=36 max_order=15 bucket_order=0
[ 0.073349] squashfs: version 4.0 (2009/01/31) Phillip Lougher
[ 0.076572] Key type asymmetric registered
[ 0.077207] Asymmetric key parser 'x509' registered
[ 0.077957] Block layer SCSI generic (bsg) driver version 0.4 loaded (major 254)
[ 0.079093] io scheduler noop registered (default)
[ 0.079851] io scheduler cfq registered
[ 0.080514] virtio-mmio virtio-mmio.0: Failed to enable 64-bit or 32-bit DMA. Trying to continue, but this might not work.
[ 0.082293] Serial: 8250/16550 driver, 1 ports, IRQ sharing disabled
[ 0.105181] serial8250: ttyS0 at I/O 0x3f8 (irq = 4, base_baud = 115200) is a U6_16550A
[ 0.108105] loop: module loaded
[ 0.109179] tun: Universal TUN/TAP device driver, 1.6
[ 0.109983] hidraw: raw HID events driver (C) Jiri Kosina
[ 0.110833] nf_conntrack version 0.5.0 (1024 buckets, 4096 max)
[ 0.111807] ip_tables: (C) 2000-2006 Netfilter Core Team
[ 0.112658] Initializing XFRM netlink socket
[ 0.113368] NET: Registered protocol family 10
[ 0.114475] Segment Routing with IPv6
[ 0.115040] NET: Registered protocol family 17
[ 0.115702] Bridge firewalling registered
[ 0.116372] sched_clock: Marking stable (116307089, 0)->(207374918, -91067829)
[ 0.117622] registered taskstats version 1
[ 0.118240] Loading compiled-in X.509 certificates
[ 0.119720] Loaded X.509 cert 'Build time autogenerated kernel key: 3472798b31ba23b86c1c5c7236c9c91723ae5ee9'
[ 0.121258] zswap: default zpool zbud not available
[ 0.121987] zswap: pool creation failed
[ 0.122679] Key type encrypted registered
[ 0.133645] EXT4-fs (vda): recovery complete
[ 0.134339] EXT4-fs (vda): mounted filesystem with ordered data mode. Opts: (null)
[ 0.135455] VFS: Mounted root (ext4 filesystem) on device 254:0.
[ 0.138045] devtmpfs: mounted
[ 0.139258] Freeing unused kernel memory: 1268K
[ 0.148094] Write protecting the kernel read-only data: 12288k
[ 0.150612] Freeing unused kernel memory: 2016K
[ 0.153638] Freeing unused kernel memory: 584K
2018-12-19T06:13:29.354949870 [anonymous-instance:WARN:vmm/src/lib.rs:945] Guest-boot-time = 353416 us 353 ms, 286617 CPU us 286 CPU ms
OpenRC init version 0.35.5.87b1ff59c1 starting
Starting sysinit runlevel
OpenRC 0.35.5.87b1ff59c1 is starting up Linux 4.14.55-84.37.amzn2.x86_64 (x86_64)
* Mounting /proc ...
[ ok ]
* Mounting /run ...
* /run/openrc: creating directory
* /run/lock: creating directory
* /run/lock: correcting owner
* Caching service dependencies ...
Service `hwdrivers' needs non existent service `dev'
[ ok ]
Starting boot runlevel
* Remounting devtmpfs on /dev ...
[ ok ]
* Mounting /dev/mqueue ...
[ ok ]
* Mounting /dev/pts ...
[ ok ]
* Mounting /dev/shm ...
[ ok ]
* Setting hostname ...
[ ok ]
* Checking local filesystems ...
[ ok ]
* Remounting filesystems ...
[ ok ][ 0.353469] random: fast init done
* Mounting local filesystems ...
[ ok ]
* Loading modules ...
modprobe: can't change directory to '/lib/modules': No such file or directory
modprobe: can't change directory to '/lib/modules': No such file or directory
[ ok ]
* Mounting misc binary format filesystem ...
[ ok ]
* Mounting /sys ...
[ ok ]
* Mounting security filesystem ...
[ ok ]
* Mounting debug filesystem ...
[ ok ]
* Mounting SELinux filesystem ...
[ ok ]
* Mounting persistent storage (pstore) filesystem ...
[ ok ]
Starting default runlevel
[ 1.088047] clocksource: tsc: mask: 0xffffffffffffffff max_cycles: 0x2127428d09e, max_idle_ns: 440795284093 ns
Welcome to Alpine Linux 3.8
Kernel 4.14.55-84.37.amzn2.x86_64 on an x86_64 (ttyS0)
localhost login:
rootfsに検証用のhello-rootfs.ext4
を使っていたらID/PWはroot
/root
でログインできます。
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[856]: root login on 'ttyS0'
localhost:~#
ログイン完了です!
後は普通にVMをいじれます!
ゲストマシンをシャットダウン
用が終わったVMはシャットダウンしましょう。
VM内でreboot
コマンドを実行するとシャットダウンします。違和感しか感じません。
一応、開発メンバーの言い分としては、microVMというのは再起動を想定した設計になっていないから、かつ、Firecrackerはゲストマシンの電源管理についての実装がないから、とのことです。
ということで、reboot
コマンドでgracefulにシャットダウンしてあげることとします。
localhost:~# reboot
おわりに
こんな感じで簡単かつ高速にVMが立ち上がって便利です!
新たにVMホストサーバを構築しようとしていて、短いライフサイクルのVMを高速にたくさん立てたいなんて要件があれば、Firecrackerの導入を検討するのも良いかもしれないですね。
とりあえず使ってみて最も感じたのは、RESTでVMの操作ができて魅力的ということですね!
RESTでの操作を基本にしているので、応用範囲が広そうだなあと思います。
あとは、ベアメタルインスタンスだと強力なスペックなので面白みがなさそうですが、ヒートランテストのような性能テストなんかもしてみるとほかのハイパーバイザとの違いなんかももっと分かってきて面白いのかなあと思いました。
そして何より、先述の通りですが今後コンテナ系技術のコミュニティと融合していくプランもあるようなので、これからの動きにも注目ですね!