As we continue writing this serie , we come to the part were we are going to deploy our apps to our Azure Kubernetes service using Azure Devops
This article is a part of a series:
- Part 1 : How to setup nginx reverse proxy for aspnet core apps with and without Docker compose
- Part 2 :How to setup nginx reverse proxy && load balancer for aspnet core apps with Docker and azure kubernetes service
- Part 3 : How to configure an ingress controller using TLS/SSL for the Azure Kubernetes Service (AKS)
- Part 4 : switch to Azure Container Registry from Docker Hub
- Part 5-A: Using Azure DevOps, Automate Your CI/CD Pipeline and Your Deployments (setup)
- Part 5-B : Using Azure DevOps, Automate Your CI/CD Pipeline and Your Deployments (terraform)
- Part 5-C : Using Azure DevOps, Automate Your CI/CD Pipeline and Your Deployments (deployments)
- Part 6 : Using Github, Automate Your CI/CD Pipeline and Your Deployments
- Possible methods to reduce your costs .
Introduction :
Auto deploying deployment using Azure DevOps and AKS (Azure Kubernetes Service) is a powerful way to streamline the deployment process for containerized applications. With Azure DevOps, you can create automated deployment pipelines that trigger a release whenever new code is pushed to a repository. AKS provides a fully managed Kubernetes service that simplifies the deployment and operation of containerized applications, ensuring high availability, scalability, and security for production deployments. By integrating Azure DevOps with AKS, you can deploy applications to the cluster with a single click, and easily roll back deployments to a previous version if needed. This approach enables teams to deliver updates and new features to production more quickly and reliably, while minimizing the risk of errors or downtime.In different blog post we have seen how to deploy our infrastructure , right now , one more step , is auto deploying to aks .
Prerequisites
– Created service connection for azure container registry .
– Create a service connection for azure Kubernetes service .
1-Creating our Pipeline
Our pipeline will have different steps and we will explain each part in this part:
Full pipline
name: $(BuildDefinitionName)_$(date:yyyyMMdd)$(rev:.r) trigger: branches: include: - dev - master #our agent pool: name: demo-privateAgentDevOps demands: - Agent.Name -equals DevopsAg01 variables: imageRepository: 'mydotnetappdemo' containerRegistry: 'crdvsaksdevfc01.azurecr.io' containerRegistryspn: 'acr-aks-001' dockerfilePath: '$(Build.SourcesDirectory)/Dockerfile' hostnamesManifest: '$(Pipeline.Workspace)/manifests/01-backend-acr-deployment.yml' frontendManifest: '$(Pipeline.Workspace)/manifests/01-nginx-frontend.yml' ingressManifest: '$(Pipeline.Workspace)/manifests/ingress.yml' imagePullSecret: 'context-auth' kubernetesServiceConnection: 'aksspn' hostnameImageRepo: 'mydotnetappdemo' tag: '$(Build.BuildId)' stages: - stage: Build condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) displayName: Build and push Docker image to ACR jobs: - job: Build displayName: Restore project steps: - task: DotNetCoreCLI@2 displayName: 'Restore packages' inputs: command: 'restore' projects: '**/*.csproj' - task: Docker@2 displayName: 'Build Project' inputs: containerRegistry: $(containerRegistryspn) repository: '$(imageRepository)' command: 'build' Dockerfile: '**/Dockerfile' tags: | $(tag) latest - task: Docker@2 displayName: 'Push Docker image' inputs: containerRegistry: $(containerRegistryspn) repository: $(imageRepository) command: 'push' tags: | $(tag) latest - task: PublishPipelineArtifact@1 inputs: targetPath: '$(Pipeline.Workspace)/s/manifests' artifact: 'manifests' publishLocation: 'pipeline' - stage: DEV condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) displayName: DEV - Deploy API to AKS dependsOn: Build jobs: - deployment: Deploy displayName: Deploy DEV environment: 'development' strategy: runOnce: deploy: steps: - task: DownloadPipelineArtifact@2 inputs: buildType: 'current' artifactName: 'manifests' targetPath: '$(Pipeline.Workspace)/manifests' - task: replacetokens@3 displayName: Replace Build tag & environment values inputs: rootDirectory: '$(Pipeline.Workspace)/manifests/' targetFiles: '*.yml' encoding: 'utf-8' writeBOM: true actionOnMissing: 'warn' keepToken: false tokenPrefix: '#{' tokenSuffix: '}#' useLegacyPattern: false enableTransforms: false enableTelemetry: false - task: KubernetesManifest@1 inputs: action: 'createSecret' connectionType: 'azureResourceManager' azureSubscriptionConnection: 'terrafromspn' azureResourceGroup: 'azure-loves-terraform-2023' kubernetesCluster: 'achrafdoingaks' secretType: 'dockerRegistry' secretName: $(imagePullSecret) dockerRegistryEndpoint: 'acr-aks-001' - task: KubernetesManifest@0 displayName: Deploy to Kubernetes cluster inputs: action: deploy kubernetesServiceConnection: $(kubernetesServiceConnection) namespace: 'ingress' manifests: | $(hostnamesManifest) $(ingressManifest) imagePullSecrets: | $(imagePullSecret) containers: | $(containerRegistry)/$(hostnameImageRepo):$(tag) - task: PublishBuildArtifacts@1 displayName: Publish Swaggers inputs: PathtoPublish: '$(Build.ArtifactStagingDirectory)' ArtifactName: 'swagger-dev' publishLocation: 'Container'
What will the pipeline look like :
Now let’s understand this pipeline by task :
The first stage of our pipeline will consist of building our project (dotnet ) , building the docker image and pushing it to our azure container registry .
The second part , will run only if the first stage is done with success .
replacetokens@3
Replace Tokens is an extension that can be used in Azure DevOps, which provides the possibility to replace tokens in the code files with variables values (which can be configured in the Pipelines Library) during the execution of the CI/CD process.
In our case , in each time we build our solution we want to update our deployment the build tag of the new image that we have created .
You may say why not using latest ?
When deploying Docker images, it is generally recommended to avoid using the “latest” tag and instead use a specific version or build tag. The reason for this is that the “latest” tag is a floating tag that always points to the most recent build of an image. This can lead to unpredictability and make it difficult to manage and track the versions of deployed images.
By using a specific version or build tag, you can ensure that the same version of an image is deployed consistently across different environments, such as development, testing, and production. This can also make it easier to troubleshoot issues and roll back to a previous version if necessary.
When using build tags, it is common to use a versioning scheme that includes the version number and the Git commit hash or build ID. This enables you to track the version of the code that was used to build the image and ensure consistency across different environments.
In summary, it is generally better to use a specific version or build tag when deploying Docker images, rather than relying on the “latest” tag, to ensure consistency and predictability in your deployment process.
- task: replacetokens@3 displayName: Replace Build tag & environment values inputs: rootDirectory: '$(Pipeline.Workspace)/manifests/' targetFiles: '*.yml' encoding: 'utf-8' writeBOM: true actionOnMissing: 'warn' keepToken: false tokenPrefix: '#{' tokenSuffix: '}#' useLegacyPattern: false enableTransforms: false enableTelemetry: false this task will replace the code inside the #{Build.BuildId}# below inside our deployement file with the build id of our pipline containers: - name: backend-restapp image: crdvsaksdevfc01.azurecr.io/mydotnetappdemo:#{Build.BuildId}#
KubernetesManifest@1
The Create imagePullSecret parameter in the KubernetesManifest@1 task in Azure DevOps is used to create a Docker registry credential secret in the target Kubernetes cluster.
When you deploy a container to a Kubernetes cluster, the container image is typically stored in a Docker registry, such as Docker Hub or Azure Container Registry. In order to pull the image from the registry, the Kubernetes cluster needs to be authenticated with the registry using credentials, such as a username and password or a token.
To avoid storing these credentials in plaintext in the Kubernetes manifest, you can create an image pull secret that contains the credentials and reference it in the manifest file. The KubernetesManifest@1 task in Azure DevOps can create this image pull secret for you by specifying the Docker registry server, username, and password as task inputs.
When you enable the Create imagePullSecret parameter in the KubernetesManifest@1 task, Azure DevOps creates a secret in the target Kubernetes cluster that contains the Docker registry credentials. The secret is named “acr-credentials” and can be referenced in the manifest file using the “imagePullSecrets” field.
By creating the image pull secret with Azure DevOps, you can securely authenticate your Kubernetes cluster with your Docker registry and ensure that your container images can be pulled successfully during deployment.
- task: KubernetesManifest@1 inputs: action: 'createSecret' connectionType: 'azureResourceManager' azureSubscriptionConnection: 'terrafromspn' azureResourceGroup: 'azure-loves-terraform-2023' kubernetesCluster: 'achrafdoingaks' secretType: 'dockerRegistry' secretName: $(imagePullSecret) dockerRegistryEndpoint: 'acr-aks-001'
Now time to deploy :
- task: KubernetesManifest@0 displayName: Deploy to Kubernetes cluster inputs: action: deploy kubernetesServiceConnection: $(kubernetesServiceConnection) namespace: 'ingress' manifests: | $(hostnamesManifest) $(ingressManifest) imagePullSecrets: | $(imagePullSecret) containers: | $(containerRegistry)/$(hostnameImageRepo):$(tag)
The “action: deploy” in the “KubernetesManifest@0” task in Azure DevOps specifies that the task should deploy the Kubernetes manifests to the target cluster.
The “KubernetesManifest@0” task is used to deploy Kubernetes manifests, which are YAML or JSON files that define Kubernetes resources, such as pods, services, and deployments. The task allows you to specify the manifest files, the Kubernetes cluster to deploy to, and various other parameters that control the deployment process.
When the “action” parameter is set to “deploy”, the task will create or update the resources in the target Kubernetes cluster based on the manifest files provided. If a resource does not exist, it will be created. If a resource already exists, it will be updated to match the configuration in the manifest file.
The “KubernetesManifest@0” task can also be configured to perform other actions, such as deleting resources or running custom kubectl commands. However, when the “action” parameter is set to “deploy”, the task will only deploy the manifests to the target cluster.
Overall, the “KubernetesManifest@0” task with “action: deploy” is a powerful tool for automating the deployment of Kubernetes resources in Azure DevOps, allowing you to quickly and easily deploy your applications and services to a Kubernetes cluster.
2-Creating Our Manifest Files
In our pipeline we are deploying an Ingress and a deployment.
Deployment file :
apiVersion: apps/v1 kind: Deployment metadata: name: backend-restapp namespace: ingress labels: app: backend-restapp tier: backend spec: replicas: 2 selector: matchLabels: app: backend-restapp template: metadata: labels: app: backend-restapp tier: backend spec: containers: - name: backend-restapp image: crdvsaksdevfc01.azurecr.io/mydotnetappdemo:#{Build.BuildId}# ports: - containerPort: 5000 --- apiVersion: v1 kind: Service metadata: name: my-backend-service ## VERY VERY IMPORTANT - NGINX PROXYPASS needs this name labels: app: backend-restapp tier: backend spec: selector: app: backend-restapp ports: - name: http port: 5000 # ClusterIP Service Port targetPort: 5000 # Container Port type: ClusterIP
Ingress
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: webapp namespace: ingress annotations: nginx.ingress.kubernetes.io/ssl-redirect: "false" nginx.ingress.kubernetes.io/use-regex: "true" nginx.ingress.kubernetes.io/rewrite-target: /$1 spec: ingressClassName: nginx rules: - http: paths: - backend: service: name: my-backend-service port: number: 5000 path: / pathType: Prefix - backend: service: name: my-backend-service port: number: 5000 path: /acr(/|$)(.*) pathType: Prefix - host: himhelloworld.com http: paths: - backend: service: name: my-backend-service port: number: 5000 path: / pathType: Prefix - backend: service: name: my-backend-service port: number: 5000 path: /acr pathType: Prefix