Không có gì ngạc nhiên khi không có các quy trình rõ ràng và có tổ chức, các nhà phát triển có thể gặp khó khăn trong việc cộng tác hiệu quả, dẫn đến sự chậm trễ trong việc cung cấp các bản cập nhật phần mềm. Một vài năm trước, nhóm Social Discovery Group đã phải đối mặt với thách thức về quy trình CI/CD chưa tối ưu. Vào thời điểm đó, nhóm đã sử dụng TeamCity và Octopus, mỗi bên đều có điểm mạnh riêng. Ví dụ: Octopus thuận tiện cho việc triển khai, trong khi TeamCity phù hợp cho việc kiểm tra tự động và đủ thuận tiện cho việc xây dựng dự án. Để xây dựng một đường dẫn CI/CD toàn diện và hấp dẫn về mặt hình ảnh, có cấu hình thuận tiện và linh hoạt tối đa, cần phải sử dụng kết hợp nhiều công cụ. Mã được lưu trữ trong kho lưu trữ cục bộ trên Bitbucket cho một số dự án. Nhóm SDG đã nghiên cứu vấn đề và quyết định tối ưu hóa quy trình bằng cách sử dụng các công cụ hiện có.
Mục tiêu tối ưu hóa chính:
Những gì đã được triển khai trong TeamCity:
Việc triển khai NuGet như sau:
Việc triển khai các dịch vụ có thể được nhìn thấy dưới đây:
Một ví dụ về bước Docker Build được trình bày trong ảnh chụp màn hình:
Sự khác biệt giữa bước 4 và 5, như đã đề cập trước đó, như sau:
Biến %deploymentTarget%
đóng vai trò là tham số (các) Môi trường mà (các) giai đoạn tương ứng trong Octopus đã được chuyển qua trong quá trình triển khai (ví dụ: Thử nghiệm, Nhà phát triển). Khi các thay đổi được đẩy đến các nhánh tương ứng (được định cấu hình) của nhóm phát triển, quá trình xây dựng và triển khai phần mềm tới môi trường thử nghiệm tương ứng sẽ được thực hiện tự động. Các cài đặt được hiển thị trong ảnh chụp màn hình bên dưới. Để kết nối với Octopus, cần thêm hai tham số chung: octopus.apiKey và octopus.url
Ảnh chụp màn hình bên dưới cho thấy điều này trông như thế nào đối với nhóm SDG:
Ở bên phải, Vòng đời được mô tả trước đó đã được chọn. Giai đoạn Triển khai Gói bao gồm các cài đặt mặc định khá đơn giản.
Đối với giai đoạn Triển khai Kubernetes Yaml thô, nhóm SDG đã sử dụng các mẫu Yaml tự viết phổ biến. Trong ví dụ này - Kubernetes Script, được giải thích chi tiết hơn bên dưới. Các thông số tương ứng được đánh dấu màu đỏ cũng được thay thế. Điều đáng chú ý là các nhóm biến toàn cục cần thiết đã được kết nối trong menu Biến->Bộ biến và các biến dành riêng cho dự án được đặt trong menu Biến->Dự án, có mức độ ưu tiên cao hơn.
Mã trong trường hợp này trông như sau:
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}
Tất cả các biến trong Octopus được chỉ định theo định dạng sau trong mã: '#{Octopus.Project.Name | ToLower}'
, trong đó phần cuối biểu thị việc chuyển đổi sang chữ thường.
Cuối cùng, bảng điều khiển dịch vụ trông như sau: