├── tf ├── aks │ ├── versions.tf │ ├── init.sh │ ├── main.tf │ ├── variables.tf │ ├── outputs.tf │ └── README.md ├── common │ ├── versions.tf │ ├── outputs.tf │ ├── variables.tf │ ├── main.tf │ ├── init.sh │ └── README.md └── core │ ├── versions.tf │ ├── variables.tf │ ├── outputs.tf │ ├── init.sh │ ├── README.md │ └── main.tf ├── assets ├── architecture.jpg └── architecture.pptx ├── .gitignore ├── scripts ├── init-env-vars.sh └── init-remote-state-backend.sh ├── LICENSE ├── azure-pipelines.yml └── README.md /tf/aks/versions.tf: -------------------------------------------------------------------------------- 1 | 2 | terraform { 3 | required_version = ">= 0.12" 4 | } 5 | -------------------------------------------------------------------------------- /tf/common/versions.tf: -------------------------------------------------------------------------------- 1 | 2 | terraform { 3 | required_version = ">= 0.12" 4 | } 5 | -------------------------------------------------------------------------------- /tf/core/versions.tf: -------------------------------------------------------------------------------- 1 | 2 | terraform { 3 | required_version = ">= 0.12" 4 | } 5 | -------------------------------------------------------------------------------- /assets/architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcorioland/terraform-azure-reference/HEAD/assets/architecture.jpg -------------------------------------------------------------------------------- /tf/common/outputs.tf: -------------------------------------------------------------------------------- 1 | output "resource_group_name" { 2 | value = module.common.resource_group_name 3 | } 4 | 5 | -------------------------------------------------------------------------------- /assets/architecture.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcorioland/terraform-azure-reference/HEAD/assets/architecture.pptx -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # .tfvars files 9 | *.tfvars 10 | -------------------------------------------------------------------------------- /tf/common/variables.tf: -------------------------------------------------------------------------------- 1 | variable "location" { 2 | description = "Azure location to use" 3 | } 4 | 5 | variable "tenant_id" { 6 | description = "The Azure tenant id" 7 | } 8 | 9 | -------------------------------------------------------------------------------- /tf/core/variables.tf: -------------------------------------------------------------------------------- 1 | variable "environment" { 2 | description = "Name of the environment" 3 | } 4 | 5 | variable "location" { 6 | description = "Azure location to use" 7 | } 8 | 9 | -------------------------------------------------------------------------------- /tf/core/outputs.tf: -------------------------------------------------------------------------------- 1 | output "resource_group_name" { 2 | value = azurerm_resource_group.rg.name 3 | } 4 | 5 | output "location" { 6 | value = var.location 7 | } 8 | 9 | output "environment" { 10 | value = var.environment 11 | } 12 | 13 | -------------------------------------------------------------------------------- /scripts/init-env-vars.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | export LOCATION=westeurope 6 | export COMMON_RESOURCE_GROUP_NAME=terraform-ref-rg 7 | export TF_STATE_STORAGE_ACCOUNT_NAME=tfstate2195 8 | export TF_STATE_CONTAINER_NAME=tfstate-ref 9 | export KEYVAULT_NAME=terraform-ref-kv -------------------------------------------------------------------------------- /tf/common/main.tf: -------------------------------------------------------------------------------- 1 | provider "azurerm" { 2 | version = "~>1.30" 3 | } 4 | 5 | terraform { 6 | backend "azurerm" { 7 | } 8 | } 9 | 10 | module "common" { 11 | source = "git@github.com:jcorioland/terraform-azure-ref-common-module" 12 | location = var.location 13 | tenant_id = var.tenant_id 14 | } 15 | 16 | -------------------------------------------------------------------------------- /tf/common/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | . ../../scripts/init-env-vars.sh 6 | 7 | terraform init -backend-config="storage_account_name=$TF_STATE_STORAGE_ACCOUNT_NAME" \ 8 | -backend-config="container_name=$TF_STATE_CONTAINER_NAME" \ 9 | -backend-config="access_key=$(az keyvault secret show --name tfstate-storage-key --vault-name $KEYVAULT_NAME --query value -o tsv)" \ 10 | -backend-config="key=common.tfstate" -------------------------------------------------------------------------------- /tf/aks/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | ENVIRONMENT_NAME=$1 6 | 7 | . ../../scripts/init-env-vars.sh 8 | 9 | terraform init -backend-config="storage_account_name=$TF_STATE_STORAGE_ACCOUNT_NAME" \ 10 | -backend-config="container_name=$TF_STATE_CONTAINER_NAME" \ 11 | -backend-config="access_key=$(az keyvault secret show --name tfstate-storage-key --vault-name $KEYVAULT_NAME --query value -o tsv)" \ 12 | -backend-config="key=$ENVIRONMENT_NAME.aks.tfstate" -------------------------------------------------------------------------------- /tf/core/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | ENVIRONMENT_NAME=$1 6 | 7 | . ../../scripts/init-env-vars.sh 8 | 9 | terraform init -backend-config="storage_account_name=$TF_STATE_STORAGE_ACCOUNT_NAME" \ 10 | -backend-config="container_name=$TF_STATE_CONTAINER_NAME" \ 11 | -backend-config="access_key=$(az keyvault secret show --name tfstate-storage-key --vault-name $KEYVAULT_NAME --query value -o tsv)" \ 12 | -backend-config="key=$ENVIRONMENT_NAME.core.tfstate" -------------------------------------------------------------------------------- /tf/core/README.md: -------------------------------------------------------------------------------- 1 | # Core Module 2 | 3 | This module is responsible for the deployment for all the core components and resources for a given environment, like: 4 | 5 | - The resource group 6 | - The virtual network and subnets 7 | - The peerings 8 | 9 | # Usage 10 | 11 | ```bash 12 | ENVIRONMENT_NAME="development" 13 | LOCATION="francecentral" 14 | 15 | # init terraform and backend storage 16 | ./init.sh $ENVIRONMENT_NAME 17 | 18 | terraform apply -var environment=$ENVIRONMENT_NAME -var location=$LOCATION -auto-approve 19 | ``` -------------------------------------------------------------------------------- /tf/aks/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | backend "azurerm" { 3 | } 4 | } 5 | 6 | module "aks" { 7 | source = "git@github.com:jcorioland/terraform-azure-ref-aks-module" 8 | environment = var.environment 9 | location = var.location 10 | kubernetes_version = var.kubernetes_version 11 | service_principal_client_id = var.service_principal_client_id 12 | service_principal_client_secret = var.service_principal_client_secret 13 | ssh_public_key = var.ssh_public_key 14 | } 15 | 16 | -------------------------------------------------------------------------------- /tf/common/README.md: -------------------------------------------------------------------------------- 1 | # Common Module 2 | 3 | This modules is responsible for deploying the common stuff required for the reference archicture for Terraform on Azure.The module is developed in its own repository [here](https://github.com/jcorioland/terraform-azure-ref-common-module). 4 | 5 | 6 | # Usage 7 | 8 | Fill environment variables and run script: 9 | 10 | ```bash 11 | export TF_VAR_location="francecentral" 12 | export TF_VAR_tenant_id="" 13 | 14 | # init terraform and backend storage 15 | ./init.sh 16 | 17 | terraform apply -auto-approve 18 | ``` -------------------------------------------------------------------------------- /tf/aks/variables.tf: -------------------------------------------------------------------------------- 1 | variable "environment" { 2 | description = "Name of the environment" 3 | } 4 | 5 | variable "location" { 6 | description = "Azure location to use" 7 | } 8 | 9 | variable "kubernetes_version" { 10 | description = "The Kubernetes version to use" 11 | } 12 | 13 | variable "service_principal_client_id" { 14 | description = "The client id of the service principal to be used by AKS" 15 | } 16 | 17 | variable "service_principal_client_secret" { 18 | description = "The client secret of the service principal to be used by AKS" 19 | } 20 | 21 | variable "ssh_public_key" { 22 | description = "The SSH public key to use with AKS" 23 | } 24 | 25 | -------------------------------------------------------------------------------- /tf/aks/outputs.tf: -------------------------------------------------------------------------------- 1 | output "resource_group_name" { 2 | value = module.aks.resource_group_name 3 | } 4 | 5 | output "aks_client_key" { 6 | value = module.aks.aks_client_key 7 | } 8 | 9 | output "aks_client_certificate" { 10 | value = module.aks.aks_client_certificate 11 | } 12 | 13 | output "aks_cluster_ca_certificate" { 14 | value = module.aks.aks_cluster_ca_certificate 15 | } 16 | 17 | output "aks_cluster_username" { 18 | value = module.aks.aks_cluster_username 19 | } 20 | 21 | output "aks_cluster_password" { 22 | value = module.aks.aks_cluster_password 23 | } 24 | 25 | output "aks_kube_config" { 26 | value = module.aks.aks_kube_config 27 | } 28 | 29 | output "aks_host" { 30 | value = module.aks.aks_host 31 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Julien Corioland 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 | -------------------------------------------------------------------------------- /tf/aks/README.md: -------------------------------------------------------------------------------- 1 | # Azure Kubernetes Service Module 2 | 3 | This module is responsible for the deployment of Azure Kubernetes Service inside a given environment. The module is developed in its own repository [here](https://github.com/jcorioland/terraform-azure-ref-aks-module). 4 | 5 | The [environment-base](../environment-base/README.md) deployment has to be run before this one. 6 | 7 | # Usage 8 | 9 | Create a service principal for Azure Kubernetes Service, following [this documentation](https://docs.microsoft.com/en-us/azure/aks/kubernetes-service-principal). 10 | 11 | Export service principal client id and client secret into Terraform environment variables: 12 | 13 | ```bash 14 | export TF_VAR_service_principal_client_id ="" 15 | export TF_VAR_service_principal_client_secret ="" 16 | export TF_VAR_environment = "development" 17 | export TF_VAR_location = "francecentral" 18 | export TF_VAR_kubernetes_version = "1.15.5" 19 | export TF_VAR_ssh_public_key = "" 20 | ``` 21 | 22 | Then run the following script to deploy in the `deployment environment`. 23 | 24 | ```bash 25 | ENVIRONMENT_NAME="development" 26 | 27 | # init terraform and backend storage 28 | ./init.sh $ENVIRONMENT_NAME 29 | 30 | terraform apply -auto-approve 31 | ``` -------------------------------------------------------------------------------- /tf/core/main.tf: -------------------------------------------------------------------------------- 1 | provider "azurerm" { 2 | version = "~> 1.39" 3 | } 4 | 5 | terraform { 6 | backend "azurerm" { 7 | } 8 | } 9 | 10 | resource "azurerm_resource_group" "rg" { 11 | name = "tf-ref-${var.environment}-rg" 12 | location = var.location 13 | } 14 | 15 | resource "azurerm_virtual_network" "aks" { 16 | name = "aks-vnet" 17 | address_space = ["10.1.0.0/16"] 18 | location = azurerm_resource_group.rg.location 19 | resource_group_name = azurerm_resource_group.rg.name 20 | } 21 | 22 | resource "azurerm_subnet" "aks" { 23 | name = "aks-subnet" 24 | resource_group_name = azurerm_resource_group.rg.name 25 | virtual_network_name = azurerm_virtual_network.aks.name 26 | address_prefix = "10.1.0.0/24" 27 | } 28 | 29 | resource "azurerm_virtual_network" "backend" { 30 | name = "backend-vnet" 31 | address_space = ["10.2.0.0/16"] 32 | location = azurerm_resource_group.rg.location 33 | resource_group_name = azurerm_resource_group.rg.name 34 | } 35 | 36 | resource "azurerm_subnet" "backend" { 37 | name = "backend-subnet" 38 | resource_group_name = azurerm_resource_group.rg.name 39 | virtual_network_name = azurerm_virtual_network.backend.name 40 | address_prefix = "10.2.0.0/24" 41 | } 42 | 43 | resource "azurerm_virtual_network_peering" "peering1" { 44 | name = "aks2backend" 45 | resource_group_name = azurerm_resource_group.rg.name 46 | virtual_network_name = azurerm_virtual_network.aks.name 47 | remote_virtual_network_id = azurerm_virtual_network.backend.id 48 | } 49 | 50 | resource "azurerm_virtual_network_peering" "peering2" { 51 | name = "backend2aks" 52 | resource_group_name = azurerm_resource_group.rg.name 53 | virtual_network_name = azurerm_virtual_network.backend.name 54 | remote_virtual_network_id = azurerm_virtual_network.aks.id 55 | } 56 | 57 | -------------------------------------------------------------------------------- /scripts/init-remote-state-backend.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | . ./init-env-vars.sh 6 | 7 | # Create the resource group 8 | if ( `az group exists --resource-group $COMMON_RESOURCE_GROUP_NAME` == "true" ); 9 | then 10 | echo "Resource group $COMMON_RESOURCE_GROUP_NAME exists..." 11 | else 12 | echo "Creating $COMMON_RESOURCE_GROUP_NAME resource group..." 13 | az group create -n $COMMON_RESOURCE_GROUP_NAME -l $LOCATION 14 | echo "Resource group $COMMON_RESOURCE_GROUP_NAME created." 15 | fi 16 | 17 | # Create the storage account 18 | if ( `az storage account check-name --name $TF_STATE_STORAGE_ACCOUNT_NAME --query 'nameAvailable'` == "true" ); 19 | then 20 | echo "Creating $TF_STATE_STORAGE_ACCOUNT_NAME storage account..." 21 | az storage account create -g $COMMON_RESOURCE_GROUP_NAME -l $LOCATION \ 22 | --name $TF_STATE_STORAGE_ACCOUNT_NAME \ 23 | --sku Standard_LRS \ 24 | --encryption-services blob 25 | echo "Storage account $TF_STATE_STORAGE_ACCOUNT_NAME created." 26 | else 27 | echo "Storage account $TF_STATE_STORAGE_ACCOUNT_NAME exists..." 28 | fi 29 | 30 | # Retrieve the storage account key 31 | echo "Retrieving storage account key..." 32 | ACCOUNT_KEY=$(az storage account keys list --resource-group $COMMON_RESOURCE_GROUP_NAME --account-name $TF_STATE_STORAGE_ACCOUNT_NAME --query [0].value -o tsv) 33 | echo "Storage account key retrieved." 34 | 35 | # Create a storage container (for the Terraform State) 36 | if ( `az storage container exists --name $TF_STATE_CONTAINER_NAME --account-name $TF_STATE_STORAGE_ACCOUNT_NAME --account-key $ACCOUNT_KEY --query exists` == "true" ); 37 | then 38 | echo "Storage container $TF_STATE_CONTAINER_NAME exists..." 39 | else 40 | echo "Creating $TF_STATE_CONTAINER_NAME storage container..." 41 | az storage container create --name $TF_STATE_CONTAINER_NAME --account-name $TF_STATE_STORAGE_ACCOUNT_NAME --account-key $ACCOUNT_KEY 42 | echo "Storage container $TF_STATE_CONTAINER_NAME created." 43 | fi 44 | 45 | # Create an Azure KeyVault 46 | if ( `az keyvault list --query "[?name == '$KEYVAULT_NAME'].name | [0] == '$KEYVAULT_NAME'"` == "true" ); 47 | then 48 | echo "Key vault $KEYVAULT_NAME exists..." 49 | else 50 | echo "Creating $KEYVAULT_NAME key vault..." 51 | az keyvault create -g $COMMON_RESOURCE_GROUP_NAME -l $LOCATION --name $KEYVAULT_NAME 52 | echo "Key vault $KEYVAULT_NAME created." 53 | fi 54 | 55 | # Store the Terraform State Storage Key into KeyVault 56 | echo "Store storage access key into key vault secret..." 57 | az keyvault secret set --name tfstate-storage-key --value $ACCOUNT_KEY --vault-name $KEYVAULT_NAME -o none 58 | 59 | echo "Key vault secret created." 60 | 61 | # Display information 62 | echo "Azure Storage Account and KeyVault have been created." 63 | echo "Run the following command to initialize Terraform to store its state into Azure Storage:" 64 | echo "terraform init -backend-config=\"storage_account_name=$TF_STATE_STORAGE_ACCOUNT_NAME\" -backend-config=\"container_name=$TF_STATE_CONTAINER_NAME\" -backend-config=\"access_key=\$(az keyvault secret show --name tfstate-storage-key --vault-name $KEYVAULT_NAME --query value -o tsv)\" -backend-config=\"key=terraform-ref-architecture-tfstate\"" -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Azure Pipeline that deploys the whole infrastructure continuously, using Terraform 2 | 3 | trigger: none 4 | 5 | variables: 6 | vmImageName: 'ubuntu-latest' 7 | terraformVersion: 0.12.3 8 | azureSubscriptionServiceConnectionName: 'jucoriol' 9 | tfStateResourceGroupName: 'terraform-ref-fr-rg' 10 | tfStateAzureStorageAccountSku: 'Standard_LRS' 11 | tfStateAzureStorageAccountName: 'tfstate201910' 12 | tfStateContainerName: 'tfstate-ref' 13 | # This pipeline uses variables and secrets defined in the Azure DevOps portal (see: https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch#secret-variables) 14 | # location: the Azure region where to deploy 15 | # tenantId: the identifier of the Azure tenant 16 | # environmentName: the name of the environment to deploy 17 | # kubernetesVersion: the Kubernetes version to deploy 18 | # aksServicePrincipalClientId: the service principal identifier to use with AKS 19 | # aksServicePrincipalClientSecret: the service principal secret to use with AKS 20 | # sshKeySecureFileName: a secure file that contains the SSH private key to use 21 | # sshPublicKey: the SSH public key to use 22 | # sshKnownHostsEntry: the SSH known hosts entry 23 | 24 | stages: 25 | - stage: CommonModule 26 | displayName: Common Module 27 | jobs: 28 | # Common Module 29 | - job: CommonModule 30 | displayName: Deploy the Terraform Common module 31 | pool: 32 | vmImage: $(vmImageName) 33 | steps: 34 | - task: InstallSSHKey@0 35 | displayName: 'Install an SSH key' 36 | inputs: 37 | knownHostsEntry: $(sshKnownHostsEntry) 38 | sshPublicKey: $(sshPublicKey) 39 | sshKeySecureFile: $(sshKeySecureFileName) 40 | - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-installer.TerraformInstaller@0 41 | displayName: 'Use Terraform $(terraformVersion)' 42 | inputs: 43 | terraformVersion: $(terraformVersion) 44 | - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0 45 | displayName: 'terraform init' 46 | inputs: 47 | command: init 48 | workingDirectory: '$(System.DefaultWorkingDirectory)/tf/common' 49 | backendType: azurerm 50 | backendServiceArm: $(azureSubscriptionServiceConnectionName) 51 | ensureBackend: true 52 | backendAzureRmResourceGroupLocation: $(location) 53 | backendAzureRmResourceGroupName: $(tfStateResourceGroupName) 54 | backendAzureRmStorageAccountName: $(tfStateAzureStorageAccountName) 55 | backendAzureRmStorageAccountSku: $(tfStateAzureStorageAccountSku) 56 | backendAzureRmContainerName: $(tfStateContainerName) 57 | backendAzureRmKey: 'common.tfstate' 58 | - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0 59 | displayName: 'terraform validate' 60 | inputs: 61 | workingDirectory: '$(System.DefaultWorkingDirectory)/tf/common' 62 | - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0 63 | displayName: 'terraform apply' 64 | inputs: 65 | command: apply 66 | workingDirectory: '$(System.DefaultWorkingDirectory)/tf/common' 67 | environmentServiceName: $(azureSubscriptionServiceConnectionName) 68 | commandOptions: '-auto-approve -var location=$(location) -var tenant_id=$(tenantId)' 69 | - stage: CoreNetworkingModule 70 | displayName: Core Networking Module 71 | jobs: 72 | # Core Networking Module 73 | - job: CoreNetworkingModule 74 | displayName: Deploy the Terraform Core Networking module 75 | pool: 76 | vmImage: $(vmImageName) 77 | steps: 78 | - task: InstallSSHKey@0 79 | displayName: 'Install an SSH key' 80 | inputs: 81 | knownHostsEntry: $(sshKnownHostsEntry) 82 | sshPublicKey: $(sshPublicKey) 83 | sshKeySecureFile: $(sshKeySecureFileName) 84 | - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-installer.TerraformInstaller@0 85 | displayName: 'Use Terraform $(terraformVersion)' 86 | inputs: 87 | terraformVersion: $(terraformVersion) 88 | - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0 89 | displayName: 'terraform init' 90 | inputs: 91 | command: init 92 | workingDirectory: '$(System.DefaultWorkingDirectory)/tf/core' 93 | backendType: azurerm 94 | backendServiceArm: $(azureSubscriptionServiceConnectionName) 95 | ensureBackend: true 96 | backendAzureRmResourceGroupLocation: $(location) 97 | backendAzureRmResourceGroupName: $(tfStateResourceGroupName) 98 | backendAzureRmStorageAccountName: $(tfStateAzureStorageAccountName) 99 | backendAzureRmStorageAccountSku: $(tfStateAzureStorageAccountSku) 100 | backendAzureRmContainerName: $(tfStateContainerName) 101 | backendAzureRmKey: 'core-$(environmentName).tfstate' 102 | - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0 103 | displayName: 'terraform validate' 104 | inputs: 105 | workingDirectory: '$(System.DefaultWorkingDirectory)/tf/core' 106 | - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0 107 | displayName: 'terraform apply' 108 | inputs: 109 | command: apply 110 | workingDirectory: '$(System.DefaultWorkingDirectory)/tf/core' 111 | environmentServiceName: $(azureSubscriptionServiceConnectionName) 112 | commandOptions: '-auto-approve -var location=$(location) -var environment=$(environmentName)' 113 | - stage: AzureKubernetesModule 114 | displayName: Azure Kubernetes Service Module 115 | jobs: 116 | # Azure Kubernetes Service Module 117 | - job: AzureKubernetesModule 118 | displayName: Deploy the Terraform Azure Kubernetes Service module 119 | pool: 120 | vmImage: $(vmImageName) 121 | steps: 122 | - task: InstallSSHKey@0 123 | displayName: 'Install an SSH key' 124 | inputs: 125 | knownHostsEntry: $(sshKnownHostsEntry) 126 | sshPublicKey: $(sshPublicKey) 127 | sshKeySecureFile: $(sshKeySecureFileName) 128 | - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-installer.TerraformInstaller@0 129 | displayName: 'Use Terraform $(terraformVersion)' 130 | inputs: 131 | terraformVersion: $(terraformVersion) 132 | - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0 133 | displayName: 'terraform init' 134 | inputs: 135 | command: init 136 | workingDirectory: '$(System.DefaultWorkingDirectory)/tf/aks' 137 | backendType: azurerm 138 | backendServiceArm: $(azureSubscriptionServiceConnectionName) 139 | ensureBackend: true 140 | backendAzureRmResourceGroupLocation: $(location) 141 | backendAzureRmResourceGroupName: $(tfStateResourceGroupName) 142 | backendAzureRmStorageAccountName: $(tfStateAzureStorageAccountName) 143 | backendAzureRmStorageAccountSku: $(tfStateAzureStorageAccountSku) 144 | backendAzureRmContainerName: $(tfStateContainerName) 145 | backendAzureRmKey: 'aks-$(environmentName).tfstate' 146 | - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0 147 | displayName: 'terraform validate' 148 | inputs: 149 | workingDirectory: '$(System.DefaultWorkingDirectory)/tf/aks' 150 | - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0 151 | displayName: 'terraform apply' 152 | inputs: 153 | command: apply 154 | workingDirectory: '$(System.DefaultWorkingDirectory)/tf/aks' 155 | environmentServiceName: $(azureSubscriptionServiceConnectionName) 156 | commandOptions: '-auto-approve -var location=$(location) -var environment=$(environmentName) -var kubernetes_version=$(kubernetesVersion) -var service_principal_client_id="$(aksServicePrincipalClientId)" -var service_principal_client_secret="$(aksServicePrincipalClientSecret)" -var ssh_public_key="$(sshPublicKey)"' 157 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform on Azure Reference Architecture 2 | 3 | This repository helps you to implement Infrastructure as Code best practices using Terraform and Microsoft Azure. 4 | 5 | If you are not familiar with Infrastructure as Code (IaC), read [this page](https://docs.microsoft.com/en-us/azure/devops/learn/what-is-infrastructure-as-code) first. 6 | 7 | ***Note: this "reference architecture" is still a work in progress. If you have any question or feedback, feel free to open an issue to start the discussion :)*** 8 | 9 | ## Overview of the architecture 10 | 11 | *Note: in this example we don't pay attention as the application that is deployed itself as the focus is on deploying the infrastructure.* 12 | 13 | This repository guides you in deploying the following architecture on Microsoft Azure, using [Terraform](https://www.terraform.io/intro/index.html). 14 | 15 | ![Sample Architecture](assets/architecture.jpg) 16 | 17 | There are 3 environments (Dev, QA and Prod). Each of the environment contains: 18 | - An [Azure Kubernetes Service](https://docs.microsoft.com/en-us/azure/aks/intro-kubernetes) cluster, in its own virtual network 19 | - A backend virtual network, that contains one or more virtual machines that act as bastion / jump boxes 20 | - An [Azure Database for MySQL](https://docs.microsoft.com/en-us/azure/mysql/overview) service instance with [virtual network service endpoint](https://docs.microsoft.com/en-us/azure/mysql/concepts-data-access-and-security-vnet) so it can be reached by jumbbox and services running in AKS (Backend virtual network and AKS virtual network are peered together) 21 | 22 | There are also common services used here: 23 | - [Azure Container Registry](https://docs.microsoft.com/en-us/azure/container-registry/), to store the Docker image 24 | - [Azure KeyVault](https://docs.microsoft.com/en-us/azure/key-vault/), to store the application secrets securely 25 | - [Azure Firewall](https://docs.microsoft.com/en-us/azure/firewall/), to protect the application 26 | 27 | We will also use [Azure Monitor](https://docs.microsoft.com/en-us/azure/azure-monitor/) with logs analytics to monitor all this infrastructure (and potentially the application). 28 | 29 | Finally, all the infrastructure will be describe using Terraform HCL manifests stored in GitHub (this repository) and we will use [Azure DevOps Pipelines](https://docs.microsoft.com/en-us/azure/devops/pipelines/get-started/overview?view=azure-devops) to deploy all the infrastructure. 30 | 31 | *Note: technically speaking, the pipeline that automates Terraform deployment can be hosted in any other CI/CD tool, like Jenkins, for example.* 32 | 33 | As you can see, some parts of the infrastructure are specific for each environment, some other will be shared. This will help to illustrate how to handle deployments of different resources having different lifecycle. 34 | 35 | ## Prerequisites 36 | 37 | In order to follow this documentation and try it by yourself, you need: 38 | 39 | - A Microsoft Azure account. You can create a free trial account [here](https://azure.microsoft.com/en-us/free/). 40 | - [Install Terraform](https://learn.hashicorp.com/terraform/getting-started/install.html) on your machine, if you want to experiment the scripts locally 41 | - Fork this repository into your GitHub account 42 | - An Azure DevOps organization. You can get started for free [here](https://azure.microsoft.com/en-us/services/devops/?nav=min) if you do not already use Azure DevOps 43 | - Install the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) 44 | 45 | If you are not familiar with Terraform yet, we strongly recommend that you follow the Terraform on Azure getting started guide on [this page](https://learn.hashicorp.com/terraform/azure/intro_az). 46 | 47 | ## Terraform State 48 | 49 | Terraform needs to maintain state between the deployments, to make sure to what needs to be added or removed. 50 | 51 | Storing Terraform state remotely is a best practice to make sure you don't loose it across your different execution environment (from your machine to any CI/CD agent). It is possible to use Azure Storage as a remote backend for Terraform state. 52 | 53 | To initialize the the Azure Storage backend, you have to execute the [scripts/init-remote-state-backend.sh](scripts/init-remote-state-backend.sh): 54 | 55 | ```bash 56 | #!/bin/bash 57 | 58 | set -e 59 | 60 | . ./init-env-vars.sh 61 | 62 | # Create the resource group 63 | echo "Creating $COMMON_RESOURCE_GROUP_NAME resource group..." 64 | az group create -n $COMMON_RESOURCE_GROUP_NAME -l $LOCATION 65 | 66 | echo "Resource group $COMMON_RESOURCE_GROUP_NAME created." 67 | 68 | # Create the storage account 69 | echo "Creating $TF_STATE_STORAGE_ACCOUNT_NAME storage account..." 70 | az storage account create -g $COMMON_RESOURCE_GROUP_NAME -l $LOCATION \ 71 | --name $TF_STATE_STORAGE_ACCOUNT_NAME \ 72 | --sku Standard_LRS \ 73 | --encryption-services blob 74 | 75 | echo "Storage account $TF_STATE_STORAGE_ACCOUNT_NAME created." 76 | 77 | # Retrieve the storage account key 78 | echo "Retrieving storage account key..." 79 | ACCOUNT_KEY=$(az storage account keys list --resource-group $COMMON_RESOURCE_GROUP_NAME --account-name $TF_STATE_STORAGE_ACCOUNT_NAME --query [0].value -o tsv) 80 | 81 | echo "Storage account key retrieved." 82 | 83 | # Create a storage container (for the Terraform State) 84 | echo "Creating $TF_STATE_CONTAINER_NAME storage container..." 85 | az storage container create --name $TF_STATE_CONTAINER_NAME --account-name $TF_STATE_STORAGE_ACCOUNT_NAME --account-key $ACCOUNT_KEY 86 | 87 | echo "Storage container $TF_STATE_CONTAINER_NAME created." 88 | 89 | # Create an Azure KeyVault 90 | echo "Creating $KEYVAULT_NAME key vault..." 91 | az keyvault create -g $COMMON_RESOURCE_GROUP_NAME -l $LOCATION --name $KEYVAULT_NAME 92 | 93 | echo "Key vault $KEYVAULT_NAME created." 94 | 95 | # Storage the Terraform State Storage Key into KeyVault 96 | echo "Storage storage access key into key vault secret..." 97 | az keyvault secret set --name tfstate-storage-key --value $ACCOUNT_KEY --vault-name $KEYVAULT_NAME 98 | 99 | echo "Key vault secret created." 100 | 101 | # Display information 102 | echo "Azure Storage Account and KeyVault have been created." 103 | echo "Run the following command to initialize Terraform to store its state into Azure Storage:" 104 | echo "terraform init -backend-config=\"storage_account_name=$TF_STATE_STORAGE_ACCOUNT_NAME\" -backend-config=\"container_name=$TF_STATE_CONTAINER_NAME\" -backend-config=\"access_key=\$(az keyvault secret show --name tfstate-storage-key --vault-name $KEYVAULT_NAME --query value -o tsv)\" -backend-config=\"key=terraform-ref-architecture-tfstate\"" 105 | ``` 106 | 107 | This script is responsible for: 108 | - Creating an Azure Resource Group 109 | - Creating an Azure Storage Account 110 | - Retrieving the Storage Account access key 111 | - Creating a container in the Storage Account (where the Terraform state will be stored) 112 | - Creating an Azure Key Vault 113 | - Storing the the Storage Account access key into a Key Vault secret named `tfstate-storage-key` 114 | 115 | Once completed, the script will print a the `terraform init` command line that you will use later to init Terraform to use this backend, like: 116 | 117 | ```bash 118 | terraform init -backend-config="storage_account_name=$STORAGE_ACCOUNT_NAME" -backend-config="container_name=$CONTAINER_NAME" -backend-config="access_key=$(az keyvault secret show --name tfstate-storage-key --vault-name $KEYVAULT_NAME --query value -o tsv)" -backend-config="key=terraform-ref-architecture-tfstate" 119 | ``` 120 | 121 | *Note: If you are working with multiple cloud providers, you may not want to spare storage state into each provider. For this reason, you may want to look the [Terraform Cloud remote state management](https://www.hashicorp.com/blog/introducing-terraform-cloud-remote-state-management) that has been introduced by HashiCorp.* 122 | 123 | ## Terraform modules 124 | 125 | ### What are Terraform modules? 126 | 127 | [Terraform modules](https://www.terraform.io/docs/modules/index.html) are used to group together a set of resources that have the same lifecycle. It is not mandatory to use modules, but in some case it might be useful. 128 | 129 | Like all mechanisms that allow to mutualize/factorize code, modules can also be dangerous: you don't want to have a big module that contains everything that you need to deploy and make all the resources strongly coupled together. This could lead to a monolith that will be really hard to maintain and to deploy. 130 | 131 | Here are some questions that you can ask yourself for before writing a module: 132 | - Do have all the resources involved the same lifecycle? 133 | - Will the resources be deployed all together all the time? 134 | - Will the resources be updated all together all the time? 135 | - Will the resources be destroyed all together all the time? 136 | - Is there multiple resources involved? If there is just one, the module is probably useless 137 | - From an architectural/functionnal perspective, does it makes sense to group all these resources together? (network, compute, storage etc...) 138 | - Does any of the resource involved depend from a resource that is not in this module? 139 | 140 | If the answer to these questions is `no` most of the time, then you probably don't need to write a module. 141 | 142 | Sometime, instead of writing a big module, it can be useful to write multiple ones and nest them together, depending on the scenario you want to cover. 143 | 144 | You can read more about Terraform modules on [this page of the Terraform documentation](https://www.terraform.io/docs/modules/index.html). 145 | 146 | ### How to test Terraform modules? 147 | 148 | Like every piece of code, Terraform modules can be tested. [Terratest](https://github.com/gruntwork-io/terratest) is the tool I have used to write the test of the modules available in this repository. 149 | 150 | ## Modules of this reference architecture 151 | 152 | This reference architecture uses different Terraform module to deploy different set of components and deal with their different lifecyle: 153 | 154 | ### Common Module 155 | 156 | It contains all the common resources like ACR, KeyVault... 157 | This module is defined in its own [GitHub repository](https://github.com/jcorioland/terraform-azure-ref-common-module). 158 | 159 | [![Build Status](https://dev.azure.com/jcorioland-msft/terraform-azure-reference/_apis/build/status/jcorioland.terraform-azure-ref-common-module?branchName=master)](https://dev.azure.com/jcorioland-msft/terraform-azure-reference/_build/latest?definitionId=33&branchName=master) 160 | 161 | More documentation [here](tf/common/README.md). 162 | 163 | ### Core Environment Module 164 | 165 | It contains the base components for an environment (resource group, network...). 166 | More documentation [here](tf/core/README.md). 167 | 168 | ### Azure Kubernetes Service Module 169 | 170 | It contains everything needed to deploy an Azure Kubernetes Service cluster inside a given environment. 171 | It is defined in its own [GitHub repository](https://github.com/jcorioland/terraform-azure-ref-aks-module). 172 | 173 | [![Build Status](https://dev.azure.com/jcorioland-msft/terraform-azure-reference/_apis/build/status/jcorioland.terraform-azure-ref-aks-module?branchName=master)](https://dev.azure.com/jcorioland-msft/terraform-azure-reference/_build/latest?definitionId=32&branchName=master) 174 | 175 | More documentation [here](tf/aks/README.md). --------------------------------------------------------------------------------