💡

Dockerはもう古い?Unikraftで普通のバイナリを『OSごと』10〜100ms秒で起動させる新常識」

に公開

はじめに

コンテナは便利だけど、実は「重い」し「OSのオーバーヘッド」がいっぱい。
「ユニカーネル」って聞いたことあるけど、ソース改造が必要でしょ? → 「いいえ、今はもう違います」。
普通のLinuxバイナリをそのままOSとして動かせる Unikraft の最新事情を紹介します。

Unikraftの「最強」なポイント(3つの「ない」)

Unikraftの利点として、以下があげられます。

  • 無駄がない: イメージサイズが数MB。メモリ消費も極小。
  • 待たない: 起動時間は10〜100ms。もはやサーバーレスの理想形。
  • 隙がない: シェル(sh)もコマンド(ls)もない。侵入されても攻撃者が何も実行できない最強のセキュリティ
比較項目 Docker (Linuxコンテナ) Unikraft (Unikernel) 備考
起動時間 (Cold Boot) 約 300ms 〜 2s 約 10ms 〜 100ms UnikraftはOS起動=アプリ起動
イメージサイズ 5MB 〜 500MB 約 100KB 〜 数MB 最小構成ならフロッピーに収まる
実行時メモリ (最小) 約 10MB 〜 約 1MB 以下 アプリ以外の無駄なメモリ消費がない
システムコール 低速 (特権切替あり) 高速 (関数呼出しのみ) セキュリティ境界の越境がない
攻撃対象領域 広い (Linux全機能) 極小 (必要機能のみ) シェルすらないので乗っ取り困難

Geminiが出力したコンテナとの比較表

一方、これまでのunikraftでは、動作させるアプリをOS専用に再コンパイルする必要があり、大変ハードルが高かった。これが導入の障害となってきた面があります。

アプリ毎の対応を不要とする”elfloader"

Unikraftでは今、OS相当部分をelfloaderとして分離、これを使用することでLinuxのバイナリをそのまま読み込んで動作させる方法が導入されています(バイナリ互換モード)。これにより従来方式のハードルがクリアされた状況になりつつあります。

  • 昔のやり方:アプリをOS専用に再コンパイル(大変、コスパ悪い)。
  • 今のやり方:elfloaderを使う。

仕組みは単純:Linuxのバイナリを読み込んで、Unikraftがシステムコールを肩代わりするだけ。これによりUnikraftのメリットをコスト0で享受できるようになりつつあるのです。

再コンパイル方式とelfloader方式の比較
elfloaderの特徴を以下に挙げておきます。

  • Linuxバイナリ(elfファイル)をVMM上で直接動作させるための、ごく薄いレイヤーとなる。
  • 実装は1つでどのLinuxバイナリに対しても適用可能。
  • 動作させるLinuxバイナリはmuslライブラリでstaticコンパイルされていることが望ましい。
    (Alpine Linux等で使用されているものと同様)

コンテナと”elfloader"

上記の特徴より、elfloaderはコンテナに対する”より小さく”、”より速く”、”よりセキュアな” 代替として機能しうるということになります。以下に示すようにLinux APPをそのままにUnikraftの利点を享受できるのです。(起動時に最も時間を消費し、メモリも消費するOS部分を”中抜き”できる!)

コンテナとの構成比較
Unikraft側もこれを強く意識した構成となっており、既存のDockerfileに対してKraftfileといわれる設定を追加する形で環境を作成するようになっています。(Alpine Linux用のDockerfileがあるとよい)

実際にやってみた(実践編)

ここでUnikraft、特にelfloaderに関連したインストールと動作確認を実施してみたいと思います。
Unikraftのインストールは以下でOK

curl -sSfL https://get.kraftkit.sh | sh

次に動作確認用のQEMU/Dockerの準備

# QEMUのインストール
sudo apt-get update
sudo apt-get install -y build-essential libncurses-dev libssl-essential flex bison qemu-system-x86 qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils

# Dockerのインストール
sudo apt-get update
sudo apt-get install -y docker.io

# Dockerサービスを起動
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -aG docker $USER
newgrp docker

これで環境は準備できたはずです。
次にサンプルプログラムを以下で取得します。

git clone https://github.com/unikraft/catalog.git

サンプルプログラムは多岐にわたりますが、以下が今回注目しているelfloaderを使った最も基本的なサンプルとなります。そしてelfloaderを使用した他のサンプルもここを参照することとなります。(最重要サンプル)

cd catalog/library/base/

Unikraftでは環境構築/実行にはKraftコマンドを使用することとなっています。
ここで”kraft build”によるqemu実行用の環境構築を実施し、

$ kraft build
[?] select target:
    base (fc/x86_64)
  ▸ base (qemu/x86_64)

”kraft run -W.”による実行を行うと、

$ kraft run -W .

以下の出力が得られるはずです。

 i  using arch=x86_64 plat=qemu
[+] building rootfs via Dockerfile... done!                                                                                                     x86_64 [40.5s]
Powered by Unikraft Kiviuq (0.20.0~667ac92)
  .--------------------.
 ( Hello from Unikraft! )
  '--------------------'
         \
          \
             _
           c'o'o  .--.
           (| |)_/


This is the default message when no root filesystem is supplied to this
'base' image.  This 'base' image is a general-purpose unikernel runtime
and is intended to be used for your application.

To use this 'base' image, place a `Kraftfile` in your application
repository.  For example:

        ```yaml
        spec: v0.6
        runtime: unikraft.org/base:latest
        rootfs: ./Dockerfile
        ```

In the above example, you can supply this 'base' image with your own 
root filesystem.  This filesystem is defined through the `Dockerfile`.
Once setup, simply call:

        ```bash
        kraft run .
        ```

For more information, how to get started and examples using this 'base'
image, please visit:

        https://unikraft.org/guides/intro-to-base-image

Happy krafting!

さて、ここで実際のelfloaderとLinuxバイナリのイメージを見てみます。kraft build実行時にカレントディレクトリへ.unikraftが生成され、そこに格納されます。

$ file .unikraft/build/base_qemu-x86_64
.unikraft/build/base_qemu-x86_64: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, stripped

上記がelfloader相当のファイルとなります。また、

cpio -itv < .unikraft/build/initramfs-x86_64.cpio
-rwxr-xr-x   1 root     root       999816 Jan  1 15:33 ./fallback

上記が起動時に参照されるファイルシステムとなり、中にfallbackという実行ファイルが格納されていることがわかります。
このようにelfloaderとLinuxバイナリが別々に生成されていることが確認できます。qemuはこのelfloaderをカーネル、cpioファイルをファイルシステムとして起動します。

さて、ここでこのbaseで作成されるelfloaderを使用して別のLinuxバイナリを動作させてみることとします。サンプルとしてgoによる簡易Httpサーバを考えてみます。
まずはKraftfileとして以下を準備します。runtime: base:latest とすることで、baseで作成したelfloaderをカーネルとして使用することを指定します。

Kraftfile
spec: v0.6
name: go-unikernel
runtime: base:latest
rootfs: ./Dockerfile
cmd: ["/main"]

次に動作させるLinuxバイナリを作成するためのDockerfileを準備します。こちらはLinuxバイナリコンパイル時にmuslライブラリによるstaticコンパイルを実施することを記載することが重要です。

Dockerfile
FROM golang:1.23-alpine AS builder

# ビルドに必要なツールをインストール
RUN apk add --no-cache gcc musl-dev

WORKDIR /src
COPY main.go .

# ポイント:
# 1. -buildmode=pie で位置独立を有効化
# 2. -linkmode external で GCC にリンクを任せる
# 3. -extldflags "-static-pie" これが最重要。Static と PIE を両立させる魔法のフラグ
RUN CGO_ENABLED=1 go build \
    -buildmode=pie \
    -ldflags "-linkmode external -extldflags '-static-pie -s -w'" \
    -o /main main.go

FROM scratch
COPY --from=builder /main /main

最後にgoによる簡易Httpサーバを準備します。

main.go
package main

import (
        "fmt"
        "log"
        "net/http"
        "time"
)

func main() {
        // 起動時刻の記録(Unikraftがいかに速いか見せるため)
        fmt.Printf("Go application started at: %v\n", time.Now().Format(time.RFC3339Nano))

        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
                fmt.Fprintf(w, "Hello! You are accessing a Go app running on Unikernel (Unikraft)!\n")
                log.Printf("Received request: %s %s", r.Method, r.URL.Path)
        })

        fmt.Println("Server starting on :8080...")
        if err := http.ListenAndServe(":8080", nil); err != nil {
                log.Fatal(err)
        }
}

ファイルの準備ができたら、ビルドと実行になります。まずはビルドでqemu動作を選択し、

$ kraft build
[?] multiple runtimes available
    unikraft.org/base:latest (fc/x86_64)
  ▸ unikraft.org/base:latest (qemu/x86_64)

以下で実行します。

$ kraft run -W .
[+] pulling unikraft.org/base:latest (qemu/x86_64) •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• 100% [15.1s]
 i  using arch=x86_64 plat=qemu
[+] building rootfs via Dockerfile... done!                                                                                                      x86_64 [3.0s]
Powered by Unikraft Kiviuq (0.20.0~667ac92)
Go application started at: 2026-01-01T16:29:09.141341784Z
Server starting on :8080...

上記よりbase:latestのelfloaderを使用したサーバの動作が確認できました。
このように、既存のDokerfileに対してelfloaderを使用するKraftfileを作成するだけでUnikraftによる動作環境が完成します。

向いていること・向いていないこと

上記のように簡単に導入できるUnikraftですが、環境構築上の制約、システムコール対応が完全でないことから、以下のような向き・不向きがあります。

  • 向いている: Go/Rustなどの静的バイナリ、マイクロサービス、サーバーレス、エッジ。
  • 向いていない: まだ対応していない複雑なシステムコールが必要な巨大アプリ(Javaなど)。

まとめ:2025年はユニカーネル「再評価」の年

Unikernel関連の日本語情報が少ないのは、今がまさに「最先端」だから。
今回記事を作成するにあたって調べましたが以下の記事が近年では唯一と言っていいほどで、使える日本語情報はあまりありませんでした。(記事は大変参考になりました。ありがとうございました。)
https://zenn.dev/zenogawa/articles/try_unikraft
このため、コード作成と確認には、grok/geminiには本当にお世話になりました。

  • gemini:難しいNativeビルドは後回しでOK。まずは base:latest で恩恵を受けよう。

とのことです。

追記と考察

今回記載したのはUnikraftのサーバサイドでの活用の内容となります。Unikraft自体もこの利用方法を想定しているようですが、個人的には端末側、Chromebookでの活用ができるのではないかと考えています。ChromebookにはcrosvmというVMMが実装されており、ここでUnikraftを動作させることができれば、セキュアで軽い最高の実行環境になるのではと考えています。(crosvm由来のfirecrackerでは動作しており、基本的には動作するはずです)
これに関しては、時間があるときに確認していきたいと考えています。

Discussion