paint-brush
Как оптимизировать CI/CD с помощью Octopus и TeamCityк@socialdiscoverygroup
4,027 чтения
4,027 чтения

Как оптимизировать CI/CD с помощью Octopus и TeamCity

к Social Discovery Group13m2024/05/14
Read on Terminal Reader

Слишком долго; Читать

Ни для кого не секрет, что без четких и организованных процессов разработчикам может быть сложно эффективно сотрудничать, что приводит к задержкам в выпуске обновлений программного обеспечения. В этой статье команда Social Discovery Group рассказывает, как построить удобный и гибкий конвейер CI/CD с использованием TeamCity и Octopus.
featured image - Как оптимизировать CI/CD с помощью Octopus и TeamCity
Social Discovery Group HackerNoon profile picture
0-item


Ни для кого не секрет, что без четких и организованных процессов разработчикам может быть сложно эффективно сотрудничать, что приводит к задержкам в выпуске обновлений программного обеспечения. Несколько лет назад команда Social Discovery Group столкнулась с проблемой неоптимального процесса CI/CD. В то время команда использовала TeamCity и Octopus, каждый из которых имел свои сильные стороны. Например, Octopus удобен для развертывания, а TeamCity хорош для автоматизированных тестов и достаточно удобен для сборки проектов. Чтобы построить комплексный и визуально привлекательный конвейер CI/CD, максимально удобный и гибкий в настройке, необходимо использовать комплекс инструментов. Код нескольких проектов хранился в локальном репозитории Bitbucket. Команда ЦУР изучила вопрос и решила оптимизировать процесс, используя существующие инструменты.


Ключевые цели оптимизации:

  1. Автоматическая сборка и развертывание из TeamCity в указанных средах.
  2. Именование сборок: в главную ветку добавляется слово «release», чтобы различать сборки по именам.
  3. Автоматическая сборка и развертывание при отправке в соответствующие ветки в соответствующих тестовых средах соответствующих сервисов.
  4. Создание процесса, в котором развертывание в средах тестирования и разработки должно быть завершено до развертывания в промежуточной, а затем в рабочей среде. Это было реализовано в Octopus.


Команда SDG решила использовать TeamCity для сборок и автоматизированных тестов, а также Octopus для развертываний.


Что было реализовано в TeamCity:

  1. TeamCity позволяет использовать в бесплатной версии три агента, чего было достаточно для команды SDG. Они установили новый агент, добавили его в пул и применили к своим шаблонам.

  2. На момент использования последней версии TeamCity команда работала над Ubuntu Server. На скриншоте показаны дополнительные плагины, используемые командой:



  1. Из инструментов команда добавила плагины, такие как Allure 2.14.0 для создания отчетов и Nuget 5.5.1.
  2. Чтобы упростить выполнение подобных задач, команда SDG создала несколько шаблонов для разных типов развертываний: NuGet и сервисов.
  3. Каждый из этих шаблонов включал в себя несколько шагов, что отражено на скриншотах ниже.


Развертывание NuGet выглядело следующим образом:




Стоит отметить, что в зависимости от того, является ли ветка основной или нет, к релизу добавлялся «-release» (шаги 3, 4).


Развертывание сервисов можно увидеть ниже:


Для каждого сервиса были заменены соответствующие переменные на основе системных переменных (имя сервиса, %build.number% и другие).


Пример шага Docker Build представлен на скриншоте:


Каждый репозиторий проекта содержал соответствующий Dockerfile.


Различия между шагами 4 и 5, как упоминалось ранее, заключались в следующем:



Переменная %deploymentTarget% служила параметром Environment(s), которому передавались соответствующие этапы Octopus во время развертывания (например, Test, Dev). Когда изменения были переданы в соответствующие филиалы (настроены) команд разработчиков, автоматически выполнялись сборки и развертывания программного обеспечения в соответствующих тестовых средах. Настройки видны на скриншоте ниже. Для подключения к Octopus необходимо было добавить два глобальных параметра: Octopus.apiKey и Octopus.url.



Кроме того, команда SDG подключила общий репозиторий NuGet и реестр контейнеров для всех проектов в разделе «Подключения».


Кроме того, SDG рекомендует настроить уведомления по электронной почте в разделе «Уведомитель по электронной почте», настроить резервное копирование в разделе «Резервное копирование», создать необходимые группы, назначить соответствующие роли и добавить пользователей в необходимые группы. Основная настройка завершена, и в заключение команда рекомендует регулярно проверять наличие обновлений и обновлять TeamCity раз в месяц.


Далее команда Social Discovery Group приступила к настройке Octopus. В этой статье не будут описываться детали установки, основные настройки прав пользователя и другие аспекты, поскольку вы легко сможете сделать это самостоятельно. Команда сразу обратилась к жизненному циклу, который настраивается в разделе «Библиотека». На скриншоте ниже вы можете увидеть пример работы команды SDG:



Затем команда создала все необходимые группы переменных по темам в наборах переменных. Для каждой переменной были заданы значения и установлены зависимости от окружения, целей и целевых ролей (тегов). Пример показан на скриншоте ниже:



Кластеры в Kubernetes служили целями, а целевыми ролями были теги, прикрепленные к соответствующим кластерам или компьютерным средам. Все это можно настроить в разделе Инфраструктура.


Проекты также можно было сгруппировать и настроить удобную панель мониторинга для отображения развернутых на них сервисов, стадий и версий.

Процесс развертывания для SDG выглядел следующим образом: все этапы тестирования были объединены в один шаг, и для них был создан общий шаблон, аналогично для этапа и этапа live.


На скриншоте ниже показано, как это выглядело для команды SDG:

Справа был выбран ранее описанный жизненный цикл. Этап «Развертывание пакета» включает довольно простые настройки по умолчанию.

На этапе развертывания Raw Kubernetes Yaml команда SDG использовала универсальные самописные шаблоны Yaml. В этом примере — Kubernetes Script, более подробно объясняется ниже. Также были заменены соответствующие параметры, отмеченные красным. Стоит отметить, что необходимые глобальные группы переменных были подключены в меню Переменные->Наборы переменных, а переменные, специфичные для проекта, задавались в меню Переменные->Проект, которые имели более высокий приоритет.


В этой статье команда SDG решила опустить такие детали, как добавление логотипа в проект, настройку триггеров и другие мелкие детали. Остановимся на двух важных пунктах меню: 1 — Релизы, где вы всегда можете увидеть версию и дату создания конкретного релиза; эта информация также отображается на панели управления проектом 2 - Переменные->Предварительный просмотр, где можно увидеть, какие переменные будут заменены на соответствующем этапе.





Переходим к самому важному — развертыванию шаблонов Yaml в кластерах Kubernetes. Они были созданы в разделе «Библиотека->Шаблоны шагов». Ниже команда 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 были указаны в коде в следующем формате: '#{Octopus.Project.Name | ToLower}' , где последняя часть указывает на преобразование в нижний регистр.


Последний файл конфигурации был создан для автоматического сохранения состояния служб .NET при достижении ими определенного предела использования памяти. Это существенно помогло выявить утечки памяти во время разработки и оперативно исправить работу сервисов.


В конечном итоге панель управления сервисом выглядела следующим образом:


Команды разработки и тестирования сочли очень удобным работать с этим дашбордом.


Результаты оптимизации:


  1. Команда SDG создала эффективный процесс CI/CD, который значительно повысил скорость и удобство разработки. Команда довольно долго работала в рамках этого процесса.
  2. SDG также внедрила автоматизированные тесты в удобный инструмент TeamCity и автоматизированное развертывание сервисов.
  3. С помощью удобной панели мониторинга Octopus команда настроила права доступа и могла управлять развертываниями и средами.

Впоследствии SDG реализовала в Octopus множество других функций. Например, автоматическое отключение кластеров ночью по расписанию.


Однако стремление к совершенству не знает границ. Команда Social Discovery Group продвинулась в своем развитии, освоив Azure DevOps. Они настроили процесс в Azure DevOps в рамках одной экосистемы Helm, еще более комплексной и эффективной. Об этом будет рассказано в следующей статье.


Нам бы хотелось услышать о вашем опыте настройки и оптимизации CI/CD с помощью Octopus и TeamCity. Поделитесь своими идеями и советами!