مهندس ارشد توسعه نرم افزار مجموعه صباایده، فیلیمو، آپارات | Staff Engineer @Sabaidea,
داکرفایل چند مرحله ای برای ایمیج سبک و سریع در Golang

سرعت خروجی گرفتن در فرآیندهای توسعه و پیادهسازی برای اعمال سریع تغییرات در کد بسیار مهم است، بنابراین در پروسههای روزمرهای که در توسعه ابزارهای زیرساختی در آپارات داشتم به تجربهای تحت عنوان داکرفایل چند مرحلهای برای ایجاد Image های سبک و سریع در زبان Golang رسیدم و در این مقاله میخواهم این تجربه رو با شما در میان بگذارم.
خیلی از ما وقتی که میخواهیم اپلکیشنِ Golang خودمان را داکرایز کنیم، از Image رسمی پیشفرض Golang استفاده میکنیم.
همانطور که Dockerfile این Image را میببینید با خطوط زیر شروع میشود :
FROM golang
FROM nginx
FROM openjdk
حالا اگر این Image را دریافت کنیم، متوجه خواهیدشد که ۸۰۳ مگابایت حجم دارد. باید گفت، برای یک Image خالی که هیچ کدی درون آن نیست حجم خیلی زیادی است، اینطور نیست؟
docker pull golang:1.13
$ docker image list
golang 1.13 3a7408f53f79 2 months ago 803MB
از سویی دیگر یک Image کم حجم به نام Alpine برای Golang وجود دارد که ۳۶۰ مگابایت حجم دارد درست است که حجم کمتری نسبت به Image رسمی golang دارد اما برای یک Image در حالت Production حجم خیلی زیادی است.
FROM golang:alpine
$ docker image list
golang alpine 459ae5e869df 10 days ago 370MB
استفاده از روش “چند مرحلهای برای کاهش حجم
به لطف استفاده از روش “چند مرحلهای” داکر در ایجاد Image ها میتوانیم فایل خروجی نرمافزار خود را در Image golang:alpine بسازیم و یک Image بر اساس alpine تولید کنیم که تنها، فایل اجرایی نرمافزار ما در آن باشد.
برای روشنتر شدن موضوع بیایید باهم یک وب سرور ساده با Go بنویسیم و در ادامه سراغ Dockerfile پروژه برویم، ابتدا از Image Alpine به عنوان Builder استفاده میکنیم. میتوانید حجم Image را در تصویر زیر مشاهده کنید:
حالا وقتش هست که ببینیم Image برنامه به چه شکل ایجاد میشود و چه حجمی خواهد داشت؟
$ docker build -t subzero/sample .
Sending build context to Docker daemon 25.6kB
Step 1/10 : FROM golang:alpine AS builder
---> 459ae5e869df
Step 2/10 : WORKDIR /src ---> Using cache
---> 6cf33681f8bb
Step 3/10 : COPY . /src---> b2e0287ec4f1
Step 4/10 : ENV GO111MODULE=on
---> Running in b2a8def41333
Removing intermediate container b2a8def41333
---> 33858d537c8c
Step 5/10 : RUN go mod download
---> Running in a57d93861ca6
Removing intermediate container a57d93861ca6
---> bc14349da12c
Step 6/10 : RUN go build -o server main.go
---> Running in 306afd335c6cRemoving intermediate container 306afd335c6c---> 970ebfd59bd1
Step 7/10 : FROM scratch
--->
Step 8/10 : WORKDIR /app
---> Running in 0c4735c45471
Removing intermediate container 0c4735c45471
---> d2ee5378c6d8
Step 9/10 : COPY --from=builder /src/server /app/
---> 4644b56bd11e
Step 10/10 : ENTRYPOINT ./server
---> Running in 18c324363942
Removing intermediate container 18c324363942
---> 2fa59a876599
Successfully built 2fa59a876599
Successfully tagged subzero/sample:latest
$ docker image list
REPOSITORY TAG IMAGE ID SIZE
subzero/sample latest 2fa59a876599 13MB
خیلی بهتر شد نه؟
باید بگویم هنوز هم جا برای کم شدن حجم این فایل وجود دارد، به طور مثال؛
اگر هنگام ایجاد فایل خروجی، اطلاعات مربوط به Debug را حذف کنیم و فایل را مستقیم برای سیستم عامل لینوکس خروجی بگیریم، این حجم باز کاهش پیدا میکند. برای این کار کافیست خط دهم داکرفایلی را که ایجاد کردیم به این شکل زیر تغییر دهیم:
RUN go build -o server main.go
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -ldflags="-w -s" -o server main.go
REPOSITORY TAG IMAGE ID SIZE
subzero/sample latest 23ad308c5a7b 9.31MB
خوب با همین تغییر بازهم حجم خروجی حدود ۲۵ درصد کاهش پیدا کرد.
استفاده از روش “چند مرحلهای” برای کاهش زمان Build
با توجه به اینکه برای استفاده از پکیجهای زبان Go در ایران با مشکلات زیادی مواجه هستیم و برای Build کردن Image از پروکسی استفاده میکنیم، کاهش مدت زمان Build در محیط Production یک دغدغه اصلی است.
به طور معمول اگر کد در مرحله Production باشد دیگر تغییرات زیادی در پکیجهای استفاده شده، ایجاد نمیشود یا حداقل در پروژههایی که من تا به امروز کار کردم، این موضوع زیاد پیش آمدهاست،
برای مثال، شرایطی را در نظر بگیرید که در روز، چیزی حدود ۱۰ بار تغییرات دارید و هنوز پروژه به روال عادی CI/CD شرکت اضافه نشده و از طرفی و مجبور هستید کد را به شکل دستی روی سرور Deploy کنید، با توجه به زمان Build میتوانید تصور کنید چه سختیای را به همراه دارد؟
در تصویر زیر به طنز، شاهد مقایسه اندازه وزن خورشید، ستاره نوترونی، سیاه چاله و node_module هستید. درست است که حجم پیکجها و ماژولهای Golang خیلی زیاد نیست ولی مشکل پروکسی سرور برای دریافت پکیجهای Golang با همین مقدار حجم، بسیار اذیت کنندهاست،

به همین دلیل برای حل مشکل و کاهش زمان Build دست به کار شدم و بعد از جستجو متوجه شدم، دوستانی که Node و React و پروژه هایی که نیاز به node_module دارد به راهحلهای خوبی رسیدند. من نیز با توجه به روشهای آنها با اضافهکردن یک مرحله جدید به روش “چند مرحلهای” قبلی، راهحلی برای رفع این مشکل در Golang به شرح زیر پیدا کردم:
- ابتدا فایل go.mod به تنهایی در یک Image قرار میدهیم.
- بعد از آن دستور `go mod download` اجرا میشود که ماژولهای مورد نیاز در یک Image واسط ذخیره شود.
- در مرحله بعد یک کد بر اساس این Image واسط تولید میشود.
- در انتها مشابه حالت قبل، خروجی در Image کم حجم alpine قرار داده میشود.
نکته:
با انجام این کار مادامی که تغییری در فایل go.mod پروژه اتفاق نیافتد، مرحله گرفتن ماژولهای مورد نیاز برای کامپایل پروژه skip میشود و سرعت ایجاد Image پروژه افزایش مییابد.
نتیجهگیری
زمان ایجاد Image در پروژه بالا که تعداد ماژولهای مورد نیاز کمی داشت، در حالت اول به مدت ۱:۱۰ ثانیه است و در حالت دوم به ۳۷ ثانیه میرسد، این مقدار در شرایطی با ماژولهای مورد نیازِ بیشتر، محسوستر است، مثلا در پروژه لایو آپارات چیزی حدود ۴ دقیقه صرفهجویی در زمان داشتیم.
مطلبی دیگر از این انتشارات
معرفی سیستم های Caching و استفاده آنها در آپارات
مطلبی دیگر از این انتشارات
طرح کسب درآمد از آپارات بروز شد
مطلبی دیگر از این انتشارات
نظارت بر کارایی فرآیندهای نرمافزاری یا APM چیست؟