diff --git a/.gitignore b/.gitignore index dadd9b73..d81bdff2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ # .tfstate files *.tfstate *.tfstate.* +*.terraform.lock.hcl +plan # Crash log files crash.log diff --git a/azurerm/modules/azurerm-vmss/README.md b/azurerm/modules/azurerm-vmss/README.md new file mode 100644 index 00000000..04b45a64 --- /dev/null +++ b/azurerm/modules/azurerm-vmss/README.md @@ -0,0 +1,120 @@ + +# PROJECT_NAME + +DESCRIPTION: +--- +Bootstraps the infrastructure for {{SELECT_APP_TYPE }}. + +Will be used within the provisioned pipeline for your application depending on the options you chose. + +Pipeline implementation for infrastructure relies on workspaces, you can pass in whatever workspace you want from {{ SELECT_DEPLOYMENT_TYPE }} pipeline YAML. + +PREREQUISITES: +--- +Azure Subscripion + - SPN + - Terraform will use this to perform the authentication for the API calls + - you will need the `client_id, subscription_id, client_secret, tenant_id` + +Terraform backend + - resource group (can be manually created for the terraform remote state) + - Blob storage container for the remote state management + + +USAGE: +--- + +To activate the terraform backend for running locally we need to initialise the SPN with env vars to ensure you are running the same way as the pipeline that will ultimately be running any incremental changes. + +```bash +docker run -it --rm -v $(pwd):/opt/tf-lib amidostacks/ci-tf:latest /bin/bash +This module was written to quickly provision a VMSS which will be used as a self hosted build agent within Azure DevOps. + +export ARM_CLIENT_ID=xxxx \ +ARM_CLIENT_SECRET=yyyyy \ +ARM_SUBSCRIPTION_ID=yyyyy \ +ARM_TENANT_ID=yyyyy + +alternatively you can run az login + +To get up and running locally you will want to create a terraform.tfvars file + +TFVAR_CONTENTS=''' +vnet_id = "amido-stacks-vnet-uks-dev" +rg_name = "amido-stacks-rg-uks-dev" +resource_group_location = "uksouth" +name_company = "amido" +name_project = "stacks" +name_component = "spa" +name_environment = "dev" +''' +$TFVAR_CONTENTS > terraform.tfvars +terraform workspace select dev || terraform workspace new dev +terraform init -backend-config=./backend.local.tfvars + +## Known Limitiations +Work is required to enhance this module to cover wider use cases of VMSS as well as using dynatmic blocks etc to support multiple NICs and IP Configurations. + +Future work should also be done to implement a packer image build. This will allow us to build the base image once and speed up the time taken to start new agents as all tools will be installed within the assigned image. + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13 | +| [azurerm](#requirement\_azurerm) | ~> 3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [azurerm](#provider\_azurerm) | ~> 3.0 | +| [local](#provider\_local) | n/a | +| [random](#provider\_random) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [azurerm_linux_virtual_machine_scale_set.vmss](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set) | resource | +| [random_password.password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | +| [azurerm_subnet.subnet](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/subnet) | data source | +| [local_file.sh](https://registry.terraform.io/providers/hashicorp/local/latest/docs/data-sources/file) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [ip\_configuration\_name](#input\_ip\_configuration\_name) | Name of the IP Config on the VMs NIC. | `string` | `"primary"` | no | +| [location\_name\_map](#input\_location\_name\_map) | Each region must have corresponding a shortend name for resource naming purposes | `map(string)` |
{| no | +| [network\_interface\_name](#input\_network\_interface\_name) | Name of the VMs NIC. | `string` | `"primary"` | no | +| [overprovision](#input\_overprovision) | Bool to set overprovisioning. | `bool` | `false` | no | +| [subnet\_name](#input\_subnet\_name) | Name of the Subnet which the VMSS will be provisioned. | `string` | `""` | no | +| [vmss\_admin\_password](#input\_vmss\_admin\_password) | Password for Admin SSH Access to VMs. | `string` | `""` | no | +| [vmss\_admin\_username](#input\_vmss\_admin\_username) | Username for Admin SSH Access to VMs. | `string` | `""` | no | +| [vmss\_disable\_password\_auth](#input\_vmss\_disable\_password\_auth) | Boolean to enable or disable password authentication to VMs. | `bool` | `false` | no | +| [vmss\_disk\_caching](#input\_vmss\_disk\_caching) | Disk Caching options. | `string` | `"ReadWrite"` | no | +| [vmss\_image\_offer](#input\_vmss\_image\_offer) | Image offer. Eg UbuntuServer | `string` | `"0001-com-ubuntu-server-jammy"` | no | +| [vmss\_image\_publisher](#input\_vmss\_image\_publisher) | Image Publisher. | `string` | `"Canonical"` | no | +| [vmss\_image\_sku](#input\_vmss\_image\_sku) | Image SKU. | `string` | `"22_04-lts-gen2"` | no | +| [vmss\_image\_version](#input\_vmss\_image\_version) | Version of VM Image SKU required. | `string` | `"latest"` | no | +| [vmss\_instances](#input\_vmss\_instances) | Default number of instances within the scaleset. | `number` | `1` | no | +| [vmss\_name](#input\_vmss\_name) | Name of the VMSS | `string` | `""` | no | +| [vmss\_resource\_group\_location](#input\_vmss\_resource\_group\_location) | Location of Resource group | `string` | `"uksouth"` | no | +| [vmss\_resource\_group\_name](#input\_vmss\_resource\_group\_name) | name of resource group | `string` | `""` | no | +| [vmss\_sku](#input\_vmss\_sku) | VM Size | `string` | `"Standard_D2s_v3"` | no | +| [vmss\_storage\_account\_type](#input\_vmss\_storage\_account\_type) | Storeage type used for VMSS Disk. | `string` | `"StandardSSD_LRS"` | no | +| [vnet\_name](#input\_vnet\_name) | Name of the VNET which the VMSS will be provisioned. | `string` | `""` | no | +| [vnet\_resource\_group](#input\_vnet\_resource\_group) | Name of the Resource Group in which the VNET is provisioned. | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [vmss\_admin\_password](#output\_vmss\_admin\_password) | n/a | +| [vmss\_id](#output\_vmss\_id) | n/a | + \ No newline at end of file diff --git a/azurerm/modules/azurerm-vmss/ci_cd_tool_install.sh b/azurerm/modules/azurerm-vmss/ci_cd_tool_install.sh new file mode 100644 index 00000000..5c3b985a --- /dev/null +++ b/azurerm/modules/azurerm-vmss/ci_cd_tool_install.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# Install Powershell +# Update the list of packages +sudo apt-get update +# Install pre-requisite packages. +sudo apt-get install -y wget apt-transport-https software-properties-common ca-certificates curl gnupg build-essential +# Download the Microsoft repository GPG keys +wget -q "https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb" +# Register the Microsoft repository GPG keys +sudo dpkg -i packages-microsoft-prod.deb +# Delete the the Microsoft repository GPG keys file +rm packages-microsoft-prod.deb +# Update the list of packages after we added packages.microsoft.com +sudo apt-get update +# Install PowerShell +sudo apt-get install -y powershell +# Install Modules +pwsh -NoProfile -Command "Install-Module -Name Az -Scope AllUsers -Repository PSGallery -Force" +pwsh -NoProfile -Command "Install-Module -Name Az.Accounts -Scope AllUsers -Force" +pwsh -NoProfile -Command "Install-Module -Name Az.DataFactory -Scope AllUsers -Force" +# Start PowerShell +pwsh + +# Install Azure CLI +curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash + +# Install Python +sudo apt-get update +sudo apt-get install -y python3 python3-pip \ No newline at end of file diff --git a/azurerm/modules/azurerm-vmss/constraints.tf b/azurerm/modules/azurerm-vmss/constraints.tf new file mode 100644 index 00000000..5257ed4f --- /dev/null +++ b/azurerm/modules/azurerm-vmss/constraints.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 0.13" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.0" + } + } +} diff --git a/azurerm/modules/azurerm-vmss/example/constraints.tf b/azurerm/modules/azurerm-vmss/example/constraints.tf new file mode 100644 index 00000000..5257ed4f --- /dev/null +++ b/azurerm/modules/azurerm-vmss/example/constraints.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 0.13" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.0" + } + } +} diff --git a/azurerm/modules/azurerm-vmss/example/main.tf b/azurerm/modules/azurerm-vmss/example/main.tf new file mode 100644 index 00000000..72d889a2 --- /dev/null +++ b/azurerm/modules/azurerm-vmss/example/main.tf @@ -0,0 +1,18 @@ +resource "azurerm_resource_group" "default" { + name = "azdo-build-test" + location = var.vmss_resource_group_location + tags = var.tags +} + +module "vmss" { + source = "../../azurerm-vmss" + vmss_name = "azdo-build-test" + vmss_resource_group_name = azurerm_resource_group.default.name + vmss_resource_group_location = azurerm_resource_group.default.location + vnet_name = "amido-stacks-nonprod-euw-core" + vnet_resource_group = "amido-stacks-nonprod-euw-core" + subnet_name = "build-agent" + vmss_instances = 1 + vmss_admin_username = "adminuser" + vmss_disable_password_auth = false +} diff --git a/azurerm/modules/azurerm-vmss/example/providers.tf b/azurerm/modules/azurerm-vmss/example/providers.tf new file mode 100644 index 00000000..7f1ac83b --- /dev/null +++ b/azurerm/modules/azurerm-vmss/example/providers.tf @@ -0,0 +1,13 @@ +terraform { + backend "azurerm" { + resource_group_name = "amido-stacks-dev-euw-de" + storage_account_name = "detestdevrb" + container_name = "tfstate" + key = "vmss.terraform.tfstate" + } + +} + +provider "azurerm" { + features {} +} diff --git a/azurerm/modules/azurerm-vmss/example/vars.tf b/azurerm/modules/azurerm-vmss/example/vars.tf new file mode 100644 index 00000000..8620a416 --- /dev/null +++ b/azurerm/modules/azurerm-vmss/example/vars.tf @@ -0,0 +1,152 @@ +############################################ +# NAMING +############################################ +variable "vnet_name" { + type = string + default = "" + description = "Name of the VNET which the VMSS will be provisioned." +} + +variable "vnet_resource_group" { + type = string + default = "" + description = "Name of the Resource Group in which the VNET is provisioned." +} + +variable "subnet_name" { + type = string + default = "" + description = "Name of the Subnet which the VMSS will be provisioned." +} + +variable "vmss_name" { + type = string + default = "" + description = "Name of the VMSS" +} + +variable "network_interface_name" { + type = string + default = "primary" + description = "Name of the VMs NIC." +} + +variable "ip_configuration_name" { + type = string + default = "primary" + description = "Name of the IP Config on the VMs NIC." +} + +variable "tags" { + description = "Tags to be assigned to all resources, NB if global tagging is enabled these will get overwritten periodically" + type = map(string) + default = {} +} + +variable "resource_tags" { + description = "Map of tags to be applied to all resources created as part of this module" + type = map(string) + default = {} +} + +############################################ +# RESOURCE INFORMATION +############################################ + +variable "vmss_resource_group_location" { + type = string + default = "westeurope" + description = "Location of Resource group" +} + +variable "vmss_resource_group_name" { + type = string + description = "name of resource group" + default = "" +} + +# Each region must have corresponding a shortend name for resource naming purposes +variable "location_name_map" { + type = map(string) + + default = { + northeurope = "eun" + westeurope = "euw" + uksouth = "uks" + ukwest = "ukw" + eastus = "use" + eastus2 = "use2" + westus = "usw" + eastasia = "ase" + southeastasia = "asse" + } +} + +variable "vmss_sku" { + type = string + default = "Standard_D2_v3" + description = "VM Size" +} + +variable "vmss_instances" { + type = number + default = 1 + description = "Default number of instances within the scaleset." +} + +variable "vmss_admin_username" { + type = string + default = "" + description = "Username for Admin SSH Access to VMs." +} + +variable "vmss_admin_password" { + type = string + default = "" + description = "Password for Admin SSH Access to VMs." +} + +variable "vmss_disable_password_auth" { + type = bool + default = false + description = "Boolean to enable or disable password authentication to VMs." +} + +variable "vmss_image_publisher" { + type = string + default = "canonical" + description = "Image Publisher." +} + +variable "vmss_image_offer" { + type = string + default = "UbuntuServer" + description = "Image offer. Eg UbuntuServer" +} + +variable "vmss_image_sku" { + type = string + default = "22_04-lts" + description = "Image SKU." +} + +variable "vmss_image_version" { + type = string + default = "latest" + description = "Version of VM Image SKU required." +} + +variable "vmss_storage_account_type" { + type = string + default = "Standard_LRS" + description = "Storeage type used for VMSS Disk." +} + +variable "vmss_disk_caching" { + type = string + default = "ReadWrite" + description = "Disk Caching options." +} + + + diff --git a/azurerm/modules/azurerm-vmss/main.tf b/azurerm/modules/azurerm-vmss/main.tf new file mode 100644 index 00000000..9cc03753 --- /dev/null +++ b/azurerm/modules/azurerm-vmss/main.tf @@ -0,0 +1,70 @@ +data "azurerm_subnet" "subnet" { + name = var.subnet_name + virtual_network_name = var.vnet_name + resource_group_name = var.vnet_resource_group +} + +# Generates Random Password for VMSS Admin +resource "random_password" "password" { + count = var.vmss_admin_password == "" ? 1 : 0 + length = 16 + min_upper = 2 + special = true + override_special = "!#$%&*()-_=+[]{}<>:?" +} + +resource "azurerm_linux_virtual_machine_scale_set" "vmss" { + name = var.vmss_name + location = var.vmss_resource_group_location + resource_group_name = var.vmss_resource_group_name + sku = var.vmss_sku + instances = var.vmss_instances + admin_username = var.vmss_admin_username + admin_password = var.vmss_admin_password == "" ? random_password.password[0].result : var.vmss_admin_password + disable_password_authentication = var.vmss_disable_password_auth + overprovision = var.overprovision + + source_image_reference { + publisher = var.vmss_image_publisher + offer = var.vmss_image_offer + sku = var.vmss_image_sku + version = var.vmss_image_version + } + + network_interface { + name = var.network_interface_name + primary = true + + ip_configuration { + name = var.ip_configuration_name + primary = true + subnet_id = data.azurerm_subnet.subnet.id + } + } + + os_disk { + storage_account_type = var.vmss_storage_account_type + caching = var.vmss_disk_caching + } + extension { + name = "CustomScript" + publisher = "Microsoft.Azure.Extensions" + type = "CustomScript" + type_handler_version = "2.0" + auto_upgrade_minor_version = true + + settings = jsonencode({ + "script" = base64encode(data.local_file.sh.content) + }) + } + lifecycle { + ignore_changes = [ + tags, + ] + } +} + +data "local_file" "sh" { + filename = "${path.module}/ci_cd_tool_install.sh" +} + diff --git a/azurerm/modules/azurerm-vmss/output.tf b/azurerm/modules/azurerm-vmss/output.tf new file mode 100644 index 00000000..a50641bb --- /dev/null +++ b/azurerm/modules/azurerm-vmss/output.tf @@ -0,0 +1,8 @@ +output "vmss_id" { + value = azurerm_linux_virtual_machine_scale_set.vmss.id +} + +output "vmss_admin_password" { + sensitive = true + value = random_password.password[0].result +} \ No newline at end of file diff --git a/azurerm/modules/azurerm-vmss/vars.tf b/azurerm/modules/azurerm-vmss/vars.tf new file mode 100644 index 00000000..a564b3ed --- /dev/null +++ b/azurerm/modules/azurerm-vmss/vars.tf @@ -0,0 +1,145 @@ +############################################ +# NAMING +############################################ +variable "vnet_name" { + type = string + default = "" + description = "Name of the VNET which the VMSS will be provisioned." +} + +variable "vnet_resource_group" { + type = string + default = "" + description = "Name of the Resource Group in which the VNET is provisioned." +} + +variable "subnet_name" { + type = string + default = "" + description = "Name of the Subnet which the VMSS will be provisioned." +} + +variable "vmss_name" { + type = string + default = "" + description = "Name of the VMSS" +} + +variable "network_interface_name" { + type = string + default = "primary" + description = "Name of the VMs NIC." +} + +variable "ip_configuration_name" { + type = string + default = "primary" + description = "Name of the IP Config on the VMs NIC." +} + +############################################ +# RESOURCE INFORMATION +############################################ + +variable "vmss_resource_group_location" { + type = string + default = "uksouth" + description = "Location of Resource group" +} + +variable "vmss_resource_group_name" { + type = string + description = "name of resource group" + default = "" +} + +# Each region must have corresponding a shortend name for resource naming purposes +variable "location_name_map" { + type = map(string) + + default = { + northeurope = "eun" + westeurope = "euw" + uksouth = "uks" + ukwest = "ukw" + eastus = "use" + eastus2 = "use2" + westus = "usw" + eastasia = "ase" + southeastasia = "asse" + } +} + +variable "vmss_sku" { + type = string + default = "Standard_D2s_v3" + description = "VM Size" +} + +variable "vmss_instances" { + type = number + default = 1 + description = "Default number of instances within the scaleset." +} + +variable "vmss_admin_username" { + type = string + default = "" + description = "Username for Admin SSH Access to VMs." +} + +variable "vmss_admin_password" { + type = string + default = "" + description = "Password for Admin SSH Access to VMs." +} + +variable "vmss_disable_password_auth" { + type = bool + default = false + description = "Boolean to enable or disable password authentication to VMs." +} + +variable "vmss_image_publisher" { + type = string + default = "Canonical" + description = "Image Publisher." +} + +variable "vmss_image_offer" { + type = string + default = "0001-com-ubuntu-server-jammy" + description = "Image offer. Eg UbuntuServer" +} + +variable "vmss_image_sku" { + type = string + default = "22_04-lts-gen2" + description = "Image SKU." +} + +variable "vmss_image_version" { + type = string + default = "latest" + description = "Version of VM Image SKU required." +} + +variable "vmss_storage_account_type" { + type = string + default = "StandardSSD_LRS" + description = "Storeage type used for VMSS Disk." +} + +variable "vmss_disk_caching" { + type = string + default = "ReadWrite" + description = "Disk Caching options." +} + +variable "overprovision" { + type = bool + default = false + description = "Bool to set overprovisioning." +} + +
"eastasia": "ase",
"eastus": "use",
"eastus2": "use2",
"northeurope": "eun",
"southeastasia": "asse",
"uksouth": "uks",
"ukwest": "ukw",
"westeurope": "euw",
"westus": "usw"
}