সেরা চর্চা


অফিসিয়াল বেস্ট প্র্যাকটিস মেনে চলুন

অফিসিয়াল Dockerfile বেস্ট প্র্যাকটিস পাতায় আপনার Dockerfile আরও উন্নত করার বিষয়ে অনেক দরকারি বিষয় আছে।

পারফরম্যান্স

আপনার মূল লক্ষ্য হওয়া উচিত পারফরম্যান্স অপ্টিমাইজ করা (বিশেষ করে টেস্ট রানারের ক্ষেত্রে)। এতে আপনার টুলিং যত দ্রুত সম্ভব চলবে এবং টাইম-আউট হবে না।

পরিমাপ করুন

এক্সিকিউশন টাইম বারবার মাপা টুলিংয়ের পারফরম্যান্স সম্পর্কে ধারণা পাওয়ার দারুণ একটি উপায়। কোনো পরিবর্তনের পরেও এবং আগেও এক্সিকিউশন টাইম মাপার অভ্যাস গড়ে তুলুন। এমনকি যখন আপনার মনে হয় কোনো পরিবর্তন নিশ্চিতভাবেই পারফরম্যান্স বাড়াবে, তখনও এক্সিকিউশন টাইম মাপা উচিত।

স্ক্রিপ্ট

সম্ভব হলে পারফরম্যান্স স্বয়ংক্রিয়ভাবে মাপার জন্য স্ক্রিপ্ট তৈরি করুন (একে _বেন্চমার্কিং_ও বলা হয়)। একটি খুবই কাজের কমান্ড-লাইন টুল হলো hyperfine, তবে আপনার টুলিংয়ের জন্য যেটি সবচেয়ে বেশি মানানসই মনে হয়, সেটিই নির্দ্বিধায় ব্যবহার করুন।

নতুন ট্র্যাক টুলিং রিপোজিটরিগুলোতে নিচের দুটি স্ক্রিপ্ট পাওয়া যাবে:

  1. ./bin/benchmark.sh: ট্র্যাক টুলিং কোড বেন্চমার্ক করে (সোর্স কোড)
  2. ./bin/benchmark-in-docker.sh: ট্র্যাক টুলিংয়ের Docker ইমেজ বেন্চমার্ক করে (সোর্স কোড)
Note

যদি আপনি এমন একটি ট্র্যাক টুলিং রিপোজিটরিতে কাজ করেন যেখানে এই ফাইলগুলো নেই, তাহলে উপরের সোর্স লিংক ব্যবহার করে নির্দ্বিধায় সেগুলো আপনার রিপোতে কপি করে নিতে পারেন।

Caution

বেন্চমার্কিং স্ক্রিপ্ট টুলিংয়ের পারফরম্যান্স অনুমান করতে সাহায্য করে। তবে মনে রাখবেন, Exercism-এর প্রোডাকশন সার্ভারে পারফরম্যান্স প্রায়ই কম হয়।

বিভিন্ন বেস ইমেজ নিয়ে পরীক্ষা করুন

বিভিন্ন বেস ইমেজ নিয়ে পরীক্ষা করে দেখুন (যেমন Ubuntu-র বদলে Alpine), দেখুন একটি অন্যটির চেয়ে (উল্লেখযোগ্যভাবে) ভালো পারফরম্যান্স দেয় কি না। পারফরম্যান্স যদি প্রায় সমান হয়, তাহলে সবচেয়ে ছোট ইমেজটি বেছে নিন।

ইন্টারনাল নেটওয়ার্ক ব্যবহার করে দেখুন

none-এর বদলে internal নেটওয়ার্ক ব্যবহার করলে পারফরম্যান্স বাড়ে কি না, তা দেখে নিন। আরও তথ্যের জন্য নেটওয়ার্ক ডকুমেন্টেশন দেখুন।

রান-টাইম কমান্ডের চেয়ে বিল্ড-টাইম কমান্ডকে প্রাধান্য দিন

ট্র্যাক টুলিং একটি একবার-চলা, অল্পস্থায়ী Docker কন্টেইনার রান করে, যা নিচের ধাপগুলো সম্পাদন করে।

  1. একটি Docker কন্টেইনার তৈরি করা হয়।
  2. সঠিক আর্গুমেন্ট দিয়ে Docker কন্টেইনারটি রান করা হয়।
  3. Docker কন্টেইনারটি ধ্বংস করা হয়।

তাই ধাপ ২-এ যে কোড চলে, সেটি প্রতিটি টুলিং রানেই চলে। এই কারণে ধাপ ২-এ চলা কোডের পরিমাণ কমানো পারফরম্যান্স বাড়ানোর দারুণ একটি উপায়। এটি করার একটি উপায় হলো কোড রান-টাইম থেকে বিল্ড-টাইমে সরিয়ে নেওয়া। রান-টাইম কোড প্রতিটি টুলিং রানে চলে, কিন্তু বিল্ড-টাইম কোড চলে মাত্র একবার (যখন Docker ইমেজ তৈরি হয়)।

বিল্ড-টাইম কোড একটি GitHub Actions ওয়ার্কফ্লোর অংশ হিসেবে একবার চলে। তাই বিল্ড-টাইমে চলা কোড (তুলনামূলকভাবে) ধীর হলেও সমস্যা নেই।

উদাহরণ: লাইব্রেরি আগেই কম্পাইল করা

Haskell টেস্ট রানারে টেস্ট চালাতে গেলে কিছু বেস লাইব্রেরি কম্পাইল করার দরকার হয়। যেহেতু প্রতিটি টেস্ট রান একটি নতুন কন্টেইনারে হয়, তাই এর মানে হলো এই কম্পাইলিং প্রতিটি টেস্ট রানেই করতে হয়! এটি এড়াতে Haskell টেস্ট রানারের Dockerfile-এ নিচের দুটি কমান্ড আছে:

COPY pre-compiled/ .
RUN stack build --resolver lts-20.18 --no-terminal --test --no-run-tests

প্রথমে pre-compiled ডিরেক্টরিটি ইমেজে কপি করা হয়। এই ডিরেক্টরিটি একটি টেস্ট অনুশীলনী হিসেবে সেট আপ করা, এবং এটি আসল অনুশীলনীটি যে বেস লাইব্রেরিগুলোর উপর নির্ভর করে, সেগুলোর উপরই নির্ভর করে। তারপর আমরা ওই ডিরেক্টরিতে টেস্ট চালাই, যা আসল অনুশীলনীর জন্য টেস্ট চালানোর মতোই। টেস্ট চালালে বেস কম্পাইল হয়ে যায়, তবে পার্থক্য হলো এটি ঘটে বিল্ড টাইমে। ফলে তৈরি হওয়া Docker ইমেজের বেস লাইব্রেরিগুলো আগেই কম্পাইল করা থাকে। এর মানে রান টাইমে আর কম্পাইল করার দরকার হয় না, যার ফলে এক্সিকিউশন হয় (অনেক) দ্রুত।

উদাহরণ: বাইনারি আগেই কম্পাইল করা

কিছু ভাষায় কোড আগেভাগে বা ঠিক চালানোর সময়ে কম্পাইল করার সুযোগ থাকে। এটি বিল্ড টাইম বনাম রান টাইম-এর একটি ভারসাম্যের বিষয়, আর এখানেও আমরা পারফরম্যান্সের কারণেই বিল্ড টাইম এক্সিকিউশনকে বেছে নিই।

C# টেস্ট রানারের Dockerfile-এ এই পদ্ধতিই ব্যবহার করা হয়েছে, যেখানে কোড ঠিক চলার সময়ে (রান টাইমে) কম্পাইল করার বদলে টেস্ট রানারকে আগেভাগে (বিল্ড টাইমে) একটি বাইনারিতে কম্পাইল করা হয়। এর মানে রান টাইমে করার কাজ কমে যায়, যা পারফরম্যান্স বাড়াতে সাহায্য করবে।

আকার

আপনার ইমেজের আকার কমানোর চেষ্টা করা উচিত, যার ফলে এটি:

  • ডিপ্লয় করতে দ্রুততর হবে
  • আমাদের খরচ কমাবে
  • প্রতিটি কন্টেইনারের স্টার্টআপ টাইম উন্নত করবে

বিভিন্ন ডিস্ট্রিবিউশন ব্যবহার করে দেখুন

ভিন্ন ভিন্ন ডিস্ট্রিবিউশনের ইমেজের আকারও ভিন্ন হয়। উদাহরণস্বরূপ, alpine:3.20.2 ইমেজটি ubuntu:24.10 ইমেজের চেয়ে দশ গুণ ছোট:

REPOSITORY   TAG       SIZE
alpine       3.20.2    8.83MB
ubuntu       24.10     101MB

সাধারণভাবে Alpine-ভিত্তিক ইমেজগুলো সবচেয়ে ছোট ইমেজগুলোর মধ্যে পড়ে, তাই অনেক টুলিং ইমেজ Alpine-এর উপর ভিত্তি করে তৈরি।

স্লিম করা ইমেজ ব্যবহার করে দেখুন

কিছু ইমেজের বিশেষ "slim" ভ্যারিয়েন্ট থাকে, যেখানে কিছু ফিচার সরিয়ে দেওয়া হয়, ফলে ইমেজের আকার ছোট হয়। উদাহরণস্বরূপ, node:20.16.0-slim ইমেজটি node:20.16.0 ইমেজের চেয়ে পাঁচ গুণ ছোট:

REPOSITORY   TAG            SIZE
node         20.16.0        1.09GB
node         20.16.0-slim   219MB

"slim" ভ্যারিয়েন্টগুলো ছোট হওয়ার কারণ হলো এতে ফিচার কম থাকে। আপনার ইমেজের হয়তো অতিরিক্ত ফিচারগুলোর দরকার নেই, তাহলে "slim" ভ্যারিয়েন্ট ব্যবহারের কথা ভাবুন।

অপ্রয়োজনীয় অংশ সরিয়ে ফেলা

আপনার ইমেজের আকার কমানোর একটি স্পষ্ট, তবে দারুণ উপায় হলো আপনার যেসব জিনিস দরকার নেই, সেগুলো সরিয়ে ফেলা। এর মধ্যে থাকতে পারে:

  • যে সোর্স ফাইলগুলো থেকে বাইনারি তৈরি হওয়ার পর আর দরকার হয় না, সেগুলো
  • Docker ইমেজ থেকে ভিন্ন আর্কিটেকচারের জন্য তৈরি ফাইল
  • ডকুমেন্টেশন

প্যাকেজ ম্যানেজারের ফাইল সরিয়ে ফেলুন

বেশিরভাগ Docker ইমেজে অতিরিক্ত প্যাকেজ ইনস্টল করতে হয়, যা সাধারণত একটি প্যাকেজ ম্যানেজারের মাধ্যমে করা হয়। এই প্যাকেজগুলো বিল্ড টাইমে ইনস্টল করতে হয় (কারণ রান টাইমে কোনো ইন্টারনেট সংযোগ থাকে না)। তাই অতিরিক্ত প্যাকেজ ইনস্টল করার পর প্যাকেজ ম্যানেজারের যেকোনো ক্যাশিং/হিসাব রাখার ফাইল সরিয়ে ফেলা উচিত।

apk

যে ডিস্ট্রিবিউশনগুলো apk প্যাকেজ ম্যানেজার ব্যবহার করে (যেমন Alpine), তারা প্যাকেজ ইনস্টল করতে apk add ব্যবহার করার সময় --no-cache ফ্ল্যাগ ব্যবহার করা উচিত:

RUN apk add --no-cache curl
apt-get/apt

যে ডিস্ট্রিবিউশনগুলো apt-get/apk প্যাকেজ ম্যানেজার ব্যবহার করে (যেমন Ubuntu), তারা প্যাকেজ ইনস্টল করার পরে এবং একই RUN কমান্ডের মধ্যে apt-get autoremove -y এবং rm -rf /var/lib/apt/lists/* কমান্ড চালানো উচিত:

RUN apt-get update && \
    apt-get install curl -y && \
    apt-get autoremove -y && \
    rm -rf /var/lib/apt/lists/*

মাল্টি-স্টেজ বিল্ড ব্যবহার করুন

Docker-এ মাল্টি-স্টেজ বিল্ড নামে একটি ফিচার আছে। এটি দিয়ে আপনি আপনার Dockerfile-কে আলাদা আলাদা স্টেজে ভাগ করতে পারেন, যেখানে কেবল শেষ স্টেজটিই তৈরি হওয়া Docker ইমেজে থাকে (বাকিগুলো কেবল শেষ স্টেজটি তৈরি করতে সহায়তা করার জন্যই থাকে)। প্রতিটি স্টেজকে নিজস্ব ছোট একটি Dockerfile হিসেবে ভাবতে পারেন; স্টেজগুলো ভিন্ন ভিন্ন বেস ইমেজ ব্যবহার করতে পারে।

আপনার Dockerfile-এ যদি এমন প্যাকেজ ইনস্টল করার দরকার হয় যেগুলো কেবল বিল্ড টাইমেই লাগে, তাহলে মাল্টি-স্টেজ বিল্ড বিশেষভাবে কাজে আসে। এমন পরিস্থিতিতে আপনার Dockerfile-এর সাধারণ গঠনটি হয় এমন:

  1. একটি নতুন স্টেজ ডিফাইন করুন (একে আমরা "build" স্টেজ বলব)। এই স্টেজ কেবল বিল্ড টাইমেই ব্যবহার হবে।
  2. দরকারি অতিরিক্ত প্যাকেজগুলো ইনস্টল করুন ("build" স্টেজে)।
  3. যে কমান্ডগুলোর জন্য অতিরিক্ত প্যাকেজ দরকার, সেগুলো চালান ("build" স্টেজের ভেতরে)।
  4. একটি নতুন স্টেজ ডিফাইন করুন (একে আমরা "runtime" স্টেজ বলব)। এই স্টেজটি দিয়েই তৈরি হবে ফলাফল Docker ইমেজ, এবং এটি রান টাইমে চালানো হবে।
  5. ধাপ ৩-এ ("build" স্টেজে) চালানো কমান্ডগুলোর ফলাফল এই স্টেজে ("runtime" স্টেজে) কপি করুন।

এই সেটআপে অতিরিক্ত প্যাকেজগুলো কেবল "build" স্টেজেই ইনস্টল হয়, "runtime" স্টেজে নয়, তাই সেগুলো তৈরি হওয়া Docker ইমেজে যুক্ত হয় না।

উদাহরণ: ফাইল ডাউনলোড করা

Fortran টেস্ট রানারের কিছু ফাইল ডাউনলোড করতে curl দরকার। কিন্তু এর রান টাইম ইমেজে curl-এর দরকার নেই, তাই এটি মাল্টি-স্টেজ বিল্ডের জন্য একেবারে উপযুক্ত উদাহরণ।

প্রথমে এর Dockerfile-এ একটি স্টেজ ডিফাইন করা হয়েছে (নাম "build"), যেখানে curl প্যাকেজ ইনস্টল করা হয়। এরপর curl দিয়ে ওই স্টেজে ফাইল ডাউনলোড করা হয়।

FROM alpine:3.15 AS build

RUN apk add --no-cache curl

WORKDIR /opt/test-runner
COPY bust_cache .

WORKDIR /opt/test-runner/testlib
RUN curl -R -O https://raw.githubusercontent.com/exercism/fortran/main/testlib/CMakeLists.txt
RUN curl -R -O https://raw.githubusercontent.com/exercism/fortran/main/testlib/TesterMain.f90

WORKDIR /opt/test-runner
RUN curl -R -O https://raw.githubusercontent.com/exercism/fortran/main/config/CMakeLists.txt

Dockerfile-এর দ্বিতীয় অংশে একটি নতুন স্টেজ ডিফাইন করা হয় এবং COPY কমান্ড ব্যবহার করে "build" স্টেজ থেকে ডাউনলোড করা ফাইলগুলো নিজের স্টেজে কপি করা হয়:

FROM alpine:3.15

RUN apk add --no-cache coreutils jq gfortran libc-dev cmake make

WORKDIR /opt/test-runner
COPY --from=build /opt/test-runner/ .

COPY . .
ENTRYPOINT ["/opt/test-runner/bin/run.sh"]
উদাহরণ: লাইব্রেরি ইনস্টল করা

Ruby টেস্ট রানারের প্রয়োজনীয় লাইব্রেরিগুলো (gem) ইনস্টল করার আগে git, openssh, build-base, gcc আর wget প্যাকেজগুলো ইনস্টল করা দরকার। এর Dockerfile শুরু হয় একটি স্টেজ দিয়ে (যার নাম দেওয়া হয়েছে build), যা ওই প্যাকেজগুলো ইনস্টল করে (apk add-এর মাধ্যমে) এবং তারপর ডিপেন্ডেন্সিগুলো ইনস্টল করে (bundle install-এর মাধ্যমে):

FROM ruby:3.2.2-alpine3.18 AS build

RUN apk update && apk upgrade && \
    apk add --no-cache git openssh build-base gcc wget git

COPY Gemfile Gemfile.lock .

RUN gem install bundler:2.4.18 && \
    bundle config set without 'development test' && \
    bundle install

এরপর এটি সেই স্টেজ ডিফাইন করে যেটি দিয়ে ফলাফল Docker ইমেজ তৈরি হবে। এই স্টেজ আগের স্টেজ যে ডিপেন্ডেন্সিগুলো ইনস্টল করেছিল, সেগুলো ইনস্টল করে না; বরং এটি COPY কমান্ড ব্যবহার করে build স্টেজ থেকে ইনস্টল করা লাইব্রেরিগুলো নিজের স্টেজে কপি করে:

FROM ruby:3.2.2-alpine3.18

RUN apk add --no-cache bash

WORKDIR /opt/test-runner

COPY --from=build /usr/local/bundle /usr/local/bundle

COPY . .

ENTRYPOINT [ "sh", "/opt/test-runner/bin/run.sh" ]
Note

C# টেস্ট রানারের Dockerfile-এও প্রায় একই কাজ করা হয়েছে, তবে এখানে build স্টেজ এমন একটি তৈরি Docker ইমেজ ব্যবহার করতে পারে যেখানে লাইব্রেরি ইনস্টল করার জন্য দরকারি অতিরিক্ত প্যাকেজগুলো আগেই ইনস্টল করা আছে।

টেস্টিং

ইন্টিগ্রেশন টেস্ট ব্যবহার করুন

ইউনিট টেস্ট খুবই কাজের হতে পারে, তবে আমরা ইন্টিগ্রেশন টেস্ট লেখার উপর মনোযোগ দেওয়ার পরামর্শ দিই। এর মূল সুবিধা হলো, এটি প্রোডাকশনে টুলিং কীভাবে চলে তা আরও ভালোভাবে পরীক্ষা করে, ফলে আপনার টুলিংয়ের ইমপ্লিমেন্টেশনের উপর আস্থা বাড়ে।

Docker ব্যবহার করুন

প্রোডাকশন এনভায়রনমেন্ট সবচেয়ে ভালোভাবে নকল করতে ইন্টিগ্রেশন টেস্টগুলো উচিত প্রোডাকশন এনভায়রনমেন্টের মতোই টুলিং চালানো। এর মানে হলো Docker ইমেজ তৈরি করা এবং তারপর তৈরি ইমেজটি একটি সলিউশনে চালিয়ে তার আউটপুট যাচাই করা।

গোল্ডেন টেস্ট ব্যবহার করুন

ইন্টিগ্রেশন টেস্টগুলো গোল্ডেন টেস্ট হিসেবে লেখা উচিত, অর্থাৎ এমন টেস্ট যেখানে প্রত্যাশিত আউটপুট একটি ফাইলে রাখা থাকে। ট্র্যাক-টুলিংয়ের ইন্টিগ্রেশন টেস্টের জন্য এটি একেবারে উপযুক্ত, কারণ টুলিংয়ের আউটপুটও ফাইল হয়।

উদাহরণ: টেস্ট রানার

কোনো সলিউশনে টেস্ট রানার চালালে তার আউটপুট হয় একটি results.json ফাইল। এরপর আমরা এই ফাইলটি একটি "known good" (অর্থাৎ "expected") আউটপুট ফাইলের (যার নাম expected_results.json) সাথে মিলিয়ে দেখতে পারি, টেস্ট রানার ঠিকভাবে কাজ করছে কি না তা যাচাই করতে।

নিরাপত্তা

নিরাপত্তাই হলো আমাদের টুলিং চালাতে Docker কন্টেইনার ব্যবহার করার একটি প্রধান কারণ।

অফিসিয়াল ইমেজকে প্রাধান্য দিন

Docker Hub-এ অনেক Docker ইমেজ আছে, তবে অফিসিয়াল ইমেজগুলো ব্যবহার করার চেষ্টা করুন। এই ইমেজগুলো বাছাই করা এবং এগুলো অনিরাপদ হওয়ার সম্ভাবনা (অনেক) কম।

ভার্সন পিন করুন

বিল্ড যেন স্থিতিশীল থাকে (অর্থাৎ হঠাৎ করে ভেঙে না পড়ে), তা নিশ্চিত করতে আপনার বেস ইমেজগুলো সবসময় নির্দিষ্ট ট্যাগে পিন করা উচিত। এর মানে এইটির বদলে:

FROM alpine:latest

আপনার ব্যবহার করা উচিত:

FROM alpine:3.20.2

পরেরটি ব্যবহার করলে বিল্ড সবসময় একই ভার্সন ব্যবহার করবে।

নন-প্রিভিলেজড ইউজার হিসেবে চালান

ডিফল্টভাবে অনেক ইমেজ এমন একটি ইউজার দিয়ে চলে যার রুট প্রিভিলেজ আছে। তাই নন-প্রিভিলেজড ইউজার হিসেবে চালানোর কথা ভাবা উচিত।

FROM alpine

RUN groupadd -r myuser && useradd -r -g myuser myuser

# RUN <COMMANDS THAT REQUIRE ROOT USER, E.G. INSTALLING PACKAGES>

USER myuser

প্যাকেজ রিপোজিটরি সর্বশেষ ভার্সনে আপডেট করুন

সর্বশেষ ভার্সনগুলো ইনস্টল করা (প্রায়) সবসময়ই ভালো একটি অভ্যাস

RUN apt-get update && \
    apt-get install curl

রিড-অনলি ফাইলসিস্টেম সাপোর্ট করুন

আমরা উৎসাহিত করি যেন Docker ফাইল রিড-অনলি ফাইলসিস্টেম ব্যবহার করে লেখা হয়। কেবল নিচের ডিরেক্টরিগুলোকেই লেখার উপযোগী ধরে নেওয়া উচিত:

  • সলিউশন ডিরেক্টরি (দ্বিতীয় আর্গুমেন্ট হিসেবে পাঠানো হয়)
  • আউটপুট ডিরেক্টরি (তৃতীয় আর্গুমেন্ট হিসেবে পাঠানো হয়)
  • /tmp ডিরেক্টরি
Caution

আমাদের প্রোডাকশন এনভায়রনমেন্টে বর্তমানে রিড-অনলি ফাইলসিস্টেম প্রয়োগ করা হয় না, তবে ভবিষ্যতে হয়তো করা হতে পারে। এই কারণে নতুন টেস্ট রানার/অ্যানালাইজার/রেপ্রেজেন্টারের বেস টেমপ্লেট শুরু হয় রিড-অনলি ফাইলসিস্টেম নিয়েই। রিড-অনলি ফাইলে যদি কাজ না চলে, তাহলে (আপাতত) নির্দ্বিধায় লেখার উপযোগী ফাইলসিস্টেম ধরে নিতে পারেন।