├── .github └── workflows │ └── main.yml ├── .gitignore ├── .vscode └── settings.json ├── 1-Azure ├── 1-Configure-Terraform-Remote-Storage.md ├── 2-Create-Azure-AD-Group-AKS-Admins.md ├── README.md ├── images │ ├── azure-ad-group.png │ └── storage-account.png └── scripts │ ├── 1-create-terraform-storage.sh │ └── 2-create-azure-ad-group.sh ├── 2-Terraform-AZURE-Services-Creation ├── 1-Create-ACR.md ├── 1-acr │ ├── README.md │ ├── acr.tf │ ├── providers.tf │ ├── terraform.tfvars │ └── variables.tf ├── 2-Create-VNET.md ├── 2-vnet │ ├── README.md │ ├── alb.tf │ ├── data.tf │ ├── nsg.tf │ ├── providers.tf │ ├── terraform.tfvars │ ├── variables.tf │ └── vnet.tf ├── 3-Create-Log-Analytics.md ├── 3-log-analytics │ ├── README.md │ ├── data.tf │ ├── la.tf │ ├── providers.tf │ ├── terraform.tfvars │ └── variables.tf ├── 4-Create-AKS-Cluster-IAM-Roles.md ├── 4-aks │ ├── README.md │ ├── aks.tf │ ├── data.tf │ ├── managed_identity.tf │ ├── providers.tf │ ├── rbac.tf │ ├── terraform.tfvars │ └── variables.tf ├── 5-Run-CICD-For-AKS-Cluster.md ├── README.md ├── images │ ├── 1-acr.png │ ├── 2-vnet.png │ ├── 3-la.png │ └── 4-aks.png └── scripts │ └── 5-create-github-oidc.sh ├── 3-Docker ├── 1-Create-Docker-Image.md ├── 2-Push Image To ACR.md ├── Dockerfile ├── README.md ├── app │ ├── app.py │ ├── requirements.txt │ └── templates │ │ └── index.html ├── images │ └── acr.png └── scripts │ └── build-push-acr.sh ├── 4-kubernetes_manifest ├── 1-Connect-To-AKS.md ├── 2-Create-Kubernetes-Manifest.md ├── 3-Deploy-Thomasthorntoncloud-App.md ├── README.md ├── deployment.yml ├── images │ └── website.png └── scripts │ ├── 1-alb-controller-install-k8s.sh │ └── 2-gateway-api-resources.sh ├── 5-Terraform-Static-Code-Analysis ├── 1-Checkov-For-Terraform.md └── 2-tfsec.md ├── 6-Terraform-Docs ├── 1-Setup-Terraform-Docs.md └── README.md ├── LICENSE ├── README.md ├── images └── website.png ├── prerequisites.md └── renovate.json /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Terrform-Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | 12 | jobs: 13 | terraform: 14 | name: Terrform-Deploy 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: write 18 | id-token: write # Required for OIDC 19 | 20 | env: 21 | ARM_CLIENT_ID: ${{ secrets.AZURE_AD_CLIENT_ID }} 22 | ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} 23 | ARM_TENANT_ID: ${{ secrets.AZURE_AD_TENANT_ID }} 24 | ARM_USE_OIDC: true 25 | tf_resource_group_name: "thomasthorntoncloud" 26 | tf_storage_account_name: "thomasthorntontfstate" 27 | tf_state_container: "devopsthehardwaygithub" 28 | tf_state_key: "terraform.tfstate" 29 | 30 | 31 | steps: 32 | - name: Checkout Code 33 | uses: actions/checkout@v4 34 | 35 | - name: Setup Terraform 36 | uses: hashicorp/setup-terraform@v3 37 | with: 38 | terraform_version: 1.11.0 39 | terraform_wrapper: true 40 | 41 | # Add in tutorial 6-Terarform-Docs 42 | # - name: Render terraform docs and push changes back to PR 43 | # uses: terraform-docs/gh-actions@main 44 | # with: 45 | # working-dir: ./2-Terraform-AZURE-Services-Creation/1-acr, ./2-Terraform-AZURE-Services-Creation/2-vnet, ./2-Terraform-AZURE-Services-Creation/3-log-analytics, ./2-Terraform-AZURE-Services-Creation/4-aks 46 | # output-file: README.md 47 | # output-method: inject 48 | # git-push: "true" 49 | 50 | - name: Terraform Init 51 | run: terraform init 52 | working-directory: ./2-Terraform-AZURE-Services-Creation/4-aks 53 | 54 | # Add in tutorial 5-Terraform-Static-Code-Analysis 55 | # - name: tfsec 56 | # uses: aquasecurity/tfsec-pr-commenter-action@v1.2.0 57 | # with: 58 | # tfsec_args: --soft-fail 59 | # github_token: ${{ github.token }} 60 | 61 | - name: Terraform Format 62 | if: github.event_name == 'pull_request' 63 | run: terraform fmt 64 | working-directory: ./2-Terraform-AZURE-Services-Creation/4-aks 65 | 66 | - name: Auto Commit Changes 67 | uses: stefanzweifel/git-auto-commit-action@v5 68 | if: github.event_name == 'pull_request' 69 | with: 70 | commit_message: "Terraform fmt" 71 | file_pattern: "*.tf *.tfvars" 72 | commit_user_name: "github-actions[bot]" 73 | 74 | - name: Terraform Plan 75 | run: terraform plan -no-color -input=false 76 | working-directory: ./2-Terraform-AZURE-Services-Creation/4-aks 77 | env: 78 | DEPLOYMENT_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} 79 | 80 | - name: Terraform Apply 81 | if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' 82 | run: terraform apply -auto-approve -input=false 83 | working-directory: ./2-Terraform-AZURE-Services-Creation/4-aks 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Crash log files 9 | crash.log 10 | 11 | # Lock file 12 | *.lock.hcl 13 | 14 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most 15 | # .tfvars files are managed as part of configuration and so should be included in 16 | # version control. 17 | # 18 | # example.tfvars 19 | 20 | # Ignore override files as they are usually used to override resources locally and so 21 | # are not checked in 22 | override.tf 23 | override.tf.json 24 | *_override.tf 25 | *_override.tf.json 26 | 27 | # Include override files you do wish to add to version control using negated pattern 28 | # 29 | # !example_override.tf 30 | 31 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 32 | # example: *tfplan* 33 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "yaml.customTags": [ 3 | "!And", 4 | "!And sequence", 5 | "!If", 6 | "!If sequence", 7 | "!Not", 8 | "!Not sequence", 9 | "!Equals", 10 | "!Equals sequence", 11 | "!Or", 12 | "!Or sequence", 13 | "!FindInMap", 14 | "!FindInMap sequence", 15 | "!Base64", 16 | "!Join", 17 | "!Join sequence", 18 | "!Cidr", 19 | "!Ref", 20 | "!Sub", 21 | "!Sub sequence", 22 | "!GetAtt", 23 | "!GetAZs", 24 | "!ImportValue", 25 | "!ImportValue sequence", 26 | "!Select", 27 | "!Select sequence", 28 | "!Split", 29 | "!Split sequence" 30 | ] 31 | } -------------------------------------------------------------------------------- /1-Azure/1-Configure-Terraform-Remote-Storage.md: -------------------------------------------------------------------------------- 1 | # Configure Storage Account for Terraform State File 2 | 3 | ## 🎯 Purpose 4 | In this lab, you'll create a secure location to store the remote Terraform State file. This is crucial for maintaining consistency and collaboration in your infrastructure-as-code projects. 5 | 6 | ## 🛠️ Create Blob Storage for Terraform State File 7 | 8 | ### Prerequisites 9 | - [ ] Azure CLI installed and configured (`az login` executed) 10 | - [ ] Contributor permissions on your Azure subscription 11 | - [ ] Basic understanding of Azure Storage concepts 12 | 13 | ### Steps 14 | 15 | 1. **Customise Variables** 16 | - Open the [create-terraform-storage.sh](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/blob/main/1-Azure/scripts/create-terraform-storage.sh) script. 17 | - Locate the following lines: 18 | 19 | ```bash 20 | RESOURCE_GROUP_NAME="devopshardway-rg" 21 | STORAGE_ACCOUNT_NAME="devopshardwaysa" 22 | ``` 23 | 24 | - Replace the placeholders with your desired names. 25 | 26 | 2. **Run the Script** 27 | 28 | - Run the following command in your terminal: 29 | 30 | ```bash 31 | ./scripts/1-create-terraform-storage.sh 32 | ``` 33 | 34 | 3. **What's Happening Behind the Scenes?** 35 | The script performs these actions: 36 | - [ ] Creates an Azure Resource Group with appropriate tags 37 | - [ ] Sets up an Azure Storage Account with enhanced security settings: 38 | - Encryption enabled for blob storage 39 | - TLS 1.2 enforced 40 | - Public access to blobs disabled 41 | - [ ] Establishes an Azure Blob storage container 42 | - [ ] Outputs configuration for your Terraform backend 43 | 44 | ## 🔍 Verification 45 | To ensure everything was set up correctly: 46 | 47 | 1. Log into the [Azure Portal](https://portal.azure.com). 48 | 2. Navigate to your newly created Resource Group. 49 | 3. Verify the presence of the Storage Account. 50 | 4. Within the Storage Account, check for the Blob container. 51 | 5. It should look similar to this: 52 | 53 | ![](images/storage-account.png) 54 | 55 | ## 🧠 Knowledge Check 56 | After running the script, try to answer these questions: 57 | 1. Why is it important to use remote state storage for Terraform? 58 | 2. What are the benefits of using Azure Blob Storage for this purpose? 59 | 3. How would you access this state file in your Terraform configurations? 60 | 61 | ## 💡 Pro Tip 62 | Consider implementing these additional security measures for production environments: 63 | 1. Enable soft delete and versioning for your blob storage to protect against accidental deletion 64 | 2. Set up a resource lock to prevent accidental deletion of the storage account 65 | 3. Use Managed Identities instead of storage account keys for authentication 66 | 4. Configure network rules to restrict access to specific networks 67 | 5. Set up Azure Key Vault to store sensitive backend configuration 68 | 69 | Example of adding a resource lock: 70 | ```bash 71 | az lock create --name LockTerraformStorage --lock-type CanNotDelete \ 72 | --resource-group devopshardway-rg \ 73 | --resource-name devopshardwaysa \ 74 | --resource-type Microsoft.Storage/storageAccounts 75 | ``` -------------------------------------------------------------------------------- /1-Azure/2-Create-Azure-AD-Group-AKS-Admins.md: -------------------------------------------------------------------------------- 1 | # Create Azure AD Group for AKS Admins 2 | 3 | ## 🎯 Purpose 4 | 5 | In this lab, you'll create an Azure AD Group for AKS Admins. These "admins" will be the designated users who can access the AKS cluster using kubectl. 6 | 7 | ## 🛠️ Create Azure AD AKS Admin Group 8 | 9 | ### Prerequisites 10 | 11 | - [ ] Azure CLI installed and configured (`az login` executed) 12 | - [ ] Sufficient permissions to create Azure AD groups (e.g., Global Administrator or User Administrator role) 13 | 14 | ### Steps 15 | 16 | 1. **Run the Script** 17 | 18 | Execute the following command in your terminal: 19 | 20 | ```bash 21 | ./scripts/2-create-azure-ad-group.sh 22 | ``` 23 | 2. What the Script Does 24 | 25 | The script performs these actions: 26 | 27 | - [ ] Creates an Azure AD Group named `devopsthehardway-aks-group` with a descriptive purpose 28 | - [ ] Adds the current user (logged into Az CLI) to the `devopsthehardway-aks-group` 29 | - [ ] Outputs the Azure AD Group ID in a clear, formatted way for later use 30 | 31 | **Important Note** 32 | 33 | Make sure to save the Azure AD Group ID displayed at the end of the script execution. You'll need this for AKS Terraform configurations in the following sections. 34 | 35 | ## 🔍 Verification 36 | 37 | To ensure the group was created successfully: 38 | 39 | 1. Log into the [Azure Portal](https://portal.azure.com) 40 | 2. Navigate to **Azure Active Directory > Groups** 41 | 3. Search for `devopsthehardway-aks-group` 42 | 4. Verify that your user account is listed as a member: 43 | 44 | ![](images/azure-ad-group.png) 45 | 46 | ## 🧠 Knowledge Check 47 | 48 | After running the script, consider these questions: 49 | 50 | 1. Why is it beneficial to use Azure AD groups for AKS admin access? 51 | 2. How does this group-based access improve security compared to individual user access? 52 | 3. In what ways might you further modify the AD group for different levels of access? 53 | 54 | ## 💡 Pro Tip 55 | 56 | Consider implementing these best practices for production environments: 57 | 58 | 1. Create multiple AD groups with different levels of access (e.g., read-only, developer, admin) 59 | 2. Integrate with Privileged Identity Management (PIM) for just-in-time access 60 | 3. Implement regular access reviews to ensure appropriate access 61 | 4. Use Conditional Access policies to enforce multi-factor authentication 62 | 63 | Example of adding another user to the group: 64 | 65 | ```bash 66 | # Get object ID of user to add 67 | USER_OBJECTID=$(az ad user show --id user@example.com --query id -o tsv) 68 | 69 | # Add user to the AKS admin group 70 | az ad group member add --group devopsthehardway-aks-group --member-id $USER_OBJECTID 71 | ``` -------------------------------------------------------------------------------- /1-Azure/README.md: -------------------------------------------------------------------------------- 1 | # Azure Setup for DevOps The Hard Way 2 | 3 | ## Overview 4 | This directory contains the foundational Azure setup needed for the DevOps The Hard Way - Azure project. These steps establish the core Azure resources that will be used throughout the tutorial. 5 | 6 | ## Labs in this Section 7 | 8 | ### [1. Configure Terraform Remote Storage](./1-Configure-Terraform-Remote-Storage.md) 9 | Set up an Azure Storage Account to securely store your Terraform state files, which is essential for team collaboration and state management. 10 | 11 | ### [2. Create Azure AD Group for AKS Admins](./2-Create-Azure-AD-Group-AKS-Admins.md) 12 | Create an Azure Active Directory group to manage administrative access to your Kubernetes clusters with proper RBAC controls. 13 | 14 | ## Scripts 15 | 16 | The `scripts` directory contains shell scripts that automate the setup process: 17 | 18 | - [`create-terraform-storage.sh`](./scripts/create-terraform-storage.sh): Creates a resource group, storage account, and blob container for Terraform state 19 | - [`create-azure-ad-group.sh`](./scripts/create-azure-ad-group.sh): Creates an Azure AD Group for AKS administrators 20 | 21 | ## Pre-requisites 22 | 23 | Before starting these labs, ensure you have: 24 | 25 | 1. An Azure account with appropriate permissions 26 | 2. Azure CLI installed and configured (`az login`) 27 | 3. Basic familiarity with Azure services and Terraform concepts 28 | 29 | ## Best Practices Applied 30 | 31 | - Resource naming conventions 32 | - Security-enhanced storage configuration 33 | - RBAC-based access control 34 | - Infrastructure as Code for reproducibility 35 | - Proper error handling in scripts 36 | -------------------------------------------------------------------------------- /1-Azure/images/azure-ad-group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomast1906/DevOps-The-Hard-Way-Azure/14c69268f159c540ef8517efae084027bf2d5f17/1-Azure/images/azure-ad-group.png -------------------------------------------------------------------------------- /1-Azure/images/storage-account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomast1906/DevOps-The-Hard-Way-Azure/14c69268f159c540ef8517efae084027bf2d5f17/1-Azure/images/storage-account.png -------------------------------------------------------------------------------- /1-Azure/scripts/1-create-terraform-storage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Configuration 4 | RESOURCE_GROUP_NAME="devopshardway-rg" 5 | STORAGE_ACCOUNT_NAME="devopshardwaysa" 6 | LOCATION="uksouth" 7 | CONTAINER_NAME="tfstate" 8 | 9 | # Error handling function 10 | handle_error() { 11 | echo "ERROR: $1" 12 | exit 1 13 | } 14 | 15 | # Verify Azure CLI is installed and user is logged in 16 | if ! command -v az &> /dev/null; then 17 | handle_error "Azure CLI is not installed. Please install it first: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli" 18 | fi 19 | 20 | # Check if user is logged in 21 | echo "Verifying Azure CLI login status..." 22 | az account show &> /dev/null || handle_error "You are not logged in to Azure CLI. Please run 'az login' first." 23 | 24 | # Check if Resource Group exists 25 | echo "Checking if resource group $RESOURCE_GROUP_NAME exists..." 26 | RESOURCE_GROUP_EXISTS=$(az group exists --name $RESOURCE_GROUP_NAME) 27 | 28 | if [ "$RESOURCE_GROUP_EXISTS" = "true" ]; then 29 | echo "Resource group $RESOURCE_GROUP_NAME already exists." 30 | else 31 | # Create Resource Group 32 | echo "Creating resource group $RESOURCE_GROUP_NAME in $LOCATION..." 33 | az group create -l $LOCATION -n $RESOURCE_GROUP_NAME --tags "Purpose=azure-devops-hardway" || handle_error "Failed to create resource group" 34 | fi 35 | 36 | # Check if Storage Account exists 37 | echo "Checking if storage account $STORAGE_ACCOUNT_NAME exists..." 38 | STORAGE_ACCOUNT_EXISTS=$(az storage account check-name --name $STORAGE_ACCOUNT_NAME --query 'nameAvailable' --output tsv) 39 | 40 | if [ "$STORAGE_ACCOUNT_EXISTS" = "false" ]; then 41 | echo "Storage account $STORAGE_ACCOUNT_NAME is already created in resource group $RESOURCE_GROUP_NAME." 42 | else 43 | # Create Storage Account with improved security settings 44 | echo "Creating storage account $STORAGE_ACCOUNT_NAME..." 45 | az storage account create \ 46 | -n $STORAGE_ACCOUNT_NAME \ 47 | -g $RESOURCE_GROUP_NAME \ 48 | -l $LOCATION \ 49 | --sku Standard_LRS \ 50 | --encryption-services blob \ 51 | --min-tls-version TLS1_2 \ 52 | --allow-blob-public-access false \ 53 | --tags "Purpose=azure-devops-hardway" || handle_error "Failed to create storage account" 54 | 55 | # Create Storage Account blob container 56 | echo "Creating blob container $CONTAINER_NAME..." 57 | az storage container create \ 58 | --name $CONTAINER_NAME \ 59 | --account-name $STORAGE_ACCOUNT_NAME \ 60 | --auth-mode login || handle_error "Failed to create blob container" 61 | 62 | # Output the access key (in a real environment, consider using managed identities instead) 63 | echo "Retrieving storage account key..." 64 | ACCOUNT_KEY=$(az storage account keys list --resource-group $RESOURCE_GROUP_NAME --account-name $STORAGE_ACCOUNT_NAME --query '[0].value' -o tsv) 65 | 66 | echo "Configuration for terraform backend:" 67 | echo "terraform {" 68 | echo " backend \"azurerm\" {" 69 | echo " resource_group_name = \"$RESOURCE_GROUP_NAME\"" 70 | echo " storage_account_name = \"$STORAGE_ACCOUNT_NAME\"" 71 | echo " container_name = \"$CONTAINER_NAME\"" 72 | echo " key = \"terraform.tfstate\"" 73 | echo " }" 74 | echo "}" 75 | 76 | echo "Setup complete!" 77 | fi -------------------------------------------------------------------------------- /1-Azure/scripts/2-create-azure-ad-group.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Configuration 4 | AZURE_AD_GROUP_NAME="devopsthehardway-aks-group" 5 | 6 | # Error handling function 7 | handle_error() { 8 | echo "ERROR: $1" 9 | exit 1 10 | } 11 | 12 | # Verify Azure CLI is installed and user is logged in 13 | if ! command -v az &> /dev/null; then 14 | handle_error "Azure CLI is not installed. Please install it first: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli" 15 | fi 16 | 17 | # Check if user is logged in 18 | echo "Verifying Azure CLI login status..." 19 | az account show &> /dev/null || handle_error "You are not logged in to Azure CLI. Please run 'az login' first." 20 | 21 | echo "Retrieving current user Object ID..." 22 | CURRENT_USER_OBJECTID=$(az ad signed-in-user show --query id -o tsv) || handle_error "Failed to retrieve current user Object ID" 23 | 24 | # Check if Azure AD Group exists 25 | echo "Checking if Azure AD Group $AZURE_AD_GROUP_NAME exists..." 26 | GROUP_EXISTS=$(az ad group list --filter "displayName eq '$AZURE_AD_GROUP_NAME'" --query "[].displayName" -o tsv) 27 | 28 | if [ "$GROUP_EXISTS" = "$AZURE_AD_GROUP_NAME" ]; then 29 | echo "Azure AD group $AZURE_AD_GROUP_NAME already exists." 30 | else 31 | # Create Azure AD Group with description 32 | echo "Creating Azure AD group $AZURE_AD_GROUP_NAME..." 33 | az ad group create \ 34 | --display-name $AZURE_AD_GROUP_NAME \ 35 | --mail-nickname $AZURE_AD_GROUP_NAME \ 36 | --description "Administrators for AKS clusters with full kubectl access" || handle_error "Failed to create Azure AD group" 37 | fi 38 | 39 | # Check if Current User is already a member of the Azure AD Group 40 | echo "Checking if current user is a member of $AZURE_AD_GROUP_NAME..." 41 | USER_IN_GROUP=$(az ad group member check --group $AZURE_AD_GROUP_NAME --member-id $CURRENT_USER_OBJECTID --query value -o tsv) 42 | 43 | if [ "$USER_IN_GROUP" = "true" ]; then 44 | echo "Current user is already a member of the Azure AD group $AZURE_AD_GROUP_NAME." 45 | else 46 | # Add Current az login user to Azure AD Group 47 | echo "Adding current user to Azure AD group $AZURE_AD_GROUP_NAME..." 48 | az ad group member add --group $AZURE_AD_GROUP_NAME --member-id $CURRENT_USER_OBJECTID || handle_error "Failed to add current user to Azure AD group" 49 | fi 50 | 51 | echo "Retrieving Azure AD Group ID..." 52 | AZURE_GROUP_ID=$(az ad group show --group $AZURE_AD_GROUP_NAME --query id -o tsv) || handle_error "Failed to retrieve Azure AD Group ID" 53 | 54 | echo "✅ Setup complete!" 55 | echo "===========================================================================" 56 | echo " AZURE AD GROUP ID: $AZURE_GROUP_ID" 57 | echo " You'll need this ID for AKS Terraform configurations" 58 | echo "===========================================================================" -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/1-Create-ACR.md: -------------------------------------------------------------------------------- 1 | # Create an Azure Container Registry Repository 2 | 3 | ## 🎯 Purpose 4 | In this lab, you'll create a repository in Azure Container Registry (ACR) to store the Docker image for the thomasthornton.cloud app. 5 | 6 | ## 🛠️ Create the ACR Terraform Configuration 7 | 8 | ### Prerequisites 9 | - [ ] Terraform installed 10 | - [ ] Azure CLI installed and configured 11 | - [ ] Storage account for Terraform state already created (from 1-Azure section) 12 | - [ ] Basic understanding of Terraform and ACR concepts 13 | 14 | ## Steps 15 | 16 | 1. **Review and Change Terraform .tfvars** 17 | - Open the [terraform.tfvars](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/tree/main/2-Terraform-AZURE-Services-Creation/1-acr/terraform.tfvars) file. 18 | - Ensure all values are accurate for your environment and unique. 19 | 20 | 2. **Understand the Terraform Configuration** 21 | Review the [ACR Terraform configuration](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/tree/main/2-Terraform-AZURE-Services-Creation/1-acr). The configuration will: 22 | - [ ] Use a Terraform backend to store the `.tfstate` in an Azure Storage Account 23 | - [ ] Use the `uksouth` region (can be changed if desired) 24 | - [ ] Create a new Resource Group using `azurerm_resource_group` 25 | - [ ] Create a new ACR using `azurerm_container_registry` with Standard SKU 26 | - [ ] Apply consistent tagging for better resource management and organization 27 | 28 | 3. **Create the ACR** 29 | Navigate to the 1-acr directory and run the following commands in your terminal: 30 | ```bash 31 | cd 1-acr 32 | terraform init 33 | terraform plan 34 | terraform apply 35 | ``` 36 | 37 | ## 🔍 Verification 38 | To ensure the ACR was created successfully: 39 | 1. Log into the [Azure Portal](https://portal.azure.com) 40 | 2. Navigate to ACR in the [Azure Portal](https://portal.azure.com/#browse/Microsoft.ContainerRegistry%2Fregistries) 41 | 3. Look for your newly created ACR 42 | 4. Verify its properties match your Terraform configuration 43 | 44 | Screenshot of the ACR in the Azure Portal: 45 | 46 | ![](images/1-acr.png) 47 | 48 | ## 🧠 Knowledge Check 49 | After creating the ACR, consider these questions: 50 | 51 | 1. Why is it beneficial to use Terraform for creating cloud resources like ACR? 52 | 2. How does storing the Terraform state in Azure Storage Account help in team environments? 53 | 3. What are the advantages of using ACR over other container registry options? 54 | 55 | ## 💡 Pro Tip 56 | Consider implementing these additional security and operational best practices for your ACR: 57 | 58 | 1. **Enhanced Security**: 59 | - Enable content trust for image signing: `admin_enabled = false` (already set) 60 | - Configure private link endpoints to restrict network access 61 | - Use Managed Identity for authentication instead of admin credentials 62 | 63 | 2. **Cost Optimisation**: 64 | - Monitor image usage and implement retention policies 65 | - Use Premium SKU only if you need geo-replication or other advanced features 66 | -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/1-acr/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Requirements 3 | 4 | | Name | Version | 5 | |------|---------| 6 | | [terraform](#requirement\_terraform) | >= 1.11 | 7 | | [azurerm](#requirement\_azurerm) | >= 4.27.0 | 8 | 9 | ## Providers 10 | 11 | | Name | Version | 12 | |------|---------| 13 | | [azurerm](#provider\_azurerm) | >= 4.27.0 | 14 | 15 | ## Modules 16 | 17 | No modules. 18 | 19 | ## Resources 20 | 21 | | Name | Type | 22 | |------|------| 23 | | [azurerm_container_registry.acr](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/container_registry) | resource | 24 | | [azurerm_resource_group.acr_resource_group](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource | 25 | 26 | ## Inputs 27 | 28 | | Name | Description | Type | Default | Required | 29 | |------|-------------|------|---------|:--------:| 30 | | [location](#input\_location) | Azure Location of resources | `string` | `"uksouth"` | no | 31 | | [name](#input\_name) | Name for resources | `string` | `"devopsthehardway"` | no | 32 | | [tags](#input\_tags) | n/a | `map(string)` | n/a | yes | 33 | 34 | ## Outputs 35 | 36 | No outputs. 37 | -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/1-acr/acr.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_resource_group" "acr_resource_group" { 2 | name = "${var.name}-rg" 3 | location = var.location 4 | 5 | tags = var.tags 6 | 7 | } 8 | 9 | resource "azurerm_container_registry" "acr" { 10 | name = "${var.name}azurecr" 11 | resource_group_name = azurerm_resource_group.acr_resource_group.name 12 | location = azurerm_resource_group.acr_resource_group.location 13 | sku = "Standard" 14 | admin_enabled = false 15 | 16 | tags = var.tags 17 | 18 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/1-acr/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.11" 3 | backend "azurerm" { 4 | resource_group_name = "devopshardway-rg" 5 | storage_account_name = "devopshardwaysa" 6 | container_name = "tfstate" 7 | key = "acr-terraform.tfstate" 8 | } 9 | 10 | required_providers { 11 | azurerm = { 12 | source = "hashicorp/azurerm" 13 | version = ">= 4.27.0" 14 | } 15 | } 16 | } 17 | 18 | provider "azurerm" { 19 | features {} 20 | subscription_id = "04109105-f3ca-44ac-a3a7-66b4936112c3" 21 | 22 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/1-acr/terraform.tfvars: -------------------------------------------------------------------------------- 1 | name = "devopsthehardway" 2 | location = "uksouth" 3 | 4 | tags = { 5 | "Purpose" = "azure-devops-hardway" 6 | "Environment" = "DevOps" 7 | "DeployedBy" = "Terraform" 8 | "Project" = "devopsthehardway" 9 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/1-acr/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | type = string 3 | default = "devopsthehardway" 4 | description = "Name for resources" 5 | } 6 | 7 | variable "location" { 8 | type = string 9 | default = "uksouth" 10 | description = "Azure Location of resources" 11 | } 12 | 13 | variable "tags" { 14 | type = map(string) 15 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/2-Create-VNET.md: -------------------------------------------------------------------------------- 1 | # Create an Azure VNET 2 | 3 | ## 🎯 Purpose 4 | In this lab, you'll set up the networking infrastructure for your AKS deployment, including a Virtual Network (VNET), Network Security Group (NSG), and Azure Application Gateway for Containers. 5 | 6 | ## 🛠️ Create the Azure VNET Terraform Configuration 7 | 8 | ### Prerequisites 9 | - [ ] Basic understanding of Azure networking concepts 10 | 11 | ### Steps 12 | 13 | 1. **Review and Change Terraform .tfvars** 14 | - Open the [terraform.tfvars](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/tree/main/2-Terraform-AZURE-Services-Creation/2-vnet/terraform.tfvars) file. 15 | - Ensure all values are accurate for your environment. 16 | 17 | 2. **Understand the Terraform Configuration** 18 | Review the [VNET Terraform configuration](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/tree/main/2-Terraform-AZURE-Services-Creation/2-vnet). The configuration includes: 19 | 20 | **vnet.tf:** 21 | - [ ] Uses a Terraform backend to store the `.tfstate` in Azure Storage 22 | - [ ] Creates a VNET using `azurerm_virtual_network` 23 | - [ ] Creates subnets using `azurerm_subnet` 24 | - [ ] Uses the `uksouth` region (can change if desired) 25 | 26 | **nsg.tf:** 27 | - [ ] Creates a NSG using `azurerm_network_security_group` 28 | - [ ] Associates NSG to subnets using `azurerm_subnet_network_security_group_association` 29 | 30 | **alb.tf:** 31 | - [ ] Creates an Azure Application Gateway for Containers using `azurerm_application_load_balancer` 32 | - [ ] Associates the Gateway with VNET using `azurerm_application_load_balancer_subnet_association` 33 | - [ ] Creates a frontend for the Gateway using `azurerm_application_load_balancer_frontend` 34 | 35 | 3. **Create the Resources** 36 | Run the following commands in your terminal: 37 | ```bash 38 | terraform init 39 | terraform plan 40 | terraform apply 41 | 42 | ## 🔍 Verification 43 | 44 | To ensure the resources were created successfully: 45 | 1. Log into the [Azure Portal](https://portal.azure.com) 46 | 2. Navigate to the Resource Group 47 | 3. Verify the presence of the VNET, NSG, and Application Gateway for Containers: 48 | 49 | Example screenshot of created resources: 50 | 51 | ![](images/2-vnet.png) 52 | 53 | 54 | ## 🧠 Knowledge Check 55 | 56 | After creating the resources, consider these questions: 57 | 1. Why is it important to plan your VNET and subnet structure before deployment? 58 | 2. How does the NSG enhance the security of your AKS deployment? 59 | 3. What benefits does the Azure Application Gateway for Containers provide? 60 | 61 | ## 💡 Pro Tip 62 | 63 | Consider using [Azure Network Watcher](https://learn.microsoft.com/en-us/azure/network-watcher/network-watcher-overview) to visualise and diagnose your network topology and connectivity issues. 64 | -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/2-vnet/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Requirements 3 | 4 | | Name | Version | 5 | |------|---------| 6 | | [terraform](#requirement\_terraform) | >= 1.11 | 7 | | [azurerm](#requirement\_azurerm) | >= 4.27.0 | 8 | 9 | ## Providers 10 | 11 | | Name | Version | 12 | |------|---------| 13 | | [azurerm](#provider\_azurerm) | >= 4.27.0 | 14 | 15 | ## Modules 16 | 17 | No modules. 18 | 19 | ## Resources 20 | 21 | | Name | Type | 22 | |------|------| 23 | | [azurerm_application_load_balancer.alb](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/application_load_balancer) | resource | 24 | | [azurerm_application_load_balancer_frontend.example](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/application_load_balancer_frontend) | resource | 25 | | [azurerm_application_load_balancer_subnet_association.alb](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/application_load_balancer_subnet_association) | resource | 26 | | [azurerm_network_security_group.nsg](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_group) | resource | 27 | | [azurerm_subnet.aks_subnet](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet) | resource | 28 | | [azurerm_subnet.app_gwsubnet](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet) | resource | 29 | | [azurerm_subnet_network_security_group_association.aks_subnet](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet_network_security_group_association) | resource | 30 | | [azurerm_subnet_network_security_group_association.app_gwsubnet](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet_network_security_group_association) | resource | 31 | | [azurerm_virtual_network.virtual_network](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network) | resource | 32 | | [azurerm_resource_group.resource_group](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | 33 | 34 | ## Inputs 35 | 36 | | Name | Description | Type | Default | Required | 37 | |------|-------------|------|---------|:--------:| 38 | | [aks\_subnet\_address\_name](#input\_aks\_subnet\_address\_name) | AKS Subnet Address Name | `string` | n/a | yes | 39 | | [aks\_subnet\_address\_prefix](#input\_aks\_subnet\_address\_prefix) | AKS Subnet Address Space | `string` | n/a | yes | 40 | | [location](#input\_location) | Azure Location of resources | `string` | `"uksouth"` | no | 41 | | [name](#input\_name) | Name for resources | `string` | `"devopsthehardway"` | no | 42 | | [network\_address\_space](#input\_network\_address\_space) | Azure VNET Address Space | `string` | n/a | yes | 43 | | [subnet\_address\_name](#input\_subnet\_address\_name) | Subnet Address Name | `string` | n/a | yes | 44 | | [subnet\_address\_prefix](#input\_subnet\_address\_prefix) | Subnet Address Space | `string` | n/a | yes | 45 | | [tags](#input\_tags) | n/a | `map(string)` | n/a | yes | 46 | 47 | ## Outputs 48 | 49 | No outputs. 50 | -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/2-vnet/alb.tf: -------------------------------------------------------------------------------- 1 | # Azure Application Load Balancer for Containers 2 | resource "azurerm_application_load_balancer" "alb" { 3 | name = "devopsthehardway-alb" 4 | location = var.location 5 | resource_group_name = data.azurerm_resource_group.resource_group.name 6 | 7 | tags = var.tags 8 | } 9 | 10 | resource "azurerm_application_load_balancer_subnet_association" "alb" { 11 | name = "alb-subnet-association" 12 | application_load_balancer_id = azurerm_application_load_balancer.alb.id 13 | subnet_id = azurerm_subnet.app_gwsubnet.id 14 | } 15 | 16 | resource "azurerm_application_load_balancer_frontend" "example" { 17 | name = "alb-frontend" 18 | application_load_balancer_id = azurerm_application_load_balancer.alb.id 19 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/2-vnet/data.tf: -------------------------------------------------------------------------------- 1 | data "azurerm_resource_group" "resource_group" { 2 | name = "${var.name}-rg" 3 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/2-vnet/nsg.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_network_security_group" "nsg" { 2 | name = "devopsthehardway-nsg" 3 | location = var.location 4 | resource_group_name = data.azurerm_resource_group.resource_group.name 5 | tags = var.tags 6 | } 7 | 8 | resource "azurerm_subnet_network_security_group_association" "aks_subnet" { 9 | subnet_id = azurerm_subnet.aks_subnet.id 10 | network_security_group_id = azurerm_network_security_group.nsg.id 11 | } 12 | 13 | resource "azurerm_subnet_network_security_group_association" "app_gwsubnet" { 14 | subnet_id = azurerm_subnet.app_gwsubnet.id 15 | network_security_group_id = azurerm_network_security_group.nsg.id 16 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/2-vnet/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.11" 3 | backend "azurerm" { 4 | resource_group_name = "devopshardway-rg" 5 | storage_account_name = "devopshardwaysa" 6 | container_name = "tfstate" 7 | key = "vnet-terraform.tfstate" 8 | } 9 | 10 | required_providers { 11 | azurerm = { 12 | source = "hashicorp/azurerm" 13 | version = ">= 4.27.0" 14 | } 15 | } 16 | } 17 | 18 | provider "azurerm" { 19 | features {} 20 | subscription_id = "04109105-f3ca-44ac-a3a7-66b4936112c3" 21 | 22 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/2-vnet/terraform.tfvars: -------------------------------------------------------------------------------- 1 | name = "devopsthehardway" 2 | location = "uksouth" 3 | network_address_space = "192.168.0.0/16" 4 | aks_subnet_address_name = "aks" 5 | aks_subnet_address_prefix = "192.168.0.0/24" 6 | subnet_address_name = "appgw" 7 | subnet_address_prefix = "192.168.1.0/24" 8 | 9 | tags = { 10 | "Purpose" = "azure-devops-hardway" 11 | "Environment" = "DevOps" 12 | "DeployedBy" = "Terraform" 13 | "Project" = "devopsthehardway" 14 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/2-vnet/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | type = string 3 | default = "devopsthehardway" 4 | description = "Name for resources" 5 | } 6 | 7 | variable "location" { 8 | type = string 9 | default = "uksouth" 10 | description = "Azure Location of resources" 11 | } 12 | 13 | variable "network_address_space" { 14 | type = string 15 | description = "Azure VNET Address Space" 16 | } 17 | 18 | variable "aks_subnet_address_name" { 19 | type = string 20 | description = "AKS Subnet Address Name" 21 | } 22 | 23 | variable "aks_subnet_address_prefix" { 24 | type = string 25 | description = "AKS Subnet Address Space" 26 | } 27 | 28 | variable "subnet_address_name" { 29 | type = string 30 | description = "Subnet Address Name" 31 | } 32 | 33 | variable "subnet_address_prefix" { 34 | type = string 35 | description = "Subnet Address Space" 36 | } 37 | 38 | variable "tags" { 39 | type = map(string) 40 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/2-vnet/vnet.tf: -------------------------------------------------------------------------------- 1 | 2 | resource "azurerm_virtual_network" "virtual_network" { 3 | name = "${var.name}-vnet" 4 | location = var.location 5 | resource_group_name = data.azurerm_resource_group.resource_group.name 6 | address_space = [var.network_address_space] 7 | 8 | tags = var.tags 9 | 10 | } 11 | 12 | resource "azurerm_subnet" "aks_subnet" { 13 | name = var.aks_subnet_address_name 14 | resource_group_name = data.azurerm_resource_group.resource_group.name 15 | virtual_network_name = azurerm_virtual_network.virtual_network.name 16 | address_prefixes = [var.aks_subnet_address_prefix] 17 | } 18 | 19 | resource "azurerm_subnet" "app_gwsubnet" { 20 | name = var.subnet_address_name 21 | resource_group_name = data.azurerm_resource_group.resource_group.name 22 | virtual_network_name = azurerm_virtual_network.virtual_network.name 23 | address_prefixes = [var.subnet_address_prefix] 24 | 25 | # Required delegation for Application Gateway (Service Networking) 26 | delegation { 27 | name = "delegation" 28 | 29 | service_delegation { 30 | name = "Microsoft.ServiceNetworking/trafficControllers" 31 | actions = ["Microsoft.Network/virtualNetworks/subnets/join/action"] 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/3-Create-Log-Analytics.md: -------------------------------------------------------------------------------- 1 | # Create an Azure Log Analytics Workspace 2 | 3 | ## 🎯 Purpose 4 | In this lab, you'll create a Log Analytics workspace to view container insights of your AKS Cluster. 5 | 6 | ## 🛠️ Create the Log Analytics Workspace Terraform Configuration 7 | 8 | ### Prerequisites 9 | - [ ] Basic understanding of Azure Log Analytics 10 | 11 | ### Steps 12 | 13 | 1. **Review and Change Terraform .tfvars** 14 | - Open the [terraform.tfvars](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/tree/main/2-Terraform-AZURE-Services-Creation/3-log-analytics/terraform.tfvars) file. 15 | - Ensure all values are accurate for your environment. 16 | 17 | 2. **Understand the Terraform Configuration** 18 | Review the [Log Analytics Terraform configuration](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/tree/main/2-Terraform-AZURE-Services-Creation/3-log-analytics). The `la.tf` file will: 19 | - [ ] Use a Terraform backend to store the `.tfstate` in an Azure Storage Account 20 | - [ ] Create a Log Analytics workspace using `azurerm_log_analytics_workspace` 21 | - [ ] Enable the ContainerInsights solution using `azurerm_log_analytics_solution` 22 | - [ ] Use the `uksouth` region (can change if desired) 23 | 24 | 3. **Create the Log Analytics Workspace** 25 | Run the following commands in your terminal: 26 | ```bash 27 | terraform init 28 | terraform plan 29 | terraform apply 30 | ``` 31 | 32 | ## 🔍 Verification 33 | To ensure the resources were created successfully: 34 | 1. Log into the [Azure Portal](https://portal.azure.com) 35 | 2. Navigate to the Resource Group 36 | 3. Verify the presence of the Log Analytics workspace and ContainerInsights solution: 37 | 38 | Example screenshot of created resources: 39 | 40 | ![](images/3-la.png) 41 | 42 | ## 🧠 Knowledge Check 43 | 44 | After creating the Log Analytics workspace, consider these questions: 45 | 1. Why is Log Analytics important for managing AKS clusters? 46 | 2. How does the ContainerInsights solution enhance your ability to monitor AKS? 47 | 3. What types of insights can you gain from Log Analytics in an AKS context? 48 | 49 | ## 💡 Pro Tip 50 | 51 | Consider setting up [custom dashboards](https://azure.microsoft.com/en-gb/free/search/?ef_id=_k_Cj0KCQjwr9m3BhDHARIsANut04aW1Bkx-AcJ5QGbPg_zxVIQw_txn1OWbyl-KpP1uzi0WxsLeZHjZDEaAmGcEALw_wcB_k_&OCID=AIDcmm3bvqzxp1_SEM__k_Cj0KCQjwr9m3BhDHARIsANut04aW1Bkx-AcJ5QGbPg_zxVIQw_txn1OWbyl-KpP1uzi0WxsLeZHjZDEaAmGcEALw_wcB_k_&gad_source=1&gclid=Cj0KCQjwr9m3BhDHARIsANut04aW1Bkx-AcJ5QGbPg_zxVIQw_txn1OWbyl-KpP1uzi0WxsLeZHjZDEaAmGcEALw_wcB) in Azure Portal using the data collected by Log Analytics to get quick insights into your AKS cluster's performance and health. 52 | -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/3-log-analytics/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Requirements 3 | 4 | | Name | Version | 5 | |------|---------| 6 | | [terraform](#requirement\_terraform) | >= 1.11 | 7 | | [azurerm](#requirement\_azurerm) | >= 4.27.0 | 8 | 9 | ## Providers 10 | 11 | | Name | Version | 12 | |------|---------| 13 | | [azurerm](#provider\_azurerm) | >= 4.27.0 | 14 | 15 | ## Modules 16 | 17 | No modules. 18 | 19 | ## Resources 20 | 21 | | Name | Type | 22 | |------|------| 23 | | [azurerm_log_analytics_solution.Log_Analytics_Solution_ContainerInsights](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/log_analytics_solution) | resource | 24 | | [azurerm_log_analytics_workspace.Log_Analytics_WorkSpace](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/log_analytics_workspace) | resource | 25 | | [azurerm_resource_group.resource_group](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | 26 | 27 | ## Inputs 28 | 29 | | Name | Description | Type | Default | Required | 30 | |------|-------------|------|---------|:--------:| 31 | | [location](#input\_location) | Azure Location of resources | `string` | `"uksouth"` | no | 32 | | [name](#input\_name) | Name for resources | `string` | `"devopsthehardway"` | no | 33 | | [tags](#input\_tags) | n/a | `map(string)` | n/a | yes | 34 | 35 | ## Outputs 36 | 37 | No outputs. 38 | -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/3-log-analytics/data.tf: -------------------------------------------------------------------------------- 1 | data "azurerm_resource_group" "resource_group" { 2 | name = "${var.name}-rg" 3 | } 4 | -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/3-log-analytics/la.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_log_analytics_workspace" "Log_Analytics_WorkSpace" { 2 | # The WorkSpace name has to be unique across the whole of azure, not just the current subscription/tenant. 3 | name = "${var.name}-la" 4 | location = var.location 5 | resource_group_name = data.azurerm_resource_group.resource_group.name 6 | sku = "PerGB2018" 7 | 8 | tags = var.tags 9 | 10 | } 11 | 12 | resource "azurerm_log_analytics_solution" "Log_Analytics_Solution_ContainerInsights" { 13 | solution_name = "ContainerInsights" 14 | location = azurerm_log_analytics_workspace.Log_Analytics_WorkSpace.location 15 | resource_group_name = data.azurerm_resource_group.resource_group.name 16 | workspace_resource_id = azurerm_log_analytics_workspace.Log_Analytics_WorkSpace.id 17 | workspace_name = azurerm_log_analytics_workspace.Log_Analytics_WorkSpace.name 18 | 19 | plan { 20 | publisher = "Microsoft" 21 | product = "OMSGallery/ContainerInsights" 22 | } 23 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/3-log-analytics/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.11" 3 | backend "azurerm" { 4 | resource_group_name = "devopshardway-rg" 5 | storage_account_name = "devopshardwaysa" 6 | container_name = "tfstate" 7 | key = "la-terraform.tfstate" 8 | } 9 | 10 | required_providers { 11 | azurerm = { 12 | source = "hashicorp/azurerm" 13 | version = ">= 4.27.0" 14 | } 15 | } 16 | } 17 | 18 | provider "azurerm" { 19 | features {} 20 | subscription_id = "04109105-f3ca-44ac-a3a7-66b4936112c3" 21 | 22 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/3-log-analytics/terraform.tfvars: -------------------------------------------------------------------------------- 1 | name = "devopsthehardway" 2 | location = "uksouth" 3 | 4 | tags = { 5 | "Purpose" = "azure-devops-hardway" 6 | "Environment" = "DevOps" 7 | "DeployedBy" = "Terraform" 8 | "Project" = "devopsthehardway" 9 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/3-log-analytics/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | type = string 3 | default = "devopsthehardway" 4 | description = "Name for resources" 5 | } 6 | 7 | variable "location" { 8 | type = string 9 | default = "uksouth" 10 | description = "Azure Location of resources" 11 | } 12 | 13 | variable "tags" { 14 | type = map(string) 15 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/4-Create-AKS-Cluster-IAM-Roles.md: -------------------------------------------------------------------------------- 1 | # Create An AKS Cluster and IAM Roles 2 | 3 | ## 🎯 Purpose 4 | In this lab, you'll create an Azure Kubernetes Service (AKS) cluster and set up the necessary Identity and Access Management (IAM) roles. 5 | 6 | ## 🛠️ Create the AKS Terraform Configuration 7 | 8 | ### Prerequisites 9 | - [ ] Basic understanding of AKS and Azure IAM concepts 10 | - [ ] Completed previous labs (VNET, Log Analytics) 11 | 12 | ### Steps 13 | 14 | 1. **Review and Change Terraform .tfvars** 15 | - Open the [terraform.tfvars](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/tree/main/2-Terraform-AZURE-Services-Creation/4-aks/terraform.tfvars) file. 16 | - Ensure all values are accurate for your environment. 17 | 18 | 2. **Understand the Terraform Configuration** 19 | Review the [AKS Terraform configuration](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/tree/main/2-Terraform-AZURE-Services-Creation/4-aks). The configuration includes: 20 | 21 | **aks.tf:** 22 | - [ ] Creates AKS Cluster using `azurerm_kubernetes_cluster` 23 | - [ ] Sets up role assignments using `azurerm_role_assignment` 24 | - [ ] Uses the `uksouth` region (can change if desired) 25 | 26 | **managed_identity.tf:** 27 | - [ ] Creates user assigned identity using `azurerm_user_assigned_identity` 28 | - [ ] Sets up federated identity credential using `azurerm_federated_identity_credential` 29 | 30 | **rbac.tf:** 31 | - [ ] Creates role assignments using `azurerm_role_assignment` 32 | - [ ] Defines role definitions using `azurerm_role_definition` 33 | 34 | 3. **Update Azure AD Group ID** 35 | - In `terraform.tfvars`, replace line 8 with the Azure AD Group ID you noted down [earlier](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/blob/main/1-Azure/2-Create-Azure-AD-Group-AKS-Admins.md). 36 | 37 | 4. **Create the AKS Cluster and IAM Roles** 38 | Run the following commands in your terminal: 39 | ```bash 40 | terraform init 41 | terraform plan 42 | terraform apply 43 | ``` 44 | 45 | ## 🔍 Verification 46 | 47 | To ensure the resources were created successfully: 48 | 1. Log into the [Azure Portal](https://portal.azure.com) 49 | 2. Navigate to the Resource Group 50 | 3. Verify the presence of the AKS cluster 51 | 4. Verify the cluster properties and node pool configuration 52 | 5. Check the IAM settings to confirm the role assignments 53 | 54 | Example screenshot of created resources: 55 | 56 | ![](images/4-aks.png) 57 | 58 | ## 🧠 Knowledge Check 59 | 60 | After creating the AKS cluster and IAM roles, consider these questions: 61 | 1. Why is it important to use managed identities with AKS? 62 | 2. How does RBAC enhance the security of your AKS cluster? 63 | 3. What are the benefits of using federated identity credentials? 64 | 65 | ## 💡 Pro Tip 66 | 67 | Consider enabling Azure Policy for Kubernetes to enforce organisational standards and assess compliance at scale for your AKS clusters. -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/4-aks/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Requirements 3 | 4 | | Name | Version | 5 | |------|---------| 6 | | [terraform](#requirement\_terraform) | >= 1.11 | 7 | | [azurerm](#requirement\_azurerm) | >= 4.27.0 | 8 | 9 | ## Providers 10 | 11 | | Name | Version | 12 | |------|---------| 13 | | [azurerm](#provider\_azurerm) | >= 4.27.0 | 14 | 15 | ## Modules 16 | 17 | No modules. 18 | 19 | ## Resources 20 | 21 | | Name | Type | 22 | |------|------| 23 | | [azurerm_federated_identity_credential.alb_federated_identity](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/federated_identity_credential) | resource | 24 | | [azurerm_kubernetes_cluster.k8s](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/kubernetes_cluster) | resource | 25 | | [azurerm_role_assignment.acr_pull](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource | 26 | | [azurerm_role_assignment.appgwcontainerfix2](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource | 27 | | [azurerm_role_assignment.appgwcontainerfix3](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource | 28 | | [azurerm_role_assignment.node_infrastructure_update_scale_set](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource | 29 | | [azurerm_user_assigned_identity.alb_identity](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/user_assigned_identity) | resource | 30 | | [azurerm_container_registry.acr](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/container_registry) | data source | 31 | | [azurerm_log_analytics_workspace.workspace](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/log_analytics_workspace) | data source | 32 | | [azurerm_resource_group.node_resource_group](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | 33 | | [azurerm_resource_group.resource_group](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | 34 | | [azurerm_subnet.akssubnet](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/subnet) | data source | 35 | | [azurerm_subnet.appgwsubnet](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/subnet) | data source | 36 | 37 | ## Inputs 38 | 39 | | Name | Description | Type | Default | Required | 40 | |------|-------------|------|---------|:--------:| 41 | | [agent\_count](#input\_agent\_count) | n/a | `any` | n/a | yes | 42 | | [aks\_admins\_group\_object\_id](#input\_aks\_admins\_group\_object\_id) | n/a | `any` | n/a | yes | 43 | | [kubernetes\_cluster\_rbac\_enabled](#input\_kubernetes\_cluster\_rbac\_enabled) | n/a | `string` | `"true"` | no | 44 | | [kubernetes\_version](#input\_kubernetes\_version) | n/a | `any` | n/a | yes | 45 | | [location](#input\_location) | Azure Location of resources | `string` | `"uksouth"` | no | 46 | | [name](#input\_name) | Name for resources | `string` | `"devopsthehardway"` | no | 47 | | [ssh\_public\_key](#input\_ssh\_public\_key) | n/a | `any` | n/a | yes | 48 | | [tags](#input\_tags) | n/a | `map(string)` | n/a | yes | 49 | | [vm\_size](#input\_vm\_size) | n/a | `any` | n/a | yes | 50 | 51 | ## Outputs 52 | 53 | No outputs. 54 | -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/4-aks/aks.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_kubernetes_cluster" "k8s" { 2 | name = "${var.name}aks" 3 | location = var.location 4 | resource_group_name = data.azurerm_resource_group.resource_group.name 5 | dns_prefix = "${var.name}dns" 6 | kubernetes_version = var.kubernetes_version 7 | oidc_issuer_enabled = true 8 | workload_identity_enabled = true 9 | node_resource_group = "${var.name}-node-rg" 10 | 11 | linux_profile { 12 | admin_username = "ubuntu" 13 | 14 | ssh_key { 15 | key_data = var.ssh_public_key 16 | } 17 | } 18 | 19 | default_node_pool { 20 | name = "agentpool" 21 | node_count = var.agent_count 22 | vm_size = var.vm_size 23 | vnet_subnet_id = data.azurerm_subnet.akssubnet.id 24 | type = "VirtualMachineScaleSets" 25 | orchestrator_version = var.kubernetes_version 26 | } 27 | 28 | identity { 29 | type = "SystemAssigned" 30 | } 31 | 32 | oms_agent { 33 | log_analytics_workspace_id = data.azurerm_log_analytics_workspace.workspace.id 34 | } 35 | 36 | network_profile { 37 | load_balancer_sku = "standard" 38 | network_plugin = "azure" 39 | } 40 | 41 | azure_active_directory_role_based_access_control { 42 | azure_rbac_enabled = false 43 | admin_group_object_ids = [var.aks_admins_group_object_id] 44 | } 45 | 46 | tags = var.tags 47 | 48 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/4-aks/data.tf: -------------------------------------------------------------------------------- 1 | data "azurerm_resource_group" "resource_group" { 2 | name = "${var.name}-rg" 3 | } 4 | 5 | data "azurerm_subnet" "akssubnet" { 6 | name = "aks" 7 | virtual_network_name = "${var.name}-vnet" 8 | resource_group_name = data.azurerm_resource_group.resource_group.name 9 | } 10 | 11 | data "azurerm_subnet" "appgwsubnet" { 12 | name = "appgw" 13 | virtual_network_name = "${var.name}-vnet" 14 | resource_group_name = data.azurerm_resource_group.resource_group.name 15 | } 16 | 17 | data "azurerm_log_analytics_workspace" "workspace" { 18 | name = "${var.name}-la" 19 | resource_group_name = data.azurerm_resource_group.resource_group.name 20 | } 21 | 22 | data "azurerm_container_registry" "acr" { 23 | name = "${var.name}azurecr" 24 | resource_group_name = data.azurerm_resource_group.resource_group.name 25 | } 26 | 27 | data "azurerm_resource_group" "node_resource_group" { 28 | name = azurerm_kubernetes_cluster.k8s.node_resource_group 29 | depends_on = [ 30 | azurerm_kubernetes_cluster.k8s 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/4-aks/managed_identity.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_user_assigned_identity" "alb_identity" { 2 | location = var.location 3 | resource_group_name = data.azurerm_resource_group.resource_group.name 4 | name = "azure-alb-identity" 5 | } 6 | 7 | resource "azurerm_federated_identity_credential" "alb_federated_identity" { 8 | name = "azure-alb-identity" 9 | resource_group_name = data.azurerm_resource_group.resource_group.name 10 | audience = ["api://AzureADTokenExchange"] 11 | issuer = azurerm_kubernetes_cluster.k8s.oidc_issuer_url 12 | parent_id = azurerm_user_assigned_identity.alb_identity.id 13 | subject = "system:serviceaccount:azure-alb-system:alb-controller-sa" 14 | 15 | depends_on = [ 16 | azurerm_user_assigned_identity.alb_identity, 17 | azurerm_kubernetes_cluster.k8s 18 | 19 | ] 20 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/4-aks/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.11" 3 | backend "azurerm" { 4 | resource_group_name = "devopshardway-rg" 5 | storage_account_name = "devopshardwaysa" 6 | container_name = "tfstate" 7 | key = "aks-terraform.tfstate" 8 | } 9 | 10 | required_providers { 11 | azurerm = { 12 | source = "hashicorp/azurerm" 13 | version = ">= 4.27.0" 14 | } 15 | } 16 | } 17 | 18 | provider "azurerm" { 19 | features {} 20 | subscription_id = "04109105-f3ca-44ac-a3a7-66b4936112c3" 21 | 22 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/4-aks/rbac.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_role_assignment" "node_infrastructure_update_scale_set" { 2 | principal_id = azurerm_kubernetes_cluster.k8s.kubelet_identity[0].object_id 3 | scope = data.azurerm_resource_group.node_resource_group.id 4 | role_definition_name = "Virtual Machine Contributor" 5 | depends_on = [ 6 | azurerm_kubernetes_cluster.k8s 7 | ] 8 | } 9 | 10 | resource "azurerm_role_assignment" "acr_pull" { 11 | principal_id = azurerm_kubernetes_cluster.k8s.kubelet_identity[0].object_id 12 | scope = data.azurerm_container_registry.acr.id 13 | role_definition_name = "acrpull" 14 | depends_on = [ 15 | azurerm_kubernetes_cluster.k8s 16 | ] 17 | } 18 | 19 | #fixing for "The client '62119122-6287-4620-98b4-bf86535e2ece' with object id '62119122-6287-4620-98b4-bf86535e2ece' does not have authorization to perform action 'Microsoft.ServiceNetworking/register/action' over scope '/subscriptions/XXXXX' or the scope is invalid. (As part of App Gw for containers - maanged by ALB controller setup)" 20 | 21 | # Delegate AppGw for Containers Configuration Manager role to RG containing Application Gateway for Containers resource 22 | # az role assignment create --assignee-object-id $principalId --assignee-principal-type ServicePrincipal --scope $resourceGroupId --role "fbc52c3f-28ad-4303-a892-8a056630b8f1" 23 | resource "azurerm_role_assignment" "appgwcontainerfix2" { 24 | principal_id = azurerm_user_assigned_identity.alb_identity.principal_id 25 | scope = data.azurerm_resource_group.resource_group.id 26 | role_definition_name = "AppGw for Containers Configuration Manager" 27 | depends_on = [ 28 | azurerm_kubernetes_cluster.k8s, 29 | azurerm_user_assigned_identity.alb_identity 30 | ] 31 | } 32 | 33 | # Delegate Network Contributor permission for join to association subnet 34 | # az role assignment create --assignee-object-id $principalId --assignee-principal-type ServicePrincipal --scope $ALB_SUBNET_ID --role "4d97b98b-1d4f-4787-a291-c67834d212e7" 35 | resource "azurerm_role_assignment" "appgwcontainerfix3" { 36 | principal_id = azurerm_user_assigned_identity.alb_identity.principal_id 37 | scope = data.azurerm_subnet.appgwsubnet.id 38 | role_definition_name = "Network Contributor" 39 | depends_on = [ 40 | azurerm_kubernetes_cluster.k8s, 41 | azurerm_user_assigned_identity.alb_identity 42 | ] 43 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/4-aks/terraform.tfvars: -------------------------------------------------------------------------------- 1 | name = "devopsthehardway" 2 | location = "uksouth" 3 | 4 | kubernetes_version = "1.32" 5 | agent_count = 3 6 | vm_size = "Standard_DS2_v2" 7 | ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDrt/GYkYpuQYRxM3lgjOr3Wqx8g5nQIbrg6Mr53wZGb35+ft+PibDMqxXZ7xq7fC3YuLnnO022IPgEjkF9fP03ZmfUeLjJJvw8YcutN9DD/2cx93BpKFPNUsqEB+za1iJ16kMsCojy35c1R64O+rw20D6iP96rmDAyIc5FR03y00eyAzQ8vo7/u9+VPwpdGEI7QCokZROcj6iNVz1V/1t6G4AEufPLokdj8J0gla/dN+tvnSLRQVBTDiD4jmVGImpWFqqKaH6R9SSXmRzj0uhvJUmSiZAZCb1caPEYgPEvNITuGQFdykPoY/4Z/3B+x/ipEQbWy8yL7bDFSXZTYhVKlPVyPbUtN5QFt7QtCtg84xDAZ6GA6AnONTtMxX2jvdzB9yh1ZsteNrOZ/Jo3ecuie573syQfG23Tu6qTqak8O7ZTOLY9iPx2ego3KvTWH/Q3lIvjnlpfCQtFtSgkNxjalMBk+NwwEgZHWRREOHwJmQIKVN0gSitN1KXobrqwxNk= tamops@Synth" 8 | aks_admins_group_object_id = "e97b6454-3fa1-499e-8e5c-5d631e9ca4d1" 9 | 10 | tags = { 11 | "Purpose" = "azure-devops-hardway" 12 | "Environment" = "DevOps" 13 | "DeployedBy" = "Terraform" 14 | "Project" = "devopsthehardway" 15 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/4-aks/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | type = string 3 | default = "devopsthehardway" 4 | description = "Name for resources" 5 | } 6 | 7 | variable "location" { 8 | type = string 9 | default = "uksouth" 10 | description = "Azure Location of resources" 11 | } 12 | 13 | variable "kubernetes_cluster_rbac_enabled" { 14 | default = "true" 15 | } 16 | 17 | variable "kubernetes_version" { 18 | } 19 | 20 | variable "agent_count" { 21 | } 22 | 23 | variable "vm_size" { 24 | } 25 | 26 | variable "ssh_public_key" { 27 | } 28 | 29 | variable "aks_admins_group_object_id" { 30 | } 31 | 32 | variable "tags" { 33 | type = map(string) 34 | } -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/5-Run-CICD-For-AKS-Cluster.md: -------------------------------------------------------------------------------- 1 | # Create AKS Cluster With CI/CD 2 | 3 | ## 🎯 Purpose 4 | In this lab, you'll learn how to create an Azure Kubernetes Service (AKS) cluster using GitHub Actions for continuous integration and continuous deployment (CI/CD). 5 | 6 | ## 🛠️ Setup and Configuration 7 | 8 | ### Prerequisites 9 | - [ ] Basic understanding of Terraform and GitHub Actions 10 | 11 | 12 | ### Steps 13 | 14 | 1. **Review and Customise Variables** 15 | - Open the `terraform.tfvars` file in the [AKS Terraform configuration](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/tree/main/2-Terraform-AZURE-Services-Creation/4-aks). 16 | - Ensure all values are accurate for your environment. 17 | 18 | 2. **Set Up GitHub OIDC Authentication with Azure** 19 | 20 | Set up a more secure authentication method using GitHub OIDC (OpenID Connect) with Azure: 21 | 22 | - First, customise the [script](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/tree/main/2-Terraform-AZURE-Services-Creation/scripts/5-create-github-oidc.sh) variables if needed: 23 | ```bash 24 | # Variables you may want to customise: 25 | APP_DISPLAY_NAME="DevOps-The-Hardway-Azure-GitHub-OIDC" # Name of the Azure AD app registration 26 | GITHUB_REPO="thomast1906/DevOps-The-Hard-Way-Azure" # Your GitHub repository name 27 | ``` 28 | Update these variables to match your specific environment if different from the defaults. 29 | 30 | - Run the provided script: 31 | ```bash 32 | ./scripts/5-create-github-oidc.sh 33 | ``` 34 | 35 | The script performs these actions: 36 | - [ ] Creates an Azure AD application registration named "DevOps-The-Hardway-Azure" 37 | - [ ] Creates a corresponding service principal 38 | - [ ] Sets up federated credentials for: 39 | - GitHub main branch workflows (`repo:thomast1906/DevOps-The-Hard-Way-Azure:ref:refs/heads/main`) 40 | - Renovate branch workflows (`repo:thomast1906/DevOps-The-Hard-Way-Azure:ref:refs/heads/renovate/configure`) 41 | - Pull request workflows (`repo:thomast1906/DevOps-The-Hard-Way-Azure:pull_request`) 42 | 43 | If you need to customise the federated credentials for different branches or repositories, edit the `create_federated_credential` function calls in the script. 44 | 45 | **Note**: After running the script, it will output all the necessary information and next steps. You'll need to assign appropriate IAM permissions (e.g., Contributor access to the subscription) to the Service Principal using: 46 | ```bash 47 | # Store the app ID in a variable 48 | APP_ID=$(az ad app list --display-name "DevOps-The-Hardway-Azure-GitHub-OIDC" --query "[].appId" -o tsv) 49 | 50 | # Get the service principal ID 51 | SP_ID=$(az ad sp list --filter "appId eq '$APP_ID'" --query "[].id" -o tsv) 52 | 53 | # Assign Contributor role to the subscription 54 | az role assignment create --assignee $SP_ID --role "Contributor" --scope "/subscriptions/YOUR_SUBSCRIPTION_ID" 55 | ``` 56 | 57 | The script will automatically output the exact commands needed with your specific IDs, so you can simply copy and paste them from the terminal output. 58 | 59 | 3. **Configure GitHub Repository Settings** 60 | Configure your GitHub repository to use the OIDC connection: 61 | 62 | - Add the following secrets to your GitHub repository (Settings > Secrets > Actions): 63 | - `AZURE_CLIENT_ID`: The App ID you created 64 | - `AZURE_TENANT_ID`: Your Azure AD tenant ID 65 | - `AZURE_SUBSCRIPTION_ID`: Your Azure subscription ID 66 | 67 | Note: All three values will be automatically displayed in the output of the `5-create-github-oidc.sh` script, so you can copy them directly from there. 68 | 69 | 4. **Set Up GitHub Actions Workflow** 70 | - Navigate to the Actions tab in your GitHub repository. 71 | - Select the existing `CI` workflow. 72 | - Choose to run the workflow from the main branch. 73 | 74 | ## 🔍 Verification 75 | After running the workflow: 76 | 1. Check the GitHub Actions logs for successful completion. 77 | 2. Log into the [Azure Portal](https://portal.azure.com) 78 | 3. Navigate to Kubernetes services 79 | 4. Verify that your new AKS cluster has been updated or created. 80 | 81 | ### 🧠 Knowledge Check 82 | The GitHub Actions workflow: 83 | - [ ] Triggers manually (`workflow_dispatch`) or on pull requests/pushes to main 84 | - [ ] Checks out the code 85 | - [ ] Authenticates with Azure using OIDC (no secrets stored in GitHub) 86 | - [ ] Sets up Terraform 87 | - [ ] Formats and validates Terraform code 88 | - [ ] Initialises Terraform 89 | - [ ] Plans the Terraform changes 90 | - [ ] Applies the Terraform configuration to create the AKS cluster 91 | 92 | ## 💡 Pro Tip 93 | Consider implementing these additional best practices: 94 | - Use separate state files for different environments (dev, staging, production) to manage multiple AKS clusters efficiently 95 | - Implement branch protection rules to prevent direct pushes to main 96 | - Set up required reviewers for pull requests to the main branch 97 | - Configure federated credentials with more specific patterns if needed: 98 | ```bash 99 | # For specific environments or branches 100 | "subject": "repo:thomast1906/DevOps-The-Hard-Way-Azure:ref:refs/heads/env-*" 101 | ``` 102 | -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/README.md: -------------------------------------------------------------------------------- 1 | # Terraform Azure Services Creation for DevOps The Hard Way 2 | 3 | ## Overview 4 | This directory contains the Terraform configurations needed to create the core Azure infrastructure components for the DevOps The Hard Way - Azure project. Each step builds upon the previous, creating a complete infrastructure for hosting containerised applications in Azure Kubernetes Service (AKS). 5 | 6 | ## Labs in this Section 7 | 8 | ### [1. Create Azure Container Registry (ACR)](./1-Create-ACR.md) 9 | Set up an Azure Container Registry to store Docker images used by your application. 10 | 11 | ### [2. Create Virtual Network](./2-Create-VNET.md) 12 | Create a Virtual Network with the necessary subnets for AKS and Application Gateway. 13 | 14 | ### [3. Create Log Analytics Workspace](./3-Create-Log-Analytics.md) 15 | Establish a Log Analytics workspace to monitor your AKS cluster and applications. 16 | 17 | ### [4. Create AKS Cluster and IAM Roles](./4-Create-AKS-Cluster-IAM-Roles.md) 18 | Deploy an Azure Kubernetes Service cluster with proper Azure AD integration and RBAC. 19 | 20 | ### [5. Set Up CI/CD for AKS Cluster](./5-Run-CICD-For-AKS-Cluster.md) 21 | Configure GitHub Actions for continuous integration and deployment to your AKS cluster. 22 | 23 | ## Terraform Structure 24 | 25 | Each component is organised in its own directory with a consistent structure: 26 | - `providers.tf`: Defines Azure provider configuration 27 | - `variables.tf`: Declares input variables 28 | - `terraform.tfvars`: Sets default values for variables 29 | - Resource-specific `.tf` files: Contain the actual resource definitions 30 | - `data.tf`: Contains data sources used by the configurations 31 | 32 | ## Pre-requisites 33 | 34 | Before starting these labs, ensure you have: 35 | 36 | 1. Completed the steps in the [1-Azure](../1-Azure) section 37 | 2. Terraform installed (version 1.9.6 or higher) 38 | 3. Azure CLI installed and configured (`az login` executed) 39 | 4. Basic familiarity with Terraform and Azure infrastructure concepts 40 | 41 | ## Best Practices Applied 42 | 43 | - Resource naming conventions following Azure recommendations 44 | - Consistent tagging across all resources for better governance 45 | - Secure network design with proper subnet segregation 46 | - RBAC-based access control 47 | - Infrastructure as Code for reproducibility and consistency 48 | - Remote state management using Azure Storage 49 | -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/images/1-acr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomast1906/DevOps-The-Hard-Way-Azure/14c69268f159c540ef8517efae084027bf2d5f17/2-Terraform-AZURE-Services-Creation/images/1-acr.png -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/images/2-vnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomast1906/DevOps-The-Hard-Way-Azure/14c69268f159c540ef8517efae084027bf2d5f17/2-Terraform-AZURE-Services-Creation/images/2-vnet.png -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/images/3-la.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomast1906/DevOps-The-Hard-Way-Azure/14c69268f159c540ef8517efae084027bf2d5f17/2-Terraform-AZURE-Services-Creation/images/3-la.png -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/images/4-aks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomast1906/DevOps-The-Hard-Way-Azure/14c69268f159c540ef8517efae084027bf2d5f17/2-Terraform-AZURE-Services-Creation/images/4-aks.png -------------------------------------------------------------------------------- /2-Terraform-AZURE-Services-Creation/scripts/5-create-github-oidc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Configuration 4 | APP_DISPLAY_NAME="DevOps-The-Hardway-Azure-GitHub-OIDC" 5 | GITHUB_REPO="thomast1906/DevOps-The-Hard-Way-Azure" 6 | 7 | # Error handling function 8 | handle_error() { 9 | echo "ERROR: $1" 10 | exit 1 11 | } 12 | 13 | # Verify Azure CLI is installed and user is logged in 14 | if ! command -v az &> /dev/null; then 15 | handle_error "Azure CLI is not installed. Please install it first: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli" 16 | fi 17 | 18 | # Check if user is logged in 19 | echo "Verifying Azure CLI login status..." 20 | az account show &> /dev/null || handle_error "You are not logged in to Azure CLI. Please run 'az login' first." 21 | 22 | # Check if Azure AD App already exists 23 | echo "Checking if Azure AD application $APP_DISPLAY_NAME already exists..." 24 | APP_EXISTS=$(az ad app list --display-name "$APP_DISPLAY_NAME" --query "[].displayName" -o tsv) 25 | 26 | if [ "$APP_EXISTS" = "$APP_DISPLAY_NAME" ]; then 27 | echo "Azure AD application $APP_DISPLAY_NAME already exists." 28 | APP_ID=$(az ad app list --display-name "$APP_DISPLAY_NAME" --query "[0].appId" -o tsv) 29 | else 30 | # Create Azure AD application registration 31 | echo "Creating Azure AD application $APP_DISPLAY_NAME..." 32 | APP_ID=$(az ad app create --display-name "$APP_DISPLAY_NAME" --query appId -o tsv) || handle_error "Failed to create Azure AD application" 33 | fi 34 | 35 | # Check if service principal exists 36 | echo "Checking if service principal for $APP_DISPLAY_NAME already exists..." 37 | SP_EXISTS=$(az ad sp list --filter "appId eq '$APP_ID'" --query "[].id" -o tsv) 38 | 39 | if [ -n "$SP_EXISTS" ]; then 40 | echo "Service principal for $APP_DISPLAY_NAME already exists." 41 | SP_ID=$SP_EXISTS 42 | else 43 | # Create service principal 44 | echo "Creating service principal for $APP_DISPLAY_NAME..." 45 | SP_ID=$(az ad sp create --id "$APP_ID" --query id -o tsv) || handle_error "Failed to create service principal" 46 | fi 47 | 48 | # Function to create or update federated credential 49 | create_federated_credential() { 50 | local name=$1 51 | local subject=$2 52 | local description=$3 53 | 54 | echo "Checking if federated credential $name already exists..." 55 | CRED_EXISTS=$(az ad app federated-credential list --id "$APP_ID" --query "[?name=='$name'].name" -o tsv) 56 | 57 | if [ "$CRED_EXISTS" = "$name" ]; then 58 | echo "Federated credential $name already exists." 59 | else 60 | echo "Creating federated credential $name..." 61 | az ad app federated-credential create \ 62 | --id "$APP_ID" \ 63 | --parameters "{ 64 | \"name\": \"$name\", 65 | \"issuer\": \"https://token.actions.githubusercontent.com\", 66 | \"subject\": \"$subject\", 67 | \"description\": \"$description\", 68 | \"audiences\": [\"api://AzureADTokenExchange\"] 69 | }" || handle_error "Failed to create federated credential $name" 70 | fi 71 | } 72 | 73 | # Create federated credentials for different GitHub workflows 74 | create_federated_credential "github-oidc-branch" "repo:$GITHUB_REPO:ref:refs/heads/main" "GitHub Actions OIDC - Branch Workflows (main)" 75 | create_federated_credential "github-oidc-branch-renovate" "repo:$GITHUB_REPO:ref:refs/heads/renovate/configure" "GitHub Actions OIDC - Branch Renovate Workflows (renovate)" 76 | create_federated_credential "github-oidc-pull-request" "repo:$GITHUB_REPO:pull_request" "GitHub Actions OIDC - Pull Request Workflows" 77 | 78 | # Get subscription ID 79 | SUBSCRIPTION_ID=$(az account show --query id -o tsv) 80 | 81 | echo "✅ Setup complete!" 82 | echo "===========================================================================" 83 | echo " APPLICATION (CLIENT) ID: $APP_ID" 84 | echo " SERVICE PRINCIPAL ID: $SP_ID" 85 | echo " TENANT ID: $(az account show --query tenantId -o tsv)" 86 | echo " SUBSCRIPTION ID: $SUBSCRIPTION_ID" 87 | echo "===========================================================================" 88 | echo "Next step: Assign appropriate roles to the service principal:" 89 | echo " az role assignment create --assignee $SP_ID --role \"Contributor\" \\" 90 | echo " --scope \"/subscriptions/$SUBSCRIPTION_ID\"" 91 | echo "===========================================================================" 92 | echo "For GitHub Actions, add these secrets to your repository:" 93 | echo " AZURE_CLIENT_ID: $APP_ID" 94 | echo " AZURE_TENANT_ID: $(az account show --query tenantId -o tsv)" 95 | echo " AZURE_SUBSCRIPTION_ID: $SUBSCRIPTION_ID" 96 | echo "===========================================================================" -------------------------------------------------------------------------------- /3-Docker/1-Create-Docker-Image.md: -------------------------------------------------------------------------------- 1 | # Creating the Docker Image for the Thomasthornton.cloud App 2 | 3 | ## 🎯 Purpose 4 | In this lab, you'll create a Docker image to containerise the Thomasthornton.cloud app and run it locally. 5 | 6 | ## 🛠️ Create The Docker Image 7 | 8 | ### Prerequisites 9 | - [ ] Docker installed and running 10 | - [ ] Basic understanding of Docker concepts 11 | 12 | ### Steps 13 | 14 | 1. **Navigate to the Docker Directory** 15 | 16 | ```bash 17 | cd 3-Docker 18 | ``` 19 | 20 | 2. **Review the Dockerfile** 21 | 22 | Open the Dockerfile and note its key components: 23 | - [ ] Uses the latest Python image as base 24 | - [ ] Creates a `/build` directory for the app 25 | - [ ] Copies the `app` directory and `requirements.txt` into `/build` 26 | - [ ] Configures the container to run the app on startup 27 | 28 | 3. **Build the Docker Image** 29 | 30 | ```bash 31 | docker build -t thomasthorntoncloud:latest . 32 | ``` 33 | 34 | If you're on Apple Silicon or need to specify a platform: 35 | 36 | ```bash 37 | docker build --platform=linux/amd64 -t thomasthorntoncloud:latest . 38 | ``` 39 | 40 | > 🔍 **Note**: The `--platform` option specifies the target platform as linux/amd64, ensuring compatibility with most cloud environments. This is particularly important when building on ARM-based systems like Apple Silicon. 41 | 42 | 4. **Verify the Docker Image** 43 | 44 | ```bash 45 | docker image ls thomasthorntoncloud 46 | ``` 47 | 48 | You should see your newly created image with its size. The multi-stage build approach helps keep the final image size smaller. 49 | 50 | ## 🏃‍♂️ Run The Docker Image Locally 51 | 52 | 1. **Run the Docker Container** 53 | 54 | ```bash 55 | docker run -tid -p 5000:5000 --name thomasthorntoncloud-app thomasthorntoncloud:latest 56 | ``` 57 | 58 | The flags used: 59 | - `-t` enables a TTY console 60 | - `-i` enables an interactive session 61 | - `-d` detaches the terminal from the Docker container 62 | - `-p 5000:5000` maps the container's port 5000 to your local port 5000 63 | 64 | 2. **Confirm the Container is Running** 65 | 66 | ```bash 67 | docker container ls 68 | ``` 69 | 70 | You should see the container running successfully. 71 | 72 | 3. **Access the Application** 73 | 74 | Open a web browser and navigate to: 75 | ``` 76 | http://localhost:5000 77 | ``` 78 | 79 | You should see the thomasthornton.cloud application running. 80 | 81 | 4. **View Container Logs** 82 | 83 | ```bash 84 | docker logs thomasthorntoncloud-app 85 | ``` 86 | 87 | 5. **Stop the Container When Done** 88 | 89 | ```bash 90 | docker stop thomasthorntoncloud-app 91 | ``` 92 | 93 | ## 🧠 Knowledge Check 94 | 95 | After creating and running the Docker image, consider these questions: 96 | 1. Why do we use a multi-stage build in our Dockerfile? 97 | 2. What security benefits do we get from running the application as a non-root user? 98 | 3. How does the HEALTHCHECK directive help with container orchestration? 99 | 4. Why might you need to specify the `--platform` option when building Docker images? 100 | 101 | ## 🔍 Verification 102 | 103 | To ensure the Docker image was created and is running successfully: 104 | 1. Check that the image appears in the output of `docker image ls` 105 | 2. Verify that the container is listed and in the "Up" state when you run `docker container ls` 106 | 3. Confirm you can access the application at http://localhost:5000 107 | 4. Check that the health check is passing with `docker inspect --format='{{.State.Health.Status}}' thomasthorntoncloud-app` 108 | 109 | ## 💡 Pro Tips 110 | 111 | 1. **Layer Optimisation**: Keep the most frequently changing content (like application code) in the later layers of your Dockerfile to take advantage of Docker's caching mechanism. 112 | 113 | 2. **Security Scanning**: Consider scanning your Docker images for vulnerabilities before deployment: 114 | ```bash 115 | docker scan thomasthorntoncloud:latest 116 | ``` 117 | 118 | 3. **Resource Limits**: In production, set resource limits for your containers: 119 | ```bash 120 | docker run -tid -p 5000:5000 --memory=512m --cpus=0.5 thomasthorntoncloud:latest 121 | ``` 122 | 123 | 4. **Using Docker Compose**: For more complex applications with multiple services, consider using Docker Compose: 124 | ```bash 125 | docker-compose up -d 126 | ``` -------------------------------------------------------------------------------- /3-Docker/2-Push Image To ACR.md: -------------------------------------------------------------------------------- 1 | # Push Image To Azure Container Registry (ACR) 2 | 3 | ## 🎯 Purpose 4 | In this lab, you'll push the Docker image you created locally to Azure Container Registry (ACR). 5 | 6 | ## 🛠️ Push Docker Image to ACR 7 | 8 | ### Prerequisites 9 | - [ ] Docker image created locally (from previous step) 10 | - [ ] Access to an Azure Container Registry 11 | - [ ] Azure CLI installed and configured 12 | 13 | ### Steps 14 | 15 | 1. **Verify Your ACR Access** 16 | 17 | First, verify that your ACR exists and you have access to it: 18 | 19 | ```bash 20 | az acr show --name devopsthehardwayazurecr --query name 21 | ``` 22 | 23 | > 🔍 **Note**: Replace `devopsthehardwayazurecr` with your actual ACR name. 24 | 25 | 2. **Log Into the ACR Repository** 26 | 27 | ```bash 28 | az acr login --name devopsthehardwayazurecr 29 | ``` 30 | 31 | This command authenticates your Docker CLI with your Azure Container Registry. 32 | 33 | 3. **Tag the Docker Image** 34 | 35 | ```bash 36 | # Format: docker tag SOURCE_IMAGE TARGET_REGISTRY/TARGET_IMAGE:TAG 37 | docker tag thomasthorntoncloud:latest devopsthehardwayazurecr.azurecr.io/thomasthorntoncloud:v1 38 | ``` 39 | 40 | > 🔍 **Notes**: 41 | > - Replace `devopsthehardwayazurecr` with your ACR name 42 | > - The `:v1` tag indicates the version of your image 43 | > - Using semantic versioning (e.g., v1.0.0) is recommended for production images 44 | 45 | 4. **Push the Docker Image to ACR** 46 | 47 | ```bash 48 | docker push devopsthehardwayazurecr.azurecr.io/thomasthorntoncloud:v1 49 | ``` 50 | 51 | This command uploads your Docker image to your Azure Container Registry. 52 | 53 | 5. **Verify the Image in ACR** 54 | 55 | ```bash 56 | az acr repository show-tags --name devopsthehardwayazurecr --repository thomasthorntoncloud 57 | ``` 58 | 59 | This will list all the tags for the thomasthorntoncloud repository in your ACR. 60 | 61 | ## 🧠 Knowledge Check 62 | 63 | After pushing the image to ACR, consider these questions: 64 | 1. Why do we need to tag the Docker image before pushing it to ACR? 65 | 2. What's the significance of the version tag (e.g., `v1`) in the image name? 66 | 3. How does ACR authentication work when pushing images? 67 | 4. What role does ACR play in the overall DevOps pipeline for container deployments? 68 | 69 | ## 🔍 Verification 70 | 71 | To ensure the Docker image was successfully pushed to ACR: 72 | 73 | 1. **Using the Azure CLI**: 74 | ```bash 75 | az acr repository list --name devopsthehardwayazurecr --output table 76 | ``` 77 | 78 | 2. **Using the Azure Portal**: 79 | - Log into the [Azure Portal](https://portal.azure.com) 80 | - Navigate to your Azure Container Registry 81 | - Check the "Repositories" section to see if your image is listed: 82 | 83 | ![ACR Repository View](images/acr.png) 84 | 85 | ## 💡 Pro Tips 86 | 87 | 1. **Use Immutable Tags in Production**: 88 | For production scenarios, consider using unique tags for each image build (like commit hashes or build IDs) rather than reusing tags like "latest". 89 | 90 | ```bash 91 | # Example using a timestamp for unique tagging 92 | BUILD_ID=$(date +%Y%m%d%H%M%S) 93 | docker tag thomasthorntoncloud:latest devopsthehardwayazurecr.azurecr.io/thomasthorntoncloud:$BUILD_ID 94 | docker push devopsthehardwayazurecr.azurecr.io/thomasthorntoncloud:$BUILD_ID 95 | ``` 96 | 97 | 2. **Enable Image Scanning**: 98 | Consider enabling vulnerability scanning in your ACR: 99 | 100 | ```bash 101 | az acr update --name devopsthehardwayazurecr --enable-defender 102 | ``` 103 | 104 | 3. **Set Up Geo-replication for Production**: 105 | For high-availability production scenarios, consider enabling geo-replication of your ACR: 106 | 107 | ```bash 108 | az acr replication create --registry devopsthehardwayazurecr --location eastus 109 | ``` 110 | 111 | 4. **CI/CD Integration**: 112 | Set up CI/CD pipelines to automatically build and push your Docker images to ACR whenever you make changes to your application code. This approach maintains consistent image tagging and versioning across environments. 113 | 114 | 5. **Consider Repository Retention Policies**: 115 | For busy repositories, set up retention policies to automatically clean up older images: 116 | 117 | ```bash 118 | az acr config retention update --registry devopsthehardwayazurecr --status enabled --days 30 --type UntaggedManifests 119 | ``` -------------------------------------------------------------------------------- /3-Docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | WORKDIR /app 4 | 5 | # Copy requirements first for better caching 6 | COPY app/requirements.txt . 7 | RUN pip install --no-cache-dir -r requirements.txt 8 | 9 | # Copy the rest of the application 10 | COPY app . 11 | 12 | EXPOSE 5000 13 | 14 | CMD ["python", "app.py"] -------------------------------------------------------------------------------- /3-Docker/README.md: -------------------------------------------------------------------------------- 1 | # Docker for the DevOps-The-Hard-Way-Azure Project 2 | 3 | ## Overview 4 | This directory contains everything needed to build, run, and deploy the Docker container for the thomasthornton.cloud application, a simple Flask-based web application. The containerization process demonstrates best practices for creating efficient, secure, and production-ready Docker images. 5 | 6 | ## Contents 7 | 8 | - [Creating the Docker Image](./1-Create-Docker-Image.md) - Learn how to build and run the Docker image locally 9 | - [Pushing to Azure Container Registry](./2-Push%20Image%20To%20ACR.md) - Learn how to push your Docker image to Azure Container Registry 10 | - [Docker Best Practices for Azure](./3-Docker-Best-Practices-For-Azure.md) - Learn about Docker best practices specifically for Azure environments 11 | 12 | ## Directory Structure 13 | 14 | ``` 15 | . 16 | ├── Dockerfile # Instructions for building the Docker image 17 | ├── app/ # Application source code directory 18 | │ ├── app.py # Flask application entrypoint 19 | │ ├── index.html # Web application HTML template 20 | │ └── requirements.txt # Python dependencies 21 | ├── images/ # Screenshots and documentation images 22 | └── scripts/ # Automation scripts 23 | └── build-push-acr.sh # Script to build and push image to ACR 24 | ``` 25 | 26 | ## Application Details 27 | 28 | This is a simple Flask web application that: 29 | 30 | - Serves a responsive HTML page 31 | - Uses a clean and modern UI 32 | - Provides links to the GitHub repository 33 | 34 | ## Key Improvements 35 | 36 | This implementation includes several improvements over basic Docker setups: 37 | 38 | 1. **Multi-stage builds** for smaller, more secure production images 39 | 2. **Non-root user execution** to enhance security 40 | 3. **Health checks** for better monitoring and orchestration 41 | 4. **Optimized layer caching** for faster builds 42 | 5. **Automated build and push script** for consistent deployments 43 | 6. **Security scanning** to identify vulnerabilities 44 | 45 | ## Automation Scripts 46 | 47 | The repository includes scripts to automate Docker workflows: 48 | 49 | ```bash 50 | # Build and push to ACR 51 | cd 3-Docker 52 | chmod +x scripts/build-push-acr.sh 53 | ./scripts/build-push-acr.sh [optional-tag] 54 | 55 | # Scan Docker image for vulnerabilities 56 | chmod +x scripts/scan-docker-image.sh 57 | ./scripts/scan-docker-image.sh thomasthorntoncloud:latest 58 | ``` 59 | 60 | ## Azure Integration 61 | 62 | This Docker container is designed to be deployed to Azure using: 63 | 64 | - Azure Container Registry (ACR) for image storage 65 | - Azure Kubernetes Service (AKS) for orchestration 66 | - GitHub Actions for CI/CD pipelines 67 | 68 | ## Next Steps 69 | 70 | After working through the documentation in this folder: 71 | 72 | 1. Review the [AKS Deployment section](../4-kubernetes_manifest/README.md) to learn how to deploy this application to Azure Kubernetes Service 73 | 2. Explore the [Terraform Static Code Analysis](../5-Terraform-Static-Code-Analysis/1-Checkov-For-Terraform.md) to implement security scanning for your infrastructure code 74 | 75 | -------------------------------------------------------------------------------- /3-Docker/app/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template 2 | import os 3 | 4 | app = Flask(__name__) 5 | 6 | @app.route('/') 7 | def hello(): 8 | return render_template('index.html') 9 | 10 | if __name__ == "__main__": 11 | port = int(os.environ.get("PORT", 5000)) 12 | app.run(host='0.0.0.0', port=port) -------------------------------------------------------------------------------- /3-Docker/app/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==2.3.3 2 | Werkzeug==2.3.8 -------------------------------------------------------------------------------- /3-Docker/app/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | DevOps the Hard Way 7 | 8 | 80 | 81 | 82 |
83 |

Hello, World from thomasthornton.cloud

84 |

Explore DevOps the Hard Way Azure: Hands-on learning for real-world skills.

85 | Start Learning 86 |
87 | 88 | -------------------------------------------------------------------------------- /3-Docker/images/acr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomast1906/DevOps-The-Hard-Way-Azure/14c69268f159c540ef8517efae084027bf2d5f17/3-Docker/images/acr.png -------------------------------------------------------------------------------- /3-Docker/scripts/build-push-acr.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # build-push-acr.sh 3 | # 4 | # This script automates the process of building a Docker image and pushing it to Azure Container Registry. 5 | # It demonstrates best practices for working with Docker and ACR. 6 | # 7 | # Usage: 8 | # ./build-push-acr.sh [image_tag] 9 | 10 | set -e # Exit immediately if a command exits with a non-zero status 11 | 12 | # Define colors for output 13 | GREEN='\033[0;32m' 14 | YELLOW='\033[1;33m' 15 | RED='\033[0;31m' 16 | NC='\033[0m' # No Color 17 | 18 | # Functions for display 19 | info() { 20 | echo -e "${GREEN}INFO:${NC} $1" 21 | } 22 | 23 | warn() { 24 | echo -e "${YELLOW}WARNING:${NC} $1" 25 | } 26 | 27 | error() { 28 | echo -e "${RED}ERROR:${NC} $1" 29 | exit 1 30 | } 31 | 32 | # Check for Azure CLI installation 33 | if ! command -v az &> /dev/null; then 34 | error "Azure CLI could not be found. Please install it first." 35 | fi 36 | 37 | # Check for Docker installation 38 | if ! command -v docker &> /dev/null; then 39 | error "Docker could not be found. Please install it first." 40 | fi 41 | 42 | # Check parameters 43 | if [ $# -lt 1 ]; then 44 | error "Usage: $0 [image_tag]" 45 | fi 46 | 47 | ACR_NAME=$1 48 | IMAGE_TAG=${2:-v1} # Default to v1 if not provided 49 | TIMESTAMP=$(date +%Y%m%d%H%M%S) 50 | IMAGE_REPO="thomasthorntoncloud" 51 | FULL_IMAGE_TAG="$ACR_NAME.azurecr.io/$IMAGE_REPO:$IMAGE_TAG" 52 | LATEST_TAG="$ACR_NAME.azurecr.io/$IMAGE_REPO:latest" 53 | 54 | info "Starting Docker build and push to ACR process..." 55 | 56 | # Verify ACR exists 57 | info "Verifying ACR '$ACR_NAME' exists..." 58 | az acr show --name "$ACR_NAME" --query name -o tsv 2>/dev/null || error "ACR '$ACR_NAME' does not exist or you don't have access to it." 59 | 60 | # Log in to ACR 61 | info "Logging in to ACR '$ACR_NAME'..." 62 | az acr login --name "$ACR_NAME" || error "Failed to log in to ACR." 63 | 64 | # Check if we're running on Apple Silicon 65 | if [[ "$(uname -m)" == "arm64" ]]; then 66 | warn "Detected Apple Silicon (ARM64). Using --platform=linux/amd64 for compatibility." 67 | PLATFORM_FLAG="--platform=linux/amd64" 68 | else 69 | PLATFORM_FLAG="" 70 | fi 71 | 72 | # Build the Docker image 73 | info "Building Docker image with tag '$FULL_IMAGE_TAG'..." 74 | docker build $PLATFORM_FLAG -t "$FULL_IMAGE_TAG" . || error "Docker build failed." 75 | 76 | # Tag with 'latest' as well 77 | info "Tagging image as 'latest'..." 78 | docker tag "$FULL_IMAGE_TAG" "$LATEST_TAG" || error "Failed to tag image as latest." 79 | 80 | # Push to ACR 81 | info "Pushing image to ACR..." 82 | docker push "$FULL_IMAGE_TAG" || error "Failed to push image to ACR." 83 | docker push "$LATEST_TAG" || error "Failed to push latest tag to ACR." 84 | 85 | # Verify the push 86 | info "Verifying image in ACR..." 87 | az acr repository show-tags --name "$ACR_NAME" --repository "$IMAGE_REPO" -o table || warn "Could not verify image in ACR." 88 | 89 | info "Image build and push completed successfully!" 90 | info "Your image is available at: $FULL_IMAGE_TAG" 91 | info "Also available as: $LATEST_TAG" 92 | 93 | # Show next steps 94 | echo "" 95 | echo "Next steps:" 96 | echo "1. Update your Kubernetes manifests to use this image" 97 | echo "2. Apply the manifests to your AKS cluster" 98 | echo "3. Verify the deployment" 99 | -------------------------------------------------------------------------------- /4-kubernetes_manifest/1-Connect-To-AKS.md: -------------------------------------------------------------------------------- 1 | # Connecting To Azure Kubernetes Service (AKS) 2 | 3 | ## 🎯 Purpose 4 | In this lab, you'll learn how to authenticate and connect to your Azure Kubernetes Service (AKS) cluster from your local terminal. 5 | 6 | ## 🛠️ Connect to AKS Cluster 7 | 8 | ### Prerequisites 9 | - [ ] kubectl installed on your local machine 10 | - [ ] Access to an AKS cluster 11 | 12 | ### Steps 13 | 14 | 1. **Authenticate and Connect to AKS** 15 | Run the following command to connect to AKS: 16 | ```bash 17 | az aks get-credentials --resource-group devopsthehardway-rg --name devopsthehardwayaks --overwrite-existing 18 | ``` 19 | 20 | > 🔍 **Note**: Note: This command generates a kubeconfig file on your local machine with the necessary connection and authentication details. 21 | 22 | 2. Verify Connection 23 | Run the following command to confirm you're connected: 24 | ```bash 25 | kubectl get nodes 26 | ``` 27 | ## 🔍 Verification 28 | 29 | To ensure you've successfully connected to your AKS cluster: 30 | 1. The `kubectl get nodes` command should return a list of nodes without any errors. 31 | 2. You should be able to run other kubectl commands, such as `kubectl get pods --all-namespaces`. 32 | 33 | ## 🧠 Knowledge Check 34 | 35 | After connecting to AKS, consider these questions: 36 | 1. What is a `kubeconfig` file and why is it important? 37 | 2. How does the `az aks get-credentials` command facilitate cluster access? 38 | 3. What other kubectl commands can you use to verify your connection to the cluster? 39 | 40 | ## 💡 Pro Tip 41 | 42 | Consider setting up different contexts in your kubeconfig file if you're working with multiple Kubernetes clusters. This allows you to switch between clusters easily using the `kubectl config use-context` command. 43 | -------------------------------------------------------------------------------- /4-kubernetes_manifest/2-Create-Kubernetes-Manifest.md: -------------------------------------------------------------------------------- 1 | # Create The Kubernetes Manifest 2 | 3 | ## 🎯 Purpose 4 | In this lab, you'll create and understand the Kubernetes manifest for deploying the Thomasthornton.cloud app to Azure Kubernetes Service (AKS). 5 | 6 | ## 🛠️ Create and Configure the Kubernetes Manifest 7 | 8 | ### Prerequisites 9 | - [ ] Docker image created and stored in Azure Container Registry (ACR) 10 | - [ ] Access to your AKS cluster 11 | - [ ] Basic understanding of Kubernetes concepts 12 | 13 | ### Steps 14 | 15 | 1. **Understand the Manifest Components** 16 | The Kubernetes manifest consists of three key components: 17 | - [ ] Deployment: Manages the application's deployment within Kubernetes 18 | - [ ] Service: Exposes the Kubernetes application for external access 19 | - [ ] Namespace: Organises and isolates resources within the cluster 20 | 21 | 2. **Locate the Manifest File** 22 | Find the `deployment.yml` file in the `4-kubernetes_manifest` directory. 23 | 24 | 3. **Update the Image URL** 25 | Open `deployment.yml` and locate line 24. Update the image URL to match the image stored in your Azure Container Registry. 26 | 27 | ## 🔍 Verification 28 | To ensure your manifest is correctly configured: 29 | 1. Review the entire `deployment.yml` file for any syntax errors 30 | 2. Verify that the image URL matches your ACR repository 31 | 3. Check that the resource requests and limits are appropriate for your application 32 | 33 | ## 🧠 Knowledge Check 34 | After reviewing the manifest, consider these questions: 35 | 1. What is the purpose of each component (Deployment, Service, Namespace) in the manifest? 36 | 2. Why is it important to update the image URL in the manifest? 37 | 3. How does the manifest help in managing your application in Kubernetes? 38 | 39 | ## 🚀 Next Steps 40 | With your Kubernetes manifest prepared, you're ready to deploy your application to AKS. In the next lab, we'll cover how to apply this manifest to your cluster. 41 | 42 | ## 💡 Pro Tip 43 | Consider using Helm charts for more complex applications. Helm allows you to template your Kubernetes manifests, making it easier to manage multiple environments or similar applications. 44 | 45 | 46 | -------------------------------------------------------------------------------- /4-kubernetes_manifest/3-Deploy-Thomasthorntoncloud-App.md: -------------------------------------------------------------------------------- 1 | # Deploy The Thomasthorntoncloud App 2 | 3 | ## 🎯 Purpose 4 | In this lab, you'll deploy the Thomasthorntoncloud app to your Azure Kubernetes Service (AKS) cluster using the prepared Kubernetes manifest. 5 | 6 | ## 🛠️ Deploy the Application 7 | 8 | ### Prerequisites 9 | - [ ] AKS cluster provisioned 10 | - [ ] Kubernetes manifest prepared 11 | - [ ] kubectl configured to communicate with your AKS cluster 12 | 13 | ### Steps 14 | 15 | 1. **Navigate to the Kubernetes Manifest Directory** 16 | ```bash 17 | cd 4-kubernetes_manifest 18 | ``` 19 | 2. Deploy the Application Components 20 | 21 | - Deploy the Thomasthorntoncloud app: 22 | ```bash 23 | kubectl create -f deployment.yml 24 | ``` 25 | 26 | - Install ALB Controller: 27 | ```bash 28 | ./scripts/1-alb-controller-install-k8s.sh 29 | ``` 30 | - Install Gateway API resources: 31 | ```bash 32 | ./scripts/2-gateway-api-resources.sh 33 | ``` 34 | 35 | 3. **Verify Deployment** 36 | Run the following command to confirm the deployment was successful: 37 | ```bash 38 | kubectl get deployments 39 | ``` 40 | 4. **Access the Thomasthornton.cloud App** 41 | 42 | To access the Thomasthorntoncloud app via Azure Application Gateway Controller for Containers, run the following command: 43 | ```bash 44 | fqdn=$(kubectl get gateway gateway-01 -n thomasthorntoncloud -o jsonpath='{.status.addresses[0].value}') 45 | echo "http://$fqdn" 46 | ``` 47 | 48 | Access the Thomasthornton.cloud app using the address provided. 49 | 50 | You've successfully deployed the Thomasthornton.cloud app to your AKS cluster using the Kubernetes manifest: 51 | 52 | ![](images/website.png) 53 | 54 | ## 🔍 Verification 55 | 56 | To ensure your application is deployed and running correctly: 57 | 1. Check that all pods are in the 'Running' state: kubectl get pods 58 | 2. Verify that the service is exposed: kubectl get services 59 | 3. Test the application by accessing it through the provided URL 60 | 61 | ## 🧠 Knowledge Check 62 | 63 | After deploying the application, consider these questions: 64 | 1. What is the purpose of the ALB Controller in this deployment? 65 | 2. How does the Gateway API enhance the application's accessibility? 66 | 3. Why is it important to verify the deployment using `kubectl get deployments`? 67 | 68 | ## 💡 Pro Tip 69 | 70 | Use Kubernetes namespaces to organise and isolate your resources, especially when deploying multiple applications or environments in the same cluster. 71 | -------------------------------------------------------------------------------- /4-kubernetes_manifest/README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Manifests for AKS Deployment 2 | 3 | ## Overview 4 | This directory contains the Kubernetes manifest files and instructions needed to deploy the thomasthornton.cloud application to Azure Kubernetes Service (AKS). These manifests define how the application will run in the AKS cluster. 5 | 6 | ## Contents 7 | 8 | - [Connecting to AKS](./1-Connect-To-AKS.md) - Learn how to connect to your AKS cluster 9 | - [Creating Kubernetes Manifests](./2-Create-Kubernetes-Manifest.md) - Learn about the manifest files for deployment 10 | - [Deploying the Application](./3-Deploy-Thomasthorntoncloud-App.md) - Deploy the application to your AKS cluster 11 | 12 | ## Directory Structure 13 | 14 | ``` 15 | . 16 | ├── deployment.yml # Kubernetes deployment manifest for the application 17 | ├── images/ # Screenshots and documentation images 18 | └── scripts/ # Helper scripts 19 | ├── 1-alb-controller-install-k8s.sh # Script to install Azure Load Balancer controller 20 | └── 2-gateway-api-resources.sh # Script to create Gateway API resources 21 | ``` 22 | 23 | ## Deployment Process 24 | 25 | The deployment process follows these key steps: 26 | 27 | 1. Connect to your AKS cluster using `kubectl` 28 | 2. Understand the Kubernetes manifest structure 29 | 3. Deploy the application using `kubectl apply` 30 | 4. Verify the deployment and access the application 31 | 32 | ## Features 33 | 34 | The Kubernetes deployment in this section includes: 35 | 36 | - Deployment resource to manage pod replicas 37 | - Service resource to expose the application 38 | - Integration with Azure Load Balancer 39 | - Configuration for scaling and high availability 40 | 41 | ## Next Steps 42 | 43 | After completing the deployment: 44 | 45 | 1. Review the [Terraform Static Code Analysis](../5-Terraform-Static-Code-Analysis/1-Checkov-For-Terraform.md) section to learn about security scanning 46 | 2. Consider implementing monitoring and observability solutions for your AKS deployment 47 | 48 | ## Best Practices 49 | 50 | The Kubernetes manifests in this section follow these best practices: 51 | 52 | 1. Resource requests and limits 53 | 2. Liveness and readiness probes 54 | 3. Proper labeling for resources 55 | 4. Security context configurations 56 | 5. Network policies (to be added) 57 | -------------------------------------------------------------------------------- /4-kubernetes_manifest/deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: thomasthorntoncloud 5 | --- 6 | 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | metadata: 10 | name: thomasthornton 11 | namespace: thomasthorntoncloud 12 | spec: 13 | replicas: 1 14 | selector: 15 | matchLabels: 16 | app: thomasthorntoncloud 17 | template: 18 | metadata: 19 | labels: 20 | app: thomasthorntoncloud 21 | spec: 22 | containers: 23 | - name: thomasthorntoncloud 24 | image: devopsthehardwayazurecr.azurecr.io/thomasthorntoncloud:v1 # Update this line 25 | ports: 26 | - containerPort: 5000 27 | resources: 28 | requests: 29 | cpu: 100m 30 | memory: 128Mi 31 | limits: 32 | cpu: 250m 33 | memory: 256Mi 34 | --- 35 | apiVersion: v1 36 | kind: Service 37 | metadata: 38 | name: thomasthorntoncloud 39 | namespace: thomasthorntoncloud 40 | spec: 41 | type: LoadBalancer 42 | ports: 43 | - port: 80 44 | targetPort: 5000 45 | selector: 46 | app: thomasthorntoncloud -------------------------------------------------------------------------------- /4-kubernetes_manifest/images/website.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomast1906/DevOps-The-Hard-Way-Azure/14c69268f159c540ef8517efae084027bf2d5f17/4-kubernetes_manifest/images/website.png -------------------------------------------------------------------------------- /4-kubernetes_manifest/scripts/1-alb-controller-install-k8s.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RESOURCE_GROUP="devopsthehardway-rg" 4 | AKS_NAME="devopsthehardwayaks" 5 | helm_resource_namespace="azure-alb-system" 6 | VNET_NAME="devopsthehardway-vnet" 7 | ALB_SUBNET_NAME="appgw" 8 | ALB_CONTROLLER_VERSION="1.0.0" 9 | 10 | #create namespace 11 | kubectl create namespace $helm_resource_namespace 12 | 13 | # az aks get-credentials --resource-group $RESOURCE_GROUP --name $AKS_NAME 14 | helm install alb-controller oci://mcr.microsoft.com/application-lb/charts/alb-controller --namespace $helm_resource_namespace --version $ALB_CONTROLLER_VERSION --set albController.namespace=$helm_resource_namespace --set albController.podIdentity.clientID=$(az identity show -g $RESOURCE_GROUP -n azure-alb-identity --query clientId -o tsv) 15 | -------------------------------------------------------------------------------- /4-kubernetes_manifest/scripts/2-gateway-api-resources.sh: -------------------------------------------------------------------------------- 1 | RESOURCE_GROUP='devopsthehardway-rg' 2 | ALB_RESOURCE_NAME='devopsthehardway-alb' 3 | ALB_FRONTEND_NAME='alb-frontend' 4 | 5 | RESOURCE_ID=$(az network alb show --resource-group $RESOURCE_GROUP --name $ALB_RESOURCE_NAME --query id -o tsv) 6 | 7 | # Create a Gateway 8 | kubectl apply -f - < 40 | ``` 41 | 42 | For example: 43 | ```bash 44 | checkov --directory DevOps-The-Hard-Way-Azure/Terraform-AZURE-Services-Creation/1-acr 45 | ``` 46 | 47 | ## 🔍 Verification 48 | 49 | To ensure Checkov is working correctly: 50 | 1. Check that the scan completes without errors 51 | 2. Review the list of passed and failed tests in the terminal output 52 | 3. Verify that you can access the results in the Bridgecrew UI 53 | 54 | ## 🧠 Knowledge Check 55 | 56 | After running Checkov, consider these questions: 57 | 1. What types of issues does Checkov identify in Terraform code? 58 | 2. How does Checkov differ from other Terraform validation tools? 59 | 3. What are the benefits of using the Bridgecrew UI alongside Checkov? 60 | 61 | ## 💡 Pro Tip 62 | 63 | Use Checkov's `--compact flag` to get a more concise output, or `--quiet` to only see failed checks. This can be helpful when integrating with CI/CD pipelines. -------------------------------------------------------------------------------- /5-Terraform-Static-Code-Analysis/2-tfsec.md: -------------------------------------------------------------------------------- 1 | # tfsec For Terraform Security Scanning 2 | 3 | ## 🎯 Purpose 4 | In this lab, you'll learn how to use tfsec, a static analysis security scanner for your Terraform code, and integrate it into your GitHub Actions workflow for automated security checks. 5 | 6 | ## 🛠️ Install and Run tfsec 7 | 8 | ### Prerequisites 9 | - [ ] Basic understanding of Terraform 10 | - [ ] GitHub repository with Terraform code 11 | - [ ] Permissions to update GitHub Actions workflows 12 | 13 | ### Steps 14 | 15 | 1. **Install tfsec Locally** 16 | 17 | Install tfsec using one of the following methods: 18 | 19 | **Homebrew (macOS/Linux)**: 20 | ```bash 21 | brew install tfsec 22 | ``` 23 | 24 | **Docker**: 25 | ```bash 26 | docker run --rm -it -v "$(pwd):/src" aquasec/tfsec /src 27 | ``` 28 | 29 | **Go**: 30 | ```bash 31 | go install github.com/aquasecurity/tfsec/cmd/tfsec@latest 32 | ``` 33 | 34 | **Chocolatey (Windows)**: 35 | ```bash 36 | choco install tfsec 37 | ``` 38 | 39 | 2. **Run tfsec Locally** 40 | 41 | Run tfsec against your Terraform code: 42 | ```bash 43 | tfsec /path/to/terraform/code 44 | ``` 45 | 46 | For example: 47 | ```bash 48 | tfsec DevOps-The-Hard-Way-Azure/2-Terraform-Azure-services-creation/4-aks 49 | ``` 50 | 51 | 3. **Add tfsec to GitHub Actions Workflow** 52 | 53 | Open your GitHub Actions workflow file (`.github/workflows/main.yml`) and add the tfsec action: 54 | 55 | ```yaml 56 | - name: tfsec 57 | uses: aquasecurity/tfsec-pr-commenter-action@v1.2.0 58 | with: 59 | tfsec_args: --soft-fail 60 | github_token: ${{ github.token }} 61 | ``` 62 | 63 | The `--soft-fail` argument ensures the workflow doesn't fail when security issues are found, but still reports them as comments on your PR. 64 | 65 | 4. **Understanding tfsec Results** 66 | 67 | tfsec checks include: 68 | - [ ] Insecure security group rules 69 | - [ ] Unencrypted resources 70 | - [ ] Public exposure of sensitive resources 71 | - [ ] Missing logging configurations 72 | - [ ] IAM misconfigurations 73 | - [ ] Azure-specific security best practices 74 | 75 | ## 🔍 Verification 76 | 77 | To ensure tfsec is working correctly: 78 | 1. Run tfsec locally to see immediate results 79 | 2. Create a pull request with Terraform code changes 80 | 3. Verify that tfsec is adding security-related comments to your PR 81 | 4. Review and address the issues identified 82 | 83 | Example tfsec output: 84 | 85 | ``` 86 | Results: 87 | HIGH: Resource 'azurerm_storage_account.storage' uses unencrypted storage for account 'mystorageaccount' 88 | Impact: Data could be read if compromised 89 | Resolution: Enable encryption for storage accounts 90 | More info: https://aquasecurity.github.io/tfsec/v1.28.0/checks/azure/storage/encrypt-in-transit/ 91 | File: ./storage.tf:Line:1:Column:1 92 | ``` 93 | 94 | ## 🧠 Knowledge Check 95 | 96 | After integrating tfsec, consider these questions: 97 | 1. How does tfsec differ from Checkov in its approach to security scanning? 98 | 2. What are the benefits of having security checks integrated directly into the PR process? 99 | 3. How would you handle false positives in tfsec findings? 100 | 4. What is the significance of the `--soft-fail` flag in the GitHub Action? 101 | 102 | ## 💡 Pro Tips 103 | 104 | 1. **Customise Checks with .tfsec.yml** 105 | 106 | Create a `.tfsec.yml` file in your repository root to customise which checks to include or exclude: 107 | 108 | ```yaml 109 | exclude: 110 | # Exclude a specific check 111 | - azure-storage-use-secure-tls-policy 112 | 113 | # Set minimum severity level 114 | minimum_severity: MEDIUM 115 | ``` 116 | 117 | 2. **Generate a Baseline** 118 | 119 | If you have existing issues that you want to ignore temporarily: 120 | 121 | ```bash 122 | tfsec --soft-fail --out=tfsec.baseline ./path/to/code 123 | ``` 124 | 125 | Then use the baseline in future scans: 126 | 127 | ```bash 128 | tfsec --baseline tfsec.baseline ./path/to/code 129 | ``` 130 | 131 | 3. **Output Formats** 132 | 133 | tfsec supports multiple output formats for CI/CD integration: 134 | 135 | ```bash 136 | # JSON output 137 | tfsec --format=json ./path/to/code 138 | 139 | # SARIF format (for GitHub Code Scanning) 140 | tfsec --format=sarif ./path/to/code 141 | 142 | # JUnit format (for test reporting) 143 | tfsec --format=junit ./path/to/code 144 | ``` 145 | -------------------------------------------------------------------------------- /6-Terraform-Docs/1-Setup-Terraform-Docs.md: -------------------------------------------------------------------------------- 1 | # Set Up Terraform-docs with GitHub Actions 2 | 3 | ## 🎯 Purpose 4 | In this lab, you'll learn how to automate Terraform documentation using terraform-docs and GitHub Actions. This ensures your infrastructure documentation is always up-to-date and consistent. 5 | 6 | ## 🛠️ Setting Up Terraform-docs 7 | 8 | ### Prerequisites 9 | - [ ] GitHub repository with Terraform code 10 | - [ ] GitHub Actions workflow set up 11 | - [ ] Permissions to modify GitHub workflow files 12 | 13 | ### Steps 14 | 15 | 1. **Understand terraform-docs** 16 | 17 | Terraform-docs is a utility tool that automatically generates documentation from Terraform modules. It extracts: 18 | - Inputs (variables) 19 | - Outputs 20 | - Providers 21 | - Requirements 22 | - Resources 23 | - And more 24 | 25 | This documentation is generated in various formats, including Markdown, which we'll use to inject into our README files. 26 | 27 | 2. **Add terraform-docs GitHub Action** 28 | 29 | - Open your GitHub Actions workflow file (`.github/workflows/main.yml`) 30 | - Add the terraform-docs action: 31 | 32 | ```yaml 33 | - name: Render terraform docs and push changes back to PR 34 | uses: terraform-docs/gh-actions@main 35 | with: 36 | working-dir: . 37 | output-file: README.md 38 | output-method: inject 39 | git-push: "true" 40 | ``` 41 | 42 | 3. **Prepare Your README.md Files** 43 | 44 | - For each Terraform module where you want documentation generated, open or create a README.md file 45 | - Add the terraform-docs markers where you want the documentation inserted: 46 | 47 | ```markdown 48 | 49 | 50 | ``` 51 | 52 | These markers tell terraform-docs where to inject the generated documentation. 53 | 54 | 4. **Test the Workflow** 55 | 56 | - Make a change to your Terraform code 57 | - Create a pull request 58 | - The GitHub Action will automatically run and update your README.md files with generated documentation 59 | - The changes will be pushed back to the same PR 60 | 61 | ## 🔍 Verification 62 | 63 | To ensure terraform-docs is working correctly: 64 | 1. Create a pull request with a change to Terraform code 65 | 2. Wait for the GitHub Action to complete 66 | 3. Check that the README.md files have been updated with documentation between the markers 67 | 4. Verify that the documentation accurately reflects your Terraform code 68 | 69 | Example of generated documentation: 70 | 71 | ``` 72 | ## Requirements 73 | 74 | | Name | Version | 75 | |------|---------| 76 | | terraform | >= 1.0.0 | 77 | | azurerm | >= 3.0.0 | 78 | 79 | ## Providers 80 | 81 | | Name | Version | 82 | |------|---------| 83 | | azurerm | >= 3.0.0 | 84 | 85 | ## Inputs 86 | 87 | | Name | Description | Type | Default | Required | 88 | |------|-------------|------|---------|:--------:| 89 | | resource_group_name | Name of the resource group | `string` | n/a | yes | 90 | | location | Azure region for resources | `string` | `"uksouth"` | no | 91 | 92 | ## Outputs 93 | 94 | | Name | Description | 95 | |------|-------------| 96 | | acr_id | ID of the Azure Container Registry | 97 | ``` 98 | 99 | ## 🧠 Knowledge Check 100 | 101 | After setting up terraform-docs, consider these questions: 102 | 1. Why is automated documentation important for infrastructure as code? 103 | 2. How does terraform-docs determine what to include in the documentation? 104 | 3. What are the benefits of injecting documentation into README files versus maintaining separate docs? 105 | 4. How can terraform-docs improve collaboration in a team environment? 106 | 107 | ## 💡 Pro Tips 108 | 109 | 1. **Customising Output Format** 110 | 111 | You can customise the output format using a `.terraform-docs.yml` file in your repository root: 112 | 113 | ```yaml 114 | formatter: "markdown table" 115 | 116 | sections: 117 | show: 118 | - requirements 119 | - providers 120 | - inputs 121 | - outputs 122 | - resources 123 | 124 | output: 125 | file: README.md 126 | mode: inject 127 | template: |- 128 | 129 | {{ .Content }} 130 | 131 | ``` 132 | 133 | 2. **Module-Specific Configuration** 134 | 135 | For different documentation in different modules, create module-specific configuration files: 136 | 137 | ``` 138 | my-terraform-project/ 139 | ├── .terraform-docs.yml # Default config 140 | ├── module1/ 141 | │ ├── .terraform-docs.yml # Module-specific config 142 | │ └── README.md 143 | └── module2/ 144 | └── README.md 145 | ``` 146 | 147 | 3. **Configure Which Files to Document** 148 | 149 | In your GitHub workflow, specify which directories to document: 150 | 151 | ```yaml 152 | with: 153 | working-dir: | 154 | terraform/module1 155 | terraform/module2 156 | output-file: README.md 157 | output-method: inject 158 | git-push: "true" 159 | ``` 160 | 161 | 4. **Pre-Commit Hook for Local Development** 162 | 163 | Install terraform-docs locally and set up a pre-commit hook to maintain documentation during development: 164 | 165 | ```bash 166 | # Install terraform-docs (macOS) 167 | brew install terraform-docs 168 | 169 | # Run manually 170 | terraform-docs markdown table --output-file README.md --output-mode inject ./my-module 171 | ``` 172 | 173 | 5. **Adding Diagrams** 174 | 175 | Consider enhancing your documentation with architecture diagrams using tools like [Terraform Graph](https://github.com/offu/terraform-graph): 176 | 177 | ```bash 178 | terraform graph | dot -Tsvg > graph.svg 179 | ``` 180 | 181 | Then include the SVG in your README.md outside the terraform-docs markers. 182 | -------------------------------------------------------------------------------- /6-Terraform-Docs/README.md: -------------------------------------------------------------------------------- 1 | # Terraform Documentation Automation 2 | 3 | ## Overview 4 | This directory contains instructions for implementing automated documentation generation for Terraform code using terraform-docs and GitHub Actions. Proper documentation is a critical aspect of infrastructure as code that is often overlooked but can greatly improve collaboration and maintenance. 5 | 6 | ## Labs in this Section 7 | 8 | ### [1. Set Up Terraform-docs with GitHub Actions](./1-Setup-Terraform-Docs.md) 9 | Learn how to automate the creation and maintenance of Terraform documentation using terraform-docs and GitHub Actions. 10 | 11 | ## Features and Benefits 12 | 13 | - Automated documentation generation on every pull request 14 | - Consistent documentation format across all Terraform code 15 | - Documentation that stays in sync with your infrastructure code 16 | - Improved developer experience and onboarding 17 | - Enhanced collaboration through better documentation 18 | 19 | ## Integration with DevOps Workflow 20 | 21 | The terraform-docs tool integrates seamlessly with your existing DevOps workflow: 22 | 23 | 1. Developers write or update Terraform code 24 | 2. A pull request is created 25 | 3. GitHub Actions automatically generates or updates documentation 26 | 4. Documentation changes are committed back to the PR 27 | 5. Reviewers can see both code and documentation changes together 28 | 6. The PR is merged with complete, up-to-date documentation 29 | 30 | ## Best Practices Applied 31 | 32 | - Documentation as code 33 | - Automated processes to ensure consistency 34 | - Integration with existing CI/CD pipelines 35 | - Standardised format for better readability 36 | - Version-controlled documentation that evolves with your code 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Mike Levan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DevOps the Hard Way on Azure 2 | 3 | Welcome to the DevOps the Hard Way on Azure tutorial! 🚀 4 | 5 | This comprehensive guide provides a real-world solution for implementing DevOps practices and technologies to deploy applications and cloud services/infrastructure on Microsoft Azure. 6 | 7 | ## 🌟 What's Inside? 8 | 9 | - Free labs 10 | - Detailed documentation 11 | - Step-by-step guides 12 | 13 | All designed to help you set up a complete DevOps environment from a real-world perspective in Azure. 14 | 15 | ## 🎭 The DevOps Scenario 16 | 17 | Imagine this: You've just joined a company stuck in the past. They're drowning in: 18 | - [ ] Bare metal servers 19 | - [ ] Manual deployments 20 | - [ ] Outdated IT practices 21 | 22 | > **Your mission, should you choose to accept it:** 23 | > Modernise everything. Make the organisation not just succeed, but lead the pack. 24 | 25 | ## 💡 The DevOps Solution 26 | 27 | We're going to deploy the thomasthornton.cloud application, transforming it from a bare-metal application to a DevOps masterpiece. 28 | 29 | ![](images/website.png) 30 | 31 | > 🔍 **Note**: As a DevOps/Platform Engineer, your focus is on deployment, not application development. That's why we're using an existing app for this tutorial. 32 | 33 | ## 🛠️ Technology Stack 34 | 35 | Get ready to utilise a range of cutting-edge technologies and platforms to establish your DevOps environment: 36 | 37 | | Technology | Purpose | 38 | |------------|---------| 39 | | Azure | Cloud hosting and services | 40 | | GitHub | Code repository | 41 | | Python | Application and automation | 42 | | Terraform | Infrastructure as Code | 43 | | Docker | Containerisation | 44 | | Kubernetes (AKS) | Container orchestration | 45 | | GitHub Actions | CI/CD | 46 | | Checkov | Automated testing | 47 | | Terraform-docs | Documentation automation | 48 | 49 | ## 🧪 Labs 50 | 51 | [ ] Check boxes have been added to each lab to help you keep track of your progress. 52 | 53 | ### Prerequisites 54 | 55 | Before you start, ensure you have the following [prerequisites](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/blob/main/prerequisites.md) in place 56 | 1. [ ] [Create a Storage Account for Terraform State file](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/blob/main/1-Azure/1-Configure-Terraform-Remote-Storage.md) 57 | 2. [ ] [Set up an Azure AD Group for AKS Admins](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/blob/main/1-Azure/2-Create-Azure-AD-Group-AKS-Admins.md) 58 | 59 | ### Main Sections 60 | 61 | 1. **Terraform** - Create all the Azure cloud services needed to run the thomasthornton.cloud application. 62 | - [ ] [Create Azure Container Registry (ACR)](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/blob/main/2-Terraform-AZURE-Services-Creation/1-Create-ACR.md) 63 | - [ ] [Create Azure Virtual Network (VNET)](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/blob/main/2-Terraform-AZURE-Services-Creation/2-Create-VNET.md) 64 | - [ ] [Create Log Analytics](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/blob/main/2-Terraform-AZURE-Services-Creation/3-Create-Log-Analytics.md) 65 | - [ ] [Create AKS Cluster with relevant IAM roles](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/blob/main/2-Terraform-AZURE-Services-Creation/4-Create-AKS-Cluster-IAM-Roles.md) 66 | 67 | 2. **Docker** - Containerise the thomasthornton.cloud application and store it in Azure Container Registry (ACR). 68 | - [ ] [Create the Docker Image](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/blob/main/3-Docker/1-Create-Docker-Image.md) 69 | - [ ] [Create a Docker Image for the thomasthornton.cloud App](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/blob/main/3-Docker/2-Push%20Image%20To%20ACR.md) 70 | 71 | 72 | 3. **Kubernetes** - Deploy application to AKS and expose the thomasthornton.cloud application to the internet. 73 | - [ ] [Connect To AKS From The Terminal](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/blob/main/4-kubernetes_manifest/1-Connect-To-AKS.md) 74 | - [ ] [Create A Kubernetes Manifest](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/blob/main/4-kubernetes_manifest/2-Create-Kubernetes-Manifest.md) 75 | - [ ] [Deploy thomasthornton.cloud App into Kubernetes](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/blob/main/4-kubernetes_manifest/3-Deploy-Thomasthorntoncloud-App.md) 76 | 77 | 78 | 4. **Automated Testing** Ensure code quality 79 | - [ ] [Install And Run Checkov](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/blob/main/5-Terraform-Static-Code-Analysis/1-Checkov-For-Terraform.md) 80 | 81 | 82 | 5. **CICD** - Automatically update AKS cluster with CICD using GitHub Actions 83 | - [ ] [Create a GitHub Actions CICD pipeline](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/blob/main/2-Terraform-AZURE-Services-Creation/5-Run-CICD-For-AKS-Cluster.md) 84 | 85 | 6. **Terraform Documentation** - Automate Terraform documentation generation 86 | - [ ] [Set Up Terraform-docs with GitHub Actions](https://github.com/thomast1906/DevOps-The-Hard-Way-Azure/blob/main/6-Terraform-Docs/1-Setup-Terraform-Docs.md) 87 | 88 | ## 🎓 Learning Checkpoints 89 | 90 | After each section, test your understanding: 91 | 92 | ```markdown 93 | - [ ] Can you explain why we're using a remote state for Terraform? 94 | - [ ] What's the significance of containerising the app? 95 | - [ ] How does AKS simplify Kubernetes management? 96 | - [ ] Why is automated testing crucial in a DevOps pipeline? 97 | - [ ] How does CI/CD improve the deployment process? 98 | - [ ] Why is automated documentation important for infrastructure as code? 99 | ``` 100 | 101 | ## Conclusion 102 | By following this tutorial, you'll not only deploy an example app on Azure but also gain valuable insights into modern DevOps practices and tools. 103 | 104 | Let's embark on this journey to transform your organisation into a lean, agile, and competitive force in the digital landscape. Happy deploying! 🚀🔧 105 | 106 | By completing this tutorial, you'll: 107 | - Deploy a real-world app on Azure 108 | - Master essential DevOps tools and practices 109 | - Transform your organisation's IT landscape 110 | 111 | Are you ready to embark on this DevOps journey? Let's turn that monolithic infrastructure into a lean, mean, deploying machine! 💪🚀 📣 112 | 113 | I value your feedback! If you find any issues or have suggestions for improvement, please open an issue or submit a pull request. 114 | -------------------------------------------------------------------------------- /images/website.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomast1906/DevOps-The-Hard-Way-Azure/14c69268f159c540ef8517efae084027bf2d5f17/images/website.png -------------------------------------------------------------------------------- /prerequisites.md: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | 3 | ## Prior Knowledge In Tech 4 | 5 | DevOps isn't an entry level role by any means if it's being done correctly. There's a lot of knowledge you need prior, including: 6 | - Some sort of cloud engineering/cloud knowledge experience. Although not all environments are running in the cloud, most of these roles will want it. 7 | - Scripting/automation/programming experience. You don't have to go out and write the next Twitter, but you should understand the basics of programming. 8 | - Network, storage, and compute knowledge. 9 | - Held a prior systems administration, infrastructure engineer, or cloud engineer role. 10 | 11 | ## Azure 12 | 13 | ### Create An Azure Account 14 | 15 | To follow along with this tutorial, you should have an Azure account. If you don't already have one, you can sign up for a free 12 month trial [here](https://azure.microsoft.com/en-gb/free/search/?&ef_id=Cj0KCQjwtrSLBhCLARIsACh6RmiaUvnIcRuC0BE8HVqtnC09Za6Y_ByYHH8Z4qHmK5-inXXdgZB3d1EaAh8EEALw_wcB:G:s&OCID=AID2200274_SEM_Cj0KCQjwtrSLBhCLARIsACh6RmiaUvnIcRuC0BE8HVqtnC09Za6Y_ByYHH8Z4qHmK5-inXXdgZB3d1EaAh8EEALw_wcB:G:s&gclid=Cj0KCQjwtrSLBhCLARIsACh6RmiaUvnIcRuC0BE8HVqtnC09Za6Y_ByYHH8Z4qHmK5-inXXdgZB3d1EaAh8EEALw_wcB). 16 | 17 | You should know that the tutorial for *DevOps The Hard Way* will cost money because some of the services that you use in Azure will not be part of the free tier. 18 | 19 | To learn more about the Azure Pricing Model so you understand what the cost will be, you can go [here](https://azure.microsoft.com/en-gb/pricing/calculator/) 20 | 21 | ### Use the Azure CLI 22 | 23 | The Azure CLI is a way for you to interact with all Azure services at a programmatic level using the terminal. 24 | 25 | To set this up, follow the directions [here](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) 26 | 27 | ## Installations 28 | You will need to download some software and services for this tutorial. 29 | 30 | ### Code Editor 31 | 32 | Because code will be written for *DevOps The Hard Way*, you will need a code editor. For the purposes of this tutorial, you can use [Visual Studio Code](https://code.visualstudio.com/download), which is a free code editor. 33 | 34 | ### Terraform 35 | 36 | [Terraform Download](https://www.terraform.io/downloads.html) - This on version 1.9.6 37 | 38 | ### Docker 39 | To build the Docker image, you can use Docker Desktop for Windows or MacOS. 40 | 41 | [Docker Desktop](https://www.docker.com/products/docker-desktop) 42 | 43 | ### Source Control 44 | To store the code that you'll be writing, you can create your very own GitHub account to showcase your project. 45 | 46 | [GitHub](https://www.github.com) 47 | 48 | ### kubectl and kubelogin 49 | To authenticate and run commands against a Kubernetes cluster. 50 | 51 | [Install Tools | Kubernetes](https://kubernetes.io/docs/tasks/tools/) 52 | [Azure/kubelogin: A Kubernetes credential (exec) plugin implementing azure authentication](https://github.com/Azure/kubelogin) 53 | [How to switch to Azure kubelogin - Aptakube Blog](https://aptakube.com/blog/how-to-use-azure-kubelogin) 54 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ], 6 | "includeForks": true 7 | } --------------------------------------------------------------------------------