はじめに
NRI OpenStandia Advent Calendar 2021、15日目はコンテナ について考えます。
Linuxのコンテナ技術はプロダクション環境でも当たり前のように使われています。これに伴いコンテナ開発・運用の様々なベストプラクティスが提案されています。distroless
なコンテナもベストプラクティスとして提唱されているものの1つです。
本記事ではこのdistroless
なコンテナについて考えます。
#distrolessイメージを作成するでは実際にdistrolessなコンテナイメージを作成します。
distrolessとは
Docker Hubをはじめ、ネット上で様々なコンテナイメージ提供されていますが、これらのイメージは基本的にLinuxディストリビューションを含むコンテナイメージをベースとしています。
Linuxディストリビューションはdistro(ディストロ)とも呼ばれます。余談ですが、DistroWatchのサイトでは注目されているLinuxディストリビューションの一覧を確認できます。
例えば、PythonのコンテナイメージではDocker Hub上で以下のようなタグのイメージが提供されています。これらのタグからPythonのコンテナイメージはalpineやdebian(bullseye、buster)をベースとして作られていることがわかります。
ベースとして利用されるLinuxディストリビューションはカーネルを除く基本的な設定ファイルやパッケージが一通り含まれます。そのため実際にほとんど使用しないような不要なファイルが多数含まれています。
distrolessなコンテナイメージは、こうした不要なファイルを削除し、アプリケーションの実行に必要な最小限のファイルのみを含んだコンテナイメージのことを指します。
distrolessはdistrolessではない
distrolessなコンテナイメージはその名前からLinuxディストリビューションが含まれていないように思われますが、そうではありません。
LinuxはLinuxカーネル(kernel space:カーネル空間)とそれ以外(user space:ユーザ空間)から構成されています。kernel spaceとuser spaceについてはRedHatのこちらの記事(Architecting Containers Part 1: Why Understanding User Space vs. Kernel Space Matters)が参考になります。
Linuxカーネルは基本的には1種類しかありません。またカーネルだけでOSを起動することもできないため、ユーザ空間に必要な設定ファイルや実行ファイルを用意しなければなりません。
この時のユーザ空間の作り込みの違いがLinuxディストリビューションの多様性を生みます。
そのためコンテナであってもOSを起動している時点でやはり何かしらのディストリビューションを含んでいると言えます。
distrolessなコンテナイメージはパッケージマネージャーやshellなど、アプリケーションを実行する際には不要となるファイルを含まないという意味でdistrolessと言われます。
重要な点なので強調しておきますが、distrolessなコンテナイメージにはshellも含まれません。
なぜdistrolessなコンテナイメージを使うのか
- 理由① distrolessはスリム
distrolessなコンテナイメージにはアプリケーションの実行に必要な設定や実行ファイルのみが含まれています。
最小限の構成にしているため、コンテナイメージのサイズを小さくすることができます。
- 理由② distrolessはセキュア
繰り返しになりますが、distrolessなコンテナイメージはアプリケーションの実行に必要な最低限の設定や実行ファイルのみが含まれています。そのため不要なバグや脆弱性を埋め込みにくいという利点があります。これにより、使用していない機能に脆弱性があったというような理由でパッチを当てることも少なくなります。
不要なファイルを含まないことで、distrolessなコンテナイメージは攻撃対象領域(Attack Surface)を最小限に抑えています。
distrolessはベストプラクティスか
sysdigが公表したTop 20 Dockerfile best practicesの中ではdistrolessがベストプラクティスの1つだと述べられています。
また、Technology Radarにおいても2021年4月時点でdistrolessはTrialのステージにあり、distrolessを検討することは企業にとって重要であるとしています。なおTechnology Raderでは技術を下記4つのステージに分類しています。
Adopt: We feel strongly that the industry should be adopting these items. We use them when appropriate in our projects.
Trial: Worth pursuing. It’s important to understand how to build up this capability. Enterprises can try this technology on a project that can handle the risk.
Assess: Worth exploring with the goal of understanding how it will affect your enterprise.
Hold: Proceed with caution.
Technology Radar
個人的な意見としても、distrolessはベストプラクティスとして良いと思います。
下記2点においてdistrolessは確かに利用する価値はあります。
- コンテナ イメージサイズが小さくなる
- セキュリティリスクが低減される
しかし、一方で課題もあります。
distrolessイメージにはshellが含まれていません。
したがって下記のような事象が発生します。
- ユーザビリティが損なわれる(デバッグがし辛いなど...)
- shellがないためコンテナ内に入れない
- shellなどでちょっとした処理を追加できない
これらの課題を克服するために、ログ設計を十分に検討し障害に備えることや、デバッグ用にshellなどを含んだイメージをあらかじめ用意しておくことが重要になるのではないかと考えています。
また、distrolessのなコンテナイメージの特徴をふまえた上でステークホルダーからの理解を得ることも大切になるのではないでしょうか。
distrolessコンテナについてはRedHatが興味深い記事を出しています。
Why distroless containers aren't the security solution you think they are
この記事ではdistrolessイメージの恩恵を十分に受けるために理解しておくべき事項が書かれています。記事内ではアプリケーションの標準化とソフウェア品質を高めることは、distrolessイメージを用いることよりも攻撃対象領域(Attack Surface)を低減させるのには有効であるとも述べられています。
distrolessイメージを作成する
GoogleContainerTools
distrolessなコンテナイメージの作成に使えるツールとしてはGoogleContainerTools/distrolessが有名(デファクトスタンダード)です。
コンテイメージのレポジトリはgcr.io/distrolessです。
イメージ作成例① Python
ここではPythonを実行するためのdistrolessイメージを作成します。
実行するのは簡単なHelloWorld...ではなくHello Distrolessアプリとします。
hello.py
に下記のように記述します。
import time
while True:
print("Hello Distroless !!")
time.sleep(1)
distrolessイメージをビルドするためにDockerfileを作成します。
ベースイメージとしてgcr.io/distroless/python3を使用します。
FROM gcr.io/distroless/python3:latest
COPY ./hello.py /app/
WORKDIR /app
CMD ["hello.py"]
作成したDockerfileを用いてコンテナイメージをビルドし、コンテナを起動してみます。
※分かりやすくするため一部出力を加工しています
D:\distroless_python>docker build -t distroless_hello_python ./
[+] Building 6.3s (8/8) FINISHED
~略~
D:\distroless_python>docker run -it --rm distroless_hello_python
Hello Distroless !!
Hello Distroless !!
^C
KeyboardInterrupt
distrolessコンテナにはshやbashなどのshellは含まれていないためコンテナ内に入ることはできません。
D:\distroless_python>docker run -it --rm distroless_hello_python sh
/usr/bin/python3.9: can't open file '/app/sh': [Errno 2] No such file or directory
D:\distroless_python>docker run -it --rm distroless_hello_python /bin/sh
SyntaxError: Non-UTF-8 code starting with '\xac' in file /bin/sh on line 2, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
D:\distroless_python>docker run -it --rm distroless_hello_python bash
/usr/bin/python3.9: can't open file '/app/bash': [Errno 2] No such file or directory
D:\distroless_python>docker run -it --rm distroless_hello_python /bin/bash
/usr/bin/python3.9: can't open file '/bin/bash': [Errno 2] No such file or directory
distrolessコンテナであっても普通にLinuxが起動しているのでrootユーザやnonroot、nobodyユーザでコンテナを実行することができます。
※分かりやすくするため一部出力を加工しています
D:\distroless_python>docker run -it --rm -u root distroless_hello_python
Hello Distroless !!
Hello Distroless !!
^C
KeyboardInterrupt
D:\distroless_python>docker run -it --rm -u nonroot distroless_hello_python
Hello Distroless !!
Hello Distroless !!
^C
KeyboardInterrupt
D:\distroless_python>docker run -it --rm -u nobody distroless_hello_python
Hello Distroless !!
Hello Distroless !!
^C
KeyboardInterrupt
イメージ作成例② WebAssembly
今度はWebAssemblyを実行するコンテナを作成してみます。
実行するのは先ほどと同様、HelloDistrolessと表示するアプリにします。
下記のように記述したhello.c
を用意します。
#include <stdio.h>
#include <unistd.h>
int main()
{
while(1){
printf("Hello Distroless !!\n");
sleep(1);
}
return 0;
}
上記のhello.cからWebAssemblyへコンパイルし、distrolessコンテナへ詰めるためにマルチステージビルドを行います。Dockerfileは以下のように記述します。
不要なものは含めないことがdistrolessなコンテナイメージの原則になります。
ベースイメージとしてgcr.io/distroless/cc、WebAssemblyのランタイムとしてwasmtimeを使用しています。
FROM hltj/wasi-sdk:latest AS builder
COPY hello.c /tmp/
WORKDIR /tmp
RUN /opt/wasi-sdk/bin/clang hello.c -o hello.wasm
FROM gcr.io/distroless/cc:latest
COPY wasmtime /app/
COPY --from=builder /tmp/hello.wasm /app/
WORKDIR /app
CMD ["./wasmtime", "hello.wasm"]
作成したDockerfileを用いてコンテナイメージをビルドし、コンテナを起動してみます。
※分かりやすくするため一部出力を加工しています
D:\distroless_wasm>docker build -t distroless_hello_wasm ./
[+] Building 1.4s (14/14) FINISHED
~略~
D:\distroless_wasm>docker run -it --rm distroless_hello_wasm
Hello Distroless !!
Hello Distroless !!
~略~