57
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

NRI OpenStandiaAdvent Calendar 2021

Day 15

セキュアで軽量なdistrolessコンテナを作成する

Last updated at Posted at 2021-12-14

はじめに

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)をベースとして作られていることがわかります。

Pythonコンテナタグ

ベースとして利用される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は確かに利用する価値はあります。

  1. コンテナ イメージサイズが小さくなる
  2. セキュリティリスクが低減される

しかし、一方で課題もあります。
distrolessイメージにはshellが含まれていません。
したがって下記のような事象が発生します。

  1. ユーザビリティが損なわれる(デバッグがし辛いなど...)
  2. shellがないためコンテナ内に入れない
  3. 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に下記のように記述します。

hello.py
import time

while True:
    print("Hello Distroless !!")
    time.sleep(1)

distrolessイメージをビルドするためにDockerfileを作成します。
ベースイメージとしてgcr.io/distroless/python3を使用します。

Dockerfile
FROM gcr.io/distroless/python3:latest
COPY ./hello.py /app/
WORKDIR /app
CMD ["hello.py"]

作成したDockerfileを用いてコンテナイメージをビルドし、コンテナを起動してみます。
※分かりやすくするため一部出力を加工しています

distrolessコンテナのビルドと起動
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は含まれていないためコンテナ内に入ることはできません。

distrolessコンテナには入れない
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ユーザでコンテナを実行することができます。
※分かりやすくするため一部出力を加工しています

rootユーザ、一般ユーザで実行する
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を用意します。

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を使用しています。

Dockerfile
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を用いてコンテナイメージをビルドし、コンテナを起動してみます。
※分かりやすくするため一部出力を加工しています

distrolessコンテナのビルドと起動
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 !!
~略~

参考資料

57
21
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
57
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?