diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9c74b5f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +# EditorConfig is awesome: https://EditorConfig.org + +root = true + +[**] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c4652ef --- /dev/null +++ b/.gitignore @@ -0,0 +1,170 @@ +# Created by https://www.gitignore.io/api/macos,terraform,terragrunt,virtualenv,intellij+all +# Edit at https://www.gitignore.io/?templates=macos,terraform,terragrunt,virtualenv,intellij+all + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Terraform ### +# Local .terraform directories +**/.terraform/* +**/.terraform.lock.hcl + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log + +# Ignore any .tfvars files that are generated automatically for each Terraform run. Most +# .tfvars files are managed as part of configuration and so should be included in +# version control. +# +# example.tfvars + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Include override files you do wish to add to version control using negated pattern +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* + +### Terragrunt ### +# terragrunt cache directories +**/.terragrunt-cache/* + +### VirtualEnv ### +# Virtualenv +# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ +.Python +pyvenv.cfg +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pip-selfcheck.json +env.sh + +**/.temp/*kube_config + +# End of https://www.gitignore.io/api/macos,terraform,terragrunt,virtualenv,intellij+all \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f0dcd93 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# Hub and spoke AKS + +This project contains sources to build an hub and spoke infrastructure on Azure with multiple AKS environments. + +## Architecture overview + +The archirecture is built on an [hub and spoke network topology](https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/hybrid-networking/hub-spoke?tabs=cli). + +## Requirements + +### Tools + +These tools must be present in your environment to execute the different stacks of the project: + +- [Git](https://git-scm.com/downloads) +- [Python 3.7](https://www.python.org/downloads/release/python-370/) +- [Azure CLI 2.18.0](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) +- [Terraform 0.14.5](https://www.terraform.io/downloads.html) +- [kubectl 1.20.1](https://kubernetes.io/docs/tasks/tools/install-kubectl/) +- [helm 3.2.1](https://helm.sh/docs/intro/install/) + +> You can build a Docker base image including all these requirements in order to guarantee that all team members and your CI tool use exactly the same environment to work with the project. + +### Service principal for Terraform + +[Setup Service Principal for Terraform](docs/tf_azure_authent.md) + +### Backend + +Terraform needs a shared storage to store state files. +In Azure, stores the state as a Blob with the given Key within the Blob Container within the Blob Storage Account. This backend also supports state locking and consistency checking via native capabilities of Azure Blob Storage. + +[Create the terraform backend if it doesn't already exists](docs/tf_backend.md) + +## Deployment + +### Infrastructure + +The infrastructure is divided in two different terraform stacks containing resources which will have different lifecycle: + +- [`aks`](terraform/aks/README.md): + - implements an AKS environment + - use terraform workspace to manage multiple environments with their specificities +- [`hub`](terraform/hub/README.md): implements the hub containing cross environment components \ No newline at end of file diff --git a/docs/tf_azure_authent.md b/docs/tf_azure_authent.md new file mode 100644 index 0000000..459cefc --- /dev/null +++ b/docs/tf_azure_authent.md @@ -0,0 +1,145 @@ +# Terraform authentication for Azure + +Terraform needs to be authenticated in order to interact with Azure resources. + +## Authentication methods + +Microsoft Azure offers a few authentication methods that allow Terraform to deploy resources, and one of them is an SP account. + +The reason an SP account is better than other methods is that we don’t need to log in to Azure before running Terraform. + +With the other methods (Azure CLI, or Cloud Shell), we need to login to Azure using az login or Cloud Shell. + +## Creating a Service Principal using the Azure CLI + +Firstly, login to the Azure CLI using: + +```bash +$ az login +``` + +Once logged in - it's possible to list the Subscriptions associated with the account via: + +```bash +$ az account list +``` + +The output (similar to below) will display one or more Subscriptions - with the id field being the subscription_id field referenced above. + +```json +[ + { + "cloudName": "AzureCloud", + "id": "00000000-0000-0000-0000-000000000000", + "isDefault": true, + "name": "PAYG Subscription", + "state": "Enabled", + "tenantId": "00000000-0000-0000-0000-000000000000", + "user": { + "name": "user@example.com", + "type": "user" + } + } +] +``` + +Should you have more than one Subscription, you can specify the Subscription to use via the following command: + +```bash +$ az account set --subscription="00000000-0000-0000-0000-000000000000" +``` + +We can now create the Service Principal which will have permissions to manage resources in the specified Subscription using the following command: + +```bash +$ az ad sp create-for-rbac --name="service_terraform" --role="Owner" --scopes="/subscriptions/00000000-0000-0000-0000-000000000000" +``` + +> The `Owner` role is necessary in order terraform will be able to assign roles on resources it will create. + +This command will output 5 values: + +```json +{ + "appId": "00000000-0000-0000-0000-000000000000", + "displayName": "service_terraform", + "name": "http://azure-cli-2017-06-05-10-41-15", + "password": "0000-0000-0000-0000-000000000000", + "tenant": "00000000-0000-0000-0000-000000000000" +} +``` + +Finally, it's possible to test these values work as expected by first logging in: + +```bash +$ az login --service-principal -u CLIENT_ID -p CLIENT_SECRET --tenant TENANT_ID +``` + +Once logged in as the Service Principal - we should be able to list the VM sizes by specifying an Azure region, for example here we use the West US region: + +```bash +$ az vm list-sizes --location francecentral +``` + +Finally, since we're logged into the Azure CLI as a Service Principal we recommend logging out of the Azure CLI (but you can instead log in using your user account): + +```bash +$ az logout +``` + +## Configuring the Service Principal in Terraform + +As we've obtained the credentials for this Service Principal - it's possible to configure them in a few different ways but it is important to avoid storing them in source code. +We prefer storing the credentials as Environment Variables, for example: + +```bash +$ export ARM_CLIENT_ID="00000000-0000-0000-0000-000000000000" +$ export ARM_CLIENT_SECRET="00000000-0000-0000-0000-000000000000" +$ export ARM_SUBSCRIPTION_ID="00000000-0000-0000-0000-000000000000" +$ export ARM_TENANT_ID="00000000-0000-0000-0000-000000000000" +``` + +These values map to the Terraform variables like so: + +- appId is the client_id defined above. +- password is the client_secret defined above. +- tenant is the tenant_id defined above. + +> You can create your own `env.sh` script at the root of the project. Take example upon the model [env.sh.example](../env.sh.example). + +## Add Azure AD permissions + +Terraform needs to create Azure AD applications and Service Principals. +So we need to add the role `Global Administrator` to the Terraform Service Principal `service_terraform`. + +## Check Terraform works + +You can create a `provider.tf` file: + +```hcl +terraform { + required_providers { + azurerm = { + source = "azurerm" + version = "= 2.45.1" + } + azuread = { + source = "azuread" + version = "= 1.3.0" + } + } +} + +provider "azurerm" { + features {} +} +``` + +And try some Terraform commands: + +```bash +$ terraform init +$ terraform plan +``` + +You will be sure of the correct functioning when you will create resources of different types with the providers `azurerm` and `azuread`. diff --git a/docs/tf_backend.md b/docs/tf_backend.md new file mode 100644 index 0000000..093aef9 --- /dev/null +++ b/docs/tf_backend.md @@ -0,0 +1,83 @@ +# Terraform backend + +The backend can be provision with Terraform, but the corresponding state file will not be stored in a share storage. On the other hand it is created once and rarely needs to be modified. So, we will create it manually with Azure CLI. + +## Create account storage with Azure CLI + +Eventually adapt the variables in the script [backend.sh](../scripts/backend.sh) and execute it. + +> Note the name of your storage account and the associated access key, you will need it later. + +## Configure the backend in Terraform + +Export the storage access key as environment variable in order that Terraform can use it: + +```bash +export ARM_ACCESS_KEY= +``` + +Configure the backend in the `provider.tf` file: + +```hcl +terraform { + + # Terraform backend configuration + backend "azurerm" { + resource_group_name = "terraform-backends" + storage_account_name = "terraform1612822914" + container_name = "hubandspokeaks" + key = "terraform.tfstate" + } + + # List required providers with version constraints + required_providers { + azurerm = { + source = "azurerm" + version = "= 2.45.1" + } + azuread = { + source = "azuread" + version = "= 1.3.0" + } + } +} + +# Initialize the azurerm provider +provider "azurerm" { + features {} +} +``` + +And test that everything is working: + +```bash +$ terraform init + +Initializing the backend... + +Successfully configured the backend "azurerm"! Terraform will automatically +use this backend unless the backend configuration changes. + +Initializing provider plugins... +- Finding hashicorp/azuread versions matching "1.3.0"... +- Finding hashicorp/azurerm versions matching "2.45.1"... +- Installing hashicorp/azuread v1.3.0... +- Installed hashicorp/azuread v1.3.0 (signed by HashiCorp) +- Installing hashicorp/azurerm v2.45.1... +- Installed hashicorp/azurerm v2.45.1 (signed by HashiCorp) + +Terraform has created a lock file .terraform.lock.hcl to record the provider +selections it made above. Include this file in your version control repository +so that Terraform can guarantee to make the same selections by default when +you run "terraform init" in the future. + +Terraform has been successfully initialized! + +You may now begin working with Terraform. Try running "terraform plan" to see +any changes that are required for your infrastructure. All Terraform commands +should now work. + +If you ever set or change modules or backend configuration for Terraform, +rerun this command to reinitialize your working directory. If you forget, other +commands will detect it and remind you to do so if necessary. +``` diff --git a/env.sh.example b/env.sh.example new file mode 100644 index 0000000..29c63d7 --- /dev/null +++ b/env.sh.example @@ -0,0 +1,33 @@ +#!/bin/bash +#------------------------------------------------ +# Set environment +# Author: Thomas Perelle +#------------------------------------------------ +# This script must be executable: +# chmod +x env.sh +# Source the script before working: +# source env.sh + +# Terraform 0.14.x +alias terraform="/usr/local/bin/terraform_0.14.5" +alias tf="terraform" + +# Install Azure CLI with your system package manager or with Pipenv environment (only the first time) +# $ pipenv --python 3.7 +# $ pipenv install azure-cli + +# Load pipenv environment +# If you have installed Azure CLI with pipenv, comment the two lines above +# export PIPENV_IGNORE_VIRTUALENVS=1 +# pipenv shell + +# Export Azure credentials for Sowesign account +export ARM_CLIENT_ID=00000000-0000-0000-0000-000000000000 +export ARM_CLIENT_SECRET=XXXXXXXXXXXXXXXXXXX +export ARM_SUBSCRIPTION_ID=00000000-0000-0000-0000-000000000000 +export ARM_TENANT_ID=00000000-0000-0000-0000-000000000000 + +# Export key for terraform backend storage account +export ARM_ACCESS_KEY=XXXXXXXXXXXXXXXXXXX + +az login \ No newline at end of file diff --git a/scripts/backend.sh b/scripts/backend.sh new file mode 100755 index 0000000..5c06bd8 --- /dev/null +++ b/scripts/backend.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +RESOURCE_GROUP_NAME=terraform-backends +STORAGE_ACCOUNT_NAME=terraform$(date +%s) +CONTAINER_NAME=hubandspokeaks + +# Create resource group +az group create --name $RESOURCE_GROUP_NAME --location francecentral + +# Create storage account +az storage account create --resource-group $RESOURCE_GROUP_NAME --name $STORAGE_ACCOUNT_NAME --sku Standard_LRS --encryption-services blob + +# Get storage account key +ACCOUNT_KEY=$(az storage account keys list --resource-group $RESOURCE_GROUP_NAME --account-name $STORAGE_ACCOUNT_NAME --query '[0].value' -o tsv) + +# Create blob container +az storage container create --name $CONTAINER_NAME --account-name $STORAGE_ACCOUNT_NAME --account-key $ACCOUNT_KEY + +echo "storage_account_name: $STORAGE_ACCOUNT_NAME" +echo "container_name: $CONTAINER_NAME" +echo "access_key: $ACCOUNT_KEY" \ No newline at end of file