مقدمه
وقتی صحبت از ایمیجهای کانتینری میشود، کوچکتر بودن تقریباً همیشه بهتر است — ایمیجهای کوچکتر سریعتر pull میشوند، آسیبپذیریهای بالقوه کمتری دارند و سطح حمله کوچکتری ارائه میدهند. ساخت چندمرحلهای (Multi-Stage Build) اصلیترین تکنیکی است که داکر برای سبک نگهداشتن ایمیجهای تولیدی ارائه میدهد.
ساخت چندمرحلهای چگونه کار میکند
یک ساخت چندمرحلهای از یک Dockerfile واحد با چند دستورالعمل FROM استفاده میکند که هرکدام نماینده یک مرحله ساخت مجزا هستند. این روش امکان میدهد ابزارهای سنگین ساخت و کامپایلرها در یک مرحله اولیه و بزرگتر باقی بمانند، در حالی که فقط خروجی کامپایلشده به یک ایمیج نهایی حداقلی برای تولید — که اغلب slim image نامیده میشود — کپی میشود.
FROM golang:1.23.4-alpine AS base
WORKDIR /src
COPY go.mod go.sum .
RUN go mod download
COPY . .
FROM base AS build-client
RUN go build -o /bin/client ./cmd/client
FROM base AS build-server
RUN go build -o /bin/server ./cmd/server
FROM scratch AS prod
COPY --from=build-client /bin/client /bin/
COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]هر مرحله نامگذاریشده یک ایمیج میانی تولید میکند که مراحل بعدی میتوانند به آن ارجاع دهند، اما داکر این ایمیجهای میانی را پس از اتمام مرحله نهایی حذف میکند. مراحلی که به یکدیگر وابسته نیستند، مانند build-client و build-server در این مثال، میتوانند بهصورت موازی اجرا شوند و فرآیند ساخت را سریعتر کنند.
ایمیج تولیدی نهایی که به این روش ساخته میشود، در مقایسه با ایمیج پایه زمان ساخت، بهطور چشمگیری کوچکتر است، زیرا فقط شامل فایلهای باینری کامپایلشده و هیچ چیز دیگری نیست.
ساخت چند ایمیج از یک Dockerfile
یک Dockerfile واحد همچنین میتواند چند ایمیج مجزا تولید کند، با تقسیم مرحله نهایی و استفاده از فلگ --target برای انتخاب اینکه کدام مرحله ساخته شود:
docker build -t multi:client --target prod-client -f Dockerfile-final .
docker build -t multi:server --target prod-server -f Dockerfile-final .Buildx و BuildKit
سیستم ساخت داکر به یک کلاینت و یک سرور تقسیم شده است: Buildx کلاینت است که بهصورت یک پلاگین CLI پیادهسازی شده، و BuildKit سروری است که واقعاً ساخت را انجام میدهد. از نسخه Docker v23.0 به بعد، Buildx کلاینت پیشفرض ساخت برای هر دستور docker build بوده است.
Buildx میتواند برای ارتباط با چند نمونه BuildKit، که builder نامیده میشوند، پیکربندی شود؛ این builderها میتوانند بهصورت محلی، در یک ابر خصوصی، یا روی سرویس Build Cloud خود داکر اجرا شوند. builderهای محلی معمولاً از درایور docker-container استفاده میکنند، در حالی که ساختهای راهدور از درایور cloud استفاده میکنند.
docker buildx lsساخت چندمعماری
داکر میتواند ایمیجهایی برای معماریهای CPU متفاوت از دستگاه محلی بسازد — برای مثال، ساخت ایمیج ARM روی یک سیستم AMD. این کار با فلگ --platform انجام میشود:
docker buildx build --builder=container \
--platform=linux/amd64,linux/arm64 \
-t nigelpoulton/ddd-book:ch8.1 --push .هر دستورالعمل Dockerfile یکبار به ازای هر معماری هدف اجرا میشود و ایمیجهای حاصل تحت همان تگ منتشر میشوند، بهطوری که یک دستور docker pull بهطور خودکار نسخه صحیح را برای هر پلتفرمی دریافت میکند.
شیوههای مناسب
دو شیوه تأثیر قابلتوجهی بر کارایی ساخت و اندازه ایمیج دارند. اول، ساختاردهی Dockerfile بهگونهای که دستورالعملهای احتمالاً پرتغییرتر در انتها قرار بگیرند، به حفظ build cache کمک میکند، زیرا یک miss در کش برای یک دستورالعمل، کش تمام دستورالعملهای بعدی را نیز باطل میکند. دوم، نصب فقط بستههای ضروری — با استفاده از فلگهایی مانند no-install-recommends در صورت وجود — میتواند بهطور چشمگیری تعداد وابستگیهای واردشده به ایمیج را کاهش دهد.
نتیجهگیری
ساخت چندمرحلهای، ابزارهای لازم برای ساخت یک برنامه را از آنچه برای اجرای آن لازم است جدا میکند و در نتیجه ایمیجهای تولیدی کوچکتر و امنتری به دست میآید. این تکنیک در کنار قابلیت Buildx و BuildKit برای ساخت چندمعماری و بهرهگیری از کش مشترک، پایه گردشکارهای کارآمد و آماده تولید با کانتینر را تشکیل میدهد.