Jenkins is a popular CI/CD tool. This example shows the creation of a Jenkins server with a system assigned managed identity. Azure CLI and Terraform will also be installed. Jeknins is then configured to use the GitHub repo and its Jenkinsfile. The Jenkinsfile includes both Azure CLI and Terraform example steps in the pipeline.
Assumes a Bash environment with the Azure CLI, plus access to a valid subscription.
The terraform config should create a resource group, terraform-demo, and an Azure Container Instance running the inspector gadget image, but this is purely to prove that your Terraform configuration can be deployed via Jenkins using a GitHub repo and a system assigned managed identity.
If you are prefer using service principals then check the companion Service Principal readme.
Before you start, fork this repo so that you have your own.
-
Create cloud-config.yaml
Create a cloud-config.yaml file with the following contents.
#cloud-config package_upgrade: true runcmd: - sudo apt install openjdk-11-jre ca-certificates curl apt-transport-https lsb-release gnupg gpg wget -y - wget -qO - https://pkg.jenkins.io/debian-stable/jenkins.io.key | sudo apt-key add - - wget -qO- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null - wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /usr/share/keyrings/microsoft.gpg > /dev/null - sh -c 'echo deb https://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list' - sh -c 'echo "deb [arch=`dpkg --print-architecture` signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com `lsb_release -cs` main" | sudo tee /etc/apt/sources.list.d/hashicorp.list' - sh -c 'echo "deb [arch=`dpkg --print-architecture` signed-by=/usr/share/keyrings/microsoft.gpg] https://packages.microsoft.com/repos/azure-cli/ `lsb_release -cs` main" | sudo tee /etc/apt/sources.list.d/azure-cli.list' - sudo chmod go+r /usr/share/keyrings/hashicorp-archive-keyring.gpg /usr/share/keyrings/microsoft.gpg - sudo apt-get update && sudo apt-get install jenkins terraform azure-cli jq -y - sudo service jenkins restart
The cloud-config.yaml file will install
- jenkins
- terraform
- azure-cli
- jq
-
Set your default region and resource group
Set these to your preferred values.
az config set defaults.location=uksouth defaults.group=jenkins
Setting defaults removes the need to specify
--location
and--resource-group
on Azure CLI commands. Defaults are stored in~/.azure/config
. Useaz config unset defaults.location defaults.group
to unset.See https://learn.microsoft.com/cli/azure/azure-cli-configuration for more.
-
Create the resource group
az group create --name $(az config get defaults.group --query value -otsv)
-
Create the VM
az vm create --name jenkins \ --image UbuntuLTS \ --admin-username "azureuser" \ --generate-ssh-keys \ --public-ip-sku Standard \ --custom-data cloud-config.yaml \ --assign-identity [system]
-
Open port 8080 and 443 on the NSG
az vm open-port --port 8080 --priority 1010 --name jenkins az vm open-port --port 443 --priority 1020 --name jenkins
-
Find the objectId for the managed identity
managed_identity=$(az vm show --resource-group jenkins --name jenkins --query identity.principalId --output tsv)
-
Create a storage account and container for the Terraform remote state
rgId=$(az group show --name $(az config get defaults.group --query value -otsv) --query id -otsv) sa=terraform$(md5sum <<< $rgId | cut -c1-12) az storage account create --name $sa --sku Standard_LRS --allow-blob-public-access false az storage container create --name "tfstate" --account-name $sa --auth-mode login
Uses md5sum to generate a predictable hash from the resource group's resource ID.
-
Assign RBAC roles for the managed identity
subscriptionId=/subscriptions/$(az account show --query id --output tsv) saId=$(az storage account show --name $sa --query id --output tsv) az role assignment create --assignee $managed_identity --role "Contributor" --scope $subscriptionId az role assignment create --assignee $managed_identity --role "Storage Blob Data Contributor" --scope $saId
-
Grab the public IP address
publicIp=$(az vm show --name jenkins --show-details --query publicIps --output tsv) echo $publicIp
-
SSH onto the Jenkins server
ssh azureuser@$publicIp
-
Checks
terraform --version az version service jenkins status
CTRL
+C
to break. -
Display the Jenkins URL and unlock password
publicIp=$(curl -sSL -H Metadata:true --noproxy "*" http://169.254.169.254/metadata/loadbalancer?api-version=2020-10-01 \ | jq -r .loadbalancer.publicIpAddresses[0].frontendIpAddress) pwdfile=/var/lib/jenkins/secrets/initialAdminPassword echo "Open http://${publicIp}:8080" sudo --user=jenkins -- test -s $pwdfile && echo "and paste in $(sudo cat $pwdfile)"
-
Connect to the URL and paste in the initialAdminPassword
-
Select plugins to install
-
Filter to GitHub
-
Check GitHub
-
Install
Getting Started will take a few minutes to run.
-
Create First Admin User
- username
- password
- full name
- email address
-
Save & Finish
-
Start using Jenkins
-
Manage Jenkins | System Configuration | Manage Plugins
-
Restart Jenkins
Once they have all installed then check the Restart Jenkins box.
Alternatively:
- Browse to
http://<ip_address>:8080/restart
, or - Run
sudo service jenkins restart
- Browse to
If you installed the Dark Theme plugin:
- Manage Jenkins | Configure System
- Scroll down to Themes
- Select your preferred theme
Jenkins can install tools (binaries, etc) on the fly with automatic installers. We installed Terraform via apt and will use the /usr/bin/terraform
binary.
- Manage Jenkins | Global Tool Configuration
- Scroll down to the Terraform section
- Add Terraform
- Save
-
Display values for secrets
rgId=$(az group show --name $(az config get defaults.group --query value -otsv) --query id -otsv) sa=terraform$(md5sum <<< $rgId | cut -c1-12) az account show --query "{tenant_id:tenantId, subscription_id:id}" --output yamlc az storage account show --name $sa --query "{resource_group:resourceGroup, storage_account:name}" --output yamlc
-
Manage Jenkins | Manage Credentials
-
System, Global credentials, + Add Credentials
-
Kind = Secret text
Create credentials for tenant_id, subscription_id, resource_group and storage_account, setting secret to the values shown by the commands above.
The Jenkinsfile maps the credentials to environment variables.
On the Jenkins dashboard:
-
+ New Item
-
Select *Pipeline"
-
Enter an item name
-
Click OK
You will now be in the Configure | General screen for your pipeline.
-
Scroll down to Build Triggers section
-
Check GitHub hook trigger for GITScm polling
-
Scroll down further, to the Pipeline section
-
Change the Definition dropdown to Pipeline script from SCM
-
SCM should be set to Git
-
In repository URL, add in the .git path to your repo
⚠️ Your repo -
Click on Add Branch
-
Set the specifier, e.g.
*/main
Note that there is a Jenkinsfile at the root of the repo, matching the default Script Path on the configuration.
-
Save
Make sure the pipeline works before creating the GitHub webhook. In Jenkins dashboard:
-
Select your pipeline
-
Click on Build Now
The pipeline view will start to show in the right hand pane.
-
Approve the deployment
Assuming the Terraform Init, Terraform Validate and Terraform Plan stages have succeeded, you will be asked to approve the deployment. Feel free to view the log output of the Terraform Plan stage.
Hover over the Waiting for approval stage and you will get the option to Proceed.
The final Terraform Apply stage should create the resource group and container instance.
-
Jenkins dashboard
The dashboard should now show a healthy pipeline run.
-
Azure Portal
You should now have a resource group called terraform-demo containing the example container instance.
Success!
In your clone of this GitHub repo:
-
Settings | Webhooks
-
Set the Payload URL to
http://<ip_address>:8080/github-webhook/
-
Set Content type to application/json
-
Click on Add webhook
Feel free to locally clone your repo, make a change to the Jenkinsfile or Terraform files and then push that commit back up to the origin. This should trigger another build pipeline.
OK, it is working. Want to harden it a little?
- https://www.jenkins.io/doc/book/pipeline/jenkinsfile/
- https://plugins.jenkins.io/credentials/
- https://learn.microsoft.com/azure/developer/jenkins/configure-on-linux-vm
- https://learn.microsoft.com/azure/developer/jenkins/deploy-to-azure-spring-apps-using-azure-cli
- https://github.com/Azure-Samples/jenkins-terraform-azure-example/blob/main/Create_Jenkins_Job.md
- https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/managed_service_identity
- https://plugins.jenkins.io/azure-credentials/
- https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/service_principal_client_secret
- https://learn.microsoft.com/en-us/azure/load-balancer/howto-load-balancer-imds?tabs=linux
- https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service?tabs=linux
- https://www.cprime.com/resources/blog/how-to-integrate-jenkins-github/
Configure https:
Configure secrets in Azure Key Vault:
Will also look at:
- Using credentials to access a private GitHub repo
- Removing the public IP and switching to Azure Bastion tunnels
There is a lot of old information out there, and a number of plugins that are moving out of Microsoft support.
Below is a list of resources that I explored and discounted as I believe that they are not on the recommended path, or they are now outdated.