পাঠ ৩৩ · ৫৭-এর মধ্যে · মডিউল ৮
Home / Courses / Cloud Computing & DevOps / CI পরিচিতি

Continuous Integration পরিচিতি

Continuous Integration intro
৮ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • Continuous Integration আসলে কী এবং কেন ঘনঘন মার্জ দীর্ঘ ফিচার ব্রাঞ্চের চেয়ে ভালো
  • একটি সাধারণ CI পাইপলাইনের স্টেজগুলো (লিন্ট, টেস্ট, বিল্ড) এবং তাদের ক্রম কেন গুরুত্বপূর্ণ
  • fail-fast নীতি — কেন একটি ব্যর্থ স্টেজের পর পাইপলাইন সাথে সাথে থামা উচিত
  • Python দিয়ে একটি genuine fail-fast পাইপলাইন রানার বানানো এবং যাচাই করা যে ব্যর্থ টেস্ট সত্যিই বিল্ড স্টেজ আটকে দেয়

১ · Continuous Integration কী

Continuous IntegrationCIডেভেলপাররা কোড পরিবর্তন ঘনঘন (দিনে একাধিকবার) একটি শেয়ার্ড রিপোজিটরিতে মার্জ করে, প্রতিটি মার্জে স্বয়ংক্রিয় বিল্ড+টেস্ট চালিয়ে সমস্যা দ্রুত ধরে ফেলার প্র্যাকটিস। মানে ডেভেলপাররা তাদের কোড পরিবর্তন ঘনঘন — আদর্শভাবে দিনে একাধিকবার — একটি শেয়ার্ড রিপোজিটরিতে মার্জ করে, এবং প্রতিটি মার্জের সাথে সাথেই একটি স্বয়ংক্রিয় বিল্ড ও টেস্ট চলে। এর বিপরীতে পুরনো প্যাটার্ন ছিল দীর্ঘদিন ধরে চলা ফিচার ব্রাঞ্চ, যা কালেভদ্রে মেইনলাইনে মার্জ হতো — এবং তখন প্রায়ই দেখা যেত বিশাল, সমাধান করা কঠিন মার্জ কনফ্লিক্ট ("ইন্টিগ্রেশন হেল"), কারণ ততদিনে দুই ব্রাঞ্চ অনেক দূর সরে গিয়েছিল।

ছোট মার্জ কেন সস্তা

একটি ছোট পরিবর্তনে বাগ বা কনফ্লিক্ট থাকলে তা মিনিটের মধ্যে ধরা পড়ে এবং ঠিক করা সহজ — কারণ কী পরিবর্তন হয়েছে তা স্পষ্ট ও সীমিত। কিন্তু কয়েক সপ্তাহের অনেক পরিবর্তন একসাথে মার্জ করলে সমস্যাটি কোন নির্দিষ্ট পরিবর্তনের কারণে হচ্ছে তা খুঁজে বের করা অনেক কঠিন হয়ে যায় — এই একটি কারণেই CI-এর মূল্য এত বেশি।

২ · CI পাইপলাইনের স্টেজগুলো

একটি সাধারণ CI পাইপলাইন সাধারণত এই ক্রমে চলে — ডিপেন্ডেন্সি ইনস্টল → লিন্ট/স্ট্যাটিক অ্যানালাইসিস (কোড স্টাইল ও সাধারণ ভুল যাচাই) → স্বয়ংক্রিয় টেস্ট (ইউনিট টেস্ট) → বিল্ড আর্টিফ্যাক্ট। প্রতিটি স্টেজ আগের স্টেজের সফলতার উপর নির্ভরশীল — লিন্ট ব্যর্থ হলে টেস্ট চালানোর কোনো মানে নেই, এবং টেস্ট ব্যর্থ হলে একটি ভাঙা বিল্ড আর্টিফ্যাক্ট তৈরি করার কোনো মানে নেই।

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

৩ · Fail-fast — কেন এটাই CI-এর সবচেয়ে গুরুত্বপূর্ণ নিয়ম

একটি CI পাইপলাইনকে অবশ্যই জোরে ও দ্রুত ব্যর্থ হতে হবে — কোনো একটি স্টেজ ব্যর্থ হলে পরবর্তী কোনো স্টেজ চালানো উচিত নয়, এবং সেই ব্যর্থ পরিবর্তনকে এগিয়ে যেতে দেওয়া উচিত নয়। এটি একটি ভাঙা পরিবর্তনকে পরবর্তী ধাপে (বা আরও খারাপ, প্রোডাকশনে) পৌঁছাতে বাধা দেয়। নিচের কোড সেলে আমরা একটি প্রকৃত fail-fast পাইপলাইন রানার বানাবো এবং সরাসরি যাচাই করবো যে একটি ব্যর্থ টেস্ট স্টেজ সত্যিই পরবর্তী বিল্ড স্টেজকে চলতে বাধা দেয়।

Python
# একটি genuine fail-fast CI পাইপলাইন রানার — কোনো real cloud/CI সার্ভিস নয়, স্বয়ংসম্পূর্ণ সিমুলেশন

def make_stages(build_log, fail_test=False):
    """প্রতিটি কল একটি নতুন lint/test/build স্টেজ সেট তৈরি করে, যা কোন স্টেজ
    আসলে চলেছে তা build_log লিস্টে রেকর্ড করে — এভাবে পরে যাচাই করা যায়।"""

    def stage_lint():
        build_log.append("lint")
        print("  লিন্ট চলছে...")
        return True  # ধরে নিচ্ছি লিন্ট সবসময় পাস করে

    def stage_test():
        build_log.append("test")
        print("  টেস্ট চলছে...")
        if fail_test:
            print("    ✗ একটি টেস্ট কেস ফেল করেছে!")
            return False
        print("    ✓ সব টেস্ট পাস করেছে")
        return True

    def stage_build():
        build_log.append("build")
        print("  বিল্ড চলছে...")
        return True

    return [("lint", stage_lint), ("test", stage_test), ("build", stage_build)]


def run_ci_pipeline(stages):
    """প্রতিটি স্টেজ ক্রমানুসারে চালায়, প্রথম ব্যর্থতাতেই থেমে যায় (fail-fast)।"""
    for name, stage_fn in stages:
        print(f"▶ স্টেজ: {name}")
        if not stage_fn():
            print(f"✗ ব্যর্থ — fail-fast: '{name}'-এর পরের কোনো স্টেজ চলবে না\n")
            return False
    print("✓ সবগুলো স্টেজ পাস করেছে — পাইপলাইন সফল\n")
    return True


print("===== রান ১: সব পাস (স্বাভাবিক ভালো commit) =====")
log_pass = []
run_ci_pipeline(make_stages(log_pass, fail_test=False))
print("যে স্টেজগুলো আসলে চলেছে:", log_pass)

print("\n===== রান ২: টেস্ট স্টেজ ফেল করলে =====")
log_fail = []
run_ci_pipeline(make_stages(log_fail, fail_test=True))
print("যে স্টেজগুলো আসলে চলেছে:", log_fail)

# --- যাচাই: টেস্ট ফেল করলে বিল্ড সত্যিই কখনো চলেনি ---
assert "build" not in log_fail, "বাগ! ব্যর্থ টেস্টের পরও বিল্ড স্টেজ চলে গেছে"
print("✓ নিশ্চিত হলো: টেস্ট ব্যর্থ হওয়ায় বিল্ড স্টেজ কখনোই চলেনি (fail-fast কাজ করছে)")

    
লক্ষ্য করুন log_fail-এ শুধু ["lint", "test"] থাকবে — "build" কখনোই যোগ হবে না, কারণ run_ci_pipeline টেস্ট স্টেজ থেকে False পেয়েই সাথে সাথে return করে দেয়, লুপের পরের আইটেমে (বিল্ড) কখনো পৌঁছায় না। এই assert লাইনটি নিছক দাবি নয় — এটি প্রকৃতপক্ষে প্রমাণ করে যে fail-fast আচরণ শুধু বর্ণনায় নয়, কোডেও সত্যি।
মূল কথা · Key takeaway

CI মানে ঘনঘন ছোট মার্জ + প্রতিটি মার্জে স্বয়ংক্রিয় বিল্ড/টেস্ট, আর এর নিরাপত্তা পুরোপুরি নির্ভর করে fail-fast আচরণের উপর — একটি ব্যর্থ স্টেজের পর পাইপলাইন থেমে যাওয়া মানেই একটি ভাঙা পরিবর্তন এগিয়ে যেতে না পারা। পরবর্তী পাঠ (L34) দেখাবে CI পাস করার পর কী হয় — প্রস্তুত পরিবর্তনটি স্বয়ংক্রিয়ভাবে ডিপ্লয় হবে নাকি একজন মানুষের অনুমোদনের জন্য অপেক্ষা করবে।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ যদি লিন্ট স্টেজ কয়েক সেকেন্ডে চলে আর টেস্ট স্টেজ কয়েক মিনিটে, তাহলে লিন্টকে প্রথমে রাখার ব্যবহারিক সুবিধা কী?

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

প্র ০২ দীর্ঘদিন বেঁচে থাকা একটি ফিচার ব্রাঞ্চ কেন "ইন্টিগ্রেশন হেল"-এর ঝুঁকি বাড়ায়, ঘনঘন মার্জ করা ব্রাঞ্চের তুলনায়?

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

প্র ০৩ একটি CI পাইপলাইন যদি fail-fast না হয়ে — টেস্ট ব্যর্থ হওয়ার পরও বিল্ড স্টেজ চালিয়ে যায় — তাহলে এর সম্ভাব্য বিপদ কী?

এতে সময় ও কম্পিউট রিসোর্স অপচয় হবে (একটি ইতিমধ্যে জানা-ভাঙা পরিবর্তনের জন্য বিল্ড চালানো), কিন্তু এর চেয়ে বড় বিপদ হলো — যদি পরবর্তী স্টেজগুলোর ফলাফল স্বয়ংক্রিয়ভাবে পরের ধাপে (যেমন স্টেজিং ডিপ্লয়মেন্ট) পাঠানো হয়, তাহলে একটি টেস্ট-ব্যর্থ, ভাঙা পরিবর্তন ভুলবশত এগিয়ে যেতে পারে — ঠিক যে সমস্যা fail-fast প্রতিরোধ করার জন্যই ডিজাইন করা হয়েছে।

অনুশীলন

  1. চিন্তা করুন: উপরের কোড সেলে যদি stage_lint ব্যর্থ হতো (তার বদলে stage_test নয়), তাহলে build_log-এ কী কী স্টেজ থাকতো এবং কেন?

    build_log-এ শুধু ["lint"] থাকতো — যেহেতু run_ci_pipeline তালিকার প্রথম আইটেম (লিন্ট) থেকেই False পেয়ে সাথে সাথে থেমে যেত, "test" বা "build" কোনোটিই build_log-এ যোগ হওয়ার সুযোগই পেত না। এটি দেখায় fail-fast আচরণ পাইপলাইনের যেকোনো স্টেজে ব্যর্থতার জন্য একইভাবে কাজ করে, শুধু নির্দিষ্ট একটি স্টেজের জন্য নয়।

  2. পরীক্ষা করুন: make_stages-এ একটি নতুন চতুর্থ স্টেজ "deploy_staging" যোগ করুন যা শুধু প্রিন্ট করে "স্টেজিং-এ ডিপ্লয় হচ্ছে" এবং True রিটার্ন করে। এটি stage_build-এর পরে তালিকায় যোগ করুন এবং দুটো রানই (পাস ও ফেল) আবার চালিয়ে দেখুন ফলাফল কী দাঁড়ায়।

    পাস-রানে (রান ১) build_log এখন ["lint", "test", "build", "deploy_staging"] দেখাবে, কারণ সব স্টেজ পাস করে। ফেল-রানে (রান ২) কোনো পরিবর্তন হবে না — এখনও ["lint", "test"]-ই থামবে, কারণ টেস্ট স্টেজেই fail-fast ট্রিগার হয়ে যায়, তালিকায় পরে কতগুলো স্টেজ যোগ করা হয়েছে তা কোনো প্রভাব ফেলে না।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
L32 · Kubernetes অপারেটর ও কাস্টম রিসোর্স পরিচিতি