├── .gitignore
├── AvailabilitySet.tf
├── Deploy.PS1
├── FirstLogonCommands.xml
├── LoadBalancer.tf
├── Network.tf
├── README.md
├── ResourceGroup.tf
├── Storage.tf
├── TerraformCredentials.ps1
├── Variables.tf
├── WebserverDsc.PS1
├── default.htm
├── testvm-SecurityGroup.tf
└── testvm.tf
/.gitignore:
--------------------------------------------------------------------------------
1 | terraform.tfstate
2 | terraform.tfstate.backup
3 | TempCommands.PS1
--------------------------------------------------------------------------------
/AvailabilitySet.tf:
--------------------------------------------------------------------------------
1 | resource "azurerm_availability_set" "availability_set" {
2 | name = "${var.vm_name_prefix}-avset"
3 | location = "${var.azure_region_fullname}"
4 | resource_group_name = "${azurerm_resource_group.resource_group.name}"
5 | platform_update_domain_count = "5"
6 | platform_fault_domain_count = "3"
7 |
8 | tags {
9 | environment = "${var.environment_tag}"
10 | }
11 | }
--------------------------------------------------------------------------------
/Deploy.PS1:
--------------------------------------------------------------------------------
1 |
2 |
3 | Start-Transcript -Path C:\Deploy.Log
4 |
5 | Write-Host "Setup WinRM for $RemoteHostName"
6 |
7 | $Cert = New-SelfSignedCertificate -DnsName $RemoteHostName, $ComputerName `
8 | -CertStoreLocation "cert:\LocalMachine\My" `
9 | -FriendlyName "Test WinRM Cert"
10 |
11 | $Cert | Out-String
12 |
13 | $Thumbprint = $Cert.Thumbprint
14 |
15 | Write-Host "Enable HTTPS in WinRM"
16 | $WinRmHttps = "@{Hostname=`"$RemoteHostName`"; CertificateThumbprint=`"$Thumbprint`"}"
17 | winrm create winrm/config/Listener?Address=*+Transport=HTTPS $WinRmHttps
18 |
19 | Write-Host "Set Basic Auth in WinRM"
20 | $WinRmBasic = "@{Basic=`"true`"}"
21 | winrm set winrm/config/service/Auth $WinRmBasic
22 |
23 | Write-Host "Open Firewall Port"
24 | netsh advfirewall firewall add rule name="Windows Remote Management (HTTPS-In)" dir=in action=allow protocol=TCP localport=$WinRmPort
25 |
26 | Stop-Transcript
--------------------------------------------------------------------------------
/FirstLogonCommands.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | cmd /c "copy C:\AzureData\CustomData.bin C:\Deploy.PS1"CopyScript
5 | 11
6 |
7 |
8 | powershell.exe -sta -ExecutionPolicy Unrestricted -file C:\Deploy.PS1RunScript
10 | 12
11 |
12 |
--------------------------------------------------------------------------------
/LoadBalancer.tf:
--------------------------------------------------------------------------------
1 | # TODO: Add the availability set and the VMs to the load balanacer
2 |
3 | # VIP address
4 | resource "azurerm_public_ip" "load_balancer_public_ip" {
5 | name = "${var.vm_name_prefix}-ip"
6 | location = "${var.azure_region_fullname}"
7 | resource_group_name = "${azurerm_resource_group.resource_group.name}"
8 | public_ip_address_allocation = "dynamic"
9 | domain_name_label = "${azurerm_resource_group.resource_group.name}"
10 | }
11 |
12 | # Front End Load Balancer
13 | resource "azurerm_lb" "load_balancer" {
14 | name = "${var.vm_name_prefix}-lb"
15 | location = "${var.azure_region_fullname}"
16 | resource_group_name = "${azurerm_resource_group.resource_group.name}"
17 |
18 | frontend_ip_configuration {
19 | name = "${var.vm_name_prefix}-ipconfig"
20 | public_ip_address_id = "${azurerm_public_ip.load_balancer_public_ip.id}"
21 | }
22 | }
23 |
24 | # Back End Address Pool
25 | resource "azurerm_lb_backend_address_pool" "backend_pool" {
26 | location = "${var.azure_region_fullname}"
27 | resource_group_name = "${azurerm_resource_group.resource_group.name}"
28 | loadbalancer_id = "${azurerm_lb.load_balancer.id}"
29 | name = "${var.vm_name_prefix}-backend_address_pool"
30 | }
31 |
32 | # Load Balancer Rule
33 | resource "azurerm_lb_rule" "load_balancer_http_rule" {
34 | location = "${var.azure_region_fullname}"
35 | resource_group_name = "${azurerm_resource_group.resource_group.name}"
36 | loadbalancer_id = "${azurerm_lb.load_balancer.id}"
37 | name = "HTTPRule"
38 | protocol = "Tcp"
39 | frontend_port = 80
40 | backend_port = 80
41 | frontend_ip_configuration_name = "${var.vm_name_prefix}-ipconfig"
42 | backend_address_pool_id = "${azurerm_lb_backend_address_pool.backend_pool.id}"
43 | probe_id = "${azurerm_lb_probe.load_balancer_probe.id}"
44 | depends_on = ["azurerm_lb_probe.load_balancer_probe"]
45 | }
46 |
47 | resource "azurerm_lb_rule" "load_balancer_https_rule" {
48 | location = "${var.azure_region_fullname}"
49 | resource_group_name = "${azurerm_resource_group.resource_group.name}"
50 | loadbalancer_id = "${azurerm_lb.load_balancer.id}"
51 | name = "HTTPSRule"
52 | protocol = "Tcp"
53 | frontend_port = 443
54 | backend_port = 443
55 | frontend_ip_configuration_name = "${var.vm_name_prefix}-ipconfig"
56 | backend_address_pool_id = "${azurerm_lb_backend_address_pool.backend_pool.id}"
57 | probe_id = "${azurerm_lb_probe.load_balancer_probe.id}"
58 | depends_on = ["azurerm_lb_probe.load_balancer_probe"]
59 | }
60 |
61 | #LB Probe - Checks to see which VMs are healthy and available
62 | resource "azurerm_lb_probe" "load_balancer_probe" {
63 | location = "${var.azure_region_fullname}"
64 | resource_group_name = "${azurerm_resource_group.resource_group.name}"
65 | loadbalancer_id = "${azurerm_lb.load_balancer.id}"
66 | name = "HTTP"
67 | port = 80
68 | }
69 |
70 | #TODO: Dynamic NAT rules for each VM for WinRM
--------------------------------------------------------------------------------
/Network.tf:
--------------------------------------------------------------------------------
1 | # Create a virtual network in the web_servers resource group
2 | resource "azurerm_virtual_network" "network" {
3 | name = "${var.azure_resource_group_name}-Network"
4 | address_space = ["10.0.0.0/16"]
5 | location = "${var.azure_region_fullname}"
6 | resource_group_name = "${azurerm_resource_group.resource_group.name}"
7 | }
8 |
9 | resource "azurerm_subnet" "subnet1" {
10 | name = "${var.azure_resource_group_name}-Subnet1"
11 | resource_group_name = "${azurerm_resource_group.resource_group.name}"
12 | virtual_network_name = "${azurerm_virtual_network.network.name}"
13 | address_prefix = "10.0.1.0/24"
14 | }
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Terraform-AzureRM-Example
2 |
3 | Quick Instruction:
4 |
5 | Download and install Terraform
6 | https://www.terraform.io/downloads.html
7 |
8 | Edit the TerraformCredentials.ps1 and enter API keys
9 |
10 | Load Powershell
11 |
12 | "dot source" the credentials to load them into environment variables
13 |
14 | . .\TerraformCredentials.ps1
15 |
16 | Test the configuration
17 |
18 | terraform plan
19 |
20 | Build the resources
21 |
22 | terraform apply
23 |
24 | Delete the resources
25 |
26 | terraform destroy
27 |
28 | Information
29 |
30 | http://superautomation.blogspot.co.uk/2016/11/terraform-with-azure-resource-manager.html
31 | http://superautomation.blogspot.co.uk/2016/11/configuring-terraform-to-use-winrm-over.html
32 | http://superautomation.blogspot.co.uk/2016/11/azure-resource-manager-load-balancer.html
33 |
--------------------------------------------------------------------------------
/ResourceGroup.tf:
--------------------------------------------------------------------------------
1 | resource "azurerm_resource_group" "resource_group" {
2 | name = "${var.azure_resource_group_name}"
3 | location = "${var.azure_region_fullname}"
4 |
5 | tags {
6 | environment = "${var.environment_tag}"
7 | }
8 | }
--------------------------------------------------------------------------------
/Storage.tf:
--------------------------------------------------------------------------------
1 |
2 | resource "azurerm_storage_account" "storage_account" {
3 | name = "${var.azure_resource_group_name}storages"
4 | resource_group_name = "${azurerm_resource_group.resource_group.name}"
5 | location = "${var.azure_region_fullname}"
6 | account_type = "Standard_LRS"
7 |
8 | tags {
9 | environment = "${var.environment_tag}"
10 | }
11 | }
12 |
13 | resource "azurerm_storage_container" "container" {
14 | name = "vhds"
15 | resource_group_name = "${azurerm_resource_group.resource_group.name}"
16 | storage_account_name = "${azurerm_storage_account.storage_account.name}"
17 | container_access_type = "private"
18 | }
19 |
--------------------------------------------------------------------------------
/TerraformCredentials.ps1:
--------------------------------------------------------------------------------
1 | #Set Environment Variables for Azure RM Terraform
2 |
3 | $ENV:ARM_SUBSCRIPTION_ID = ""
4 | $ENV:ARM_CLIENT_ID = ""
5 | $ENV:ARM_CLIENT_SECRET = "" # This should end with an '=' symbol
6 | $ENV:ARM_TENANT_ID = ""
7 |
8 |
--------------------------------------------------------------------------------
/Variables.tf:
--------------------------------------------------------------------------------
1 | variable "azure_resource_group_name" {
2 | description = "Resource Group Name"
3 | default = "ninjagroup"
4 | }
5 |
6 | variable "vm_name_prefix" {
7 | description = "The Virtual Machine Name"
8 | default = "ninjatestvm"
9 | }
10 |
11 | variable "vm_count" {
12 | description = "Number of VMs to create"
13 | default = "2"
14 | }
15 |
16 | #Re-applying a new size reboots the VMs and re-runs the provisioner scripts - Use DSC Push to configure to avoid errors
17 | variable "vm_size" {
18 | description = "Azure VM Size"
19 | default = "Standard_A1"
20 | }
21 |
22 | variable "vm_winrm_port" {
23 | description = "WinRM Public Port"
24 | default = "5986"
25 | }
26 |
27 | variable "azure_region" {
28 | description = "Azure Region for all resources"
29 | default = "northeurope"
30 | }
31 |
32 | variable "azure_region_fullname" {
33 | description = "Long name for the Azure Region, ie. North Europe"
34 | default = "North Europe"
35 | }
36 |
37 | variable "azure_dns_suffix" {
38 | description = "Azure DNS suffix for the Public IP"
39 | default = "cloudapp.azure.com"
40 | }
41 |
42 | variable "admin_username" {
43 | description = "Username for the Administrator account"
44 | default = "TestAdmin"
45 | }
46 |
47 | variable "admin_password" {
48 | description = "Password for the Administrator account"
49 | default = "jgjgJGJG!!!!"
50 | }
51 |
52 | variable "environment_tag" {
53 | description = "Tag to apply to the resoucrces"
54 | default = "Terraform-AzureRM-Example"
55 | }
56 |
57 | #Null resource to make the VM intermediate varable - probably not the right way to do this
58 | #resource "null_resource" "intermediates" {
59 | # triggers = {
60 | # full_vm_dns_name = "Param($RemoteHostName = \"${var.vm_name_prefix}-1.${var.azure_region}.${var.azure_dns_suffix}\", $ComputerName = \"${var.vm_name_prefix}-1\", $WinRmPort = ${var.vm_winrm_port}) ${file("Deploy.PS1")}"
61 | # #full_vm_dns_name = "Param($RemoteHostName = \"${null_resource.intermediates.triggers.full_vm_dns_name}\", $ComputerName = \"${var.vm_name}\", $WinRmPort = ${var.vm_winrm_port}) ${file("Deploy.PS1")}"
62 | # }
63 | #}
64 |
65 | #output "full_vm_dns_name" {
66 | # value = "${null_resource.intermediates.triggers.full_vm_dns_name}"
67 | #}
68 |
--------------------------------------------------------------------------------
/WebserverDsc.PS1:
--------------------------------------------------------------------------------
1 | Start-Transcript -Path C:\Scripts\WebserverDsc.Log
2 | Write-Host "Provisioning $($env:COMPUTERNAME)"
3 |
4 | configuration LocalIIS {
5 | Import-DscResource -ModuleName 'PSDesiredStateConfiguration'
6 | Node $ComputerName {
7 |
8 | WindowsFeature IIS{
9 | Name = 'web-server'
10 | Ensure = 'Present'
11 | }
12 |
13 | }
14 | }
15 | $computername = 'localhost'
16 |
17 | LocalIIS -OutputPath c:\DSC\Config
18 |
19 | Start-DscConfiguration -Path C:\DSC\Config -ComputerName localhost
20 |
21 | Write-Host "DSC Configuration Started."
22 |
23 | Stop-Transcript
24 |
--------------------------------------------------------------------------------
/default.htm:
--------------------------------------------------------------------------------
1 | $env:COMPUTERNAME
--------------------------------------------------------------------------------
/testvm-SecurityGroup.tf:
--------------------------------------------------------------------------------
1 |
2 | resource "azurerm_network_security_group" "vm_security_group" {
3 | name = "${var.vm_name_prefix}-sg"
4 | location = "${var.azure_region_fullname}"
5 | resource_group_name = "${azurerm_resource_group.resource_group.name}"
6 |
7 | tags {
8 | environment = "${var.environment_tag}"
9 | }
10 | }
11 |
12 | resource "azurerm_network_security_rule" "rdpRule" {
13 | name = "rdpRule"
14 | priority = 100
15 | direction = "Inbound"
16 | access = "Allow"
17 | protocol = "Tcp"
18 | source_port_range = "*"
19 | destination_port_range = "3389"
20 | source_address_prefix = "*"
21 | destination_address_prefix = "*"
22 | resource_group_name = "${azurerm_resource_group.resource_group.name}"
23 | network_security_group_name = "${azurerm_network_security_group.vm_security_group.name}"
24 | }
25 |
26 | resource "azurerm_network_security_rule" "winrmRule" {
27 | name = "winrmRule"
28 | priority = 110
29 | direction = "Inbound"
30 | access = "Allow"
31 | protocol = "Tcp"
32 | source_port_range = "*"
33 | destination_port_range = "${var.vm_winrm_port}"
34 | source_address_prefix = "*"
35 | destination_address_prefix = "*"
36 | resource_group_name = "${azurerm_resource_group.resource_group.name}"
37 | network_security_group_name = "${azurerm_network_security_group.vm_security_group.name}"
38 | }
39 |
40 | resource "azurerm_network_security_rule" "httpRule" {
41 | name = "HTTP"
42 | priority = 120
43 | direction = "Inbound"
44 | access = "Allow"
45 | protocol = "Tcp"
46 | source_port_range = "*"
47 | destination_port_range = "80"
48 | source_address_prefix = "*"
49 | destination_address_prefix = "*"
50 | resource_group_name = "${azurerm_resource_group.resource_group.name}"
51 | network_security_group_name = "${azurerm_network_security_group.vm_security_group.name}"
52 | }
53 |
54 | resource "azurerm_network_security_rule" "httpsRule" {
55 | name = "HTTPS"
56 | priority = 130
57 | direction = "Inbound"
58 | access = "Allow"
59 | protocol = "Tcp"
60 | source_port_range = "*"
61 | destination_port_range = "443"
62 | source_address_prefix = "*"
63 | destination_address_prefix = "*"
64 | resource_group_name = "${azurerm_resource_group.resource_group.name}"
65 | network_security_group_name = "${azurerm_network_security_group.vm_security_group.name}"
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/testvm.tf:
--------------------------------------------------------------------------------
1 | resource "azurerm_lb_nat_rule" "winrm_nat" {
2 | location = "${var.azure_region_fullname}"
3 | resource_group_name = "${azurerm_resource_group.resource_group.name}"
4 | loadbalancer_id = "${azurerm_lb.load_balancer.id}"
5 | name = "WinRM-HTTPS-vm-${count.index}"
6 | protocol = "Tcp"
7 | frontend_port = "${count.index + 10000}"
8 | backend_port = "${var.vm_winrm_port}"
9 | frontend_ip_configuration_name = "${var.vm_name_prefix}-ipconfig"
10 | count = "${var.vm_count}"
11 | }
12 |
13 | resource "azurerm_lb_nat_rule" "rdp_nat" {
14 | location = "${var.azure_region_fullname}"
15 | resource_group_name = "${azurerm_resource_group.resource_group.name}"
16 | loadbalancer_id = "${azurerm_lb.load_balancer.id}"
17 | name = "RDP-vm-${count.index}"
18 | protocol = "Tcp"
19 | frontend_port = "${count.index + 11000}"
20 | backend_port = "3389"
21 | frontend_ip_configuration_name = "${var.vm_name_prefix}-ipconfig"
22 | count = "${var.vm_count}"
23 | }
24 |
25 |
26 | resource "azurerm_network_interface" "vm_nic" {
27 | name = "${var.vm_name_prefix}-${count.index}-nic"
28 | location = "${var.azure_region_fullname}"
29 | resource_group_name = "${azurerm_resource_group.resource_group.name}"
30 | network_security_group_id = "${azurerm_network_security_group.vm_security_group.id}"
31 | count = "${var.vm_count}"
32 |
33 | ip_configuration {
34 | name = "${var.vm_name_prefix}-${count.index}-ipConfig"
35 | subnet_id = "${azurerm_subnet.subnet1.id}"
36 | private_ip_address_allocation = "dynamic"
37 | load_balancer_backend_address_pools_ids = ["${azurerm_lb_backend_address_pool.backend_pool.id}"]
38 | load_balancer_inbound_nat_rules_ids = ["${element(azurerm_lb_nat_rule.winrm_nat.*.id, count.index)}"]
39 | }
40 |
41 | tags {
42 | environment = "${var.environment_tag}"
43 | }
44 | }
45 |
46 | resource "azurerm_virtual_machine" "virtual_machine" {
47 | name = "${var.vm_name_prefix}-${count.index}"
48 | location = "${var.azure_region_fullname}"
49 | resource_group_name = "${azurerm_resource_group.resource_group.name}"
50 | network_interface_ids = ["${element(azurerm_network_interface.vm_nic.*.id, count.index)}"]
51 | vm_size = "${var.vm_size}"
52 | count = "${var.vm_count}"
53 | availability_set_id = "${azurerm_availability_set.availability_set.id}"
54 | delete_os_disk_on_termination = true
55 | delete_data_disks_on_termination = true
56 |
57 | storage_image_reference {
58 | publisher = "MicrosoftWindowsServer"
59 | offer = "WindowsServer"
60 | sku = "2016-Datacenter"
61 | version = "latest"
62 | }
63 |
64 | storage_os_disk {
65 | name = "${var.vm_name_prefix}-${count.index}-osdisk"
66 | vhd_uri = "${azurerm_storage_account.storage_account.primary_blob_endpoint}${azurerm_storage_container.container.name}/${var.vm_name_prefix}-${count.index}-osdisk.vhd"
67 | caching = "ReadWrite"
68 | create_option = "FromImage"
69 | }
70 |
71 | os_profile {
72 | computer_name = "${var.vm_name_prefix}-${count.index}"
73 | admin_username = "${var.admin_username}"
74 | admin_password = "${var.admin_password}"
75 | #Include Deploy.PS1 with variables injected as custom_data
76 | custom_data = "${base64encode("Param($RemoteHostName = \"${var.vm_name_prefix}-${count.index}.${var.azure_region}.${var.azure_dns_suffix}\", $ComputerName = \"${var.vm_name_prefix}-${count.index}\", $WinRmPort = ${var.vm_winrm_port}) ${file("Deploy.PS1")}")}"
77 | }
78 |
79 | tags {
80 | environment = "${var.environment_tag}"
81 | }
82 |
83 | os_profile_windows_config {
84 | provision_vm_agent = true
85 | enable_automatic_upgrades = true
86 |
87 | additional_unattend_config {
88 | pass = "oobeSystem"
89 | component = "Microsoft-Windows-Shell-Setup"
90 | setting_name = "AutoLogon"
91 | content = "${var.admin_password}true1${var.admin_username}"
92 | }
93 | #Unattend config is to enable basic auth in WinRM, required for the provisioner stage.
94 | additional_unattend_config {
95 | pass = "oobeSystem"
96 | component = "Microsoft-Windows-Shell-Setup"
97 | setting_name = "FirstLogonCommands"
98 | content = "${file("FirstLogonCommands.xml")}"
99 | }
100 | }
101 |
102 | provisioner "file" {
103 | source = "WebserverDsc.PS1"
104 | destination = "C:\\Scripts\\WebserverDsc.PS1"
105 | connection {
106 | type = "winrm"
107 | https = true
108 | insecure = true
109 | user = "${var.admin_username}"
110 | password = "${var.admin_password}"
111 | host = "${azurerm_resource_group.resource_group.name}.${var.azure_region}.${var.azure_dns_suffix}"
112 | port = "${count.index + 10000}"
113 | }
114 | }
115 |
116 | provisioner "file" {
117 | content = "${var.vm_name_prefix}-${count.index}"
118 | destination = "C:\\inetpub\\wwwroot\\default.htm"
119 | connection {
120 | type = "winrm"
121 | https = true
122 | insecure = true
123 | user = "${var.admin_username}"
124 | password = "${var.admin_password}"
125 | host = "${azurerm_resource_group.resource_group.name}.${var.azure_region}.${var.azure_dns_suffix}"
126 | port = "${count.index + 10000}"
127 | }
128 | }
129 |
130 | provisioner "remote-exec" {
131 | inline = [
132 | "powershell.exe -sta -ExecutionPolicy Unrestricted -file C:\\Scripts\\WebserverDsc.ps1",
133 | ]
134 | connection {
135 | type = "winrm"
136 | timeout = "20m"
137 | https = true
138 | insecure = true
139 | user = "${var.admin_username}"
140 | password = "${var.admin_password}"
141 | host = "${azurerm_resource_group.resource_group.name}.${var.azure_region}.${var.azure_dns_suffix}"
142 | port = "${count.index + 10000}"
143 | }
144 | }
145 |
146 | }
147 |
148 |
149 | # Get Server types
150 | # Get-AzureRmVMImagePublisher -Location "North Europe" | ? {$_.PublisherName -match "MicrosoftWindows"}
151 | # Get-AzureRmVMImageOffer -Location "North Europe" -PublisherName "MicrosoftWindowsServer"
152 | # Get-AzureRmVMImageSku -Location "North Europe" -PublisherName "MicrosoftWindowsServer" -Offer "WindowsServer"
153 | # Get-AzureRmVMImage -Location "North Europe" -PublisherName "MicrosoftWindowsServer" -Offer "WindowsServer" -Skus "2016-Nano-Server"
--------------------------------------------------------------------------------