This post is the second blog post of a mini series where I describe the steps needed to implement a set of Jenkins pipelines to build and deploy the code of a containerized Sitecore 10 custom solution to an existing Azure Kubernetes Services cluster resource. In the first blog post I explored in details how to setup the Jenkins build pipeline. In this blog post I am going to illustrate the second and last part of this automated deployment process: the deploy pipeline.

POSTS IN THIS SERIES:

Assumptions

Let’s start reviewing some assumptions, before diving into the details of the deploy pipeline implementation. In my Azure subscription, I already have an AKS cluster with a clean Sitecore 10 instance. If you don’t have one yet, follow the steps described in the official Sitecore Installation Guide for Production Environments with Kubernetes and read my observations in one of my previous blog post.

The Jenkins pipelines shared in this series assume the usage of a containerized solution, with the recommended folder structure for container development as shared in the custom-images section of the Sitecore Docker Examples Github repository. For this implementation, I directly used a forked version of that repository.

In this blog post I am not covering the deployment and execution of init jobs for external services, since this task would not be a step that would be constantly executed in every Jenkins deployment, but only on demand when a new Sitecore module will need to be added to an existing solution.

Software Prerequisites

The full list of software prerequisites has been described in the first blog post of this series in the Software Prerequisites section.

Kubernetes CLI Jenkins Plugin Configuration

The Kubernetes CLI, known also as kubectl command line tool, offers a set of commands to manage a Kubernetes cluster. The kubectl tool authenticates with a cluster using the token of a service account created on the cluster with the required level of access. This token and other AKS cluster API information are usually stored in a configuration file on the machine where the tool is used, called kubeconfig.

The Kubernetes CLI Jenkins plugin allows to dynamically generate a kubeconfig temporary configuration file that is deleted at the end of a stage execution where its withKubeConfig declarative command is used, avoiding to permanently store a kubeconfig file in the Jenkins virtual machine file system. The token of the account service used to authenticate with the cluster is stored in the Jenkins credentials store as a Secret Text credential and then its credentialId value is used as parameter for the withKubeConfig command in the pipeline stages.

The following commands can be used to create a service account for an existing AKS cluster and retrieve its token:

# Create a service account named `jenkins-robot` for the "sitecore" namespace
kubectl -n sitecore create serviceaccount jenkins-robot

# Grant administrator permissions to the `jenkins-robot` service account.
kubectl -n sitecore create rolebinding jenkins-robot-binding --clusterrole=cluster-admin --serviceaccount=sitecore:jenkins-robot

# Get the name of the token that was automatically generated for the ServiceAccount `jenkins-robot`.
$tokenName = kubectl -n sitecore get serviceaccount jenkins-robot -o go-template --template='{{range .secrets}}{{.name}}{{end}}'

# Retrieve the token from the "token" property in the yaml response
kubectl -n sitecore get secrets $tokenName -o yaml

# Decode the token from base64 to a string
$token = "...encoded value retrieved in previous step..."
[Text.Encoding]::Utf8.GetString([Convert]::FromBase64String($token))

As commented in the script above, the token value retrieved using the kubectl get secrets command is encoded in base64 format and needs to be decoded, before storing it in the Jenkins credentials store.

The Stages of the Deploy Pipeline

The last stage of the build pipeline described in the first blog post of this series pushes the built images of my custom solution to an Azure Container Registry resource. The deploy pipeline has the goal of deploying the artifacts of the build process to an existing AKS cluster resource. The deploy pipeline should be executed in a job after the build pipeline job has succeeded.

The deploy pipeline consists of four main stages (displayed in the stage view picture below) that have the main goal of identifying the correct latest version of the images built in the previous build pipeline and then performing a Kubernetes deployment to an AKS cluster using the Kubernetes CLI.

Stage View of the Jenkins deploy pipeline for Sitecore on AKS

The Jenkins Deploy Pipeline Script

The deploy pipeline script, similarly to the build pipeline script, starts with the parameters section to define the Environment parameter, used to dynamically set the name of the git repository branch that matches a specific environment (for example develop, uat, regression,…). If this Jenkins pipeline becomes part of a Jenkins Pipeline library that can be referenced in a Jenkinsfile, then the parameters section should be converted to a dynamic mapping parameters input to allow to define the input parameters in the Jenkinsfile where it is referenced.

The environment section defines the environment variables used in the stages of the pipeline. Some environment variables, like REGISTRY (or REGISTRY NAME), GIT_REPO_URL, COMPOSE_PROJECT_NAME and PRINCIPAL_SERVICE_CREDENTIAL_ID were also defined in the build pipeline and they should have the same configured values in both pipelines. New environment variables defined in the deploy pipeline include some AKS resource specific inputs, like AKS_RESOURCE_GROUP, AKS_CLUSTER_NAME, AKS_NAMESPACE and AKS_CLUSTER_API_URL, and the Kubernetes CLI token credentials, KUBERNETES_CREDENTIALS_ID.

The Checkout stage is the first of the four stages in the deploy pipeline. It consists in simply invoking the checkout command to pull the code of a GitHub repository for a specific branch. It is not strictly needed for this pipeline to exist, but it can be useful if customized Kubernetes configuration files are managed in the source code repository of the solution.

The Find Version of Latest Built Images stage has the purpose to automatically identify the version (or tag) of the latest built images available on the project ACR resource. In this step, the az acr repository show-tags Azure CLI command is used to pull the list of available tags for one of the custom image repositories (in this implementation, the –solution image, but any other image would work fine for this scope as well). The command is wrapped in a PowerShell script step to filter the existing tags and find the latest for the specific environment or branch specified with the dynamic pipeline Environment parameter.

The Deploy Images to AKS stage is the third stage in this pipeline and consists in invoking the kubectl set image deployments/<deployment_name> Kubernetes CLI command to set a new image in the existing deployment configuration for a specific deployment (ie. cd, cm, …). In this stage, this command is invoked twice for the cd deployment and for the cm deployment only, but of course it can be invoked for additional deployment roles as well, if needed.

The final stage of the deploy pipeline is the Wait for Deployment Rollout to Finish stage, that checks the rollout status of the deployment resources in the AKS cluster and waits for the rollout update process of their replicas to finish.

The following script shows the full implemented Jenkins Deploy pipeline:

pipeline {
agent any
parameters {
string(name: 'Environment', defaultValue: 'develop', description: 'Environment/Branch name')
}
environment {
REGISTRY_NAME = 'myacrregistry'
REGISTRY = 'myacrregistry.azurecr.io/'
GIT_REPO_URL = 'https://github.com/afaniuolo/docker-examples.git'
COMPOSE_PROJECT_NAME = "mycustomsolution"
PRINCIPAL_SERVICE_CREDENTIAL_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
AKS_RESOURCE_GROUP = 'sc10'
AKS_CLUSTER_NAME = 'sc10cluster'
AKS_NAMESPACE = 'sitecore'
KUBERNETES_CREDENTIALS_ID = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
AKS_CLUSTER_API_URL = 'https://sc10cluste-sc10-xxxxxx-xxxxxxxx.hcp.eastus.azmk8s.io:443'
VERSION = ''
}
stages {
stage('Checkout') {
steps {
checkout([$class: 'GitSCM', branches: [[name: '*/' + params.Environment]], extensions: [], userRemoteConfigs: [[url: GIT_REPO_URL]]])
}
}
stage('Find Version of Latest Built Images')
{
steps {
script {
azureCLI commands: [[exportVariablesString: '', script: 'az acr login --name ' + REGISTRY]], principalCredentialId: PRINCIPAL_SERVICE_CREDENTIAL_ID
def latest = powershell(returnStdout: true, script: '(az acr repository show-tags -n ' + REGISTRY_NAME + ' --repository ' + COMPOSE_PROJECT_NAME + '-solution --orderby time_desc --output tsv | Where-Object -FilterScript { $_ -match "' + params.Environment + '-*" })[0]')
VERSION = latest.trim()
}
}
}
stage('Deploy Images to AKS') {
steps {
script {
dir('custom-images') {
withKubeConfig(credentialsId: KUBERNETES_CREDENTIALS_ID, namespace: AKS_NAMESPACE, clusterName: AKS_CLUSTER_NAME, serverUrl: AKS_CLUSTER_API_URL) {
bat 'kubectl set image deployments/cm sitecore-xm1-cm=' + REGISTRY + COMPOSE_PROJECT_NAME + '-xm1-cm:' + VERSION + ' -n ' + AKS_NAMESPACE + ' --record'
bat 'kubectl set image deployments/cd sitecore-xm1-cd=' + REGISTRY + COMPOSE_PROJECT_NAME + '-xm1-cd:' + VERSION + ' -n ' + AKS_NAMESPACE + ' --record'
}
}
}
}
}
stage('Wait for Deployment Rollout to Finish') {
steps {
script {
dir('custom-images') {
withKubeConfig(credentialsId: KUBERNETES_CREDENTIALS_ID, namespace: AKS_NAMESPACE, clusterName: AKS_CLUSTER_NAME, serverUrl: AKS_CLUSTER_API_URL) {
bat 'kubectl rollout status deployments/cm'
bat 'kubectl rollout status deployments/cd'
}
}
}
}
}
}
}

A Jenkins job using this deploy pipeline takes about 2 minutes to complete and most of the time is spent in the last stage waiting for the actual rollout deployment in AKS to complete.

Welcome to Sitecore 
https:// 
cd.globalhost 
sitecore 
Üdocker 
kubernetes 
Sitecore Experience Platform 
From a single connected platform that also integrates with other customer-facing platforms, to a single view of the 
customer in a big data marketing repository, to completely eliminating much of the complexity that has previously held 
marketers back, the latest version of Sitecore makes customer experience highly achievable. Learn how the latest 
version of Sitecore gives marketers the complete data, integrated tools, and automation capabilities to engage 
customers throughout an iterative lifecycle - the technology foundation absolutely necessary to win customers for life. 
For further information, please go to the Sitecore Documentation site 
@ 2021 Sitecore 
Ill \

Future Improvements

The deploy pipeline described in this post should be considered a starting point for an automated deployment process and can be enhanced to support additional automation of tasks that are part of a release, like for example a Sitecore item synchronization task. The implementation of this task would depend of course on which item serialization tools is adopted by your solution (TDS, Unicorn or Sitecore Serialization).

Another useful improvement could be the implementation of an automatic rollback of the deployment to the latest stable release in case of a failure after images have been deployed to the AKS cluster resource. The Kubernetes CLI offers the rollout undo command to perform a Kubernetes deployment rollback, that can be performed in a post failure step in the Jenkins deploy pipeline.

Conclusions

This blog post described the implementation of the Jenkins deploy pipeline to deploy a containerized Sitecore 10 solution to an Azure Kubernetes Service cluster. I hope that this mini series helps you to implement your own pipeline with Jenkins or, if you are using a different CI/CD tool, helps you to understand the needed stages to implement it using your own tool. If you have any questions please don’t hesitate to reach out or comment on this post.

Thank you for reading!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s