и заранее извиняюсь за то, что не могу сделать это короче.
Я работаю со своей командой над относительно большой структурой связанных сервисов (/микросервисов), все из которых размещены в контейнерах Docker и работают в тандеме. По мере продвижения нашей работы добавлялось все больше услуг, а старые устаревшие системы даже были перестроены в соответствии с нашими новыми стандартами, что сделало нашу внутреннюю ИТ-структуру более прозрачной для людей, работающих в нашей команде разработчиков.
Моя самая большая проблема заключалась в том, сколько одинаковых новых шаблонных элементов приходилось добавлять каждый раз, когда вводился новый сервис. Конечно, это проблема дизайна, и мы можем решать ее разными способами в разных ключевых моментах нашего дизайна. Но моя самая большая проблема сейчас заключается в том, что я не могу найти элегантное решение для конвейеров сборки и развертывания.
В нашем текущем проекте используются стандартные конвейеры YAML для сборок и система конвейеров Devops «Releases» для развертываний. Последний, к сожалению, больше не поддерживается и имеет ряд досадных предостережений. Самым большим из них является то, что между ними нет возможности даже использовать общий шаблон. Вместо этого каждый конвейер необходимо обслуживать индивидуально, даже если они практически идентичны.
Я успешно преобразовал их в конвейер YAML, который может повторно использовать один и тот же файл шаблона между каждым конвейером развертывания. Но проблема все еще существует: каждая новая добавленная служба требует настройки различных утомительных вещей как в нашем репозитории кода, так и в самой среде DevOps. И чем больше вещей вам нужно настроить одинаковым образом, тем выше риск того, что некоторые вещи не будут поддерживаться должным образом, даже если большая часть из них теперь обрабатывается с помощью повторно используемых шаблонов.
Мне бы очень хотелось, чтобы это работало одним из трех способов, но, пытаясь использовать каждый из них, я сталкиваюсь с тем, что считаю ограничениями DevOps:
Один конвейер BUILD, общий для служб, и один конвейер РАЗВЕРТЫВАНИЯ
Это было бы самое элегантное решение, поскольку оно могло бы полностью масштабироваться. Все уникальное в каждом из наших сервисов определено в нашем файле docker-compose, поэтому, по сути, в конвейер сборки нужно просто передать пару параметров, определяющих, какой сервис собирать, и он создаст правильные артефакты для использования конвейером развертывания ( используя конвейер сборки в качестве ресурса).
Это прекрасно работает со сборками, запускаемыми вручную, но проблема, с которой я здесь сталкиваюсь, связана со сборками, запускаемыми автоматически. Поскольку сборка конкретной службы должна происходить на основе изменений кода, передаваемых в наши основные или релизные ветки, необходимо добавить все пути, вызывающие триггер. Но, насколько я могу судить, конвейер не имеет возможности определить, какие изменения вызвали триггер.
В идеале мне нужно было бы определить несколько триггеров (некоторые из которых также соответствуют перекрывающимся путям), каждый из которых запускает отдельные запуски с разными установленными значениями параметров. Но я не смог найти никакого способа сделать это. DevOps позволяет определять триггеры извне, что в данном случае звучит полезно, но у них есть то же ограничение. Та же проблема существует и с запросами на включение, которые должны запускать тестовые сборки любого сервиса, затронутого изменениями.
Один комбинированный конвейер CI/CD для каждого сервиса с несколькими взаимозависимыми этапами
На бумаге это выглядит очень аккуратно и даже отображает приятный визуальный эффект для пользователя. У вас есть один этап для создания артефактов, а затем он обеспечивает три параллельных этапа для каждой из наших текущих онлайн-сред (разработка, тестирование и производство), при этом непрерывное развертывание происходит автоматически в Dev, а остальные блокируются либо параметрами, либо ворота развертывания в стадии разработки.
Проблема с этой схемой заключается в том, что конвейер «Выполнение» предназначен для выполнения ровно один раз как часть стандартного процесса CI/CD. Это означает, что сборка создана и либо развернута, либо нет. В идеале мы хотим иметь возможность взять старую/ранее существовавшую сборку (или конкретную сборку из функциональной ветки и т. д.) и развернуть ее по своему желанию, точно так же, как вы это делаете с конвейерами выпуска в DevOps (где сборка запускает релиз, и развертывание может выполняться индивидуально из релиза столько раз, сколько вы захотите).
Другими словами, наш типичный подход требует, чтобы развертывание было отделено от сборки. Хотя мы всегда могли бы просто создать новую сборку на основе определенного коммита, это означало бы огромные бесполезные накладные расходы. Наши конвейеры сборки занимают значительно больше времени, чем развертывания.
Один повторно используемый конвейер развертывания для каждого конвейера сборки
Учитывая, что для каждого конвейера сборки требуются свои собственные уникальные триггеры и свои уникальные параметры, которые можно просто определить как переменную в конвейере, я думаю, что это все же оправдывает создание одного конвейера сборки для каждого доступного сервиса, в котором будут представлены только те вещи, которые их разделяют. , например пути триггера. Поскольку у нас нет правил, согласно которым сервисы должны быть на 100% похожими, это также облегчит поддержку потенциальных различий между ними.
Однако развертывание во всех них остается в основном идентичным. Если мы разделим конвейеры сборки и развертывания, конвейер сборки можно будет определить в конвейере развертывания как ресурс, аналогичный решению № 1, что позволит развертыванию загружать правильные артефакты и определять, какие онлайн-ресурсы необходимо обновить.
Но ресурс конвейера в конвейере YAML по какой-то причине не может быть определен динамически, а параметры или переменные не могут использоваться в его свойстве «источник». Он жестко привязан к одному конвейеру, определенному в исходном коде YAML.
Если бы можно было определить источник в вызове API, который запускает конвейер, это решило бы эту проблему, но единственное значение, которое можно установить, — это то, какой запуск/версию использовать из конвейера ресурсов.
Итак, вкратце: что мы делаем неправильно в нашем рабочем процессе, из-за чего он не поддерживается способами работы конвейеров DevOps? Я чувствую, что это очень близко, но нам просто не хватает того или другого, чтобы это сработало.
Или, может быть, есть какая-то «волшебная» особенность, о которой я не знаю.
Или мы могли бы сделать что-нибудь, чтобы получить то, что нам нужно, о чем я не подумал? В идеале мне бы хотелось не слишком сложное решение.
Например, одним из способов сделать это, конечно, была бы автоматизация многих процессов, создающих новые конвейеры, но я лично убежден, что чем больше у вас такой нестандартной автоматизации, тем менее прозрачным становится ваш проект. Если возможно, я бы предпочел полагаться на стандартные узнаваемые подходы и лучшие практики, а не пытаться найти обходные пути.
* Примечание. Я понимаю, что можно найти обходной путь, используя помеченные образы контейнеров в нашем онлайн-репозитории контейнеров вместо артефактов сборки. Но из-за других вариантов проектирования нашим конвейерам сборки НЕОБХОДИМО создавать артефакты, и образ нашего контейнера хранится вместе с этими артефактами, чтобы гарантировать, что каждый результат сборки всегда соответствует одной и той же версии кода.
Ideally we want to be able to take an old/pre-existing build (or specific build from a feature branch, etc.) and deploy at will
- вы можете повторно запустить успешный этап старого конвейера столько раз, сколько захотите, при условии, что вы не удалите какие-либо ветки, которые могут потребоваться.
Рад видеть вас в Stack Overflow! Вы упомянули ограничение триггера в первом варианте, This works perfectly with manually triggered builds
, так как/какой правильный способ определить его запуск вручную для вашего конвейера? для автоматического триггера, проверяете ли вы настройки фильтра триггера и триггер ресурса... и т. д.? Если проблема здесь в триггере, боюсь, та же проблема сохранится, даже если вы измените структуру конвейера. В любом случае, рекомендуется немного уточнить ваш запрос, чтобы подробно показать проблему, это может помочь в устранении неполадок, спасибо.
На мой взгляд, один комбинированный конвейер CI/CD для каждого сервиса с несколькими взаимозависимыми этапами является хорошей отправной точкой:
Рассмотрим конвейер со следующими этапами:
Вы можете реализовать это следующим образом:
Трубопровод для обслуживания foo
:
# /pipelines/foo-pipeline.yaml
name: foo_$(Date:yyMMdd)$(Rev:rr)
# Add specific triggers, resources, etc for foo service here
extends:
template: /pipelines/base-pipeline.yaml
parameters:
serviceName: foo # <----------- service name is hard-coded
Трубопровод для обслуживания bar
:
# /pipelines/bar-pipeline.yaml
name: bar_$(Date:yyMMdd)$(Rev:rr)
# Add specific triggers, resources, etc for bar service here
extends:
template: /pipelines/base-pipeline.yaml
parameters:
serviceName: bar # <----------- service name is hard-coded
Базовый трубопровод:
# /pipelines/base-pipeline.yaml
parameters:
- name: serviceName
displayName: Name of the service to build and deploy
type: string
# As an alternative, and in case the stages are always the same for all services,
# consider removing this parameter and hard-code the stages in the pipeline.
- name: environments
displayName: List of environments to deploy to
type: object
default:
- name: dev
dependsOn: build
- name: qa
dependsOn: dev
- name: prod
dependsOn: qa
variables:
# Common variables that are used by all services/environments
- template: /pipelines/variables/common-variables.yaml
# add common resources (used by all services) here
stages:
- stage: Build
dependsOn: []
jobs:
- job: Build
displayName: Build
steps:
- script: echo Building the service
displayName: 'Build the service'
- ${{ each environment in parameters.environments }}:
- stage: ${{ environment.name }}
displayName: Deploy ${{ environment.name }}
dependsOn: ${{ environment.dependsOn }}
jobs:
- deployment: Deploy${{ environment.name }}
displayName: Build and Deploy to ${{ environment.name }}
environment: ${{ environment.name }} # Azure DevOps environment. Use one per environment or service/environment
variables:
# Get the variables for the specific service and environment
- template: /pipelines/variables/${{ parameters.serviceName }}/${{ environment.name }}-variables.yaml
strategy:
runOnce:
deploy:
steps:
- script: echo Deploying to ${{ environment.name }} Environment
displayName: 'Deploy to ${{ environment.name }}'
Примечание:
Нажмите кнопку в правом верхнем углу, чтобы развернуть сцену, а затем выберите один из следующих вариантов:
Спасибо, что нашли время, чтобы вдаваться в подробности! Мой многоэтапный шаблон уже выглядит почти так же, как ваш пример, главное, чего мне не хватало, — это возможность перезапустить этап. Он был хорошо спрятан в пользовательском интерфейсе и выглядел так, будто API обрабатывает его как «обновление» сцены. Большое спасибо за помощь
Мое первое предложение — прекратить использование классических конвейеров выпуска. Конвейеры YAML гораздо более гибкие и поддерживают больше функций.