壊して理解する Docker のポータビリティ

Docker のポータビリティにはいくつか注意点がある。

そこで今回は、壊しながらポータビリティについて理解する。

コンテナ型の仮想化

KVM などの仮想化技術(CPU などのハードウェア含めて完全に仮想化する)と異なり、コンテナはメモリやファイルシステムなどのリソースを隔離することで独立した空間を用意している。 このおかげで起動が早かったりリソース効率が良かったりする。

ユーザ空間のソフトウェアはコンテナごとに用意し、ホストのカーネルは共有するイメージ。

f:id:kimulla:20210612232511p:plain

システムコールについては以前に調べたので省略する。

www.kimullaa.com

ファイルシステムやプロセスがホストと独立しているので、ホストとは異なるディストリビューションを実行できる。例えば、ホストに Ubuntu 20.04LTS を利用していても、コンテナ上では alpine を実行できる。

# docker run alpine cat /etc/os-release
Unable to find image 'alpine:latest' locally
latest: Pulling from library/alpine
540db60ca938: Pull complete
Digest: sha256:69e70a79f2d41ab5d637de98c1e0b055206ba40a8145e7bddb55ccc04e13cf8f
Status: Downloaded newer image for alpine:latest
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.13.5
PRETTY_NAME="Alpine Linux v3.13"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://bugs.alpinelinux.org/"

ポータビリティがないもの

CPU アーキテクチャの異なるコンテナ

カーネル以降のハードウェアは共有してるので、コンテナイメージのバイナリに含まれる命令セットがホストと異なると実行できない。たとえば alpine の arm 用のイメージを実行すると、エラーになる。

# docker run alpine@sha256:9663906b1c3bf891618ebcac857961531357525b25493ef717bca0f86f581ad6 cat /etc/os-release
Unable to find image 'alpine@sha256:9663906b1c3bf891618ebcac857961531357525b25493ef717bca0f86f581ad6' locally
docker.io/library/alpine@sha256:9663906b1c3bf891618ebcac857961531357525b25493ef717bca0f86f581ad6: Pulling from library/alpine
e160e00eb35d: Pull complete
Digest: sha256:9663906b1c3bf891618ebcac857961531357525b25493ef717bca0f86f581ad6
Status: Downloaded newer image for alpine@sha256:9663906b1c3bf891618ebcac857961531357525b25493ef717bca0f86f581ad6
WARNING: The requested image's platform (linux/arm) does not match the detected host platform (linux/amd64) and no specific platform was requested
standard_init_linux.go:219: exec user process caused: exec format error

※ タグ名を指定して pull すると、適切な CPU アーキテクチャのイメージをダウンロードしてくれるが、今回は実験のために sha256 を直接指定している。

Linux ではないコンテナ

ホストのカーネルを共有するので、Linux カーネル上で動作しないコンテナは実行できない。たとえば windows 用のイメージを実行すると、エラーになる。

# docker run python@sha256:4b5561adc10048ccbe95fdfef486d4a4bb10c7424a347ed9441768d534965098
Unable to find image 'python@sha256:4b5561adc10048ccbe95fdfef486d4a4bb10c7424a347ed9441768d534965098' locally
docker.io/library/python@sha256:4b5561adc10048ccbe95fdfef486d4a4bb10c7424a347ed9441768d534965098: Pulling from library/python
3889bb8d808b: Pulling fs layer
2f52abeee6d6: Pulling fs layer
c82e82e95dba: Pulling fs layer
43dfa55ab4db: Waiting
c630a7e18920: Waiting
7a6418955f2d: Waiting
25623f3f96a5: Waiting
6e43e384dc88: Waiting
19aef2ea307a: Waiting
213dd8e728a1: Waiting
0387077c68d3: Waiting
239fddfee327: Waiting
docker: image operating system "windows" cannot be used on this platform.

※ イメージダウンロード時およびコンテナ実行時に OS のチェックをしてくれるので、実行前にエラーになる。

新しすぎるシステムコールを利用するコンテナ

コンテナ作成環境がコンテナ実行環境よりも新しい場合、問題になる場合がある。

最新のシステムコール(今回は v5.3 から追加された pidfd_open。新しすぎて glibc のラッパーがないので syscall(2) で呼び出す)を利用するアプリケーションを用意し、

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/syscall.h>

int main(void) {
        pid_t fd;

        fd = fork();
        if (fd > 0) {
                int pidfd;
                if ((pidfd = syscall(434, fd, 0)) == -1) {
                        perror("failed pidfd_open\n");
                } else {
                        printf("succeed pidfd_open\n");
                }
        }
}

コンテナ作成環境(今回は Ubuntu 20.04LTS)でビルドし、コンテナイメージを作成する。

FROM ubuntu:focal AS builder
WORKDIR /build
COPY main.c /build
RUN apt-get update && apt-get install -y gcc
RUN gcc -o newfeature main.c

FROM ubuntu:focal
WORKDIR /app
COPY --from=builder /build/newfeature .
CMD ["/app/newfeature"]

コンテナ作成環境(今回は Ubuntu 20.04LTS)ではコンテナ実行に成功するが、

# docker build -t kimullaa/newfeature -f Dockerfile .
# docker run kimullaa/newfeature
succeed pidfd_open

このイメージを push して、コンテナ実行環境(今回は CentOS 7)で実行すると、エラーになる。

# docker run -it kimullaa/newfeature
Unable to find image 'kimullaa/newfeature:latest' locally
latest: Pulling from kimullaa/newfeature
345e3491a907: Pull complete
57671312ef6f: Pull complete
5e9250ddb7d0: Pull complete
1a51d0c3f193: Pull complete
b7518134f906: Pull complete
Digest: sha256:dc4f30a78726bf889a30567e0b49fdacbe2423392e6cb445561d52311eacd0a6
Status: Downloaded newer image for kimullaa/newfeature:latest
failed pidfd_open
: Function not implemented

※ 最新の機能を利用するときは注意が必要じゃないかと思う。最近だと io_uring など。アプリケーション側でフォールバックしてたりすればいいけど。ちなみに CentOS7 は uname -a すると kernel 3.x と表示されるので kernel 4.x で追加されたシステムコール(mlock2 とか copy_file_range とか preadv2 とか)は存在しないと思ったんだけど、だいたいほとんどバックポートされていてさすが Red Hat 様やで…と思った。

補足

逆に コンテナ実行環境 が コンテナ作成環境 よりも新しい場合は、ほぼ問題にならない。これは Linux カーネル開発者がめちゃくちゃ ABI の後方互換性に気を使ってくれているから。ありがてぇ…。

Linuxカーネルの開発においては、基本的には、このようなABIの変更をしないことがルールになっています。特にLinusは厳格で、開発者(メンテナ)の誰かがABIを変更するパッチを取り入れようとすると、頭から湯気が出ているのが見えるぐらいに怒ります。LKML(Linuxカーネル開発者のメーリングリスト)を検索すると、このような例があり、「WE DO NOT BREAK USERSPACE!(ユーザスペースを壊す変更はするな!)」と叫んでいます。
引用元: コラム - クラウド時代のオープンソース実践活用 | 第43回 「Dockerイメージ」のポータビリティとLinuxカーネルのABI|CTC教育サービス 研修/トレーニング

上記で紹介されている LKML: Linus Torvalds: Re: [Regression w/ patch] Media commit causes user space to misbahave (was: Re: Linux 3.8-rc1) を見ると、ひくほどブチギレしている様子がわかる。

まとめ

壊してみると分かることありますね。