paint-brush
Cách hợp lý hóa CI/CD với Octopus và TeamCitytừ tác giả@socialdiscoverygroup
4,259 lượt đọc
4,259 lượt đọc

Cách hợp lý hóa CI/CD với Octopus và TeamCity

từ tác giả Social Discovery Group13m2024/05/14
Read on Terminal Reader

dài quá đọc không nổi

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. Trong bài viết này, nhóm Social Discovery Group chia sẻ cách xây dựng quy trình CI/CD thuận tiện và linh hoạt với sự kết hợp giữa TeamCity và Octopus.
featured image - Cách hợp lý hóa CI/CD với Octopus và TeamCity
Social Discovery Group HackerNoon profile picture
0-item


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:

  1. Tự động xây dựng và triển khai từ TeamCity đến các môi trường được chỉ định.
  2. Đặt tên cho các bản dựng: "bản phát hành" được thêm vào nhánh chính để phân biệt các bản dựng theo tên.
  3. Tự động xây dựng và triển khai khi đẩy tới các nhánh tương ứng trên môi trường thử nghiệm tương ứng của các dịch vụ tương ứng.
  4. Thiết lập một quy trình trong đó việc triển khai vào môi trường thử nghiệm và phát triển phải được hoàn thành trước khi triển khai sang giai đoạn thử nghiệm và sau đó là sản xuất. Điều này đã được thực hiện trong Octopus.


Nhóm SDG đã quyết định sử dụng TeamCity để xây dựng và kiểm tra tự động cũng như Octopus để triển khai.


Những gì đã được triển khai trong TeamCity:

  1. TeamCity cho phép sử dụng ba tác nhân trong phiên bản miễn phí, đủ cho nhóm SDG. Họ đã cài đặt một tác nhân mới, thêm nó vào nhóm và áp dụng nó vào các mẫu của họ.

  2. Vào thời điểm sử dụng phiên bản TeamCity mới nhất, nhóm đang làm việc trên Ubuntu Server. Ảnh chụp màn hình hiển thị các plugin bổ sung được nhóm sử dụng:



  1. Từ các công cụ, nhóm đã thêm các plugin, chẳng hạn như Allure 2.14.0 để tạo báo cáo và Nuget 5.5.1.
  2. Để đơn giản hóa việc thực hiện các nhiệm vụ tương tự, nhóm SDG đã tạo một số mẫu cho các loại triển khai khác nhau: NuGet và dịch vụ.
  3. Mỗi mẫu này bao gồm một số bước, được phản ánh trong ảnh chụp màn hình bên dưới.


Việc triển khai NuGet như sau:




Điều đáng lưu ý là tùy thuộc vào nhánh có phải là nhánh chính hay không, "-release" sẽ được thêm vào bản phát hành (bước 3, 4).


Việc triển khai các dịch vụ có thể được nhìn thấy dưới đây:


Đối với mỗi dịch vụ, các biến tương ứng được thay thế dựa trên các biến hệ thống (tên dịch vụ, %build.number% và các biến khác).


Một ví dụ về bước Docker Build được trình bày trong ảnh chụp màn hình:


Mỗi kho lưu trữ dự án chứa Dockerfile tương ứng.


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



Ngoài ra, nhóm SDG đã kết nối kho lưu trữ NuGet và Sổ đăng ký vùng chứa chung cho tất cả các dự án trong phần Kết nối.


Hơn nữa, SDG khuyên bạn nên định cấu hình thông báo qua email trong phần Trình thông báo email, thiết lập bản sao lưu trong phần Sao lưu, tạo các nhóm cần thiết, chỉ định vai trò thích hợp và thêm người dùng vào các nhóm được yêu cầu. Quá trình thiết lập chính đã hoàn tất và tóm lại, nhóm khuyên bạn nên thường xuyên kiểm tra các bản cập nhật và cập nhật TeamCity mỗi tháng một lần.


Tiếp theo, nhóm Social Discovery Group chuyển sang cấu hình Octopus. Bài viết này sẽ không mô tả chi tiết cài đặt, cài đặt quyền người dùng cơ bản và các khía cạnh khác vì bạn có thể dễ dàng tự mình thực hiện chúng. Nhóm đã ngay lập tức giải quyết vòng đời được định cấu hình trong phần Thư viện. Trong ảnh chụp màn hình bên dưới, bạn có thể thấy quy trình ví dụ của nhóm SDG:



Sau đó, nhóm tạo tất cả các nhóm biến cần thiết theo chủ đề trong Bộ biến. Đối với mỗi biến, các giá trị được đặt và sự phụ thuộc vào môi trường, mục tiêu và vai trò mục tiêu (thẻ) được thiết lập. Một ví dụ được hiển thị trong ảnh chụp màn hình bên dưới:



Các cụm trong Kubernetes đóng vai trò là mục tiêu và vai trò mục tiêu là các thẻ được gắn vào các cụm hoặc môi trường máy tính tương ứng. Tất cả điều này có thể được cấu hình trong phần Cơ sở hạ tầng.


Các dự án cũng có thể được nhóm lại và có thể thiết lập một bảng điều khiển thuận tiện để hiển thị các dịch vụ, giai đoạn và phiên bản được triển khai trên chúng.

Quá trình triển khai SDG như sau: tất cả các giai đoạn thử nghiệm được kết hợp thành một bước và một mẫu chung được tạo cho chúng, tương tự cho giai đoạn và giai đoạn trực tiếp.


Ả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.


Trong bài viết này, nhóm SDG đã quyết định bỏ qua các chi tiết như thêm logo vào dự án, thiết lập trình kích hoạt hoặc các chi tiết nhỏ khác. Hãy tập trung vào hai mục menu quan trọng: 1 - Bản phát hành, nơi bạn luôn có thể xem phiên bản và ngày tạo của một bản phát hành cụ thể; thông tin này cũng được hiển thị trên bảng điều khiển dự án, 2 - Biến->Xem trước, nơi bạn có thể xem biến nào sẽ được thay thế cho giai đoạn tương ứng.





Chuyển sang phần quan trọng nhất - triển khai các mẫu Yaml trong cụm Kubernetes. Chúng được tạo trong phần Thư viện->Mẫu bước. Dưới đây, nhóm SDG đã trình bày ảnh chụp màn hình sử dụng các thông số của họ. Đối với mỗi thông số, bạn có thể chọn thẻ, loại và giá trị mặc định cũng như thêm mô tả, điều này rất được khuyến khích.




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.


Tệp cấu hình cuối cùng được tạo để tự động lưu trạng thái của các dịch vụ .NET khi chúng đạt đến giới hạn sử dụng bộ nhớ nhất định. Điều này đã giúp đáng kể việc xác định rò rỉ bộ nhớ trong quá trình phát triển và khắc phục kịp thời các dịch vụ.


Cuối cùng, bảng điều khiển dịch vụ trông như sau:


Các nhóm phát triển và thử nghiệm nhận thấy rất thuận tiện khi làm việc với trang tổng quan này.


Kết quả tối ưu hóa:


  1. Nhóm SDG đã xây dựng một quy trình CI/CD hiệu quả giúp cải thiện đáng kể tốc độ và sự thuận tiện trong quá trình phát triển. Nhóm đã làm việc khá lâu trong khuôn khổ quy trình này.
  2. SDG cũng đưa các thử nghiệm tự động vào công cụ TeamCity tiện lợi và triển khai dịch vụ tự động.
  3. Thông qua bảng điều khiển Octopus thân thiện với người dùng, nhóm đã định cấu hình quyền truy cập và có thể quản lý việc triển khai cũng như môi trường.

Sau đó, SDG đã triển khai nhiều tính năng khác trong Octopus. Ví dụ: tự động tắt các cụm vào ban đêm theo lịch trình.


Tuy nhiên, việc theo đuổi sự hoàn hảo không có giới hạn. Nhóm Social Discovery Group đã nâng cao quá trình phát triển của họ bằng cách thành thạo Azure DevOps. Họ thiết lập một quy trình trong Azure DevOps trong một hệ sinh thái trên Helm, thậm chí còn toàn diện và hiệu quả hơn. Điều đó sẽ được đề cập trong bài viết tiếp theo.


Chúng tôi rất muốn nghe về trải nghiệm của bạn trong việc thiết lập và tối ưu hóa CI/CD bằng Octopus và TeamCity. Chia sẻ những hiểu biết và lời khuyên của bạn!