प्रौद्योगिकी

मैंने Jenkins + Kubernetes के साथ service-based selective deployment कैसे डिज़ाइन किया

AAnonymous
10 मिनट पढ़ें

शुरुआत

यह लेख मैंने AI की मदद से Omnilude backend को monolith से MSA में कैसे बदला का अगला हिस्सा है। पिछली पोस्ट में मैंने बताया था कि service boundaries क्यों बांटीं। इस बार मैं समझाना चाहता हूं कि वही boundaries वास्तविक deployment units में कैसे बदलीं।

MSA की चर्चा अक्सर इस सवाल पर रुक जाती है कि services कितनी हैं। लेकिन operations का असली फर्क तब महसूस होता है जब deployment unit बदलती है, सिर्फ code folders बदलने पर नहीं।

इस पोस्ट में मैं यह व्यवस्थित करना चाहता हूं कि मौजूदा structure ने कौन-सी समस्याएं कम कीं, यह कैसे काम करता है, और अभी क्या अधूरा है।

Code separation से पहले deployment separation क्यों ज्यादा महसूस हुई

जब सब कुछ एक बड़े backend में था, तब सबसे असुविधाजनक बात सिर्फ यह नहीं थी कि features आपस में मिले हुए थे। असली समस्या यह थी कि एक छोटा बदलाव भी पूरे system को फिर से deploy करने का दबाव बना देता था।

अगर मैं सिर्फ blog feature को थोड़ा छूता, तब भी पूरी app फिर से build करनी पड़ती, image दोबारा बनानी पड़ती, और वही बड़ा bundle फिर से चढ़ाना पड़ता। ऐसी structure में सबसे पहले वही हिस्से धीमे पड़ते हैं जो बार-बार बदलते हैं। उससे भी बड़ी समस्या यह थी कि लंबे connection बनाए रखने वाले services भी उसी deployment blast radius में आ जाते थे।

उदाहरण के लिए backbone-service जैसा service, जो WebSocket, SSE और event streams के करीब है, अगर साधारण feature changes जैसी ही pressure में बार-बार हिलता है तो production experience तुरंत खराब हो जाता है। इसलिए मेरे लिए service separation का मतलब यह कम था कि कौन-सा API किसके पास है, और यह ज्यादा था कि क्या चीज़ अलग से deploy की जा सकती है।

Jenkins deployment target कैसे चुनता है

Services Kubernetes पर चलती हैं और CI/CD Jenkins संभालता है।

इस लेख में मैं implementation details से ज्यादा इस बात पर फोकस कर रहा हूं कि CI/CD के नजरिए से deployment boundaries कैसे तय की गईं।

मौजूदा pipeline में deployment की शुरुआत BUILD_MODE और हर service के boolean parameters से होती है। all के साथ मैं सब कुछ deploy कर सकता हूं। auto में मैं सिर्फ manually checked services deploy कर सकता हूं, या अगर कुछ नहीं चुना गया हो तो Git changes के आधार पर targets auto-detect हो सकते हैं। यानी manual mode में full और partial दोनों deployment संभव हैं, और automatic triggers changed files देखकर target set को छोटा कर सकते हैं।

Selectable services हैं auth-service, backbone-service, storage-service, ai-service, game-service, blog-service और legacy-service। रिपॉज़िटरी अभी भी एक ही है, लेकिन deployment targets शुरुआत से service level पर चुने जाते हैं।

Auto-detection logic भी सीधी है। यह git diff देखती है और modules/{service} के नीचे बदली हुई files को संबंधित services से जोड़ती है। लेकिन यहां एक अहम exception है। अगर shared module common(jspring) बदलता है, तो pipeline इसे common foundation में बदलाव मानती है और सभी services को फिर से build करती है। यह rule ज़रूरी है ताकि common के बदलाव partial deployment के पीछे छिप न जाएं।

मेरे हिसाब से यही बिंदु बहुत महत्वपूर्ण है। Selective deployment का फायदा यह है कि सिर्फ ज़रूरी चीज़ें तेज़ी से आगे बढ़ती हैं। लेकिन अगर shared contracts में बदलाव को हल्के में लिया जाए, तो यही approach ज्यादा जोखिमभरी हो सकती है।

Artifacts भी service unit पर काटे गए हैं

असल build flow भी इसी सोच का पालन करती है। पहले common को local Maven में publish किया जाता है, फिर सिर्फ चुने गए services को क्रम से bootJar तक build किया जाता है। Parallel builds की जगह sequential builds चुनना एक practical decision था। इस stage पर Q class generation issues और Kotlin daemon conflicts कम करना ज्यादा stable था।

Image build kaniko container के अंदर होती है। यह चरण भी parallel नहीं बल्कि sequential है। सिर्फ speed देखें तो parallel ज्यादा impressive लग सकता है, लेकिन Omnilude की current stage में resource conflicts घटाना और failure points को पढ़ना आसान बनाना ज्यादा सही लगा।

Image names भी service के हिसाब से अलग map किए गए हैं। उदाहरण के लिए auth-service का jongmin-auth, ai-service का jongmin-ai और blog-service का jongmin-blog बनता है। इससे registry में भी deployment unit सीधे पढ़ी जा सकती है, और बाद में किसी खास service को trace करना आसान हो जाता है।

Folder structure भी operations unit के साथ मेल खाती है

अगर Jenkins यह तय करता है कि क्या build होगा, तो Kubernetes उस नतीजे को वास्तविक operations units में बदल देता है। वर्तमान structure kubernetes/services/{service}/{dev|prd} है। हर service का अपना Deployment और Service है, और ingress को ingress/{env} के नीचे environment के हिसाब से अलग manage किया जाता है।

इस structure का फायदा यह है कि इसे समझाना आसान है। अगर देखना हो कि कोई service कैसे deploy होती है, तो उस service का folder देखना काफी है। Code boundary और manifest boundary काफी हद तक मेल खाती हैं, इसलिए operations के नजरिए से ownership भी कम धुंधली होती है।

Routing भी इसी logic को follow करती है। dev-api.omnilude.com और api.omnilude.com के नीचे path prefix के आधार पर services जुड़ी हैं। यानी service separation सिर्फ repository के अंदर का concept नहीं रह जाती, बल्कि असली API ownership और ingress routing तक जाती है।

Basic stability devices भी यहीं जुड़ती हैं। हर service Deployment में readinessProbe और livenessProbe हैं, और कुछ production services में HPA, PDB और topology spread constraint भी लगे हुए हैं। अभी सभी services के पास एक जैसा operational guardrail level नहीं है, लेकिन कम से कम अब यह अलग-अलग manage किया जा सकता है कि किस service में कौन-सी stability mechanism कितनी दूर तक लागू की गई है।

Deployment flow को जानबूझकर simple रखा

मौजूदा deployment flow देखने से ज्यादा सीधी है।

  • Jenkins इस run के deployment targets तय करता है।
  • सिर्फ चुने गए services build होकर images में बदलते हैं।
  • kubectl apply हर service manifest को apply करता है।
  • kubectl rollout restart नया version ऊपर लाता है।
  • kubectl rollout status हर service के ऊपर आने तक इंतज़ार करता है।
  • Start, success और failure states Slack पर भेजे जाते हैं।

यहां सबसे अहम चीज़ चमकदार complexity नहीं, बल्कि readability है। इस stage पर मैं ऐसी flow को ज्यादा सही मानता हूं जिसमें कोई भी साफ़ देख सके कि क्या deploy हुआ, कहां fail हुआ, और system किस चीज़ का इंतज़ार कर रहा है। बाद में और complex deployment strategies जोड़ी जा सकती हैं, लेकिन अगर मौजूदा structure को समझाना ही मुश्किल हो, तो operations ज्यादा unstable हो सकती हैं।

इसे लागू करने के बाद क्या बदला

इस selective deployment structure का सबसे बड़ा असर यह था कि छोटे बदलाव अब पूरे system के full redeploy को मजबूर नहीं करते। ai-service, जहां experimentation speed महत्वपूर्ण है, blog-service, जहां मैं product functionality को तेजी से polish करना चाहता हूं, और relatively independent game-side work अब अलग-अलग tempo में आगे बढ़ सकते हैं।

दूसरा बड़ा लाभ explainability था। अगर कोई पूछे कि आज Omnilude की operations structure कैसी है, तो अब मैं service list, image names, Kubernetes folders, ingress paths और Slack notification flow को एक ही line में समझा सकता हूं। Personal project में भी मुझे लगता है कि ऐसी explainability बहुत महत्वपूर्ण है। Operations सिर्फ चलनी नहीं चाहिए, यह भी समझ आना चाहिए कि इन्हें इस तरह क्यों बांटा गया है, तभी अगला improvement cycle जुड़ पाता है।

फिर भी current structure complete नहीं है। automatic rollback, post-deploy smoke test और metric-based deployment gates अभी नहीं जुड़े हैं। फिलहाल असली केंद्र rollout checks और Slack summaries ही हैं।

समापन

मौजूदा structure को अभी किसी बड़े CI/CD platform का नाम नहीं दिया जा सकता, लेकिन कम से कम one repository, split deployment by service वाला principle अब Jenkins parameters, image names, Kubernetes folders, ingress paths और Slack notifications तक लगातार जुड़ा हुआ है।

अगला कदम साफ़ है। इसी structure के ऊपर automatic rollback, smoke tests और बेहतर operational alerts जैसे असली guardrails जोड़ने हैं। यह हिस्सा अभी progress में है, इसलिए जब implementation पूरी होगी तब मैं इसे अगली पोस्ट में आगे लिखूंगा।