$30 off During Our Annual Pro Sale. View Details »

PythonにとってのよりよいDockerfileを考える

RevComm_inc
February 07, 2023

 PythonにとってのよりよいDockerfileを考える

2022年8月に社内のTechTalkで発表したスライドです。
「良いコンテナイメージ」に関する考察とPythonを例にしたDockerfileのサンプルを示します。

RevComm_inc

February 07, 2023
Tweet

More Decks by RevComm_inc

Other Decks in Technology

Transcript

  1. Copyright © 2022 RevComm Inc. contents 1. はじめに 2. “よい

    Dockerfile” とは 3. Let’s practice! 4. 実践事例の紹介
  2. Copyright © 2022 RevComm Inc. はじめに 3 この資料の想定読者 • コンテナ技術に入門済みの人

    • Dockerfile を書いたことがある人 • Web アプリケーション開発者(インタプリタ言語)
  3. Copyright © 2022 RevComm Inc. はじめに 4 この資料の想定読者 • コンテナ技術に入門済みの人

    • Dockerfile を書いたことがある人 • Web アプリケーション開発者(インタプリタ言語) この資料で話さないこと • コンテナとは何か?コンテナ技術の仕組み • ML/DL、バッチ処理用途のワークロード • コンパイル言語向けのこと(Java, Go, Rust) ◦ …Dockerfile の戦略が異なってくる (alpine とか distroless とか)
  4. Copyright © 2022 RevComm Inc. “よい Dockerfile” とは 6 コンテナイメージにとって重要な性質(抜粋)

    参考: Dockerfile のベスト・プラクティス — Docker-docs-ja 20.10 ドキュメント • ※“コンテナ” 自体の理解 • 開発利便性 • 可搬性 / セキュリティ
  5. Copyright © 2022 RevComm Inc. “よい Dockerfile” とは 7 コンテナイメージにとって重要な性質(抜粋)

    参考: Dockerfile のベスト・プラクティス — Docker-docs-ja 20.10 ドキュメント • ※“コンテナ” 自体の理解 ◦ 仮想マシン (VM) と同じ使い方をしない ◦ (原則) 1コンテナ1プロセス • 開発利便性 • 可搬性 / セキュリティ
  6. Copyright © 2022 RevComm Inc. “よい Dockerfile” とは 8 コンテナイメージにとって重要な性質(抜粋)

    参考: Dockerfile のベスト・プラクティス — Docker-docs-ja 20.10 ドキュメント • ※“コンテナ” 自体の理解 ◦ 仮想マシン(VM)と同じ使い方をしない ◦ (原則) 1コンテナ1プロセス • 開発利便性 ◦ ビルド時キャッシュを活用する • 可搬性 / セキュリティ ◦ .dockerignore で除外 ◦ レイヤの数は最小に ◦ ベースイメージとパッケージを最新化する ◦ 不要なパッケージをインストールしない
  7. Copyright © 2022 RevComm Inc. “よい Dockerfile” とは 9 コンテナイメージにとって重要な性質(抜粋)

    参考: Dockerfile のベスト・プラクティス — Docker-docs-ja 20.10 ドキュメント • ※“コンテナ” 自体の理解 ◦ 仮想マシン(VM)と同じ使い方をしない ◦ (原則) 1コンテナ1プロセス • 開発利便性 ◦ ビルド時キャッシュを活用する • 可搬性 / セキュリティ ◦ .dockerignore で除外 ◦ レイヤの数は最小に ◦ ベースイメージとパッケージを最新化する ◦ 不要なパッケージをインストールしない ⇒Python アプリケーションを想定した実例とともに深堀り
  8. Copyright © 2022 RevComm Inc. 前提 11 • Django を動かすだけの簡単なアプリケーションを想定

    • uWSGI でサーバー起動するまでを目標 JSON で応答を返す
  9. Copyright © 2022 RevComm Inc. (再掲) “よい Dockerfile” とは 12

    コンテナイメージにとって重要な性質(抜粋) 参考: Dockerfile のベスト・プラクティス — Docker-docs-ja 20.10 ドキュメント • ※“コンテナ” 自体の理解 ◦ 仮想マシン(VM)と同じ使い方をしない ◦ (原則) 1コンテナ1プロセス • 開発利便性 ◦ ビルド時キャッシュを活用する • 可搬性 / セキュリティ ◦ .dockerignore で除外 ◦ レイヤの数は最小に ◦ ベースイメージとパッケージを最新化する ◦ 不要なパッケージをインストールしない
  10. Copyright © 2022 RevComm Inc. • 先頭で COPY ./ ./

    をしない ◦ docker build で毎回(≒ソースコードを書き換える度)初めからになってしまう(= キャッシュを活用できない) ◦ ビルド過程で必要となる requirements.txt などを先に配置する 開発利便性 - ビルド時キャッシュを活用する(1/2) 13
  11. Copyright © 2022 RevComm Inc. • 先頭で COPY ./ ./

    をしない ◦ docker build で毎回(≒ソースコードを書き換える度)初めからになってしまう(= キャッシュを活用できない) ◦ ビルド過程で必要となる requirements.txt などを先に配置する 開発利便性 - ビルド時キャッシュを活用する(1/2) 14 ソースコード変更の度に「pip install …」が実行される
  12. Copyright © 2022 RevComm Inc. • 先頭で COPY ./ ./

    をしない ◦ docker build で毎回(≒ソースコードを書き換える度)初めからになってしまう(= キャッシュを活用できない) ◦ ビルド過程で必要となる requirements.txt などを先に配置する 開発利便性 - ビルド時キャッシュを活用する(1/2) 15 ソースコード変更後は 7行目から開始
  13. Copyright © 2022 RevComm Inc. • 先頭で COPY ./ ./

    をしない ◦ docker build で毎回(≒ソースコードを書き換える度)初めからになってしまう(= キャッシュを活用できない) ◦ ビルド過程で必要となる requirements.txt などを先に配置する ◦ パッケージマネージャを使用する場合 開発利便性 - ビルド時キャッシュを活用する(1/2) 16 • Pipenv ※以降、Pipenv を使用する前提のサンプルとする • Poetry
  14. Copyright © 2022 RevComm Inc. • 必要な定型処理を先に実行する ◦ パッケージのインストールを先に行う 開発利便性

    - ビルド時キャッシュを活用する(2/2) 18 Pipfile / Pipfile.lock を変更した場合、 直後のビルドは7行目から開始する
  15. Copyright © 2022 RevComm Inc. “よい Dockerfile” とは 19 コンテナイメージにとって重要な性質(抜粋)

    参考: Dockerfile のベスト・プラクティス — Docker-docs-ja 20.10 ドキュメント • ※“コンテナ” 自体の理解 ◦ 仮想マシン(VM)と同じ使い方をしない ◦ (原則) 1コンテナ1プロセス • 開発利便性 ◦ ビルド時キャッシュを活用する • 可搬性 / セキュリティ ◦ .dockerignore で除外 ◦ レイヤの数は最小に ◦ ベースイメージとパッケージを最新化する ◦ 不要なパッケージをインストールしない
  16. Copyright © 2022 RevComm Inc. (再掲) “よい Dockerfile” とは 21

    コンテナイメージにとって重要な性質(抜粋) 参考: Dockerfile のベスト・プラクティス — Docker-docs-ja 20.10 ドキュメント • ※“コンテナ” 自体の理解 ◦ 仮想マシン(VM)と同じ使い方をしない ◦ (原則) 1コンテナ1プロセス • 開発利便性 ◦ ビルド時キャッシュを活用する • 可搬性 / セキュリティ ◦ .dockerignore で除外 ◦ レイヤの数は最小に ◦ ベースイメージとパッケージを最新化する ◦ 不要なパッケージをインストールしない
  17. Copyright © 2022 RevComm Inc. • 同じ命令は可能な限りまとめる レイヤの数を最小にする > RUN

    、 COPY 、 ADD 命令のみレイヤを作成します。他の命令では、一時的な中間イメージ(temporary intermediate images)を作成し、構築時の容 量は増えません。 1. RUN 可搬性 / セキュリティ (1/2) - レイヤの数は最小に 22 2. COPY 3. ADD ※COPY が推奨される
  18. Copyright © 2022 RevComm Inc. • 同じ命令は可能な限りまとめる レイヤの数を最小にする > RUN

    、 COPY 、 ADD 命令のみレイヤを作成します。他の命令では、一時的な中間イメージ(temporary intermediate images)を作成し、構築時の容 量は増えません。 1. RUN 可搬性 / セキュリティ (1/2) - レイヤの数は最小に 23 2. COPY 3. ADD ※COPY が推奨される 1行目の中間レイヤーを保持するため 3-4行目はイメージサイズ削減に効果が無い
  19. Copyright © 2022 RevComm Inc. (再掲) “よい Dockerfile” とは 24

    コンテナイメージにとって重要な性質(抜粋) 参考: Dockerfile のベスト・プラクティス — Docker-docs-ja 20.10 ドキュメント • ※“コンテナ” 自体の理解 ◦ 仮想マシン(VM)と同じ使い方をしない ◦ (原則) 1コンテナ1プロセス • 開発利便性 ◦ ビルド時キャッシュを活用する • 可搬性 / セキュリティ ◦ .dockerignore で除外 ◦ レイヤの数は最小に ◦ ベースイメージとパッケージを最新化する ◦ 不要なパッケージをインストールしない
  20. Copyright © 2022 RevComm Inc. • ベースイメージ ◦ “buster” イメージを使っていませんか?(python:3.x-buster、python:3.x-slim-buster)

    ◦ No!最新は “bullseye” です(= Debian 11.0) ※2022年11月時点 ◦ ベースイメージには debian バージョンを指定しなくても良いかも(alias があるので) ▪ python:3.10.8 = python:3.10.8-bullseye ▪ python:3.10.8-slim = python:3.10.8-slim-bullseye ベースイメージとパッケージを最新化する 25
  21. Copyright © 2022 RevComm Inc. • ベースイメージ ◦ “buster” イメージを使っていませんか?(python:3.x-buster、python:3.x-slim-buster)

    ◦ No!最新は “bullseye” です(= Debian 11.0) ※2022年11月時点 ◦ ベースイメージには debian バージョンを指定しなくても良いかも(alias があるので) ▪ python:3.10.8 = python:3.10.8-bullseye ▪ python:3.10.8-slim = python:3.10.8-slim-bullseye • パッケージ ◦ Docker 公式ドキュメントで「Dockerfile の中で apt-get upgrade しないように」と書かれていたのは昔の話 ◦ OWASP が提唱して、Docker コミュニティも修正済み ◦ Not installing security updates is bad advice by itamarst · Pull Request #614 · OWASP/CheatSheetSeries ◦ Stop telling people not to install security updates by itamarst · Pull Request #12571 · docker/docs ◦ パッケージは必ず最新化しましょう! ◦ 定期的にイメージを更新しましょう! ベースイメージとパッケージを最新化する 26
  22. Copyright © 2022 RevComm Inc. (再掲) “よい Dockerfile” とは 27

    コンテナイメージにとって重要な性質(抜粋) 参考: Dockerfile のベスト・プラクティス — Docker-docs-ja 20.10 ドキュメント • ※“コンテナ” 自体の理解 ◦ 仮想マシン(VM)と同じ使い方をしない ◦ (原則) 1コンテナ1プロセス • 開発利便性 ◦ ビルド時キャッシュを活用する • 可搬性 / セキュリティ ◦ .dockerignore で除外 ◦ レイヤの数は最小に ◦ ベースイメージとパッケージを最新化する ◦ 不要なパッケージをインストールしない
  23. Copyright © 2022 RevComm Inc. • デフォルトのベースイメージには無駄なパッケージが(非常に)多い ⇒slim イメージを使いましょう •

    slim イメージには gcc などビルドツール群が含まれないため、Python ライブラリをビルドできない場合がある ⇒マルチステージビルドを使う ◦ slim イメージに gcc をインストールするのは 可搬性 / セキュリティ (2/2) - 不要なパッケージをインストールしない 28
  24. Copyright © 2022 RevComm Inc. ここまでの整理 • slim イメージを使用する ◦

    Python ライブラリの準備を “not slim イメージ” で行う (マルチステージビルド) 可搬性 / セキュリティ (2/2) - 不要なパッケージをインストールしない 29
  25. Copyright © 2022 RevComm Inc. ここまでの整理 • slim イメージを使用する ◦

    Python ライブラリの準備を “not slim イメージ” で行う (マルチステージビルド) • 課題 ◦ パッケージマネージャー (Pipenv / Poetry) をアプリケーションに含めたくない ◦ dev-packages (テスト用ツールなど)を本番用イメージから分離した ▪ CI では dev-packages を使いたい 可搬性 / セキュリティ (2/2) - 不要なパッケージをインストールしない 30 アプリケーション自体に Pipenv は要らない (普通は)
  26. Copyright © 2022 RevComm Inc. • 結論 可搬性 / セキュリティ

    (2/2) - 不要なパッケージをインストールしない 31 こちらでも見れます
  27. Copyright © 2022 RevComm Inc. • 結論 可搬性 / セキュリティ

    (2/2) - 不要なパッケージをインストールしない 32 FROM [base image] AS [stage name] という形式 1つの FROM 命令のブロックが1ステージと対応
  28. Copyright © 2022 RevComm Inc. • 結論 可搬性 / セキュリティ

    (2/2) - 不要なパッケージをインストールしない 33 1. ステージ: export ◦ requirements.txt 形式のライブラリ一覧を生成する ◦ ⇒後続ステージにて Pipenv が不要となる
  29. Copyright © 2022 RevComm Inc. • 結論 可搬性 / セキュリティ

    (2/2) - 不要なパッケージをインストールしない 34 1. ステージ: export ◦ requirements.txt 形式のライブラリ一覧を生成する ◦ ⇒後続ステージにて Pipenv 不要となる 2. ステージ: builder / dev-builder ◦ requirements.txt を元にライブラリを準備する ◦ COPY --from=[stage name] … でステージ間でファイルを受け取る ◦ prod 用、dev 用の2通りを用意
  30. Copyright © 2022 RevComm Inc. • 結論 可搬性 / セキュリティ

    (2/2) - 不要なパッケージをインストールしない 35 1. ステージ: export ◦ requirements.txt 形式のライブラリ一覧を生成する ◦ ⇒後続ステージにて Pipenv 不要となる 2. ステージ: builder / dev-builder ◦ requirements.txt を元にライブラリを準備する ◦ COPY --from=[stage name] … でステージ間でファイルを受け取る ◦ prod 用、dev 用の2通りを用意 3. ステージ: base ◦ 最終的なアプリケーション用の環境をセットアップ ◦ 必要なパッケージをインストール ◦ ※一般ユーザーを作成、スイッチなど(割愛) libpq5: PostgreSQL 接続に必要 libxml2: uWSGI 実行に必要
  31. Copyright © 2022 RevComm Inc. • 結論 可搬性 / セキュリティ

    (2/2) - 不要なパッケージをインストールしない 36 1. ステージ: export ◦ requirements.txt 形式のライブラリ一覧を生成する ◦ ⇒後続ステージにて Pipenv 不要となる 2. ステージ: builder / dev-builder ◦ requirements.txt を元にライブラリを準備する ◦ COPY --from=[stage name] … でステージ間でファイルを受け取る ◦ prod 用、dev 用の2通りを用意 3. ステージ: base ◦ 最終的なアプリケーション用の環境をセットアップ ◦ 必要なパッケージをインストール ◦ ※一般ユーザーを作成、スイッチなど(割愛) 4. ステージ: dev / prod ◦ Python ライブラリをインストール ◦ ソースコードを配置 ◦ ※dev / prod で CMD を分ける、など適宜 ◦ docker build --target [stage-name] .... でビルド対象ステージを 指定する
  32. Copyright © 2022 RevComm Inc. • 結論 可搬性 / セキュリティ

    (2/2) - 不要なパッケージをインストールしない 37
  33. Copyright © 2022 RevComm Inc. After 実践事例の紹介 41 ①slim イメージを使用するように変更。

    builder ステージでライブラリをでインストール・ビルドし、 runner ステージで利用。 ①一般ユーザーでアプリケーションを起動するように変更。 ※80番ポートを起動できないためポート番号も変更。
  34. Copyright © 2022 RevComm Inc. • 開発利便性と軽量性/セキュリティ は両立できる(相反する性質ではない) • なるべく

    slim イメージを使用しましょう • マルチステージビルドを活用することで「アプリケーションに必要十分な環境」が作りやすくなる ◦ 開発 / 本番用イメージを同一 Dockerfile で管理できる(DRY 原則を保てる) ※必要に応じて Dockerfile を分離して可読性を維持する(要はバランス) リンク • サンプルコード: GitHub revcomm/public-slide-docker-multistage-build-for-python ◦ dev コンテナ: ユニットテスト実行 ◦ prod コンテナ:アプリケーション起動、動作確認 • 仕事でPythonコンテナをデプロイする人向けのDockerfile (1): オールマイティ編 | フューチャー技術ブログ • 軽量Dockerイメージに安易にAlpineを使うのはやめたほうがいいという話 - inductor's blog まとめ 42