How to deploy multiple micro services using DevOps pipelines

6/24/2019

I am attempting to deploy two .Net Core applications to Azure Kubernetes using DevOps. The default pipeline only builds and deploys the first project I've added.

I have created a single solution with two solution folders, each containing a .Net Core project. I have added orchestration support to both projects and created an Azure pipeline using the "wizard" in DevOps. I have added the secondary project after successfully deploying the first project. I thought that the issue exists because the Docker Registry Service Connection did not contain the new project, so I've deleted that and re-created the connection, but it still only deploys the first project. I am using Azure Git.

The azure-pipeline.yaml is pretty standard. I have accepted the default that was created when the pipeline was created. Here is my buildAndPush stage.

stages:
- stage: Build
  displayName: Build stage
  jobs:  
  - job: Build
    displayName: Build job
    pool:
      vmImage: $(vmImageName)
    steps:
    - task: Docker@2
      displayName: Build and push an image to container registry
      inputs:
        command: buildAndPush
        repository: $(imageRepository)
        dockerfile: $(dockerfilePath)
        containerRegistry: $(dockerRegistryServiceConnection)
        tags: |
          $(tag)
    - task: PublishPipelineArtifact@0
      inputs:
        artifactName: 'manifests'
        targetPath: 'manifests'

How do I get it to build and deploy the other remaining project?

-- Tinus
.net-core
azure-devops
azure-pipelines
docker
kubernetes

4 Answers

6/24/2019

I've managed to deploy two projects using a single azure-pipelines.yml file; however, i am almost certain it's incorrect. I have duplicated the Build Stage for each project and also specified two seperate Dockerfiles. In addition, i have added a deployment.yml and service.yml file for each project. See my azure-pipelines.yml below. Any advice on how to do this correctly will be appreciated.

# Deploy to Azure Kubernetes Service
# Build and push image to Azure Container Registry; Deploy to Azure Kubernetes Service
# https://docs.microsoft.com/azure/devops/pipelines/languages/docker

trigger:
- master

resources:
- repo: self

variables:

  # Container registry service connection established during pipeline creation
  dockerRegistryServiceConnection: '<hidden>'
  imageRepository1: 'k8spocfront'
  imageRepository2: 'k8spocback'
  containerRegistry: '<hidden>.azurecr.io'
  dockerfilePath1: 'k8sPOC/Dockerfile'
  dockerfilePath2: 'k8sPOCApi/Dockerfile'
  tag: '$(Build.BuildId)'

  # Kubernetes Namespace
  k8sNamespace: 'default'
  imagePullSecret: '<hidden>'

  # Agent VM image name
  vmImageName: 'ubuntu-latest'

stages:
- stage: Build_FrontEnd
  displayName: Build stage 1
  jobs:  
  - job: Build
    displayName: Build job
    pool:
      vmImage: $(vmImageName)
    steps:
    - task: Docker@2
      displayName: Build and push an image to container registry
      inputs:
        command: buildAndPush
        repository: $(imageRepository1)
        dockerfile: $(dockerfilePath1)
        containerRegistry: $(dockerRegistryServiceConnection)
        tags: |
          $(tag)

- stage: Build_BackEnd
  displayName: Build stage 2
  jobs:  
  - job: Build
    displayName: Build job
    pool:
      vmImage: $(vmImageName)
    steps:
    - task: Docker@2
      displayName: Build and push an image to container registry
      inputs:
        command: buildAndPush
        repository: $(imageRepository2)
        dockerfile: $(dockerfilePath2)
        containerRegistry: $(dockerRegistryServiceConnection)
        tags: |
          $(tag)          

    - task: PublishPipelineArtifact@0
      inputs:
        artifactName: 'manifests'
        targetPath: 'manifests'

- stage: Deploy_FrontEnd
  displayName: Deploy stage
  dependsOn: Build_BackEnd
  jobs:
  - deployment: Deploy
    displayName: Deploy job
    pool:
      vmImage: $(vmImageName)
    environment: '<hidden>.default'
    strategy:
      runOnce:
        deploy:
          steps:
          - task: DownloadPipelineArtifact@1
            inputs:
              artifactName: 'manifests'
              downloadPath: '$(System.ArtifactsDirectory)/manifests'

          - task: KubernetesManifest@0
            displayName: Create imagePullSecret
            inputs:
              action: createSecret
              secretName: $(imagePullSecret)
              namespace: $(k8sNamespace)
              dockerRegistryEndpoint: $(dockerRegistryServiceConnection)

          - task: KubernetesManifest@0
            displayName: Deploy to Kubernetes cluster
            inputs:
              action: deploy
              namespace: $(k8sNamespace)
              manifests: |
                $(System.ArtifactsDirectory)/manifests/deployment1.yml
                $(System.ArtifactsDirectory)/manifests/service1.yml
                $(System.ArtifactsDirectory)/manifests/deployment2.yml
                $(System.ArtifactsDirectory)/manifests/service2.yml                
              imagePullSecrets: |
                $(imagePullSecret)
              containers: |
                $(containerRegistry)/$(imageRepository1):$(tag)
                $(containerRegistry)/$(imageRepository2):$(tag)
-- Tinus
Source: StackOverflow

6/24/2019

You need to delete the azure-pipelines.yml file from your repository should you wish to create a new pipeline. This will allow you to step through the Devops "Deploy to Azure Kubernetes Service" again from the beginning. This should give you the opportunity to add any additional projects to the release.

-- Tinus
Source: StackOverflow

6/24/2019

You need a different build pipeline for each solution. my suggestion is to put both project under one solution. You can have any different type of projects under the same solution.

-- Daniel
Source: StackOverflow

11/7/2019

I haven't tried it myself but you could take advantage of the matrix strategy.
Jobs will get duplicated and run in parallel, each building and pushing a different image.
Using your yaml, it would look like this:

# Deploy to Azure Kubernetes Service
# Build and push image to Azure Container Registry; Deploy to Azure Kubernetes Service
# https://docs.microsoft.com/azure/devops/pipelines/languages/docker

trigger:
- master

resources:
- repo: self

variables:
  # Container registry service connection established during pipeline creation
  dockerRegistryServiceConnection: '<hidden>'
  imageRepository1: 'k8spocfront'
  imageRepository2: 'k8spocback'
  containerRegistry: '<hidden>.azurecr.io'
  dockerfilePath1: 'k8sPOC/Dockerfile'
  dockerfilePath2: 'k8sPOCApi/Dockerfile'
  tag: '$(Build.BuildId)'

  # Kubernetes Namespace
  k8sNamespace: 'default'
  imagePullSecret: '<hidden>'

  # Agent VM image name
  vmImageName: 'ubuntu-latest'

stages:
- stage: Build
  displayName: Build stage
  jobs:  
  - job: Build
    displayName: Build job
    strategy:
      matrix:
        image1:
          imageRepository: $(imageRepository1)
          dockerfilePath: $(dockerfilePath1)
        image2:
          imageRepository: $(imageRepository2)
          dockerfilePath: $(dockerfilePath2)
      maxParallel: 2
    pool:
      vmImage: $(vmImageName)
    steps:
    - task: Docker@2
      displayName: Build and push an image to container registry
      inputs:
        command: buildAndPush
        repository: $(imageRepository)
        dockerfile: $(dockerfilePath)
        containerRegistry: $(dockerRegistryServiceConnection)
        tags: |
          $(tag)
    - task: PublishPipelineArtifact@0
      inputs:
        artifactName: 'manifests'
        targetPath: 'manifests'
      condition: and(succeeded(), eq(variables['imageRepository'], $(imageRepository1)))

- stage: Deploy_FrontEnd
  displayName: Deploy stage
  dependsOn: Build_BackEnd
  jobs:
  - deployment: Deploy
    displayName: Deploy job
    pool:
      vmImage: $(vmImageName)
    environment: '<hidden>.default'
    strategy:
      runOnce:
        deploy:
          steps:
          - task: DownloadPipelineArtifact@1
            inputs:
              artifactName: 'manifests'
              downloadPath: '$(System.ArtifactsDirectory)/manifests'

          - task: KubernetesManifest@0
            displayName: Create imagePullSecret
            inputs:
              action: createSecret
              secretName: $(imagePullSecret)
              namespace: $(k8sNamespace)
              dockerRegistryEndpoint: $(dockerRegistryServiceConnection)

          - task: KubernetesManifest@0
            displayName: Deploy to Kubernetes cluster
            inputs:
              action: deploy
              namespace: $(k8sNamespace)
              manifests: |
                $(System.ArtifactsDirectory)/manifests/deployment1.yml
                $(System.ArtifactsDirectory)/manifests/service1.yml
                $(System.ArtifactsDirectory)/manifests/deployment2.yml
                $(System.ArtifactsDirectory)/manifests/service2.yml                
              imagePullSecrets: |
                $(imagePullSecret)
              containers: |
                $(containerRegistry)/$(imageRepository1):$(tag)
                $(containerRegistry)/$(imageRepository2):$(tag)

As you can see, I've used only one stage for building the Docker images. I've noticed you publish the back-end manifest as a pipeline artifact so I kept it and added a condition so the publish task will only run when it should.

-- RoiZentner
Source: StackOverflow