見出し画像

Docker 入門 (9) - イメージバインディングのベストプラクティス

以下の記事を参考に書いてます。

Image-building best practices | Docker Documentation

前回

1. セキュリティスキャン

コンテナイメージを作成したら、「docker scan」コマンドでセキュリティの脆弱性をスキャンするのがおすすめです。Docker社はSnyk社と提携し、脆弱性スキャンサービスを提供しています。

チュートリアルで作成したコンテナイメージをスキャンするコマンドは、次のとおりです。

$ docker scan getting-started

このスキャンでは、常に更新される脆弱性のDBを使用しているため、新しい脆弱性が発見されるたびに表示される出力は異なりますが、次のようなものになります。

✗ Low severity vulnerability found in freetype/freetype
 Description: CVE-2020-15999
 Info: https://snyk.io/vuln/SNYK-ALPINE310-FREETYPE-1019641
 Introduced through: freetype/freetype@2.10.0-r0, gd/libgd@2.2.5-r2
 From: freetype/freetype@2.10.0-r0
 From: gd/libgd@2.2.5-r2 > freetype/freetype@2.10.0-r0
 Fixed in: 2.10.0-r1

✗ Medium severity vulnerability found in libxml2/libxml2
 Description: Out-of-bounds Read
 Info: https://snyk.io/vuln/SNYK-ALPINE310-LIBXML2-674791
 Introduced through: libxml2/libxml2@2.9.9-r3, libxslt/libxslt@1.1.33-r3, nginx-module-xslt/nginx-module-xslt@1.17.9-r1
 From: libxml2/libxml2@2.9.9-r3
 From: libxslt/libxslt@1.1.33-r3 > libxml2/libxml2@2.9.9-r3
 From: nginx-module-xslt/nginx-module-xslt@1.17.9-r1 > libxml2/libxml2@2.9.9-r3
 Fixed in: 2.9.9-r4

出力には、脆弱性の種類、詳細を知るためのURL、そして重要なこととして、関連するライブラリのどのバージョンがその脆弱性を修正しているかが表示されます。

新しくビルドしたイメージをコマンドラインでスキャンするだけでなく、新しくプッシュしたイメージを自動的にスキャンするように「Docker Hub」に設定することもできます。

画像1

2. イメージレイヤリング

docker image history」コマンドを使うと、イメージ内の各レイヤーの作成に使われたコマンドを見ることができます。

(1) 「docker image history」コマンドでコンテナイメージ「get-started」のレイヤーを確認。

$ docker image history getting-started

以下のような出力が得られます。

IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
a78a40cbf866        18 seconds ago      /bin/sh -c #(nop)  CMD ["node" "src/index.j…    0B                  
f1d1808565d6        19 seconds ago      /bin/sh -c yarn install --production            85.4MB              
a2c054d14948        36 seconds ago      /bin/sh -c #(nop) COPY dir:5dc710ad87c789593…   198kB               
9577ae713121        37 seconds ago      /bin/sh -c #(nop) WORKDIR /app                  0B                  
b95baba1cfdb        13 days ago         /bin/sh -c #(nop)  CMD ["node"]                 0B                  
<missing>           13 days ago         /bin/sh -c #(nop)  ENTRYPOINT ["docker-entry…   0B                  
<missing>           13 days ago         /bin/sh -c #(nop) COPY file:238737301d473041…   116B                
<missing>           13 days ago         /bin/sh -c apk add --no-cache --virtual .bui…   5.35MB              
<missing>           13 days ago         /bin/sh -c #(nop)  ENV YARN_VERSION=1.21.1      0B                  
<missing>           13 days ago         /bin/sh -c addgroup -g 1000 node     && addu…   74.3MB              
<missing>           13 days ago         /bin/sh -c #(nop)  ENV NODE_VERSION=12.14.1     0B                  
<missing>           13 days ago         /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B                  
<missing>           13 days ago         /bin/sh -c #(nop) ADD file:e69d441d729412d24…   5.59MB   

各行は、イメージのレイヤーを表しています。ここでは、下にベース、上に最新のレイヤーが表示されています。これにより、各レイヤーの大きさが一目瞭然となり、大きなイメージの診断にも役立ちます。

(2) いくつかの行が切り捨てられているので、「--no-trunc」を追加して、完全な出力を取得。

$ docker image history --no-trunc getting-started

3. レイヤーのキャッシング

レイヤリングの動作を確認したところで、コンテナイメージのビルド時間を短縮するための重要な教訓があります。

レイヤーを変更すると、下流レイヤーもすべて再作成する必要がある。

「Dockerfile」をもう1度見てみましょう。

# syntax=docker/dockerfile:1
FROM node:12-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]

「Dockerfile」の各コマンドがイメージの新しいレイヤーになっていることがわかります。イメージに変更を加える度に、yarnの依存関係を再インストールしなければなりません。

これを解決するには、依存関係のキャッシュをサポートするためにDockerfileを再構築する必要があります。Nodeベースのアプリでは、依存関係はpackage.jsonで定義されます。そこで、まずこのファイルだけをコピーし、依存関係をインストールしてから、他のすべてのファイルをコピーしたらどうでしょうか。そうすれば、「package.json」に変更があった場合にのみ、yarnの依存関係を再構築することができます。

(1) 「Dockerfile」を更新して、はじめに「package.json」をコピーし、依存関係をインストールしてから、他のすべてをコピー。

# syntax=docker/dockerfile:1
FROM node:12-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --production
COPY . .
CMD ["node", "src/index.js"]

(2) Dockerfileと同じフォルダに、「.dockerignore」を作成し、以下のように編集。

 node_modules

.dockerignore」は、イメージに関連するファイルだけを選択的にコピーする簡単な方法です。

今回は、2回目のCOPYステップで「node_modules」を省略する必要があります。そうしないと、RUNで作成したファイルを上書きしてしまう可能性があるからです。Node.jsアプリでこの方法が推奨される理由や、その他のベストプラクティスについては、「Dockerizing a Node.js web app」を参照してください。

(3) 「docker build」で新しいコンテナイメージを作成。

$ docker build -t getting-started .
Sending build context to Docker daemon  219.1kB
Step 1/6 : FROM node:12-alpine
---> b0dc3a5e5e9e
Step 2/6 : WORKDIR /app
---> Using cache
---> 9577ae713121
Step 3/6 : COPY package.json yarn.lock ./
---> bd5306f49fc8
Step 4/6 : RUN yarn install --production
---> Running in d53a06c9e4c2
yarn install v1.17.3
[1/4] Resolving packages...
[2/4] Fetching packages...
info fsevents@1.2.9: The platform "linux" is incompatible with this module.
info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
[4/4] Building fresh packages...
Done in 10.89s.
Removing intermediate container d53a06c9e4c2
---> 4e68fbc2d704
Step 5/6 : COPY . .
---> a239a11f68d8
Step 6/6 : CMD ["node", "src/index.js"]
---> Running in 49999f68df8f
Removing intermediate container 49999f68df8f
---> e709c03bc597
Successfully built e709c03bc597
Successfully tagged getting-started:latest

(4) src/static/index.htmlに変更を加える。
<title>を "The Awesome Todo App "と変更するなど行ってください。

(5) 「docker build -t getting-started .」でコンテナイメージを再度ビルド。

Sending build context to Docker daemon  219.1kB
Step 1/6 : FROM node:12-alpine
---> b0dc3a5e5e9e
Step 2/6 : WORKDIR /app
---> Using cache
---> 9577ae713121
Step 3/6 : COPY package.json yarn.lock ./
---> Using cache
---> bd5306f49fc8
Step 4/6 : RUN yarn install --production
---> Using cache
---> 4e68fbc2d704
Step 5/6 : COPY . .
---> cccde25a3d9a
Step 6/6 : CMD ["node", "src/index.js"]
---> Running in 2be75662c150
Removing intermediate container 2be75662c150
---> 458e5c6f080c
Successfully built 458e5c6f080c
Successfully tagged getting-started:latest

出力が少し変わっているはずです。ビルドも格段に速くなったことに気づくはずです。また、ステップ1~4では全てキャッシュを使用していることがわかります。

4. マルチステージビルド

このチュートリアルではあまり詳しく説明しませんが、マルチステージビルドは、複数のステージを使ってイメージを作成するための非常に強力なツールです。それには、いくつかの利点があります。

・ビルドタイムの依存性とランタイムの依存性を分離。
・アプリの実行に必要なものだけを配信することで、全体のイメージサイズを縮小。

◎ Maven/Tomcatの例
Javaベースのアプリを構築する場合、ソースコードをJavaバイトコードにコンパイルするためにJDKが必要です。しかし、そのJDKは本番では必要ありません。また、アプリの構築にはMavenやGradleなどのツールを使用しているかもしれません。これらも最終的なイメージでは必要ありません。マルチステージビルドが役立ちます。

# syntax=docker/dockerfile:1
FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package

FROM tomcat
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps 

この例では、1つのステージ(buildと呼ばれる)で、Mavenを使用して実際にJavaのビルドを行います。2番目のステージ(FROM tomcatから始まる)では、buildステージからファイルをコピーしています。最終的なイメージは、最後のステージが作成されているだけです(--target フラグを使用してオーバーライドできます)。

◎ Reactの例
Reactアプリを構築する際には、JSコード(通常はJSX)やSASSスタイルシートなどを静的なHTML、JS、CSSにコンパイルするためにNode環境が必要です。サーバーサイドレンダリングを行わないのであれば、本番のビルドにNode環境は必要ありません。静的リソースを静的なnginxコンテナに入れて出荷すればいいのです。

# syntax=docker/dockerfile:1
FROM node:12 AS build
WORKDIR /app
COPY package* yarn.lock ./
RUN yarn install
COPY public ./public
COPY src ./src
RUN yarn run build

FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html

ここでは、「node:12」イメージを使ってビルドを実行し(レイヤーキャッシュを最大限に活用)、出力を「nginx」コンテナにコピーしています。

この記事が気に入ったらサポートをしてみませんか?