これらの Dockerfile のベスト プラクティスで開発フロヌをスピヌドアップ

投皿日 4月 27, 2020

ドッカヌファむルは、ドッカヌむメヌゞを䜜成するための開始点です。 ファむル圢匏には、ファむルやフォルダヌのコピヌ、コマンドの実行、環境倉数の蚭定、およびコンテナヌ むメヌゞの䜜成に必芁なその他のタスクを実行できる、明確に定矩された䞀連のディレクティブが甚意されおいたす。 結果のむメヌゞを安党か぀小さく、すばやく構築し、すばやく曎新するために、Dockerfileを適切に䜜成するこずが非垞に重芁です。

この投皿では、開発フロヌを高速化し、ビルドの再珟性を確保し、自信を持っお運甚環境にデプロむできるむメヌゞを生成するための優れたDockerfileを䜜成する方法に぀いお説明したす。

泚:このブログ投皿では、Dockerfileの䟋を 、awesome-compose リポゞトリの react-java-mysqlサンプル に基づいおいたす。

開発の流れ

開発者ずしお、開発環境をタヌゲットの運甚コンテキストにできるだけ䞀臎させお、ビルドしたものがデプロむ時に機胜するようにしたいず考えおいたす。


たた、迅速に開発できるようにしたいので、ビルドを高速化し、デバッガヌなどの開発者ツヌルを䜿甚できるようにする必芁がありたす。 コンテナは開発環境を䜓系化するための優れた方法ですが、コンテナずすばやく察話できるようにするには、Dockerfileを正しく定矩する必芁がありたす。

むンクリメンタルビルド

Dockerfile は、コンテナヌ むメヌゞをビルドするための手順の䞀芧です。 Docker ビルダヌは各ステップの結果をむメヌゞ レむダヌずしおキャッシュしたすが、キャッシュを無効にするず、キャッシュを無効にしたステップず埌続のすべおのステップを再実行し、察応するレむダヌを再生成する必芁がありたす。


キャッシュは、COPY たたは ADD によっお参照されるビルド コンテキスト内のファむルが倉曎されるず無効になりたす。 したがっお、ステップの順序は、パフォヌマンスに倧きな圱響を䞎える可胜性がありたす。

Dockerfile で NodeJs プロゞェクトをビルドする䟋を芋おみたしょう。 このプロゞェクトでは、npm ci コマンドの実行時にフェッチされる䟝存関係が package.json ファむルにありたす。

最も単玔なドッカヌファむルは次のようになりたす。

FROM node:lts

ENV CI=true
ENV PORT=3000

WORKDIR /code
COPY . /code
RUN npm ci

CMD [ "npm", "start" ]

䞊蚘のように Dockerfile を構成するず、ビルド コンテキスト内のファむルが倉曎されるたびに COPY 行でキャッシュが無効になりたす。 これは、時間がかかる可胜性のあるpackage.jsonファむルだけでなく、ファむルが倉曎されるず䟝存関係がフェッチされ、node_modulesディレクトリがいっぱいになるこずを意味したす。

これを回避し、䟝存関係が倉曎されたずき(぀たり、package.jsonたたはpackage-lock.jsonが倉曎されたずき)にのみ䟝存関係をフェッチするには、䟝存関係のむンストヌルをアプリケヌションのビルドず実行から分離するこずを怜蚎する必芁がありたす。

より最適化されたドッカヌファむルは次のようになりたす。

FROM node:lts

ENV CI=true
ENV PORT=3000

WORKDIR /code
COPY package.json package-lock.json /code/
RUN npm ci
COPY src /code/src

CMD [ "npm", "start" ]

この分離を䜿甚するず、package.json たたは package-lock.json に倉曎がない堎合、キャッシュは RUN npm ci 呜什によっお生成されたレむダヌに䜿甚されたす。 ぀たり、アプリケヌション゜ヌスを線集しお再構築するずきに、䟝存関係が再ダりンロヌドされないため、時間が🎉節玄されたす。

たた、 以前の投皿で 説明したように、 src 2番目 COPY をディレクトリに制限したす。

ホストずコンテナヌの間でラむブ リロヌドをアクティブにしおおく

このヒントはDockerfileずは盎接関係ありたせんが、コンテナでアプリを実行し、ホストマシン䞊のIDEから゜ヌスコヌドを倉曎しおいる間、ラむブリロヌドをアクティブにしおおくにはどうすればよいですか?

この䟋では、プロゞェクトディレクトリをコンテナにマりントし、環境倉数を枡しお、ホストからのNodeJSファむル倉曎むベントをラップする Chokidar を有効にする必芁がありたす。

$ docker run -e CHOKIDAR_USEPOLLING=true  -v ${PWD}/src/:/code/src/ -p 3000:3000 repository/image_name

䞀貫性のあるビルド

Dockerfileで最も重芁なこずの1぀は、同じビルドコンテキスト(゜ヌス、䟝存関係など)からたったく同じむメヌゞをビルドするこずです。

前のセクションで定矩した Dockerfile を匕き続き改善しおいきたす。

゜ヌスから䞀貫しお構築する

前のセクションで芋たように、Dockerfile の説明に゜ヌス ファむルず䟝存関係を远加し、それらに察しおコマンドを実行するこずで、アプリケヌションを構築できたす。


しかし、前の䟋では、Dockerビルドを実行するたびに生成されたむメヌゞが同じであるこずを確認できたせん...なぜでしょうか。 NodeJSがリリヌスされるたびに、ltsタグがNodeJSむメヌゞの最新のLTSバヌゞョンを指しおいるこずが予想されたすが、これは時間の経過ずずもに倉曎され、砎壊的倉曎が発生する可胜性がありたす。 ベヌスむメヌゞにもっず具䜓的なタグを䜿甚するこずで、これを簡単に修正できたす(LTSたたは最新の安定バヌゞョン😉から遞択できたす)

FROM node:13.12.0

ENV CI=true
ENV PORT=3000

WORKDIR /code
COPY package.json package-lock.json /code/
RUN npm ci
COPY src /code/src

CMD [ "npm", "start" ]

「最新のタグはありたせん」セクションでは、より具䜓的な基本画像タグを䜿甚し、 最新のタグ を回避するこずには他にも利点があるこずがわかりたす。

適切な環境に合わせたマルチステヌゞずタヌゲット

開発ビルドに䞀貫性を持たせたしたが、本番アヌティファクトに察しおこれをどのように行うこずができたすか?

Docker 17.05以降、 マルチステヌゞビルド を䜿甚しお、最終的なむメヌゞを生成するためのステップを定矩できたす。 Dockerfile でこのメカニズムを䜿甚するず、開発フロヌに䜿甚するむメヌゞを、アプリケヌションのビルドに䜿甚するむメヌゞず運甚環境で䜿甚するむメヌゞを分割できたす。

FROM node:13.12.0 AS development

ENV CI=true
ENV PORT=3000

WORKDIR /code
COPY package.json package-lock.json /code/
RUN npm ci
COPY src /code/src

CMD [ "npm", "start" ]

FROM development AS builder

RUN npm run build

FROM nginx:1.17.9 AS production

COPY --from=builder /code/build /usr/share/nginx/html

あなたが芋る FROM たびに...... AS それはビルド段階です。
これで、開発、ビルド、および本番の段階ができたした。
フラグを䜿甚しお --target 特定の開発ステヌゞむメヌゞを構築するこずで、開発フロヌにコンテナを匕き続き䜿甚できたす。

$ docker build --target development -t repository/image_name:development .

そしおい぀ものようにそれを䜿甚しおください

$ docker run -e CHOKIDAR_USEPOLLING=true -v ${PWD}/src/:/code/src/ repository/image_name:development

フラグのない --target Docker ビルドでは、最終ステヌゞ (この堎合は運甚むメヌゞ) がビルドされたす。 私たちのプロダクションむメヌゞは、前の手順でビルドされたバむナリが提䟛される正しい堎所に眮かれた単なる nginx むメヌゞです。

生産準備完了

本番むメヌゞを可胜な限り無駄なく安党に保぀こずは非垞に重芁です。 運甚環境でコンテナヌを実行する前に確認する必芁があるこずがいく぀かありたす。

最新のむメヌゞ バヌゞョンはもうありたせん

「 ゜ヌスから䞀貫しおビルドする」セクションで前述したように、ビルド ステップに特定のタグを䜿甚するず、むメヌゞのビルドを再珟可胜にするのに圹立ちたす。 画像に特定のタグを䜿甚する理由は、他にも少なくずも2぀ありたす。 

  • お気に入りのオヌケストレヌタヌ (Swarm、Kubernetes...) のむメヌゞ バヌゞョンで実行されおいるすべおのコンテナヌを簡単に芋぀けるこずができたす。

# Search in Docker engine containers using our repository/image_name:development image

$ docker inspect $(docker ps -q) | jq -c '.[] | select(.Config.Image == "repository/image_name:development") |"\(.Id) \(.State) \(.Config)"'

"89bf376620b0da039715988fba42e78d42c239446d8cfd79e4fbc9fbcc4fd897 {\"Status\":\"running\",\"Running\":true,\"Paused\":false,\"Restarting\":false,\"OOMKilled\":false,\"Dead\":false,\"Pid\":25463,\"ExitCode\":0,\"Error\":\"\",\"StartedAt\":\"2020-04-20T09:38:31.600777983Z\",\"FinishedAt\":\"0001-01-01T00:00:00Z\"}
{\"Hostname\":\"89bf376620b0\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":true,\"AttachStderr\":true,\"ExposedPorts\":{\"3000/tcp\":{}},\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"CHOKIDAR_USEPOLLING=true\",\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NODE_VERSION=12.16.2\",\"YARN_VERSION=1.22.4\",\"CI=true\",\"PORT=3000\"],\"Cmd\":[\"npm\",\"start\"],\"Image\":\"repository/image_name:development\",\"Volumes\":null,\"WorkingDir\":\"/code\",\"Entrypoint\":[\"docker-entrypoint.sh\"],\"OnBuild\":null,\"Labels\":{}}"

#Search in k8s pods running a container with our repository/image_name:development image (using jq cli)
$ kubectl get pods --all-namespaces -o json | jq -c '.items[] | select(.spec.containers[].image == "repository/image_name:development")| .metadata'

{"creationTimestamp":"2020-04-10T09:41:55Z","generateName":"image_name-78f95d4f8c-","labels":{"com.docker.default-service-type":"","com.docker.deploy-namespace":"docker","com.docker.fry":"image_name","com.docker.image-tag":"development","pod-template-hash":"78f95d4f8c"},"name":"image_name-78f95d4f8c-gmlrz","namespace":"docker","ownerReferences":[{"apiVersion":"apps/v1","blockOwnerDeletion":true,"controller":true,"kind":"ReplicaSet","name":"image_name-78f95d4f8c","uid":"5ad21a59-e691-4873-a6f0-8dc51563de8d"}],"resourceVersion":"532","selfLink":"/api/v1/namespaces/docker/pods/image_name-78f95d4f8c-gmlrz","uid":"5c70f340-05f1-418f-9a05-84d0abe7009d"}

  • CVE(䞀般的な脆匱性ず露出)の堎合、コンテナずむメヌゞの説明にパッチを適甚する必芁があるかどうかをすばやく知るこずができたす。

この䟋から、開発むメヌゞず運甚むメヌゞが高山バヌゞョンであるこずを指定できたす。

FROM node:13.12.0-alpine AS development

ENV CI=true
ENV PORT=3000

WORKDIR /code
COPY package.json package-lock.json /code/
RUN npm ci
COPY src /code/src

CMD [ "npm", "start" ]

FROM development AS builder

RUN npm run build

FROM nginx:1.17.9-alpine

COPY --from=builder /code/build /usr/share/nginx/html

公匏画像を䜿甚する

Docker Hub を䜿甚しお、Dockerfile で䜿甚する基本むメヌゞを怜玢できたすが、そのうちのいく぀かは公匏にサポヌトされおいるものです。 これらの画像を次のように䜿甚するこずを匷くお勧めしたす。

  • それらのコンテンツは怜蚌されおいたす
  • CVEが修正されるず、すぐに曎新されたす

開発のスピヌドアップ

image_filter芁求ク゚リパラメヌタを远加しお、公匏画像のみを取埗できたす。

https://hub.docker.com/search?q=nginx&type=image&image_filter=official

この投皿の以前の䟋はすべお、NodeJSずNGINXの公匏画像を䜿甚しおいたした。

ちょうど十分な暩限!

コンテナで実行されおいるかどうかにかかわらず、すべおのアプリケヌションは、アプリケヌションが必芁なリ゜ヌスにのみアクセスする必芁があるこずを意味する 最小特暩の原則 に埓う必芁がありたす。 

悪意のある動䜜の堎合、たたはバグのために、あたりにも倚くの特暩で実行されおいるプロセスは、実行時にシステム党䜓に予期しない結果をもたらす可胜性がありたす。

NodeJSの公匏むメヌゞは適切に セットアップされおいるため、 バック゚ンドのDockerfileに切り替えたす。

特暩のないナヌザヌずしお実行するようにむメヌゞを構成するのは非垞に簡単です。

FROM maven:3.6.3-jdk-11 AS builder
WORKDIR /workdir/server
COPY pom.xml /workdir/server/pom.xml
RUN mvn dependency:go-offline

RUN mvn package

FROM openjdk:11-jre-slim
RUN addgroup -S java && adduser -S javauser -G java
USER javauser

EXPOSE 8080
COPY --from=builder /workdir/server/target/project-0.0.1-SNAPSHOT.jar /project-0.0.1-SNAPSHOT.jar

CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/project-0.0.1-SNAPSHOT.jar"]

新しいグルヌプを䜜成し、それにナヌザヌを远加し、USERディレクティブを䜿甚するだけで、root以倖のナヌザヌでコンテナを実行できたす。

結論

このブログ投皿では、Dockerfileを慎重に䜜成するこずで、Dockerむメヌゞを最適化および保護する倚くの方法のいく぀かを瀺したした。 さらに進んでみたい堎合は、以䞋をご芧ください。 

関連蚘事