এনভায়রনমেন্ট কনফিগারেশন ও সিক্রেটস ম্যানেজমেন্ট
এই পাঠে যা শিখবেন
- বেস কনফিগ + পার-এনভায়রনমেন্ট ওভাররাইড মার্জ করার প্যাটার্ন
- কেন dev/staging/prod-এর কনফিগ মান আলাদা হওয়া দরকার — বাস্তব উদাহরণসহ
- সিক্রেট ভ্যালু কেন কখনো সোর্স কোডে হার্ডকোড করা উচিত নয়, এবং সঠিক বিকল্প কী
- একটি সত্যিকারের
get_config()ওget_secret()ফাংশন লিখে দেখা
১ · কেন প্রতিটি এনভায়রনমেন্টের কনফিগ আলাদা হয়
একটি ফুল-স্ট্যাক অ্যাপ্লিকেশনের একই কোড সাধারণত কমপক্ষে তিনটি ভিন্ন এনভায়রনমেন্টে চলে — development (ডেভেলপারের নিজের মেশিনে), staging (প্রোডাকশনের আগে টেস্ট করার জন্য একটি প্রায়-প্রোডাকশন কপি), এবং production (আসল ব্যবহারকারীরা যা ব্যবহার করেন)। প্রতিটি এনভায়রনমেন্টে কিছু জিনিস আলাদা হওয়া দরকার — ডেটাবেস হোস্ট আলাদা, ডিবাগ মোড শুধু development-এ চালু থাকা উচিত, ক্যাশ টাইমআউট production-এ বেশি হতে পারে ইত্যাদি। কিন্তু বেশিরভাগ মান (লগ ফরম্যাট, ডিফল্ট পোর্ট) সব এনভায়রনমেন্টেই একই থাকে।
সমাধান হলো একটি বেস কনফিগ (কমন ডিফল্ট) রাখা, আর প্রতিটি এনভায়রনমেন্টের জন্য শুধু যা আলাদা তা একটি ছোট "ওভাররাইড" ডিকশনারিতে রাখা — তারপর রানটাইমে দুটো মার্জ করে চূড়ান্ত কনফিগ পাওয়া যায়। এভাবে পুরো কনফিগ তিনবার কপি-পেস্ট করে লিখতে হয় না, এবং কমন মান পরিবর্তন করলে এক জায়গায় করলেই হয়।
debug=True, লোকাল ডেটাবেস, ভার্বোস লগিং — যাতে বাগ দ্রুত ধরা যায়।প্রায়-প্রোডাকশন সেটিংস, কিন্তু আলাদা ডেটাবেস — যাতে প্রোডাকশনের আগে বাস্তব-সদৃশ পরিবেশে টেস্ট করা যায়।
debug=False (নিরাপত্তার জন্য বাধ্যতামূলক), আসল ডেটাবেস, বড় ক্যাশ টাইমআউট — পারফরম্যান্স ও নিরাপত্তা অগ্রাধিকার।.env ফাইল, কনটেইনার orchestration-এ কনফিগ পাস করা) ../cloud-devops/ কোর্সে গভীরভাবে
কভার করা হয়েছে — এই পাঠ শুধু ফুল-স্ট্যাক অ্যাপ্লিকেশন কোডের ভেতরে কনফিগ কীভাবে সংগঠিত করা উচিত তার উপর
ফোকাস করে।
২ · বেস + ওভাররাইড মার্জ করা — get_config()
নিচের কোড সেলে একটি BASE_CONFIG ডিকশনারি আছে যা সব এনভায়রনমেন্টের কমন ডিফল্ট মান ধারণ করে, আর
OVERRIDES_BY_ENV নামের আরেকটি ডিকশনারি আছে যেখানে প্রতিটি এনভায়রনমেন্টের জন্য শুধু আলাদা মানগুলো
আছে। get_config(env_name, overrides_by_env) ফাংশনটি বেস কনফিগের একটি কপি নিয়ে তার উপর নির্দিষ্ট
এনভায়রনমেন্টের ওভাররাইড বসিয়ে দেয় (dict.update()) — যা ওভাররাইড করা হয়নি তা বেস থেকেই থেকে
যায়।
# ধাপ ১ -- বেস (কমন) কনফিগ -- সব এনভায়রনমেন্টে প্রযোজ্য ডিফল্ট মান
BASE_CONFIG = {
"debug": False,
"db_host": "localhost",
"db_port": 5432,
"log_level": "INFO",
"cache_ttl_seconds": 300,
}
# ধাপ ২ -- প্রতিটি এনভায়রনমেন্টের জন্য শুধু যা আলাদা, তাই ওভাররাইড হিসেবে রাখা হলো
OVERRIDES_BY_ENV = {
"development": {
"debug": True,
"db_host": "127.0.0.1",
"log_level": "DEBUG",
},
"staging": {
"db_host": "staging-db.internal",
"log_level": "DEBUG",
},
"production": {
"db_host": "prod-db.internal",
"cache_ttl_seconds": 3600,
},
}
def get_config(env_name, overrides_by_env):
"""BASE_CONFIG-এর একটি কপি নিয়ে তার উপর env_name-এর ওভাররাইড বসিয়ে
চূড়ান্ত, সম্পূর্ণ কনফিগ ডিকশনারি রিটার্ন করে। BASE_CONFIG নিজে অপরিবর্তিত থাকে।"""
merged = dict(BASE_CONFIG) # কপি -- মূল BASE_CONFIG-কে মিউটেট করা হয় না
env_overrides = overrides_by_env.get(env_name, {})
merged.update(env_overrides) # শুধু যা ওভাররাইড করা হয়েছে তা বদলায়
merged["environment"] = env_name
return merged
environments = ["development", "staging", "production"]
configs = {env: get_config(env, OVERRIDES_BY_ENV) for env in environments}
# তিনটি এনভায়রনমেন্টের রেজল্ভড কনফিগ পাশাপাশি প্রিন্ট করা
keys_to_show = list(BASE_CONFIG.keys()) + ["environment"]
header = f"{'কনফিগ কী':20s} | {'development':14s} | {'staging':16s} | {'production':16s}"
print(header)
print("-" * len(header))
for key in keys_to_show:
row = (
f"{key:20s} | "
f"{str(configs['development'][key]):14s} | "
f"{str(configs['staging'][key]):16s} | "
f"{str(configs['production'][key]):16s}"
)
print(row)
print(f"\nBASE_CONFIG এখনো অপরিবর্তিত: {BASE_CONFIG}")
db_port ও log_level-এর মতো কিছু কী কোনো এনভায়রনমেন্টে ওভাররাইড করা
হয়নি বলে BASE_CONFIG-এর মানই তিনটি এনভায়রনমেন্টেই দেখা যাচ্ছে (production-এ log_level
এখনো INFO) — অথচ db_host ও debug প্রতিটি এনভায়রনমেন্টে ভিন্ন। এটাই
"বেস + ওভাররাইড" প্যাটার্নের মূল সুবিধা — যা বদলায়নি তা পুনরায় লিখতে হয় না।
৩ · সিক্রেট ম্যানেজমেন্ট — কখনো সোর্স কোডে হার্ডকোড নয়
কনফিগ মান (যেমন db_host) সাধারণত গোপনীয় নয় — কিন্তু সিক্রেট (API কী, ডেটাবেস
পাসওয়ার্ড, JWT সাইনিং কী) আলাদা শ্রেণির ডেটা। সিক্রেট কখনো সোর্স কোডে সরাসরি লেখা (hardcode) উচিত নয় —
কারণ সোর্স কোড গিট হিস্ট্রিতে চিরস্থায়ীভাবে থেকে যায়, কোড রিভিউতে অন্য মানুষ দেখে, এবং দুর্ঘটনাক্রমে পাবলিক
রিপোজিটরিতে পুশ হয়ে যেতে পারে। বদলে, সিক্রেট একটি আলাদা "সিক্রেট স্টোর"-এ রাখা হয় (বাস্তবে: এনভায়রনমেন্ট
ভ্যারিয়েবল, একটি secrets manager সার্ভিস, বা এনক্রিপ্টেড ভল্ট) এবং কোড শুধু নাম দিয়ে সেটি চায়।
# সিক্রেট মান আলাদা একটি "সিক্রেট স্টোর"-এ রাখা হয়েছে -- কোনো সিক্রেট মান
# কোডের অন্য কোথাও সরাসরি লেখা নেই, শুধু এই একটি জায়গায়
_SECRETS_STORE = {
"STRIPE_API_KEY": "sk_live_9f2a7c8e1b4d6f0a3c5e7b9d1f3a5c7e",
"DB_PASSWORD": "p@ssW0rd_x7Q2",
"JWT_SIGNING_SECRET": "b7e2f9a1c4d8e6f0a2b4c6d8e0f2a4b6",
}
def get_secret(name):
"""নাম দিয়ে সিক্রেট স্টোর থেকে মান খুঁজে আনে। এই ফাংশনের বাইরে কোথাও
কোনো সিক্রেটের আসল মান কোডে লেখা থাকা উচিত নয়।"""
if name not in _SECRETS_STORE:
raise KeyError(f"সিক্রেট '{name}' পাওয়া যায়নি")
return _SECRETS_STORE[name]
stripe_key = get_secret("STRIPE_API_KEY")
print(f"STRIPE_API_KEY লোড হয়েছে -- দৈর্ঘ্য: {len(stripe_key)} অক্ষর")
print(f"শুধু প্রথম ৪ ও শেষ ৪ অক্ষর দেখানো হলো (পুরো মান কখনো লগ করা উচিত নয়): "
f"{stripe_key[:4]}...{stripe_key[-4:]}")
# --- অ্যান্টি-প্যাটার্ন (এভাবে করবেন না!) ---
# নিচের লাইনটি নিছক উদাহরণ হিসেবে দেখানো হলো -- এই সেলে এটি চালানো হচ্ছে না:
#
# stripe_key = "sk_live_9f2a7c8e1b4d6f0a3c5e7b9d1f3a5c7e" # ভুল! সোর্সে হার্ডকোড করা সিক্রেট
#
# এভাবে লিখলে সিক্রেটটি গিট হিস্ট্রি, পুল রিকোয়েস্ট ডিফ, এবং যেকোনো ব্যাকআপ/লগে
# প্লেইনটেক্সট আকারে চিরস্থায়ীভাবে থেকে যায় -- get_secret() দিয়ে নাম-ভিত্তিক অ্যাক্সেস
# করলে সিক্রেটের আসল মান কোডের কোথাও লেখা থাকে না।
print()
try:
get_secret("NON_EXISTENT_KEY")
except KeyError as e:
print(f"অনুপস্থিত সিক্রেট চাওয়া হলে সঠিকভাবে ব্যর্থ হয়: {e}")
কনফিগারেশন = একটি বেস (কমন) ডিফল্ট সেট + প্রতিটি এনভায়রনমেন্টের জন্য শুধু যা আলাদা তার একটি ছোট
ওভাররাইড — get_config()-এর মতো একটি ফাংশন দিয়ে রানটাইমে মার্জ করা হয়। সিক্রেট আলাদা শ্রেণির
ডেটা — কখনো সোর্স কোডে হার্ডকোড না করে, নাম দিয়ে অ্যাক্সেসযোগ্য একটি আলাদা সিক্রেট স্টোরে রাখা হয়। এই দুটো
নীতি একসাথে একটি অ্যাপ্লিকেশনকে dev থেকে prod পর্যন্ত নিরাপদে ও পূর্বানুমানযোগ্যভাবে চালানো সম্ভব করে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
production-এ debug=True রেখে দিলে কী সমস্যা হতে পারে?
ডিবাগ মোড সাধারণত ডিটেইলড এরর পেজ দেখায় — যাতে স্ট্যাক ট্রেস, ফাইল পাথ, এমনকি মাঝে মাঝে এনভায়রনমেন্ট
ভ্যারিয়েবলের মান পর্যন্ত থাকতে পারে। production-এ এটি চালু থাকলে একজন আক্রমণকারী ইচ্ছাকৃতভাবে এরর
ঘটিয়ে অ্যাপ্লিকেশনের অভ্যন্তরীণ গঠন সম্পর্কে সংবেদনশীল তথ্য জানতে পারে — তাই debug=False
production কনফিগে বাধ্যতামূলক হওয়া উচিত, যেমনটা এই পাঠের BASE_CONFIG-এ ডিফল্ট
False রাখা হয়েছে এবং শুধু development ওভাররাইড করে True করেছে।
প্র ০২
সিক্রেট স্টোর থেকে get_secret() দিয়ে মান আনার বদলে যদি প্রতিটি ফাইলে সরাসরি সিক্রেট
হার্ডকোড করা হতো, তাহলে একটি ফাঁস হওয়া (leaked) কী রিভোক (revoke) করা কেন কঠিন হয়ে যেত?
কারণ তখন সিক্রেটটি একাধিক জায়গায় ছড়িয়ে থাকত — প্রতিটি ফাইল খুঁজে বের করে বদলাতে হতো, এবং যেকোনো একটি
জায়গা মিস হয়ে গেলে পুরনো (ফাঁস হওয়া) কী-টি তখনও সক্রিয় থেকে যেত। get_secret()-এর মতো
একটি কেন্দ্রীয় অ্যাক্সেস পয়েন্ট থাকলে সিক্রেট স্টোরে একবার মান বদলালেই পুরো অ্যাপ্লিকেশন নতুন মান
ব্যবহার করা শুরু করে — কোডে কোনো পরিবর্তন লাগে না।
প্র ০৩
উপরের কোডে staging-এর জন্য cache_ttl_seconds ওভাররাইড করা হয়নি — এর মান
কত হবে, এবং কেন?
staging-এ cache_ttl_seconds হবে 300 — কারণ
OVERRIDES_BY_ENV["staging"]-এ এই কী নেই, তাই get_config()-এর
merged.update(env_overrides) ধাপে এটি পরিবর্তিত হয় না এবং BASE_CONFIG-এর
ডিফল্ট মান (300) সেখানেই থেকে যায়। শুধু production-এ এটি স্পষ্টভাবে
3600-এ ওভাররাইড করা হয়েছে।
অনুশীলন
-
চিন্তা করুন:
API_BASE_URL-এর মতো একটি মান কি "কনফিগ" নাকি "সিক্রেট"? আরTHIRD_PARTY_API_KEY? দুটোর পার্থক্য কী নীতির ভিত্তিতে ঠিক করবেন?API_BASE_URLসাধারণত কনফিগ — এটি ফাঁস হলে সরাসরি কোনো ক্ষতি হয় না (এটি তো একটি পাবলিক URL, ব্রাউজারের নেটওয়ার্ক ট্যাবেও দেখা যায়)। কিন্তুTHIRD_PARTY_API_KEYসিক্রেট — এটি জানলে কেউ আপনার হয়ে সেই থার্ড-পার্টি সার্ভিস ব্যবহার করতে পারবে (বিল করাতে পারবে, ডেটা অ্যাক্সেস করতে পারবে)। মূল প্রশ্ন: "এই মানটি ফাঁস হলে কি কেউ ক্ষতিকর কিছু করতে পারবে?" — উত্তর হ্যাঁ হলে এটি সিক্রেট, সিক্রেট স্টোরে যাবে; না হলে এটি সাধারণ কনফিগ। -
পরীক্ষা করুন: উপরের প্রথম কোড সেলে
OVERRIDES_BY_ENV-এ একটি নতুন এনভায়রনমেন্ট"test"যোগ করুন যেখানেdebug=Trueওdb_host="test-db", তারপরenvironmentsলিস্টে"test"যোগ করে Run চেপে দেখুন এটি সঠিকভাবে টেবিলে যোগ হয় কি না (হিন্ট: প্রিন্ট লুপেরheaderও প্রতিটি সারির f-string-এও নতুন কলাম যোগ করতে হবে)।OVERRIDES_BY_ENV["test"] = {"debug": True, "db_host": "test-db"}যোগ করার পরconfigs["test"]-এ বাকি সব কী (db_port,log_level,cache_ttl_seconds)BASE_CONFIGথেকেই আসবে, শুধুdebugওdb_hostওভাররাইড হবে। টেবিলে দেখাতে হলেheaderও প্রতিটি সারির f-string-এconfigs['test'][key]-এর জন্য একটি নতুন কলাম যোগ করতে হবে — এটাই দেখায় নতুন এনভায়রনমেন্ট যোগ করার খরচ কতটা কম (শুধু একটি ওভাররাইড এন্ট্রি), তুলনায় প্রতিটি এনভায়রনমেন্টের জন্য সম্পূর্ণ আলাদা কনফিগ ফাইল লেখার চেয়ে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ আর্কিটেকচার প্যাটার্ন, ফ্রন্ট-এন্ড/ব্যাক-এন্ড ফ্রেমওয়ার্ক ফান্ডামেন্টাল, স্টেট ম্যানেজমেন্ট, REST API, ORM, অথেন্টিকেশন, রেন্ডারিং স্ট্র্যাটেজি ও ডিপ্লয়মেন্ট — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
- Cloud Computing & DevOps কোর্স সহোদর কোর্স এনভায়রনমেন্ট ভ্যারিয়েবল, secrets manager সার্ভিস ও কনটেইনার orchestration-এ কনফিগ ইনজেকশনের বিস্তারিত সেই কোর্সে কভার করা হয়েছে।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics ও Full-Stack Web Frameworks — সব এক জায়গায়।