পাঠ ৩৯ · ৫৭-এর মধ্যে · মডিউল ৯
Home / Courses / Cloud Computing & DevOps / কনফিগারেশন ড্রিফট

কনফিগারেশন ড্রিফট ও ইমিউটেবল ইনফ্রাস্ট্রাকচার

Configuration drift & immutable infrastructure
৭ মিনিট পড়া মধ্যবর্তী · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

  • সার্ভার-লেভেল কনফিগারেশন ড্রিফট কীভাবে ধীরে ধীরে জমে ওঠে এবং কেন এটি বিপজ্জনক
  • ইমিউটেবল ইনফ্রাস্ট্রাকচার দর্শন কীভাবে "প্যাচ করার বদলে প্রতিস্থাপন করো" নীতিতে ড্রিফট দূর করে
  • মিউটেবল বনাম ইমিউটেবল পদ্ধতির মধ্যে ট্রেসেবিলিটির (traceability) পার্থক্য
  • Python দিয়ে দুটো পদ্ধতির একটি সরাসরি তুলনামূলক সিমুলেশন লেখা

১ · সার্ভার-লেভেল কনফিগারেশন ড্রিফট

L20-এ আমরা ইনফ্রাস্ট্রাকচার-লেভেল ড্রিফট দেখেছি (Terraform-এর জানা অবস্থা বনাম বাস্তব ইনফ্রাস্ট্রাকচার) — একই মূল সমস্যা সার্ভার-লেভেল ড্রিফটConfiguration Drift (Server-Level)সময়ের সাথে সাথে একটি চলমান সার্ভারে জমে ওঠা ম্যানুয়াল, ছোট-ছোট পরিবর্তন যা সার্ভারকে তার মূল ইচ্ছাকৃত কনফিগারেশন থেকে, এবং একই ধরনের অন্য সার্ভার থেকে ভিন্ন করে তোলে। নামে একটু ভিন্ন স্তরে ঘটে — একজন ইঞ্জিনিয়ার হয়তো একটি জরুরি সমস্যা ঠিক করতে সরাসরি একটি লাইভ সার্ভারে SSH করে একটি কনফিগ ফাইল বদলে দিলেন, বা একটি প্যাকেজ ম্যানুয়ালি আপগ্রেড করলেন। এমন ছোট ছোট ম্যানুয়াল পরিবর্তন সময়ের সাথে জমতে থাকে — "একই" কনফিগারেশনে থাকা সার্ভার A ও সার্ভার B আসলে ধীরে ধীরে ভিন্ন হয়ে যায়, কেউ খেয়াল না করেই।

এই ধরনের ড্রিফট বিশেষভাবে বিপজ্জনক কারণ এটি নিঃশব্দ — কোনো এলার্ম বাজে না, কোনো এরর দেখায় না। সমস্যা তখনই ধরা পড়ে যখন কেউ লক্ষ্য করে "সার্ভার A-তে এই ফিচারটা কাজ করছে, সার্ভার B-তে করছে না" — এবং তখন কারণ খুঁজে বের করা (কোন ম্যানুয়াল পরিবর্তন, কবে, কে করেছিল — প্রায়ই কোনো রেকর্ডই থাকে না) খুবই সময়সাপেক্ষ ও হতাশাজনক।

২ · ইমিউটেবল ইনফ্রাস্ট্রাকচার — প্যাচ নয়, প্রতিস্থাপন

ইমিউটেবল ইনফ্রাস্ট্রাকচারImmutable Infrastructureএকটি চলমান সার্ভারকে কখনো সরাসরি পরিবর্তন না করে, প্রতিটি পরিবর্তনের জন্য একটি সম্পূর্ণ নতুন ইমেজ/সার্ভার তৈরি করে পুরনোটি সম্পূর্ণ প্রতিস্থাপন করার দর্শন। দর্শনে নিয়ম একটাই — কোনো চলমান সার্ভারে কখনো ম্যানুয়ালি হাত দেওয়া হয় না। তার বদলে, যেকোনো পরিবর্তনের জন্য (ties to L23) একটি সম্পূর্ণ নতুন ইমেজ তৈরি করা হয় যাতে সেই পরিবর্তন "বেক-ইন" থাকে, এবং তারপর (ties to L28/L37) পুরনো ইনস্ট্যান্সগুলো সম্পূর্ণভাবে নতুন ইনস্ট্যান্স দিয়ে প্রতিস্থাপন করা হয় — কখনোই বিদ্যমান ইনস্ট্যান্সে প্যাচ প্রয়োগ করা হয় না। যেহেতু কিছুই ম্যানুয়ালি জায়গায় বসে পরিবর্তন করা হয় না, তাই গঠনগতভাবেই ড্রিফট ঘটতে পারে না।

বাড়তি সুবিধা — সম্পূর্ণ ভার্সন ইতিহাস

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

Python
# মিউটেবল বনাম ইমিউটেবল সার্ভার আপডেট — সিমুলেশন (fake in-memory স্টেট)

# ---- মিউটেবল পদ্ধতি: সরাসরি জায়গায় পরিবর্তন ----
mutable_server = {"nginx_version": "1.18", "max_connections": 512, "debug_mode": False}

def apply_adhoc_fix(server, key, value):
    server[key] = value  # সরাসরি জায়গায় বদলে ফেলা হয়, কোনো ইতিহাস রাখা হয় না

apply_adhoc_fix(mutable_server, "max_connections", 1024)   # জরুরি ফিক্স #১
apply_adhoc_fix(mutable_server, "debug_mode", True)          # ডিবাগিং-এর জন্য #২ (ভুলে বন্ধ করা হয়নি)
apply_adhoc_fix(mutable_server, "nginx_version", "1.20")     # ম্যানুয়াল আপগ্রেড #৩

print("মিউটেবল সার্ভারের চূড়ান্ত অবস্থা (কোনো ইতিহাস নেই):")
print(" ", mutable_server)

# ---- ইমিউটেবল পদ্ধতি: প্রতিটি পরিবর্তনে নতুন ভার্সনড সার্ভার ----
version_history = []

def create_new_version(base_config, changes, version_label):
    new_config = dict(base_config)  # নতুন কপি — পুরনোটি অপরিবর্তিত থাকে
    new_config.update(changes)
    version_history.append((version_label, new_config))
    return new_config

v1 = create_new_version({"nginx_version": "1.18", "max_connections": 512, "debug_mode": False}, {}, "v1 (বেসলাইন)")
v2 = create_new_version(v1, {"max_connections": 1024}, "v2 (কানেকশন বাড়ানো)")
v3 = create_new_version(v2, {"debug_mode": True}, "v3 (ডিবাগ চালু)")
v4 = create_new_version(v3, {"nginx_version": "1.20"}, "v4 (nginx আপগ্রেড)")

print("\nইমিউটেবল সার্ভারের সম্পূর্ণ ভার্সন ইতিহাস:")
for label, config in version_history:
    print(f"  {label}: {config}")

print(f"\nবর্তমানে লাইভ ভার্সন: {version_history[-1][0]} -> {version_history[-1][1]}")

    
লক্ষ্য করুন মিউটেবল সার্ভারের শেষে শুধু একটি চূড়ান্ত অবস্থা দেখা যায় — কোন পরিবর্তন কখন, কেন হয়েছিল তা হারিয়ে গেছে। ইমিউটেবল পদ্ধতিতে version_history-তে প্রতিটি ধাপ সংরক্ষিত — যেকোনো সময় ঠিক কোন পরিবর্তনের ফলে বর্তমান অবস্থা তৈরি হয়েছে তা সম্পূর্ণভাবে ট্রেস করা যায়, এবং প্রয়োজনে যেকোনো পুরনো ভার্সনে ফিরে যাওয়াও সহজ।
মূল কথা · Key takeaway

ড্রিফট এড়ানোর সবচেয়ে নির্ভরযোগ্য উপায় হলো ম্যানুয়াল পরিবর্তনের সুযোগই না রাখা — ইমিউটেবল ইনফ্রাস্ট্রাকচার ঠিক এই নিয়মটাই প্রয়োগ করে। এই দর্শন কন্টেইনার (M6) ও Kubernetes Deployment (M7)-এর সাথে স্বাভাবিকভাবেই মানানসই, যেখানে "নতুন ভার্সন ডিপ্লয় করা" মানেই সবসময় নতুন ইমেজ থেকে নতুন ইনস্ট্যান্স তৈরি করা।

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

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

প্র ০১ "জরুরি অবস্থায় একটি লাইভ সার্ভারে সরাসরি ম্যানুয়াল ফিক্স করা দ্রুততম সমাধান" — এই যুক্তির ঝুঁকি কী?

স্বল্পমেয়াদে এটি সত্যিই দ্রুততম মনে হতে পারে, কিন্তু এটি ঠিক সেই ড্রিফট তৈরি করে যা পরবর্তীতে ডিবাগিং কঠিন করে তোলে — এবং যদি সেই সার্ভার পরে কোনো IaC/কনফিগারেশন ম্যানেজমেন্ট প্লেবুক আবার চালানো হয়, সেই ম্যানুয়াল ফিক্সটি নিঃশব্দে উল্টে যেতে পারে (কারণ প্লেবুক জানে না এটি সম্পর্কে)। জরুরি ফিক্স করার পর সেই পরিবর্তনটি অবিলম্বে ইচ্ছাকৃত কনফিগারেশনে (IaC/প্লেবুকে) ফিরিয়ে আনা জরুরি, নাহলে ঝুঁকি কেবল পিছিয়ে যায়, দূর হয় না।

প্র ০২ ইমিউটেবল ইনফ্রাস্ট্রাকচার সবসময় সম্ভব নয় — কোন ধরনের ওয়ার্কলোডে এটি প্রয়োগ করা কঠিন হতে পারে?

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

প্র ০৩ উপরের কোডে create_new_version ফাংশনে dict(base_config) দিয়ে একটি নতুন কপি তৈরি না করে সরাসরি base_config.update(changes) করলে কী সমস্যা হতো?

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

অনুশীলন

  1. পরীক্ষা করুন: উপরের কোড সেলে ইমিউটেবল অংশে একটি পঞ্চম ভার্সন v5 যোগ করুন যা debug_mode আবার False-এ ফিরিয়ে দেয় (v4-কে বেস ধরে), তারপর সম্পূর্ণ version_history প্রিন্ট করুন।

    v5 = create_new_version(v4, {"debug_mode": False}, "v5 (ডিবাগ বন্ধ)") যোগ করলে version_history-তে পাঁচটি এন্ট্রি দেখা যাবে, প্রতিটি তার নিজস্ব সম্পূর্ণ কনফিগারেশনসহ — দেখাচ্ছে কীভাবে প্রতিটি পরিবর্তন (এমনকি একটি আগের পরিবর্তন উল্টে দেওয়াও) একটি নতুন, ট্রেসেবল ভার্সন হিসেবে রেকর্ড হয়, পুরনো এন্ট্রিগুলো অপরিবর্তিত থেকে যায়।

  2. চিন্তা করুন: আপনার প্রতিষ্ঠানে (বা কল্পনা করা একটি টিমে) সার্ভার-লেভেল ড্রিফট কীভাবে ধরা হবে যদি কোনো ইমিউটেবল ইনফ্রাস্ট্রাকচার বা কনফিগারেশন ম্যানেজমেন্ট টুল ব্যবহার না করা হয়?

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

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

পূর্ববর্তী পাঠ
L38 · কনফিগারেশন ম্যানেজমেন্ট পরিচিতি