dockerに入門してみる(その9)(終)
前回の記事はこちら↓
今回は"dockerに入門してみる"シリーズの最終回です。最後は、コンテナイメージをビルドする上でのベストプラクティスを見ていきます。
イメージをスキャンする
事前に見つけられる脆弱性があるならば、見つけて潰してしまいたいですよね。
DockerはSnykとパートナーになっているため、docker scan
によってコンテナイメージの脆弱性をスキャンすることができます。
試しにtutorialを動かしているgetting-started
をスキャンしてみます。
PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker scan getting-started Docker Scan relies upon access to Snyk, a third party provider, do you consent to proceed using Snyk? (y/N) y Testing getting-started... ✗ Medium severity vulnerability found in openssl/libcrypto1.1 Description: NULL Pointer Dereference Info: https://snyk.io/vuln/SNYK-ALPINE311-OPENSSL-1051931 Introduced through: openssl/libcrypto1.1@1.1.1g-r0, openssl/libssl1.1@1.1.1g-r0, apk-tools/apk-tools@2.10.5-r0, libtls-standalone/libtls-standalone@2.9.1-r0 From: openssl/libcrypto1.1@1.1.1g-r0 From: openssl/libssl1.1@1.1.1g-r0 > openssl/libcrypto1.1@1.1.1g-r0 From: apk-tools/apk-tools@2.10.5-r0 > openssl/libcrypto1.1@1.1.1g-r0 and 4 more... Fixed in: 1.1.1i-r0 ✗ Medium severity vulnerability found in musl/musl Description: Out-of-bounds Write Info: https://snyk.io/vuln/SNYK-ALPINE311-MUSL-1042763 Introduced through: musl/musl@1.1.24-r2, busybox/busybox@1.31.1-r9, alpine-baselayout/alpine-baselayout@3.2.0-r3, openssl/libcrypto1.1@1.1.1g-r0, openssl/libssl1.1@1.1.1g-r0, zlib/zlib@1.2.11-r3, apk-tools/apk-tools@2.10.5-r0, libtls-standalone/libtls-standalone@2.9.1-r0, busybox/ssl_client@1.31.1-r9, gcc/libgcc@9.3.0-r0, musl/musl-utils@1.1.24-r2, pax-utils/scanelf@1.2.4-r0, libc-dev/libc-utils@0.7.2-r0 From: musl/musl@1.1.24-r2 From: busybox/busybox@1.31.1-r9 > musl/musl@1.1.24-r2 From: alpine-baselayout/alpine-baselayout@3.2.0-r3 > musl/musl@1.1.24-r2 and 12 more... Fixed in: 1.1.24-r3 Organization: undefined Package manager: apk Project name: docker-image|getting-started Docker image: getting-started Platform: linux/amd64 Tested 16 dependencies for known vulnerabilities, found 2 vulnerabilities. For more free scans that keep your images secure, sign up to Snyk at https://dockr.ly/3ePqVcp
スキャン結果として、見つかった脆弱性の種類やどのバージョンのライブラリで修正されているかを吐き出してくれます。docker scan
のオプションについては、下記の公式に詳しく載っています。
docs.docker.com
コマンドラインからイメージをスキャンするほか、Docker Hubごとスキャンすることも出来るようです。
docs.docker.com
イメージ構成を見る
docker image history
コマンドを用いると、そのイメージがどのレイヤーで構成されているかを見ることができます。
PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker image history getting-started IMAGE CREATED CREATED BY SIZE COMMENT 7d2a760d3274 13 days ago CMD ["node" "src/index.js"] 0B buildkit.dockerfile.v0 <missing> 13 days ago RUN /bin/sh -c yarn install --production # b… 85.2MB buildkit.dockerfile.v0 <missing> 13 days ago COPY . . # buildkit 4.62MB buildkit.dockerfile.v0 <missing> 2 weeks ago WORKDIR /app 0B buildkit.dockerfile.v0 <missing> 5 weeks ago /bin/sh -c #(nop) CMD ["node"] 0B <missing> 5 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B <missing> 5 weeks ago /bin/sh -c #(nop) COPY file:238737301d473041… 116B <missing> 5 weeks ago /bin/sh -c apk add --no-cache --virtual .bui… 7.62MB <missing> 5 weeks ago /bin/sh -c #(nop) ENV YARN_VERSION=1.22.5 0B <missing> 5 weeks ago /bin/sh -c addgroup -g 1000 node && addu… 76.5MB <missing> 5 weeks ago /bin/sh -c #(nop) ENV NODE_VERSION=12.20.0 0B <missing> 8 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 8 months ago /bin/sh -c #(nop) ADD file:b91adb67b670d3a6f… 5.61MB
出力された各行がレイヤーを表しています。手軽にレイヤーのサイズを見ることができるので、どのレイヤーが肥大化しているかをパッと判別することができます。
レイヤーキャッシュを用いてビルド回数を減らす
Dockerfileの書き方を工夫することで、ビルド回数を減らすことができます。まずは、以前のチュートリアルで触ったDockerfileを引用します。
FROM node:12-alpine WORKDIR /app COPY . . RUN yarn install --production CMD ["node", "src/index.js"]
docker image history
で見た通り、コマンドそれぞれは一つずつレイヤーになっています。また、イメージに変更を加えるとyarnの依存関係を再インストールする必要があります。
ビルドするたびに同じ依存関係をインストールし直していくのは非効率なので、上手いこと依存関係をキャッシュして解決していきます。
Nodeベースのアプリケーションでは、アプリケーションの依存関係は全てpackage.json
に記述されます。そのため、まずpackage.json
をコピーして依存関係をインストールしてしまい、その後に他のソースコードをコピーすれば余計な再インストールを避けられます。
具体的には、以下のようにDockerfileを書きかえます。
FROM node:12-alpine WORKDIR /app COPY package.json yarn.lock ./ RUN yarn install --production COPY . . CMD ["node", "src/index.js"]
次に、Dockerfileと同じ階層に.dockerignore
ファイルを作成します。
node_modules
.dockerignore
はDocker(正確にはビルド時に動作するdaemon)に無視してほしいファイルを指定するためのファイルで、今回node_modules
はRUNコマンドを実行した際に上書きされるため無視する対象とします。
では、一度イメージをビルドしてみます。
PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker build -t getting-started . [+] Building 13.9s (10/10) FINISHED => [internal] load build definition from Dockerfile 0.1s => => transferring dockerfile: 175B 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 52B 0.0s => [internal] load metadata for docker.io/library/node:12-alpine 0.0s => [1/5] FROM docker.io/library/node:12-alpine 0.2s => => resolve docker.io/library/node:12-alpine 0.0s => [internal] load build context 0.1s => => transferring context: 8.65kB 0.0s => [2/5] WORKDIR /app 0.0s => [3/5] COPY package.json yarn.lock ./ 0.1s => [4/5] RUN yarn install --production 12.1s => [5/5] COPY . . 0.1s => exporting to image 1.3s => => exporting layers 1.3s => => writing image sha256:d71dd886d28378420a1fb3f6782c4d9816f52e4761d08ecca02ca3c893818ebc 0.0s => => naming to docker.io/library/getting-started
Dockerfileの記述通りに処理が進んでいますね。
では次に、src/static/index.html
を適当にいじってみて再度ビルドしてみます。
PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker build -t getting-started . [+] Building 0.3s (10/10) FINISHED => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 32B 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 34B 0.0s => [internal] load metadata for docker.io/library/node:12-alpine 0.0s => [1/5] FROM docker.io/library/node:12-alpine 0.0s => [internal] load build context 0.0s => => transferring context: 3.43kB 0.0s => CACHED [2/5] WORKDIR /app 0.0s => CACHED [3/5] COPY package.json yarn.lock ./ 0.0s => CACHED [4/5] RUN yarn install --production 0.0s => [5/5] COPY . . 0.1s => exporting to image 0.1s => => exporting layers 0.1s => => writing image sha256:2807d4c8a4f026a88ac4005c791d060de6a8a23ae1275046047b1b08499688c3 0.0s => => naming to docker.io/library/getting-started
途中CACHED
とある通り、イメージのキャッシュが上手く働いて余計なインストールを省けたことが分かります。これで、より効率的なビルド作業が実現できますね!
マルチステージビルド
ビルドするだけのステージと、成果物を載せるだけのステージ…と複数のステージを使い分けていくことで、最終的なイメージのサイズを小さくすることができます。
Maven/Tomcatの例
Javaベースのアプリケーションをビルドするには、コンパイル時にJDKが必要となります。しかし、JDK自体はプロダクション環境には必要ないものです。MavenやGradleを使用する際も同様ですね。こういう場合に、マルチステージビルドが有用となります。
FROM maven AS build WORKDIR /app COPY . . RUN mvn package FROM tomcat COPY --from=build /app/target/file.war /usr/local/tomcat/webapps
この例では、build
ステージでMavenを使用したJavaアプリケーションのビルドを行い、tomcat
ステージでbuild
ステージからビルド成果物をコピーしています。
Reactの例
Reactアプリケーションをビルドするには、Node環境が必要となります。しかし、サーバーサイドで画面の描画を行わない限りはプロダクションビルドにNode環境は必要ありません。こういう場合にも、マルチステージビルドが有用です。
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コンテナにコピーするだけ…。Nodeまわりのことはよく分かりませんが、確かに効率的っぽいですね。
コンテナイメージのビルドをより速く、より効率的に行う方法を(少し)知ったところで、このチャプターは終わりです。そして、これにてDocker 101 Tutorialも終了です!
Dockerの世界はまだまだ広く、一口にDockerといってもコンテナオーケストレーションやCNCFなど様々な領域があります。 landscape.cncf.io 会社ではKubernetesを利用しているため、次は話題のイラストでわかるDockerとKubernetesを読んで更にDocker自体の理解やKubernetesへの理解を深めたいです。
とりあえずは、このチュートリアルを年内に終えられてよかったです。 よいお年をお迎えください!