{"id":47,"date":"2024-10-06T00:36:21","date_gmt":"2024-10-06T00:36:21","guid":{"rendered":"https:\/\/blogs.oregonstate.edu\/ozzythecomputerguy\/?p=47"},"modified":"2024-10-06T00:50:53","modified_gmt":"2024-10-06T00:50:53","slug":"overcoming-azure-devops-pipeline-challenges-with-terraform-for-aks-deployments","status":"publish","type":"post","link":"https:\/\/blogs.oregonstate.edu\/ozzythecomputerguy\/2024\/10\/06\/overcoming-azure-devops-pipeline-challenges-with-terraform-for-aks-deployments\/","title":{"rendered":"Overcoming Azure DevOps Pipeline Challenges with Terraform for AKS Deployments"},"content":{"rendered":"\n<p>Day two of developing KubeConductor is officially done. <\/p>\n\n\n\n<p>Today, I focused on configuring an Azure DevOps CI\/CD pipeline to deploy an Azure Kubernetes Service (AKS) cluster using Terraform. I&#8217;ll touch on the hurdles encountered while provisioning the infrastructure and managing existing resources, as well as the steps taken to resolve them.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Code<\/h2>\n\n\n\n<p>My public repo can he found [<a href=\"https:\/\/github.com\/Osbaldo-Arellano\/KubeConductor\">here<\/a>]. <\/p>\n\n\n\n<p><strong>aks-cluster.tf<\/strong>: This is the Terraform configuration file that provisions an Azure Kubernetes Service (AKS) cluster using the AzureRM provider.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># aks-cluster.tf\nprovider \"azurerm\" {\n  features {}\n\n  client_id       = var.client_id\n  client_secret   = var.client_secret\n  tenant_id       = var.tenant_id\n  subscription_id = var.subscription_id\n}\n\n\nresource \"azurerm_resource_group\" \"default\" {\n  name     = \"terraform-aks-rg\"\n  location = \"West US\"\n}\n\nresource \"azurerm_kubernetes_cluster\" \"default\" {\n  name                = \"terraform-aks-cluster\"\n  location            = azurerm_resource_group.default.location\n  resource_group_name = azurerm_resource_group.default.name\n  dns_prefix          = \"terraform-aks\"\n\n  default_node_pool {\n    name            = \"default\"\n    node_count      = 2\n    vm_size         = \"Standard_DS2_v2\"\n    os_disk_size_gb = 30\n  }\n\n  identity {\n    type = \"SystemAssigned\"\n  }\n\n  role_based_access_control_enabled = true\n\n  tags = {\n    environment = \"Development\"\n  }\n}<\/code><\/pre>\n\n\n\n<p><strong>variables.tf:<\/strong> This Terraform configuration file defines a set of variables used in the <code>aks-cluster.tf<\/code> file to dynamically configure the Azure resources<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># variables.tf\nvariable \"resource_group_name\" {\n  description = \"Name of the Resource Group\"\n  default     = \"terraform-aks-rg\"\n}\n\nvariable \"client_id\" {\n  description = \"The Client ID of the Service Principal\"\n}\n\nvariable \"client_secret\" {\n  description = \"The Client Secret of the Service Principal\"\n}\n\nvariable \"tenant_id\" {\n  description = \"The Tenant ID of the Azure Active Directory\"\n}\n\nvariable \"subscription_id\" {\n  description = \"The Subscription ID where the resources will be created\"\n}<\/code><\/pre>\n\n\n\n<p><strong>versions.tf:<\/strong> This Terraform configuration file specifies the required Terraform and provider versions needed. It helps ensure compatibility and stability by enforcing versioning.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># versions.tf\nterraform {\n  required_version = \"&gt;= 0.14\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"&gt;= 2.56\"\n    }\n  }\n}<\/code><\/pre>\n\n\n\n<p><strong>outputs.tf:<\/strong> This Terraform configuration file defines output variables for the infrastructure. In my case, these outputs are used to display values after the resources have been created. This functionality is not implemented yet, however. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># outputs.tf\noutput \"kubernetes_cluster_name\" {\n  value = azurerm_kubernetes_cluster.default.name\n}\n\noutput \"resource_group_name\" {\n  value = azurerm_resource_group.default.name\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Initial Pipeline Configuration<\/strong><\/h2>\n\n\n\n<p>My goal for today was to automate the provisioning of an AKS cluster using Terraform in an Azure DevOps pipeline. The basic setup included a self-hosted Ubuntu agent and a multi-step pipeline with the following stages:<\/p>\n\n\n\n<p><strong>Terraform Init:<\/strong> Initializes a new or existing Terraform configuration<\/p>\n\n\n\n<p><strong>Terraform Plan: <\/strong>Generates an execution plan to show what changes Terraform will make to your infrastructure.<\/p>\n\n\n\n<p><strong>Terraform Apply: <\/strong>Applies the changes required to reach the desired state of the configuration.<\/p>\n\n\n\n<p>Below is a screenshot of the steps passing the CI\/CD pipeline, which runs on a self-hosted Ubuntu machine. <\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"323\" height=\"123\" src=\"https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/7141\/files\/2024\/10\/image.png\" alt=\"\" class=\"wp-image-48\" srcset=\"https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/7141\/files\/2024\/10\/image.png 323w, https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/7141\/files\/2024\/10\/image-300x114.png 300w\" sizes=\"auto, (max-width: 323px) 100vw, 323px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Debugging Pipeline Environment Variables<\/h2>\n\n\n\n<p>From the beginning, I knew I wanted an application that mirrored real-world cloud infrastructure as closely as possible. This meant that I needed to be careful with how I handle secret values such as IDs, passwords, etc. I opted to use Azure Pipeline Group Variables to store the sensitive information relating to my cloud infrastructure. My CI\/CD pipeline fetches these variables on runtime, eliminating the need to hard-code these values. <\/p>\n\n\n\n<p>Terraform relies on a consistent mapping of environment variables for Azure authentication. I encountered errors with variables like <code>client_id<\/code> and <code>subscription_id<\/code> not being recognized correctly. To fix this issue, I standardized the variable mapping using the <code>TF_VAR_<\/code> prefix to align Terraform\u2019s environment variables (<code>TF_VAR_client_id<\/code>, <code>TF_VAR_client_secret<\/code>, <code>TF_VAR_tenant_id<\/code>, and <code>TF_VAR_subscription_id<\/code>) with Azure\u2019s environment requirements (<code>ARM_CLIENT_ID<\/code>, <code>ARM_CLIENT_SECRET<\/code>, etc.).<\/p>\n\n\n\n<p>Here is the updated yml to match those requirements:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Updated azure-pipelines.yml with complete TF_VAR_ variable mapping\n\ntrigger:\n  branches:\n    include:\n      - main\n\npool:\n  name: \"SelfHostedUbuntu\"\n\nvariables:\n  - group: Terraform-SP-Credentials\n\njobs:\n  - job: \"Deploy_AKS\"\n    displayName: \"Provision AKS Cluster Using Terraform\"\n    steps:\n      # Step 1: Checkout Code\n      - checkout: self\n\n      # Step 2: Verify Terraform Version\n      - script: |\n          terraform --version\n        displayName: \"Verify Installed Terraform Version\"\n\n      # Step 3: Terraform Init (Set Working Directory and Pass All Variables with TF_VAR_ Prefix)\n      - script: |\n          terraform init\n        displayName: \"Terraform Init\"\n        workingDirectory: $(System.DefaultWorkingDirectory)\/terraform\n        env:\n          ARM_CLIENT_ID: $(appId)\n          ARM_CLIENT_SECRET: $(password)\n          ARM_TENANT_ID: $(tenant)\n          ARM_SUBSCRIPTION_ID: $(AZURE_SUBSCRIPTION_ID)\n          TF_VAR_client_id: $(appId) # Client ID\n          TF_VAR_client_secret: $(password) # Client Secret\n          TF_VAR_tenant_id: $(tenant) # Tenant ID\n          TF_VAR_subscription_id: $(AZURE_SUBSCRIPTION_ID) # Subscription ID\n\n      # Step 4: Terraform Plan (Pass All Variables with TF_VAR_ Prefix)\n      - script: |\n          terraform plan -out=tfplan\n        displayName: \"Terraform Plan\"\n        workingDirectory: $(System.DefaultWorkingDirectory)\/terraform\n        env:\n          ARM_CLIENT_ID: $(appId)\n          ARM_CLIENT_SECRET: $(password)\n          ARM_TENANT_ID: $(tenant)\n          ARM_SUBSCRIPTION_ID: $(AZURE_SUBSCRIPTION_ID)\n          TF_VAR_client_id: $(appId)\n          TF_VAR_client_secret: $(password)\n          TF_VAR_tenant_id: $(tenant)\n          TF_VAR_subscription_id: $(AZURE_SUBSCRIPTION_ID)\n      # Step 5: Terraform Apply (Set Working Directory and Pass All Variables)\n      - script: |\n          terraform apply -auto-approve tfplan\n        displayName: \"Terraform Apply\"\n        workingDirectory: $(System.DefaultWorkingDirectory)\/terraform\n        env:\n          ARM_CLIENT_ID: $(appId)\n          ARM_CLIENT_SECRET: $(password)\n          ARM_TENANT_ID: $(tenant)\n          ARM_SUBSCRIPTION_ID: $(AZURE_SUBSCRIPTION_ID)\n          TF_VAR_client_id: $(appId)\n          TF_VAR_client_secret: $(password)\n          TF_VAR_tenant_id: $(tenant)\n          TF_VAR_subscription_id: $(AZURE_SUBSCRIPTION_ID)<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Azure CLI Summary:<\/strong><\/h2>\n\n\n\n<p>Here&#8217;s a list of Azure CLI commands I used today. Sometimes these were used for debugging, sanity checking, or they were just necessary for Terraform to work. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. <strong>Login to Azure Using Service Principal:<\/strong><\/h3>\n\n\n\n<p>Authenticates to Azure using Service Principal credentials (<code>appId<\/code>, <code>password<\/code>, and <code>tenant<\/code>).<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">az login --service-principal -u &lt;appId&gt; -p &lt;password&gt; --tenant &lt;tenant&gt;<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2. <strong>Retrieve AKS Cluster Credentials:<\/strong><\/h3>\n\n\n\n<p>Fetches the AKS cluster credentials and configures <code>kubectl<\/code> to use this cluster.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">az aks get-credentials --resource-group &lt;resource_group_name&gt; --name &lt;kubernetes_cluster_name&gt;<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3. <strong>Browse Kubernetes Dashboard:<\/strong><\/h3>\n\n\n\n<p>Opens the Kubernetes dashboard for the specified AKS cluster in the Azure Portal.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">az aks browse --resource-group &lt;resource_group_name&gt; --name &lt;kubernetes_cluster_name&gt;<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">4. <strong>Create Service Principal:<\/strong><\/h3>\n\n\n\n<p>Creates an Azure Active Directory Service Principal for authentication in Terraform.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">az ad sp create-for-rbac --skip-assignment<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Takeaways<\/h2>\n\n\n\n<p><strong>State Management Needs Improvement:<\/strong> The pipeline deploys the AKS cluster and pods successfully if the resource group does not exist. However, the pipeline fails since state management is not set up yet &#8212; this a task that requires configuring a remote state backend. <\/p>\n\n\n\n<p><strong>Streamlined Pipeline Steps:<\/strong> The final pipeline configuration effectively provisions the AKS cluster but needs fine-tuning for state management and validation.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Next Steps:<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Implement a remote state backend to centralize state management.<\/li>\n\n\n\n<li>Reintroduce verification and post-deployment steps (<code>kubectl<\/code> commands) in the pipeline.<\/li>\n\n\n\n<li>Optimize the CI\/CD flow to handle both new and existing infrastructure.<\/li>\n<\/ul>\n\n\n\n<p>Finally, I would like to thank my incredibly hard-working agent &#8212; SelfHostedUbuntu! Look at him just chugging along and handling errors like a champ :,-)<br><\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"560\" src=\"https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/7141\/files\/2024\/10\/image-3-1024x560.png\" alt=\"\" class=\"wp-image-51\" srcset=\"https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/7141\/files\/2024\/10\/image-3-1024x560.png 1024w, https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/7141\/files\/2024\/10\/image-3-300x164.png 300w, https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/7141\/files\/2024\/10\/image-3-768x420.png 768w, https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/7141\/files\/2024\/10\/image-3-1536x841.png 1536w, https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/7141\/files\/2024\/10\/image-3.png 1895w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Bye for now!<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Day two of developing KubeConductor is officially done. Today, I focused on configuring an Azure DevOps CI\/CD pipeline to deploy an Azure Kubernetes Service (AKS) cluster using Terraform. I&#8217;ll touch on the hurdles encountered while provisioning the infrastructure and managing existing resources, as well as the steps taken to resolve them. The Code My public &hellip; <a href=\"https:\/\/blogs.oregonstate.edu\/ozzythecomputerguy\/2024\/10\/06\/overcoming-azure-devops-pipeline-challenges-with-terraform-for-aks-deployments\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Overcoming Azure DevOps Pipeline Challenges with Terraform for AKS Deployments<\/span><\/a><\/p>\n","protected":false},"author":13702,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-47","post","type-post","status-publish","format-standard","hentry","category-kubeconductor"],"_links":{"self":[{"href":"https:\/\/blogs.oregonstate.edu\/ozzythecomputerguy\/wp-json\/wp\/v2\/posts\/47","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.oregonstate.edu\/ozzythecomputerguy\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.oregonstate.edu\/ozzythecomputerguy\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.oregonstate.edu\/ozzythecomputerguy\/wp-json\/wp\/v2\/users\/13702"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.oregonstate.edu\/ozzythecomputerguy\/wp-json\/wp\/v2\/comments?post=47"}],"version-history":[{"count":3,"href":"https:\/\/blogs.oregonstate.edu\/ozzythecomputerguy\/wp-json\/wp\/v2\/posts\/47\/revisions"}],"predecessor-version":[{"id":54,"href":"https:\/\/blogs.oregonstate.edu\/ozzythecomputerguy\/wp-json\/wp\/v2\/posts\/47\/revisions\/54"}],"wp:attachment":[{"href":"https:\/\/blogs.oregonstate.edu\/ozzythecomputerguy\/wp-json\/wp\/v2\/media?parent=47"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.oregonstate.edu\/ozzythecomputerguy\/wp-json\/wp\/v2\/categories?post=47"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.oregonstate.edu\/ozzythecomputerguy\/wp-json\/wp\/v2\/tags?post=47"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}