यह कोई रहस्य नहीं है कि स्पष्ट और संगठित प्रक्रियाओं के बिना, डेवलपर्स प्रभावी रूप से सहयोग करने के लिए संघर्ष कर सकते हैं, जिससे सॉफ़्टवेयर अपडेट देने में देरी हो सकती है। कुछ साल पहले, सोशल डिस्कवरी ग्रुप टीम को एक सबऑप्टिमल CI/CD प्रक्रिया की चुनौती का सामना करना पड़ा। उस समय, टीम ने TeamCity और Octopus का उपयोग किया, जिनमें से प्रत्येक की अपनी खूबियाँ थीं। उदाहरण के लिए, Octopus परिनियोजन के लिए सुविधाजनक है, जबकि TeamCity स्वचालित परीक्षणों के लिए अच्छा है और प्रोजेक्ट बिल्ड के लिए पर्याप्त रूप से सुविधाजनक है। एक व्यापक और दिखने में आकर्षक CI/CD पाइपलाइन का निर्माण करने के लिए जो कॉन्फ़िगरेशन में अधिकतम सुविधाजनक और लचीला हो, उपकरणों के मिश्रण का उपयोग करना आवश्यक है। कोड को कई परियोजनाओं के लिए बिटबकेट पर एक स्थानीय रिपॉजिटरी में संग्रहीत किया गया था। SDG टीम ने इस मुद्दे का अध्ययन किया और मौजूदा उपकरणों का उपयोग करके प्रक्रिया को अनुकूलित करने का निर्णय लिया।
मुख्य अनुकूलन लक्ष्य:
एसडीजी टीम ने निर्माण और स्वचालित परीक्षणों के लिए टीमसिटी और तैनाती के लिए ऑक्टोपस का उपयोग करने का निर्णय लिया।
टीमसिटी में क्या क्रियान्वित किया गया:
टीमसिटी मुफ़्त संस्करण में तीन एजेंटों के उपयोग की अनुमति देता है, जो SDG टीम के लिए पर्याप्त था। उन्होंने एक नया एजेंट स्थापित किया, उसे पूल में जोड़ा, और उसे अपने टेम्प्लेट पर लागू किया।
TeamCity के नवीनतम संस्करण का उपयोग करते समय, टीम Ubuntu सर्वर पर काम कर रही थी। स्क्रीनशॉट में टीम द्वारा उपयोग किए गए अतिरिक्त प्लगइन्स दिखाए गए हैं:
NuGet के लिए परिनियोजन इस प्रकार था:
यह ध्यान देने योग्य है कि शाखा मास्टर है या नहीं, इस पर निर्भर करते हुए, "-release" को रिलीज़ में जोड़ा गया था (चरण 3, 4)।
सेवाओं की तैनाती नीचे देखी जा सकती है:
प्रत्येक सेवा के लिए, सिस्टम चर (सेवा नाम, %build.number%, और अन्य) के आधार पर संगत चर प्रतिस्थापित किए गए थे।
स्क्रीनशॉट में Docker बिल्ड चरण का एक उदाहरण प्रस्तुत किया गया है:
प्रत्येक प्रोजेक्ट रिपोजिटरी में संबंधित Dockerfile शामिल था।
जैसा कि पहले बताया गया है, चरण 4 और 5 के बीच अंतर इस प्रकार थे:
%deploymentTarget%
वैरिएबल एनवायरनमेंट(s) पैरामीटर के रूप में कार्य करता है, जिसके लिए ऑक्टोपस में संबंधित चरण(s) को परिनियोजन के दौरान पारित किया जाता था (उदाहरण के लिए, टेस्ट, डेव)। जब विकास टीमों की संबंधित शाखाओं (कॉन्फ़िगर) में परिवर्तन किए गए, तो संबंधित परीक्षण वातावरण में बिल्ड और सॉफ़्टवेयर परिनियोजन स्वचालित रूप से किए गए। सेटिंग्स नीचे दिए गए स्क्रीनशॉट में दिखाई दे रही हैं। ऑक्टोपस से कनेक्ट करने के लिए, दो वैश्विक पैरामीटर जोड़े जाने की आवश्यकता थी: octopus.apiKey और octopus.url
इसके अतिरिक्त, SDG टीम ने कनेक्शन अनुभाग में सभी परियोजनाओं के लिए एक सामान्य NuGet रिपोजिटरी और कंटेनर रजिस्ट्री को जोड़ा।
इसके अलावा, SDG ईमेल नोटिफ़ायर अनुभाग में ईमेल सूचनाएँ कॉन्फ़िगर करने, बैकअप अनुभाग में बैकअप सेट करने, आवश्यक समूह बनाने, उचित भूमिकाएँ असाइन करने और आवश्यक समूहों में उपयोगकर्ताओं को जोड़ने की अनुशंसा करता है। मुख्य सेटअप पूरा हो गया है, और निष्कर्ष में, टीम नियमित रूप से अपडेट की जाँच करने और महीने में एक बार TeamCity को अपडेट करने की अनुशंसा करती है।
इसके बाद, सोशल डिस्कवरी ग्रुप टीम ऑक्टोपस को कॉन्फ़िगर करने के लिए आगे बढ़ी। यह लेख इंस्टॉलेशन विवरण, बुनियादी उपयोगकर्ता अधिकार सेटिंग और अन्य पहलुओं का वर्णन नहीं करेगा, क्योंकि आप उन्हें आसानी से स्वयं कर सकते हैं। टीम ने तुरंत जीवनचक्र को संबोधित किया, जिसे लाइब्रेरी अनुभाग में कॉन्फ़िगर किया गया है। नीचे दिए गए स्क्रीनशॉट में, आप SDG टीम का एक उदाहरण प्रवाह देख सकते हैं:
फिर, टीम ने वेरिएबल सेट में थीम के अनुसार सभी आवश्यक वेरिएबल समूह बनाए। प्रत्येक वेरिएबल के लिए, मान सेट किए गए और पर्यावरण, लक्ष्य और लक्ष्य भूमिकाओं (टैग) पर निर्भरताएँ स्थापित की गईं। नीचे दिए गए स्क्रीनशॉट में एक उदाहरण दिखाया गया है:
Kubernetes में क्लस्टर लक्ष्य के रूप में काम करते थे, और लक्ष्य भूमिकाएँ संबंधित क्लस्टर या कंप्यूटर वातावरण से जुड़े टैग थे। यह सब इंफ्रास्ट्रक्चर सेक्शन में कॉन्फ़िगर किया जा सकता है।
परियोजनाओं को समूहीकृत भी किया जा सकता है, तथा उन पर तैनात सेवाओं, चरणों और संस्करणों को प्रदर्शित करने के लिए एक सुविधाजनक डैशबोर्ड स्थापित किया जा सकता है।
एसडीजी के लिए तैनाती प्रक्रिया इस प्रकार थी: सभी परीक्षण चरणों को एक चरण में संयोजित किया गया, तथा उनके लिए एक सामान्य टेम्पलेट बनाया गया, इसी प्रकार चरण और लाइव चरणों के लिए भी।
नीचे दिया गया स्क्रीनशॉट दर्शाता है कि SDG टीम के लिए यह कैसा था:
दाईं ओर, पहले वर्णित जीवनचक्र का चयन किया गया था। पैकेज तैनात करने के चरण में काफी सरल डिफ़ॉल्ट सेटिंग्स शामिल थीं।
डिप्लॉय रॉ कुबेरनेट्स यामल चरण के लिए, SDG टीम ने सार्वभौमिक स्व-लिखित यामल टेम्प्लेट का उपयोग किया। इस उदाहरण में - कुबेरनेट्स स्क्रिप्ट, नीचे अधिक विस्तार से समझाया गया है। लाल रंग में चिह्नित संगत पैरामीटर भी प्रतिस्थापित किए गए थे। यह ध्यान देने योग्य है कि आवश्यक वैश्विक चर समूह वैरिएबल्स-> वैरिएबल सेट मेनू में जुड़े हुए थे, और प्रोजेक्ट-विशिष्ट चर वैरिएबल्स-> प्रोजेक्ट मेनू में सेट किए गए थे, जिनकी प्राथमिकता अधिक थी।
इस लेख में, SDG टीम ने प्रोजेक्ट में लोगो जोड़ने, ट्रिगर्स सेट अप करने या अन्य छोटी-मोटी जानकारी जैसे विवरणों को छोड़ने का फैसला किया। आइए दो महत्वपूर्ण मेनू आइटम पर ध्यान केंद्रित करें: 1 - रिलीज़, जहाँ आप हमेशा किसी विशेष रिलीज़ का संस्करण और निर्माण तिथि देख सकते हैं; यह जानकारी प्रोजेक्ट डैशबोर्ड पर भी प्रदर्शित होती है, 2 - वैरिएबल्स-> पूर्वावलोकन, जहाँ आप देख सकते हैं कि संबंधित चरण के लिए कौन से वैरिएबल प्रतिस्थापित किए जाएँगे।
सबसे महत्वपूर्ण भाग पर चलते हुए - Kubernetes क्लस्टर में Yaml टेम्प्लेट की तैनाती। उन्हें लाइब्रेरी->स्टेप टेम्प्लेट सेक्शन में बनाया गया था। नीचे, SDG टीम ने अपने मापदंडों का उपयोग करके एक स्क्रीनशॉट प्रस्तुत किया है। प्रत्येक पैरामीटर के लिए, आप एक टैग, प्रकार और डिफ़ॉल्ट मान चुन सकते हैं, साथ ही एक विवरण भी जोड़ सकते हैं, जो अत्यधिक अनुशंसित है।
इस मामले में कोड इस प्रकार था:
apiVersion: apps/v1 kind: Deployment metadata: name: '#{Octopus.Project.Name | ToLower}' namespace: #{Octopus.Environment.Name | ToLower} labels: Octopus.Kubernetes.DeploymentName: '#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}' spec: replicas: #{Replicas} strategy: type: RollingUpdate rollingUpdate: maxSurge: 25% maxUnavailable: 25% revisionHistoryLimit: 10 progressDeadlineSeconds: 600 selector: matchLabels: Octopus.Kubernetes.DeploymentName: '#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}' template: metadata: labels: Octopus.Kubernetes.DeploymentName: '#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}' spec: volumes: #{if usesidecar} - name: dump-storage persistentVolumeClaim: claimName: dumps-#{Octopus.Environment.Name | ToLower} #{/if} #{if MountFolders} #{each folder in MountFolders} - name: volume-#{folder | ToBase64 | Replace "\W" X | ToLower} hostPath: path: #{folder} type: DirectoryOrCreate #{/each} #{/if} - name: logs-volume hostPath: path: #{LogsDir} type: DirectoryOrCreate - name: appsettings secret: secretName: #{Octopus.Project.Name | ToLower} #{if Secrets} #{each secret in Secrets} - name: #{secret.name} secret: secretName: #{secret.name} #{/each} #{/if} #{if usesidecar} - name: diagnostics emptyDir: {} - name: dumps configMap: name: dumps defaultMode: 511 #{/if} containers: - name: #{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}-container image: #{DockerRegistry}/projectname.#{Octopus.Project.Name | ToLower}:#{Octopus.Release.Notes} #{if resources} resources: #{each resource in resources} #{resource.Key}: #{each entry in resource.Value} #{entry.Key}: #{entry.Value} #{/each} #{/each} #{/if} ports: - name: http containerPort: 80 protocol: TCP env: - value: "Development" name: "ASPNETCORE_ENVIRONMENT" - name: DD_ENV value: "#{Octopus.Environment.Name | ToLower}" - name: DD_SERVICE value: "#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}" - name: DD_VERSION value: "1.0.0" - name: DD_AGENT_HOST value: "#{DatadogAgentHost}" - name: DD_TRACE_ROUTE_TEMPLATE_RESOURCE_NAMES_ENABLED value: "true" - name: DD_RUNTIME_METRICS_ENABLED value: "true" volumeMounts: #{if usesidecar} - name: dump-storage mountPath: /tmp/dumps #{/if} #{if MountFolders} #{each folder in MountFolders} - mountPath: #{folder} name: volume-#{folder | ToBase64 | Replace "\W" X | ToLower} #{/each} #{/if} - mountPath: #{LogsDir} name: logs-volume #{if usesidecar} - name: diagnostics mountPath: /tmp #{/if} - name: appsettings readOnly: true mountPath: /app/appsettings.json subPath: appsettings.json #{if Secrets} #{each secret in Secrets} - name: #{secret.name} readOnly: true mountPath: #{secret.mountPath} subPath: #{secret.subPath} #{/each} #{/if} readinessProbe: httpGet: path: hc port: http scheme: HTTP initialDelaySeconds: #{InitialDelaySeconds} imagePullPolicy: IfNotPresent securityContext: {} #{if usesidecar} - name: sidecar image: '#{DockerRegistry}/monitor:3' command: - /bin/sh args: - '-c' - while true; do . /app/init.sh; sleep 1m;done env: - name: USE_MEMORY value: '2048' - name: PROJECT value: "#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}" resources: {} volumeMounts: - name: diagnostics mountPath: /tmp - name: dump-storage mountPath: /tmp/dumps - name: dumps mountPath: /app/init.sh subPath: init.sh shareProcessNamespace: true #{/if} affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: environment operator: In values: - "#{Node}" --- apiVersion: v1 kind: Service metadata: name: #{Octopus.Project.Name | ToLower} namespace: #{Octopus.Environment.Name | ToLower} labels: Octopus.Kubernetes.DeploymentName: '#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}' spec: type: ClusterIP selector: Octopus.Kubernetes.DeploymentName: '#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}' ports: - name: http port: 80 targetPort: http protocol: TCP --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: ingress.kubernetes.io/ssl-redirect: 'false' nginx.ingress.kubernetes.io/ssl-redirect: 'false' cert-manager.io/cluster-issuer: "letsencrypt-cluster-issuer" cert-manager.io/renew-before: '#{LetsencryptRenewBefore}' kubernetes.io/ingress.class: nginx #{if IngressAnnotations} #{each annotation in IngressAnnotations} #{annotation.Key}: #{annotation.Value} #{/each} #{/if} name: #{Octopus.Project.Name | ToLower} namespace: #{Octopus.Environment.Name | ToLower} labels: Octopus.Kubernetes.DeploymentName: '#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}' spec: tls: #{if ExternalHost} #{each host in ExternalHost} - hosts: - #{host} secretName: #{Octopus.Project.Name | ToLower}-#{host | ToBase64 | Replace "\W" X | ToLower}-tls #{/each} #{/if} rules: #{if ExternalHost} #{each host in ExternalHost} - host: '#{host}' http: paths: - path: / pathType: ImplementationSpecific backend: service: name: #{Octopus.Project.Name | ToLower} port: name: http #{/each} #{/if} #{if usesidecar} --- apiVersion: v1 kind: ConfigMap metadata: name: dumps namespace: #{Octopus.Environment.Name | ToLower} data: init.sh: |- #!/usr/bin/env bash mem=$(ps aux | awk '{print $6}' | sort -rn | head -1) mb=$(($mem/1024)) archiveDumpPath="/tmp/dumps/$PROJECT-$(date +"%Y%m%d%H%M%S").zip" fullPathGc="/tmp/$PROJECT-$(date +"%Y%m%d%H%M%S").dump" echo "mem:" $mb" project:" $PROJECT "use:" $USE_MEMORY if [ "$mb" -gt "$USE_MEMORY" ]; then export USE_MEMORY=$(($USE_MEMORY*2)) pid=$(dotnet-dump ps | awk '{print $1}') dotnet-dump collect -p $pid -o $fullPathGc zip $fullPathGc.zip $fullPathGc mv $fullPathGc.zip $archiveDumpPath rm $fullPathGc fi #{/if}
ऑक्टोपस में सभी चर कोड में निम्नलिखित प्रारूप में निर्दिष्ट किए गए थे: '#{Octopus.Project.Name | ToLower}'
, जहां अंतिम भाग लोअरकेस में परिवर्तित करने का संकेत देता है।
अंतिम कॉन्फ़िगरेशन फ़ाइल .NET सेवाओं की स्थिति को स्वचालित रूप से सहेजने के लिए बनाई गई थी जब वे एक निश्चित मेमोरी उपयोग सीमा तक पहुँच गए थे। इससे विकास के दौरान मेमोरी लीक की पहचान करने और सेवाओं को तुरंत ठीक करने में काफी मदद मिली।
अंततः, सेवा डैशबोर्ड इस प्रकार दिखाई दिया:
विकास और परीक्षण टीमों को इस डैशबोर्ड के साथ काम करना बहुत सुविधाजनक लगा।
अनुकूलन परिणाम:
इसके बाद, एसडीजी ने ऑक्टोपस में कई अन्य विशेषताएं लागू कीं। उदाहरण के लिए, रात में शेड्यूल के अनुसार क्लस्टरों का स्वचालित शटडाउन।
हालाँकि, पूर्णता की खोज की कोई सीमा नहीं होती। सोशल डिस्कवरी ग्रुप की टीम ने Azure DevOps में महारत हासिल करके अपने विकास को आगे बढ़ाया। उन्होंने हेल्म पर एक पारिस्थितिकी तंत्र के भीतर Azure DevOps में एक प्रक्रिया स्थापित की, जो और भी अधिक व्यापक और कुशल है। अगले लेख में इस पर चर्चा की जाएगी।
हम ऑक्टोपस और टीमसिटी का उपयोग करके CI/CD को सेट अप करने और अनुकूलित करने में आपके अनुभव के बारे में सुनना पसंद करेंगे। अपनी अंतर्दृष्टि और सुझाव साझा करें!