LinuxKit meets AWS

AWS

本記事は、Docker Weekly #190 の Newsletter に掲載されていた下記ブログポストをベースに執筆しております。

はじめに

そもそも LinuxKit をご存知という方が現状は少ないかもしれませんので少し紹介します。 LinuxKit とは、DockerCon 2017 で発表され GitHub 上で OSS として公開されている新しい Linux ディストリビューションです。

下記の資料が日本語訳されており、読みやすかったので興味があればご一読ください。

なお、LinuxKit をさらっと紹介すると以下のとおりです。

  • いくつかの企業(HPE、Intel、ARM、IBM、Microsoft)および Linux Foundation と Docker が提携し構築
  • 安全でリーンでポータブルなOSの目標を達成するために、コンテナネイティブに設計されている
  • コンテナネイティブで、全てのシステムサービスはコンテナ内で動作する。システム全体が immutable infrastructure

詳細については、以下を参照ください。

ここまで読まれて少し興味を持って頂けたなら、AWS 上で動作する LinuxKit AMI イメージが作成出来ますので 最後までお付き合い頂けますと幸いです。

AWS EC2 上で動作する LinuxKit イメージの構築

構築手順は、以下のとおりです。

  1. macOS で LinuxKit をビルドする
  2. LinuxKit の RAW イメージを作成する
  3. LinuxKit RAW イメージを S3 バケットにアップロードする
  4. LinuxKit RAW イメージから EC2 スナップショットとしてインポートする
  5. EC2 スナップショットから AMI を作成する
  6. LinuxKit AMI の EC2 インスタンスを起動する
  7. SSH ログインし、動作確認

なお、筆者が利用している PC 環境は、以下のとおりです。

  • MacBook Pro (Retina, 13-inch, Early 2015)
  • macOS Sierra 10.12.5

順を追って、説明します。

本記事の手順を実施するにあたって、以下のツールを導入しておく必要があるとのこと。

1. macOS で LinuxKit をビルドする

まずは、linuxkit のリポジトリからソースを clone します。

$ mkdir ~/github
$ cd ~/github
$ git clone https://github.com/linuxkit/linuxkit
$ cd linuxkit

次に LinuxKit をビルドします。

 ~/github/linuxkit $ make clean
rm -rf bin *.log *-kernel *-cmdline *-state *.img *.iso *.gz *.qcow2 *.vhd *.vmx *.vmdk *.tar 
 ~/github/linuxkit $ make && make install
mkdir -p bin
docker run --rm --log-driver=none -e GOOS=darwin linuxkit/go-compile:6579a00b44686d0e504d513fc4860094769fe7df --clone-path github.com/moby/tool --clone https://github.com/moby/tool.git --commit e0aac90f4476c7dadfec9ab4116cf1363e51ce77 --package github.com/moby/tool/cmd/moby --ldflags "-X main.GitCommit=dfb4f292d33c4a5daa725c481a415f8e7b5da9c0 -X main.Version="0.0" " -o bin/moby > tmp_moby_bin.tar
Unable to find image 'linuxkit/go-compile:6579a00b44686d0e504d513fc4860094769fe7df' locally
6579a00b44686d0e504d513fc4860094769fe7df: Pulling from linuxkit/go-compile
7bee6021d73b: Pulling fs layer
bc2351846d69: Pulling fs layer
f445d9c10f23: Pulling fs layer
f445d9c10f23: Verifying Checksum
...

ビルドが成功していることを確認します。

 ~/github/linuxkit $ ls -al ./bin/
total 151472
drwxr-xr-x   5 shimoda.yuji  staff       170  6 16 09:25 .
drwxr-xr-x  31 shimoda.yuji  staff      1054  6 16 09:25 ..
-rwxr-xr-x   1 shimoda.yuji  staff  54679728  6 16 09:24 linuxkit
-rwxr-xr-x   1 shimoda.yuji  staff  12907248  6 16 09:24 moby
-rwxr-xr-x   1 shimoda.yuji  staff   9960064  6 16 09:25 rtf
 ~/github/linuxkit $ linuxkit version
linuxkit version 0.0
commit: dfb4f292d33c4a5daa725c481a415f8e7b5da9c0
 ~/github/linuxkit $ moby version
moby version 0.0
commit: dfb4f292d33c4a5daa725c481a415f8e7b5da9c0

2. LinuxKit の RAW イメージを作成する

現状のバージョンではまず、QEMU で利用される qcow2 仮想ディスクを作成し qemu-img コマンドで RAW イメージへコンバートする必要があるようです。(いずれ、直接 RAW イメージを作成する機能が提供される予定とのこと) また、作成される LinuxKit の RAW イメージについては aws.yml ファイルに従って作成されるようです。

以下は、examples/aws.yml です。

kernel:
  image: "linuxkit/kernel:4.9.x"
  cmdline: "console=ttyS0 page_poison=1"
init:
  - linuxkit/init:2599bcd5013ce5962aa155ee8929c26160de13bd
  - linuxkit/runc:3a4e6cbf15470f62501b019b55e1caac5ee7689f
  - linuxkit/containerd:b50181bc6e0084e5fcd6b6ad3cf433c4f66cae5a
  - linuxkit/ca-certificates:75cf419fb58770884c3464eb687ec8dfc704169d
onboot:
  - name: sysctl
    image: "linuxkit/sysctl:3aa6bc663c2849ef239be7d941d3eaf3e6fcc018"
  - name: dhcpcd
    image: "linuxkit/dhcpcd:7d2b8aaaf20c24ad7d11a5ea2ea5b4a80dc966f1"
    command: ["/sbin/dhcpcd", "--nobackground", "-f", "/dhcpcd.conf", "-1"]
  - name: metadata
    image: "linuxkit/metadata:31a0b0f5557c6123beaa9c33e3400ae3c03447e0"
services:
  - name: rngd
    image: "linuxkit/rngd:1fa4de44c961bb5075647181891a3e7e7ba51c31"
  - name: sshd
    image: "linuxkit/sshd:abc1f5e096982ebc3fb61c506aed3ac9c2ae4d55"
    binds:
     - /var/config/ssh/authorized_keys:/root/.ssh/authorized_keys
  - name: nginx
    image: "nginx:alpine"
    capabilities:
     - CAP_NET_BIND_SERVICE
     - CAP_CHOWN
     - CAP_SETUID
     - CAP_SETGID
     - CAP_DAC_OVERRIDE
trust:
  org:
    - linuxkit
  image:
    - nginx:alpine

起動するサービスとして、rngd や sshd および nginx が指定されていることが確認できます。

それでは、qcow2 イメージを作成します。

 ~/github/linuxkit $ moby build -output qcow2 examples/aws.yml
Building LinuxKit image mkimage to generate output formats
Extract kernel image: linuxkit/kernel:4.9.x
Pull image: docker.io/linuxkit/kernel:4.9.x@sha256:1631c1ceea2007b308ae4efeee9179c1e7d578a5dedab54fbcc64746532f2828
Add init containers:
Process init image: linuxkit/init:1b8a7e394d2ec2f1fdb4d67645829d1b5bdca037
Pull image: docker.io/linuxkit/init:1b8a7e394d2ec2f1fdb4d67645829d1b5bdca037@sha256:8e80db5f2ccd7214ccbee3ea1d2cf3b844d668676a2352c3739fa891942fe1df
Process init image: linuxkit/runc:3a4e6cbf15470f62501b019b55e1caac5ee7689f
...
 ~/github/linuxkit $ ls -al aws.*
-rw-r--r--  1 shimoda.yuji  staff  70189056  6 16 09:38 aws.qcow2

次に、qemu-img コマンドが利用出来るように Docker イメージを作成します。

 ~ $ mkdir -p ~/docker/qemu-img
 ~ $ cd ~/docker/qemu-img
 ~/docker/qemu-img $ cat > Dockerfile <<EOF
> FROM alpine:3.6
> RUN apk add --no-cache qemu-img
> ENTRYPOINT ["/usr/bin/qemu-img"]
> EOF
 ~/docker/qemu-img $ docker build -t qemu-img .
Sending build context to Docker daemon 2.048 kB
Step 1/3 : FROM alpine:3.6
3.6: Pulling from library/alpine
...
 ~/docker/qemu-img $ alias qemu-img='docker run --rm -ti -v $(pwd):$(pwd) -w $(pwd) qemu-img'

最後に、qcow2 から raw へ変換します。

 ~/github/linuxkit $ qemu-img info aws.qcow2 
image: aws.qcow2
file format: qcow2
virtual size: 1.0G (1073741824 bytes)
disk size: 67M
cluster_size: 65536
Format specific information:
    compat: 1.1
    lazy refcounts: false
    refcount bits: 16
    corrupt: false
 ~/github/linuxkit $ qemu-img convert aws.qcow2 aws.raw
 ~/github/linuxkit $ qemu-img info aws.raw 
image: aws.raw
file format: raw
virtual size: 1.0G (1073741824 bytes)
disk size: 1.0G
 ~/github/linuxkit $ 

RAW イメージが作成されました。

3. LinuxKit RAW イメージを S3 バケットにアップロードする

今回は、linuxkit バケットを作成し aws.raw イメージファイルをアップロードしました。

 ~/github/linuxkit $ aws s3 mb s3://linuxkit
make_bucket: linuxkit
 ~/github/linuxkit $ aws s3 cp aws.raw s3://linuxkit
upload: ./aws.raw to s3://linuxkit/aws.raw                        
 ~/github/linuxkit $ aws s3 ls s3://linuxkit/aws.raw
2017-06-16 09:51:15 1073741824 aws.raw
 ~/github/linuxkit $ 

4. LinuxKit RAW イメージから EC2 スナップショットとしてインポートする

ここでは、S3 にアップロードした RAW イメージファイルを元に、EC2 スナップショットとしてインポートします。

まずは、json ファイルを作成します。

 ~/github/linuxkit $ cat > container.json <<EOF
> {
>     "Description": "LinuxKit AWS",
>     "Format": "raw",
>     "UserBucket": {
>         "S3Bucket": "linuxkit",
>         "S3Key": "aws.raw"
>     }
> }
> EOF
 ~/github/linuxkit $

次に、インポートします。

 ~/github/linuxkit $ aws ec2 import-snapshot --description "LinuxKit AWS" --disk-container file://container.json
 {
    "SnapshotTaskDetail": {
        "Status": "active", 
        "Description": "LinuxKit AWS", 
        "Format": "RAW", 
        "DiskImageSize": 0.0, 
        "Progress": "3", 
        "UserBucket": {
            "S3Bucket": "linuxkit", 
            "S3Key": "aws.raw"
        }, 
        "StatusMessage": "pending"
    }, 
    "Description": "LinuxKit AWS", 
    "ImportTaskId": "import-snap-fgtkq76n"
}

出力結果の ImportTaskId を元に、スナップショットタスクの進行状況を確認できます。

 ~/github/linuxkit $ aws ec2 describe-import-snapshot-tasks --import-task-ids import-snap-fgtkq76n
{
    "ImportSnapshotTasks": [
        {
            "SnapshotTaskDetail": {
                "Status": "completed", 
                "Description": "LinuxKit AWS", 
                "Format": "RAW", 
                "DiskImageSize": 1073741824.0, 
                "SnapshotId": "snap-061ecf2458d05eadf", 
                "UserBucket": {
                    "S3Bucket": "linuxkit", 
                    "S3Key": "aws.raw"
                }
            }, 
            "Description": "LinuxKit AWS", 
            "ImportTaskId": "import-snap-fgtkq76n"
        }
    ]
}
 ~/github/linuxkit $ 

Status が completed になっているので、インポートは完了済みです。

もしも、import-snapshot コマンド実行時に以下のエラーが発生した場合は、以下のステップを実行して作業を進めてください。(インポートが完了している読者は、下記の内容をスキップしステップ5へ)

An error occurred (InvalidParameter) when calling the ImportSnapshot operation: The sevice role <vmimport> does not exist or does not have sufficient permissions for the service to continue

json ファイルを作成する。

 ~/github/linuxkit $ cat > trust-policy.json <<EOF
> {
>    "Version": "2012-10-17",
>    "Statement": [
>       {
>          "Effect": "Allow",
>          "Principal": { "Service": "vmie.amazonaws.com" },
>          "Action": "sts:AssumeRole",
>          "Condition": {
>             "StringEquals":{
>                "sts:Externalid": "vmimport"
>             }
>          }
>       }
>    ]
> }
> EOF

次に、IAM ロールを作成します。

 ~/github/linuxkit $ aws iam create-role --role-name vmimport --assume-role-policy-document file://trust-policy.json

最後に、linuxkit バケットへのアクセスを許可しておきます。

 ~/github/linuxkit $ cat > role-policy.json <<EOF
> {
>    "Version": "2012-10-17",
>    "Statement": [
>       {
>          "Effect": "Allow",
>          "Action": [
>             "s3:ListBucket",
>             "s3:GetBucketLocation"
>          ],
>          "Resource": [
>             "arn:aws:s3:::linuxkit"
>          ]
>       },
>       {
>          "Effect": "Allow",
>          "Action": [
>             "s3:GetObject"
>          ],
>          "Resource": [
>             "arn:aws:s3:::linuxkit/*"
>         ]
>       },
>       {
>          "Effect": "Allow",
>          "Action":[
>             "ec2:ModifySnapshotAttribute",
>             "ec2:CopySnapshot",
>             "ec2:RegisterImage",
>             "ec2:Describe*"
>          ],
>          "Resource": "*"
>       }
>    ]
> }
> EOF
~/github/linuxkit $ aws iam put-role-policy --role-name vmimport --policy-name vmimport --policy-document file://role-policy.json

再度、インポートしてください。

 ~/github/linuxkit $ aws ec2 import-snapshot --description "LinuxKit AWS" --disk-container file://container.json

5. EC2 スナップショットから AMI を作成する

EC2 スナップショットから、AMI を作成します。

 ~/github/linuxkit $ aws ec2 register-image \
> --name 'LinuxKit AWS AMI' \
> --architecture x86_64 \
> --virtualization-type hvm \
> --root-device-name /dev/sda1 \
> --block-device-mappings \
> '[{"DeviceName": "/dev/sda1", "Ebs": {"SnapshotId": "snap-061ecf2458d05eadf", "VolumeType": "gp2"}}]'
{
    "ImageId": "ami-1cd2da7b"
}
 ~/github/linuxkit $ 

6. LinuxKit AMI の EC2 インスタンスを起動する

AMI が作成されたので、後は EC2 インスタンスを起動するだけですね。 AWS CLI およびマネジメントコンソールいずれか好みの手順でインスタンスを起動してください。

ここでは、ブログポストに従い AWS CLI でインスタンスを起動します。

まずは、TCP/22 および TCP/80 のインバウンドを許可するセキュリティグループを作成します。

 ~/github/linuxkit $ aws ec2 create-security-group --group-name linuxkit-sg --description "Security group for LinuxKit in EC2"
{
    "GroupId": "sg-30d90956"
}
 ~/github/linuxkit $ aws ec2 authorize-security-group-ingress --group-name linuxkit-sg --protocol tcp --port 22 --cidr 0.0.0.0/0
 ~/github/linuxkit $ aws ec2 authorize-security-group-ingress --group-name linuxkit-sg --protocol tcp --port 80 --cidr 0.0.0.0/0

キーペアについては、既に既存のキーペアを利用するため割愛させていただきます。まだ、キーペアを作成されていない方については下記のドキュメントをご参照ください。

それでは、インスタンスを起動しましょう。

 ~/github/linuxkit $ aws ec2 run-instances --image-id ami-1cd2da7b --count 1 --instance-type t2.micro \
> --security-group-ids sg-30d90956 --key-name HL00213.local
...

7. SSH ログインし、動作確認

まずは、SSH でログインしてみます。ログインユーザーは、root

 ~/github/linuxkit $ ssh root@ec2-52-199-113-247.ap-northeast-1.compute.amazonaws.com
The authenticity of host 'ec2-52-199-113-247.ap-northeast-1.compute.amazonaws.com (52.199.113.247)' can't be established.
ECDSA key fingerprint is SHA256:UMf772t/jp87dwsjsHhM1cAn+Ek+aSfcNwjYFMb8v/A.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'ec2-52-199-113-247.ap-northeast-1.compute.amazonaws.com,52.199.113.247' (ECDSA) to the list of known hosts.
Welcome to LinuxKit 
ip-172-31-29-175:~# 

ログイン出来ました。 SSH ログイン後、プロセスの状況やディスクの使用状況を確認すると、以下のとおりでした。

ip-172-31-29-175:~# pstree
init---010-containerd-+-containerd-+-containerd-shim---nginx---nginx
                      |            |-containerd-shim---tini---rngd
                      |            `-containerd-shim---tini---sshd---sshd---ash---pstree
                      `-3*[ctr]v
ip-172-31-29-175:~# df -m
Filesystem           1M-blocks      Used Available Use% Mounted on
tmpfs                      495       142       353  29% /
tmpfs                       64         0        64   0% /dev
tmpfs                      495         0       495   0% /sys/fs/cgroup
tmpfs                      495       142       353  29% /root/.ssh/authorized_keys

init プロセスから containerd プロセスが起動し、各コンテナが実行されているようです。 また、全てが tmpfs で構成されており、まさに immutable infrastructure という感じを覚えました。

コンテナの起動状況を確認するには、namespace 内に入る必要があるみたいです。

ip-172-31-29-175:~# nsenter -t 1 -m -u -n -i ash
ip-172-31-29-175:/# runc list
ID          PID         STATUS      BUNDLE                        CREATED                          OWNER
nginx       524         running     /run/containerd/linux/nginx   2017-06-16T01:36:13.400674363Z   root
rngd        572         running     /run/containerd/linux/rngd    2017-06-16T01:36:13.447738335Z   root
sshd        604         running     /run/containerd/linux/sshd    2017-06-16T01:36:13.485589128Z   root

すでに nginx が起動しており、ブラウザからも確認することが出来ました。

最後に

今回はじめて LinuxKit を触ることが出来ました。

LinuxKit を触り始めて1時間も経っておりませんが、 本当の意味で、コンテナを起動するためだけに作成されている Linux ディストリビューションという印象を受けました。

また、コンテナ特化型 Linux ディストリビューションとされる CoreOS(Container Linux) なども、コンテナを動作させる機能以外で 不要なものは極力入れないという印象でしたが、LinuxKit と比較すると それなりの自由度はある程度確保されているんだなという印象を、改めて感じました。

ここまで潔い Linux ディストリビューションは始めてであり、凄く興味が湧いたため もう少し LinuxKit と戯れたいと思います。

ではでは