Açık ve organize süreçler olmadığında geliştiricilerin etkili bir şekilde işbirliği yapmakta zorlanabileceği ve bunun da yazılım güncellemelerinin sağlanmasında gecikmelere yol açabileceği bir sır değil. Birkaç yıl önce Social Discovery Group ekibi, idealin altında bir CI/CD süreci sorunuyla karşı karşıya kaldı. O dönemde ekip, her birinin kendine has güçlü yönleri olan TeamCity ve Octopus'u kullanıyordu. Örneğin, Octopus dağıtımlar için uygundur, TeamCity ise otomatik testler için iyidir ve proje oluşturma için yeterince uygundur. Yapılandırma açısından maksimum kullanışlı ve esnek olan kapsamlı ve görsel olarak çekici bir CI/CD hattı oluşturmak için çeşitli araçların kullanılması gerekir. Kod, birkaç proje için Bitbucket'teki yerel bir depoda saklandı. SDG ekibi konuyu inceledi ve mevcut araçları kullanarak süreci optimize etmeye karar verdi.
Temel optimizasyon hedefleri:
SDG ekibi, derlemeler ve otomatik testler için TeamCity'yi, dağıtımlar için ise Octopus'u kullanmaya karar verdi.
TeamCity'de neler uygulandı:
TeamCity, ücretsiz sürümde üç aracının kullanımına izin veriyor ve bu da SDG ekibi için yeterliydi. Yeni bir aracı yüklediler, onu havuza eklediler ve şablonlarına uyguladılar.
TeamCity'nin en son sürümünün kullanıldığı sırada ekip Ubuntu Sunucusu üzerinde çalışıyordu. Ekran görüntüsünde ekip tarafından kullanılan ek eklentiler gösterilmektedir:
NuGet'in dağıtımı aşağıdaki gibi görünüyordu:
Dalın ana olup olmamasına bağlı olarak sürüme "-release" eklendiğini belirtmekte fayda var (adım 3, 4).
Hizmetlere yönelik dağıtım aşağıda görülebilir:
Her hizmet için karşılık gelen değişkenler, sistem değişkenlerine (hizmet adı, %build.number% ve diğerleri) dayalı olarak değiştirildi.
Docker Build adımının bir örneği ekran görüntüsünde sunulmaktadır:
Her proje deposu karşılık gelen Docker dosyasını içeriyordu.
Daha önce de belirtildiği gibi 4. ve 5. adımlar arasındaki farklar aşağıdaki gibidir:
%deploymentTarget%
değişkeni, dağıtım sırasında Octopus'taki ilgili aşamaların (ör. Test, Geliştirme) geçirildiği Ortam(lar) parametresi olarak görev yaptı. Değişiklikler geliştirme ekiplerinin ilgili dallarına (yapılandırılan) gönderildiğinde, ilgili test ortamlarına yapılan derlemeler ve yazılım dağıtımları otomatik olarak gerçekleştirildi. Ayarlar aşağıdaki ekran görüntüsünde görülebilir. Octopus'a bağlanmak için iki genel parametrenin eklenmesi gerekiyordu: octopus.apiKey ve octopus.url
Ayrıca SDG ekibi, Bağlantılar bölümündeki tüm projeler için ortak bir NuGet deposunu ve Container Registry'yi bağladı.
Ayrıca SDG, E-posta Bildiricisi bölümünde e-posta bildirimlerinin yapılandırılmasını, Yedekleme bölümünde yedeklemelerin kurulmasını, gerekli grupların oluşturulmasını, uygun rollerin atanmasını ve kullanıcıların gerekli gruplara eklenmesini önerir. Ana kurulum tamamlandı ve sonuç olarak ekip, güncellemelerin düzenli olarak kontrol edilmesini ve TeamCity'nin ayda bir güncellenmesini öneriyor.
Daha sonra Social Discovery Group ekibi Octopus'u yapılandırmaya geçti. Bu makale kurulum ayrıntılarını, temel kullanıcı hakları ayarlarını ve diğer hususları açıklamayacaktır çünkü bunları kendiniz kolayca yapabilirsiniz. Ekip, Kitaplık bölümünde yapılandırılan yaşam döngüsünü hemen ele aldı. Aşağıdaki ekran görüntüsünde SDG ekibinin örnek akışını görebilirsiniz:
Daha sonra ekip, Değişken Kümeleri'nde temalara göre gerekli tüm değişken gruplarını oluşturdu. Her değişken için değerler belirlendi ve çevreye, hedeflere ve hedef rollere (etiketlere) bağımlılıklar belirlendi. Aşağıdaki ekran görüntüsünde bir örnek gösterilmektedir:
Kubernetes'teki kümeler hedef görevi görüyordu ve hedef roller, karşılık gelen kümelere veya bilgisayar ortamlarına eklenen etiketlerdi. Bütün bunlar Altyapı bölümünde yapılandırılabilir.
Projeler ayrıca gruplandırılabilir ve bunlara dağıtılan hizmetleri, aşamaları ve versiyonları görüntülemek için kullanışlı bir kontrol paneli ayarlanabilir.
SDG'nin dağıtım süreci şu şekilde görünüyordu: tüm test aşamaları tek adımda birleştirildi ve hem sahne hem de canlı aşamalar için ortak bir şablon oluşturuldu.
Aşağıdaki ekran görüntüsü bunun SDG ekibi için nasıl göründüğünü gösteriyor:
Sağda daha önce açıklanan Yaşam Döngüsü seçildi. Paket Dağıtma aşaması oldukça basit varsayılan ayarları içeriyordu.
SDG ekibi, Raw Kubernetes Yaml'ı Dağıtma aşaması için, kendi yazdığı evrensel Yaml şablonlarını kullandı. Bu örnekte - Kubernetes Komut Dosyası, aşağıda daha ayrıntılı olarak açıklanmaktadır. Kırmızıyla işaretlenen ilgili parametreler de değiştirildi. Değişkenler->Değişken Kümeleri menüsünde gerekli global değişken gruplarının bağlandığını ve daha yüksek önceliğe sahip olan Değişkenler->Proje menüsünde projeye özgü değişkenlerin ayarlandığını belirtmekte fayda var.
Bu makalede SDG ekibi, projeye logo ekleme, tetikleyicileri ayarlama veya diğer küçük ayrıntılar gibi ayrıntıları atlamaya karar verdi. İki önemli menü öğesine odaklanalım: 1 - Belirli bir sürümün sürümünü ve oluşturulma tarihini her zaman görebileceğiniz Sürümler; bu bilgi aynı zamanda proje kontrol panelinde de görüntülenir, 2 - Değişkenler->Önizleme, burada ilgili aşama için hangi değişkenlerin değiştirileceğini görebilirsiniz.
En önemli kısma geçiyoruz: Yaml şablonlarının Kubernetes kümelerine dağıtımı. Bunlar Kitaplık->Adım Şablonları bölümünde oluşturulmuştur. Aşağıda SDG ekibi parametrelerini kullanarak bir ekran görüntüsü sundu. Her parametre için bir etiket, tür ve varsayılan değer seçebilir, ayrıca bir açıklama da ekleyebilirsiniz; bu kesinlikle tavsiye edilir.
Bu durumda kod aşağıdaki gibi görünüyordu:
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'taki tüm değişkenler kodda şu biçimde belirtildi: '#{Octopus.Project.Name | ToLower}'
, burada son bölüm küçük harfe dönüştürmeyi gösterir.
Son yapılandırma dosyası, .NET hizmetlerinin belirli bir bellek kullanım sınırına ulaştığında durumlarını otomatik olarak kaydetmek için oluşturuldu. Bu, geliştirme sırasında bellek sızıntılarının tespit edilmesine ve hizmetlerin derhal düzeltilmesine önemli ölçüde yardımcı oldu.
Son olarak hizmet kontrol paneli aşağıdaki gibi görünüyordu:
Geliştirme ve test ekipleri bu kontrol paneliyle çalışmayı çok uygun buldu.
Optimizasyon sonuçları:
Daha sonra SDG, Octopus'ta birçok başka özelliği uygulamaya koydu. Örneğin, kümelerin geceleri belirli bir programa göre otomatik olarak kapatılması.
Ancak mükemmellik arayışı sınır tanımıyor. Social Discovery Group ekibi, Azure DevOps konusunda uzmanlaşarak geliştirmelerini ilerletti. Azure DevOps'ta Helm'deki tek bir ekosistemde daha kapsamlı ve verimli bir süreç kurdular. Bir sonraki makalede bu ele alınacak.
Octopus ve TeamCity'yi kullanarak CI/CD'yi ayarlama ve optimize etme konusundaki deneyiminizi duymak isteriz. Görüşlerinizi ve ipuçlarınızı paylaşın!