Есть ли способ создать повторно используемые и масштабируемые конвейеры сборки и развертывания для аналогичных служб в среде Azure DevOps?

и заранее извиняюсь за то, что не могу сделать это короче.
Я работаю со своей командой над относительно большой структурой связанных сервисов (/микросервисов), все из которых размещены в контейнерах Docker и работают в тандеме. По мере продвижения нашей работы добавлялось все больше услуг, а старые устаревшие системы даже были перестроены в соответствии с нашими новыми стандартами, что сделало нашу внутреннюю ИТ-структуру более прозрачной для людей, работающих в нашей команде разработчиков.

Моя самая большая проблема заключалась в том, сколько одинаковых новых шаблонных элементов приходилось добавлять каждый раз, когда вводился новый сервис. Конечно, это проблема дизайна, и мы можем решать ее разными способами в разных ключевых моментах нашего дизайна. Но моя самая большая проблема сейчас заключается в том, что я не могу найти элегантное решение для конвейеров сборки и развертывания.

В нашем текущем проекте используются стандартные конвейеры YAML для сборок и система конвейеров Devops «Releases» для развертываний. Последний, к сожалению, больше не поддерживается и имеет ряд досадных предостережений. Самым большим из них является то, что между ними нет возможности даже использовать общий шаблон. Вместо этого каждый конвейер необходимо обслуживать индивидуально, даже если они практически идентичны.

Я успешно преобразовал их в конвейер YAML, который может повторно использовать один и тот же файл шаблона между каждым конвейером развертывания. Но проблема все еще существует: каждая новая добавленная служба требует настройки различных утомительных вещей как в нашем репозитории кода, так и в самой среде DevOps. И чем больше вещей вам нужно настроить одинаковым образом, тем выше риск того, что некоторые вещи не будут поддерживаться должным образом, даже если большая часть из них теперь обрабатывается с помощью повторно используемых шаблонов.

Мне бы очень хотелось, чтобы это работало одним из трех способов, но, пытаясь использовать каждый из них, я сталкиваюсь с тем, что считаю ограничениями DevOps:

  1. Один конвейер BUILD, общий для служб, и один конвейер РАЗВЕРТЫВАНИЯ
    Это было бы самое элегантное решение, поскольку оно могло бы полностью масштабироваться. Все уникальное в каждом из наших сервисов определено в нашем файле docker-compose, поэтому, по сути, в конвейер сборки нужно просто передать пару параметров, определяющих, какой сервис собирать, и он создаст правильные артефакты для использования конвейером развертывания ( используя конвейер сборки в качестве ресурса).
    Это прекрасно работает со сборками, запускаемыми вручную, но проблема, с которой я здесь сталкиваюсь, связана со сборками, запускаемыми автоматически. Поскольку сборка конкретной службы должна происходить на основе изменений кода, передаваемых в наши основные или релизные ветки, необходимо добавить все пути, вызывающие триггер. Но, насколько я могу судить, конвейер не имеет возможности определить, какие изменения вызвали триггер.
    В идеале мне нужно было бы определить несколько триггеров (некоторые из которых также соответствуют перекрывающимся путям), каждый из которых запускает отдельные запуски с разными установленными значениями параметров. Но я не смог найти никакого способа сделать это. DevOps позволяет определять триггеры извне, что в данном случае звучит полезно, но у них есть то же ограничение. Та же проблема существует и с запросами на включение, которые должны запускать тестовые сборки любого сервиса, затронутого изменениями.

  2. Один комбинированный конвейер CI/CD для каждого сервиса с несколькими взаимозависимыми этапами
    На бумаге это выглядит очень аккуратно и даже отображает приятный визуальный эффект для пользователя. У вас есть один этап для создания артефактов, а затем он обеспечивает три параллельных этапа для каждой из наших текущих онлайн-сред (разработка, тестирование и производство), при этом непрерывное развертывание происходит автоматически в Dev, а остальные блокируются либо параметрами, либо ворота развертывания в стадии разработки.
    Проблема с этой схемой заключается в том, что конвейер «Выполнение» предназначен для выполнения ровно один раз как часть стандартного процесса CI/CD. Это означает, что сборка создана и либо развернута, либо нет. В идеале мы хотим иметь возможность взять старую/ранее существовавшую сборку (или конкретную сборку из функциональной ветки и т. д.) и развернуть ее по своему желанию, точно так же, как вы это делаете с конвейерами выпуска в DevOps (где сборка запускает релиз, и развертывание может выполняться индивидуально из релиза столько раз, сколько вы захотите).
    Другими словами, наш типичный подход требует, чтобы развертывание было отделено от сборки. Хотя мы всегда могли бы просто создать новую сборку на основе определенного коммита, это означало бы огромные бесполезные накладные расходы. Наши конвейеры сборки занимают значительно больше времени, чем развертывания.

  3. Один повторно используемый конвейер развертывания для каждого конвейера сборки
    Учитывая, что для каждого конвейера сборки требуются свои собственные уникальные триггеры и свои уникальные параметры, которые можно просто определить как переменную в конвейере, я думаю, что это все же оправдывает создание одного конвейера сборки для каждого доступного сервиса, в котором будут представлены только те вещи, которые их разделяют. , например пути триггера. Поскольку у нас нет правил, согласно которым сервисы должны быть на 100% похожими, это также облегчит поддержку потенциальных различий между ними.
    Однако развертывание во всех них остается в основном идентичным. Если мы разделим конвейеры сборки и развертывания, конвейер сборки можно будет определить в конвейере развертывания как ресурс, аналогичный решению № 1, что позволит развертыванию загружать правильные артефакты и определять, какие онлайн-ресурсы необходимо обновить.
    Но ресурс конвейера в конвейере YAML по какой-то причине не может быть определен динамически, а параметры или переменные не могут использоваться в его свойстве «источник». Он жестко привязан к одному конвейеру, определенному в исходном коде YAML.
    Если бы можно было определить источник в вызове API, который запускает конвейер, это решило бы эту проблему, но единственное значение, которое можно установить, — это то, какой запуск/версию использовать из конвейера ресурсов.

Итак, вкратце: что мы делаем неправильно в нашем рабочем процессе, из-за чего он не поддерживается способами работы конвейеров DevOps? Я чувствую, что это очень близко, но нам просто не хватает того или другого, чтобы это сработало.
Или, может быть, есть какая-то «волшебная» особенность, о которой я не знаю. Или мы могли бы сделать что-нибудь, чтобы получить то, что нам нужно, о чем я не подумал? В идеале мне бы хотелось не слишком сложное решение.
Например, одним из способов сделать это, конечно, была бы автоматизация многих процессов, создающих новые конвейеры, но я лично убежден, что чем больше у вас такой нестандартной автоматизации, тем менее прозрачным становится ваш проект. Если возможно, я бы предпочел полагаться на стандартные узнаваемые подходы и лучшие практики, а не пытаться найти обходные пути.

* Примечание. Я понимаю, что можно найти обходной путь, используя помеченные образы контейнеров в нашем онлайн-репозитории контейнеров вместо артефактов сборки. Но из-за других вариантов проектирования нашим конвейерам сборки НЕОБХОДИМО создавать артефакты, и образ нашего контейнера хранится вместе с этими артефактами, чтобы гарантировать, что каждый результат сборки всегда соответствует одной и той же версии кода.

Мое первое предложение — прекратить использование классических конвейеров выпуска. Конвейеры YAML гораздо более гибкие и поддерживают больше функций.

Rui Jarimba 19.07.2024 14:29
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 - вы можете повторно запустить успешный этап старого конвейера столько раз, сколько захотите, при условии, что вы не удалите какие-либо ветки, которые могут потребоваться.
Rui Jarimba 19.07.2024 14:34

Рад видеть вас в Stack Overflow! Вы упомянули ограничение триггера в первом варианте, This works perfectly with manually triggered builds, так как/какой правильный способ определить его запуск вручную для вашего конвейера? для автоматического триггера, проверяете ли вы настройки фильтра триггера и триггер ресурса... и т. д.? Если проблема здесь в триггере, боюсь, та же проблема сохранится, даже если вы измените структуру конвейера. В любом случае, рекомендуется немного уточнить ваш запрос, чтобы подробно показать проблему, это может помочь в устранении неполадок, спасибо.

wade zhou - MSFT 19.07.2024 15:26
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
3
71
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

На мой взгляд, один комбинированный конвейер 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 обрабатывает его как «обновление» сцены. Большое спасибо за помощь

Morten 22.07.2024 08:03

Другие вопросы по теме

Похожие вопросы