Dockerイメージのサイズを縮小する6つの方法

Dockerイメージのサイズを縮小する6つの方法

2017 年に Vulhub に取り組み始めてから、私は厄介な問題に悩まされてきました。Dockerfile を書くときに、 docker buildによって生成されるイメージのサイズを縮小するにはどうすればよいでしょうか?この記事では、画像のサイズを縮小するために私が使用した 6 つの方法をまとめます。

1. Alpine Linuxの使用

Alpine Linux は、BusyBox と Musl Libc をベースにした Linux ディストリビューションです。最大の利点はサイズが小さいことです。純粋なベースの Alpine Docker イメージは、圧縮するとわずか 2.67 MB になります。

多くの公式 Docker イメージには、PHP などの Alpine バージョンがあります。

比較してみると、アルパインバージョンの画像の大きさは通常バージョンの約1/5であることがわかります。

しかし、Docker Hub では、Mysql や PHP-Apache など、ほとんどのイメージに Alpine バージョンがありません。これらの環境をベースに開発する必要がある場合は、自分で Alpine バージョンを作成するか、サードパーティのイメージを探す必要があります。

さらに、Alpine のもう 1 つの欠点は、従来の glibc の代わりに Musl Libc を使用していることです。ソフトウェアをコンパイルするときに、予期しない問題が発生する可能性があり、多くの無駄な時間を費やすことになります。

2. 最小限の依存関係のみをインストールする

apt-get、yum、apk などのパッケージ マネージャーは、イメージをコンパイルするときに使用する必要があるツールです。純粋な Docker ベースのイメージには通常、wget、curl、git、gcc などのツールがないため、手動でインストールする必要があります。

apt を例に挙げてみましょう。ソフトウェアをインストールするときに、apt-get はオプション--no-install-recommendsを指定できます。このパラメータを指定すると、必須でない依存関係の一部は一緒にインストールされなくなります。たとえば、wget をインストールするときにこのオプションを追加すると、インストールされるパッケージの数は 6 から 3 に減ります。

これにより、イメージのサイズはある程度縮小されますが、その副作用として、対象ソフトウェアの一部の機能が不足する可能性があります。

たとえば、この時点で wget はサーバー証明書の信頼性を検証できず、コマンド エラーが発生します。

したがって、私たちの一般的なやり方は、apt を使用するときに--no-install-recommendsを追加し、後でエラーが発生した場合にすぐに修正することです。 wget のような既知の問題は事前に予測して対処できます。

apt-get install --no-install-recommends wget ca-certificates

3. aptの混乱を解消する

一部のツールはコンパイル段階でのみ使用されます。貴重なイメージ容量を占有させたくありません。イメージのコンパイルが完了したら、これらの中間依存関係を削除できます。

apt を例に挙げてみましょう。apt を使用した後は、次の操作を行う必要があります。

  • 不要な依存関係を削除します: apt-get pruge --autoremove ...
  • ローカルパッケージリストを削除します: rm -rf /var/lib/apt/lists/*

このプロセスでは、どの依存関係が「不要」なのかという非常に難しい問題に遭遇します。

たとえば、PHP をコンパイルする場合、wget、libxml、gcc の 3 つのツールを使用できます。 PHP をコンパイルする前に、これら 3 つのツールをインストールする必要があります。しかし、コンパイルが完了した後、wget と gcc はアンインストールできますが、libxml はアンインストールできません。

その理由は、libxml は PHP が依存するダイナミック リンク ライブラリだからです。これをアンインストールすると、共有リンク ライブラリが見つからないというエラーが発生します。

ルート@8eab53da8d5b:/#php -v
php: 共有ライブラリのロード中にエラーが発生しました: libxml2.so.2: 共有オブジェクトファイルを開けません: そのようなファイルまたはディレクトリはありません

では、「共有リンク ライブラリ」ではない依存関係のみを自動的に検出して削除する、より便利な方法はありますか?

もちろんあります。もっと簡単な方法は、新しくコンパイルされた実行可能ファイルを走査し、ldd コマンドを使用してそれが依存する共有リンク ライブラリ ファイル名を一覧表示し、ソース内でこのファイル名に対応するパッケージ名を検索することです。

これらのパッケージはすべて、PHP が依存するダイナミック リンク ライブラリです。次に、 apt-markを使用してこれらのパッケージを「手動でインストールされたパッケージ」として宣言し、 apt purgeによって自動的にアンインストールされるのを防ぎます。

その後、残りの未使用のパッケージを自動的にアンインストールできます。完全なシェル スクリプトは次のとおりです。

/usr/local を見つけます -type f -executable -exec ldd '{}' ';' \
 | awk '/=>/ { $(NF-1) を印刷 }' \
 | ソート -u \
 | xargs -r dpkg-query --search \
 | カット -d: -f1 \
 | ソート -u \
 | xargs -r apt-mark マニュアル \
; \
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false;

4. 中間依存関係を1ステップでインストールおよびアンインストールする

Docker イメージは、レイヤーで構成された階層化されたケーキです。docker docker history <image name>コマンドを使用すると、イメージがどのレイヤーで構成されているか、および各レイヤーのサイズを確認できます。

Dockerfile の場合、後者のレイヤーが前のレイヤーに保存されたファイルを削除したとしても、これらのレイヤーのデータはイメージに保存されます。

たとえば、次の Dockerfile があります。

アルパインより:3.12
実行 truncate -s 50M /sample.dat
rm -rf /sample.dat を実行します。

このコンパイルされたイメージの大きさは 58 MB です。

比較すると、通常の alpine:3.12 はわずか 5.57 MB です。つまり、 /sample.datファイルを削除しても、最終イメージにはそのようなコンテンツは存在しませんが、イメージ履歴には常に残ります。

そのため、前述の「中間依存関係」を削除する際には、確実に領域を解放するために、インストール、使用、アンインストールの 3 つの部分を 1 つのステップで記述する必要があります。例えば:

debian:buster より

apt-get update を実行する\
 && apt-get install gcc \
 && gcc ... \
 && apt-get purge --autoremove gcc \
 && rm -rf /var/lib/apt/lists/*

5. 多段階コンパイル

Docker バージョン 17.05 以降では、マルチステージ ビルドの概念が導入され、上記のすべての操作が大幅に簡素化されます。

簡単に言えば、マルチステージ ビルドを使用すると、Docker イメージのコンパイルを複数の「ステージ」に分割できます。たとえば、一般的なソフトウェアのコンパイルの場合、コンパイル段階を分離し、ソフトウェアのコンパイルが完了した後にバイナリファイルを新しいベースイメージに直接コピーすることができます。これの最大の利点は、2 番目のイメージにはコンパイル段階で使用される中間依存関係が含まれなくなり、クリーンで明確になることです。

最も一般的な Java プロジェクトを例にとると、Jar パッケージをコンパイルするときには JDK や Maven などのツールを使用する必要がありますが、実際の運用段階では JRE 環境のみが必要になります。 2 つのイメージ ( maven:3-openjdk-8openjdk:8-jreのサイズを比較してみましょう。

その差は2倍以上です。

Vulhub の Shiro 1.2.4 環境を例にとると、Dockerfile には 2 つのFROMコマンドがあります。

maven:3-jdk-8 AS ビルダーから

ラベル MAINTAINER="phithon <[email protected]>"

./code/ /usr/src/ をコピー

ワークディレクトリ /usr/src

実行cd /usr/src; \
 mvn -U クリーンパッケージ -Dmaven.test.skip=true

openjdk:8u102-jre から

ラベル MAINTAINER="phithon <[email protected]>"

コピー --from=builder /usr/src/target/shirodemo-1.0-SNAPSHOT.jar /shirodemo-1.0-SNAPSHOT.jar

エクスポーズ8080

CMD ["java", "-jar", "/shirodemo-1.0-SNAPSHOT.jar"]

最初のFROMmaven:3-jdk-8環境に入り、Maven を使用してソース コードをコンパイルするために使用されます。2 番目のFROM 、より小さいopenjdk:8u102-jre環境に入り、 COPY --from=構文を使用して、前のステージのコンパイル結果から jar ファイルを jre 環境にコピーします。

最終的に、マシン上に 2 つのイメージが残ります。1 つはビルダーで、もう 1 つは最終的に必要となる shiro 1.2.4 環境です。後者は他のユーザーが独立して使用できますが、前者は直接削除できます。

ユーザーは、ソフトウェアをコンパイルしてイメージを小さくするときに、中間依存関係をどのように削除するかを心配する必要がなくなりました。いずれにしても、最初の段階で使用された依存関係は、正式な本番環境に残されません。

しかし、マルチステージコンパイルでは、ダイナミックリンクライブラリに依存するという前述の問題が依然として残ります。コンパイル結果をコピーするときに実行可能ファイルのみをコピーすると、新しい環境で実行したときに共有リンクライブラリが見つからないというエラーが依然として発生します。そのため、個人的には、マルチステージコンパイルは、Java や golang などのクロスプラットフォームまたは静的コンパイルが可能な言語にのみ適しており、C や Python などの依存関係が多いプロジェクトにはまだ適していないと感じています。

6. 画像のスリムバージョンを使用する

注意深い学生なら、公式の Docker Debian イメージに、デフォルト バージョンの 2 倍以上のサイズのスリム バージョンがあることに気付いたかもしれません。

slim の中国語の意味は「スリム」です。名前が示すように、 debian:stretch-slimコンテナで使用されない man ドキュメントなどの多くのファイルを削除するため、実際にははるかにスリムです。

一部の上位レベルのイメージは、Python など、Debian のスリム バージョンに基づいて作成されています。 Python プロジェクトを開発する場合は、 python:slimベースイメージを使用できます。

まとめると、6 つの方法は互いに影響を及ぼさず、同時に使用できます。しかし、5番目の多段階コンパイルは、将来的には主流の方法になるでしょう。

以上で、Docker イメージのサイズを小さくする 6 つの方法についての説明は終了です。Docker イメージのサイズを小さくする方法の詳細については、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • Alibaba Cloud イメージリポジトリの Docker 構成変更の実装
  • SpringBootでDockerイメージを構築する3つの方法の詳しい説明
  • Springboot docker jenkins でイメージを自動的にデプロイしてアップロードするための詳細な手順
  • Dockerイメージ内のファイルを表示する方法
  • Dockerとイメージの操作方法

<<:  MySQL NULLがピットを引き起こした

>>:  階段を転がす特殊効果を実現する JavaScript (jQuery 実装)

推薦する

RPM パッケージを使用して MySQL 5.7.18 をインストールするチュートリアル

システム:セントOS7 RPM パッケージ: mysql-コミュニティクライアント-5.7.18-1...

Vue codemirrorはオンラインコードコンパイラの効果を実現します

序文Web 上でオンライン コード コンパイルの効果を実現したい場合は、 CodeMirrorを再度...

HTML における要素の水平および垂直中央揃えに関する議論

ページをデザインするときには、ログイン ウィンドウを中央に配置するなど、DIV を中央に配置し、ペー...

SVN のインストールと基本操作 (グラフィック チュートリアル)

目次1. SVNとは何か2. SVNサーバーとクライアントの取得方法3. SVN ワークフローとアー...

JS ES 新機能テンプレート文字列

目次1. テンプレート文字列とは何ですか? 2. 複数行のテンプレート文字列2.1 式付きテンプレー...

MySQL インデックスの正しい使い方とインデックスの原理の詳細な説明

1. はじめになぜインデックスが必要なのでしょうか?一般的なアプリケーション システムでは、読み取り...

Node の SMS API で検証コード ログインを実装するためのサンプル コード

1. ノードサーバーのセットアップ + データベース接続ここでの操作は比較的簡単でわかりやすいです。...

js を使用して過去 1 週間、1 か月、3 か月の時間を取得する簡単な例

目次過去1週間の時間を取得する過去1か月の時間を取得する過去3か月分を取得新しい Date() と ...

Docker に Elasticsearch 7.6.2 をインストールするチュートリアル

DockerをインストールするDocker をインストールする必要がありますが、それ以上の指示はあり...

tbodyタグの魔法はテーブルコンテンツの表示を高速化します

他の人のウェブページを保存して見たことがあると思いますが、特にdwで開くと、多くのウェブページに&l...

HTML初心者や初級者向けの提案。専門家は無視してかまいません。

感想:私はバックエンド開発者です。静的 (HTML) ページを取得すると、ページ構造と命名規則が極端...

Nginx ロードバランシングとは何か、そしてそれをどのように設定するか

負荷分散とは負荷分散は主に、専用のハードウェア デバイスまたはソフトウェア アルゴリズムによって実現...

MySQL にテーブルが存在するかどうかを確認し、それを一括で削除する方法

1. インターネットで長時間検索しましたが、判定表が存在するかどうかがわからなかったので、漠然と削除...

SSH経由でローカルLinux仮想マシンに接続するプロセスを記録する

実験環境:物理マシン Windows 10 x64物理NIC情報IPv4 アドレス: 192.168...

JavaScript の例におけるループの使用法の詳細な説明

退屈だったので、ループに関する簡単な演習をいくつかまとめてみました。JS を学び始めたばかりの方に役...