diff --git a/terraform/azurerm/README.md b/terraform/azurerm/README.md new file mode 100644 index 00000000..ee4c9feb --- /dev/null +++ b/terraform/azurerm/README.md @@ -0,0 +1,13 @@ +### Azure Resource Manager terraform configuration +--------------------------------------------------- + +This folder contains the terraform configuration for a public and private infrastructure provisioned in Azure and is created by attempting to reverse engineering and match the existing AWS architecture, https://github.com/Capgemini/Apollo/tree/master/terraform/aws. + +The configuration is based on Terraform's ARM provider found here, https://www.terraform.io/docs/providers/azurerm/index.html. +Before Terrafom can create infrastructure within your Azure subscription the following information is required by the 'provider.tf' file including, subscription id, client id, client secret, tenant id. To set up oAuth authentication follow this guide https://www.terraform.io/docs/providers/azurerm/index.html. + +Connection to the server instances is via ssh authenticated by a public / private key certificate in openssh format. Putty was used to generate the public / private key files. There was an issue with certificate only authentication **so please use Terraform verion v0.6.16 or higher**. + +The infrastructure is configured inline with the ARM architecture as shown in the diagram below. Terraform does not yet support creating Load Balance resources thus it was not possible to replicate this feature from AWS. + +![architecture](images/ARMArchitecture.png) diff --git a/terraform/azurerm/images/ARMArchitecture.png b/terraform/azurerm/images/ARMArchitecture.png new file mode 100644 index 00000000..078833de Binary files /dev/null and b/terraform/azurerm/images/ARMArchitecture.png differ diff --git a/terraform/azurerm/private-cloud/agent-cloud-config.yml.tpl b/terraform/azurerm/private-cloud/agent-cloud-config.yml.tpl new file mode 100644 index 00000000..4a8640b3 --- /dev/null +++ b/terraform/azurerm/private-cloud/agent-cloud-config.yml.tpl @@ -0,0 +1,48 @@ +#cloud-config + +coreos: + units: + - name: format-ebs-volume.service + command: start + content: | + [Unit] + Description=Formats the ebs volume if needed + Before=docker.service + [Service] + Type=oneshot + RemainAfterExit=yes + ExecStart=/bin/bash -c '(/usr/sbin/blkid -t TYPE=ext4 | grep /dev/xvdb) || (/usr/sbin/wipefs -fa /dev/xvdb && /usr/sbin/mkfs.ext4 /dev/xvdb)' + - name: var-lib-docker.mount + command: start + content: | + [Unit] + Description=Mount ephemeral to /var/lib/docker + Requires=format-ebs-volume.service + After=format-ebs-volume.service + [Mount] + What=/dev/xvdb + Where=/var/lib/docker + Type=ext4 + - name: docker.service + drop-ins: + - name: 10-wait-docker.conf + content: | + [Unit] + After=var-lib-docker.mount + Requires=var-lib-docker.mount + etcd2: + proxy: on + listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001 + discovery: ${etcd_discovery_url} + fleet: + metadata: "role=agent,region=${region}" + public-ip: "$public_ipv4" + etcd_servers: "http://localhost:2379" + locksmith: + endpoint: "http://localhost:2379" + units: + - name: etcd2.service + command: start + update: + reboot-strategy: best-effort +manage_etc_hosts: localhost \ No newline at end of file diff --git a/terraform/azurerm/private-cloud/bastion-cloud-config.yml.tpl b/terraform/azurerm/private-cloud/bastion-cloud-config.yml.tpl new file mode 100644 index 00000000..e99c9fde --- /dev/null +++ b/terraform/azurerm/private-cloud/bastion-cloud-config.yml.tpl @@ -0,0 +1,20 @@ +#cloud-config + +coreos: + etcd2: + proxy: on + listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001 + discovery: ${etcd_discovery_url} + fleet: + metadata: "role=bastion,region=${region}" + etcd_servers: "http://localhost:2379" + locksmith: + endpoint: "http://localhost:2379" + units: + - name: docker.service + command: start + - name: etcd2.service + command: start + update: + reboot-strategy: best-effort +manage_etc_hosts: localhost \ No newline at end of file diff --git a/terraform/azurerm/private-cloud/bastion-publicip.tf b/terraform/azurerm/private-cloud/bastion-publicip.tf new file mode 100644 index 00000000..c34bf8ec --- /dev/null +++ b/terraform/azurerm/private-cloud/bastion-publicip.tf @@ -0,0 +1,20 @@ +#Create Public IP Address for bastion server +resource "azurerm_public_ip" "bastion_publicip" { + name = "BastionPublicIp" + location = "${var.region}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + public_ip_address_allocation = "static" +} + +#Output +output "bastion_publicip_id" { + value = "${azurerm_public_ip.bastion_publicip.id}" +} + +output "bastion_publicip_ipaddress" { + value = "${azurerm_public_ip.bastion_publicip.ip_address}" +} + +output "bastion_publicip_fqdn" { + value = "${azurerm_public_ip.bastion_publicip.fqdn}" +} \ No newline at end of file diff --git a/terraform/azurerm/private-cloud/bastion-security-group.tf b/terraform/azurerm/private-cloud/bastion-security-group.tf new file mode 100644 index 00000000..bd851209 --- /dev/null +++ b/terraform/azurerm/private-cloud/bastion-security-group.tf @@ -0,0 +1,63 @@ +#Create Network Security Group +resource "azurerm_network_security_group" "bastion_security_group" { + name = "AzureRM_NetworkSecurityGroup" + location = "${var.region}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + + security_rule { + name = "AzureRM_SecurityRuleSSH" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "22" + source_address_prefix = "*" + destination_address_prefix = "*" + } + + security_rule { + name = "AzureRM_SecurityRuleOpenVPN" + priority = 101 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "1194" + source_address_prefix = "*" + destination_address_prefix = "*" + } + + security_rule { + name = "AzureRM_SecurityRuleOpenHTTPS" + priority = 102 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "443" + source_address_prefix = "*" + destination_address_prefix = "*" + } + + security_rule { + name = "AzureRM_SecurityRuleOpenHTTP" + priority = 103 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "80" + source_address_prefix = "*" + destination_address_prefix = "*" + } + + tags { + Name = "bastion-apollo-sg" + } +} + +#Output +output "network_security_group_id" { + value = "${azurerm_network_security_group.bastion_security_group.id}" +} diff --git a/terraform/azurerm/private-cloud/bastion-server.tf b/terraform/azurerm/private-cloud/bastion-server.tf new file mode 100644 index 00000000..83d7fc42 --- /dev/null +++ b/terraform/azurerm/private-cloud/bastion-server.tf @@ -0,0 +1,128 @@ +# Create a network interface for bastion server +resource "azurerm_network_interface" "bastion_network_interface" { + name = "Bastion_NetworkInterface" + location = "${var.region}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + network_security_group_id = "${azurerm_network_security_group.bastion_security_group.id}" + + ip_configuration { + name = "bastionipconfiguration" + subnet_id = "${element(azurerm_subnet.public.*.id, 0)}" + private_ip_address_allocation = "dynamic" + public_ip_address_id = "${azurerm_public_ip.bastion_publicip.id}" + } +} + +# User profile template +resource "template_file" "bastion_cloud_init" { + template = "${file("bastion-cloud-config.yml.tpl")}" + depends_on = ["template_file.etcd_discovery_url"] + vars { + etcd_discovery_url = "${file(var.etcd_discovery_url_file)}" + size = "${var.master_count}" + vpc_cidr_block = "${var.vpc_cidr_block}" + region = "${var.region}" + } +} + +# NAT/VPN server +resource "azurerm_virtual_machine" "bastion" { + name = "apollo-bastion" + location = "${var.region}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + network_interface_ids = ["${azurerm_network_interface.bastion_network_interface.id}"] + vm_size = "${var.instance_type.bastion}" + + storage_image_reference { + publisher = "${var.artifact_bastion.publisher}" + offer = "${var.artifact_bastion.offer}" + sku = "${var.artifact_bastion.sku}" + version = "${var.artifact_bastion.version}" + } + + storage_os_disk { + name = "bastiondisk" + vhd_uri = "${azurerm_storage_account.storage_account.primary_blob_endpoint}${azurerm_storage_container.storage_container.name}/bastiondisk.vhd" + caching = "ReadWrite" + create_option = "FromImage" + } + + os_profile { + computer_name = "${var.bastion_server_computername}" + admin_username = "${var.bastion_server_username}" + admin_password = "${var.bastion_server_password}" + custom_data = "${base64encode(template_file.bastion_cloud_init.rendered)}" + } + + os_profile_linux_config { + disable_password_authentication = true + + ssh_keys { + path = "/home/${var.bastion_server_username}/.ssh/authorized_keys" + key_data = "${file("${var.ssh_public_key_file}")}" # openssh format + } + } + + tags { + Name = "apollo-mesos-bastion" + role = "bastion" + } + + connection { + host = "${azurerm_public_ip.bastion_publicip.ip_address}" + user = "${var.bastion_server_username}" + private_key = "${file("${var.ssh_private_key_file}")}" # openssh format + } + + # Do some early bootstrapping of the CoreOS machines. This will install + # python and pip so we can use as the ansible_python_interpreter in our playbooks + provisioner "file" { + source = "../../scripts/coreos" + destination = "/tmp" + } + + provisioner "remote-exec" { + inline = [ + "sudo chmod -R +x /tmp/coreos", + "/tmp/coreos/bootstrap.sh", + "~/bin/python /tmp/coreos/get-pip.py", + "sudo mv /tmp/coreos/runner ~/bin/pip && sudo chmod 0755 ~/bin/pip", + "sudo rm -rf /tmp/coreos", + + # Initialize open VPN container and server config + "sudo iptables -t nat -A POSTROUTING -j MASQUERADE", + "sudo docker run --name ovpn-data -v /etc/openvpn busybox", + "sudo docker run --volumes-from ovpn-data --rm gosuri/openvpn ovpn_genconfig -p ${var.vpc_cidr_block} -u udp://${azurerm_public_ip.bastion_publicip.ip_address}" + ] + } +} + +# Bastion network interface outputs +output "bastion_network_interface_id" { + value = "${azurerm_network_interface.bastion_network_interface.id}" +} + +output "bastion_network_interface_macaddress" { + value = "${azurerm_network_interface.bastion_network_interface.mac_address}" +} + +output "bastion_network_interface_privateipaddress" { + value = "${azurerm_network_interface.bastion_network_interface.private_ip_address}" +} + +output "bastion_network_interface_virtualmachineid" { + value = "${azurerm_network_interface.bastion_network_interface.virtual_machine_id}" +} + +output "bastion_network_interface_applieddnsservers" { + value = "${azurerm_network_interface.bastion_network_interface.applied_dns_servers}" +} + +output "bastion_network_interface_internalfqdn" { + value = "${azurerm_network_interface.bastion_network_interface.internal_fqdn}" +} + +# Bastion virtual machine outputs +output "bastion_virtual_machine_id" { + value = "${azurerm_virtual_machine.bastion.id}" +} \ No newline at end of file diff --git a/terraform/azurerm/private-cloud/gateway-local-network.tf b/terraform/azurerm/private-cloud/gateway-local-network.tf new file mode 100644 index 00000000..39f91649 --- /dev/null +++ b/terraform/azurerm/private-cloud/gateway-local-network.tf @@ -0,0 +1,13 @@ +#Create Public IP Address for local network gateway +resource "azurerm_local_network_gateway" "gateway" { + name = "AzureRM_LocalNetworkGateway" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + location = "${var.region}" + gateway_address = "${azurerm_public_ip.bastion_publicip.ip_address}" + address_space = ["${var.vpc_cidr_block}"] +} + +#Output +output "gateway_local_network_id" { + value = "${azurerm_local_network_gateway.gateway.id}" +} diff --git a/terraform/azurerm/private-cloud/master-cloud-config.yml.tpl b/terraform/azurerm/private-cloud/master-cloud-config.yml.tpl new file mode 100644 index 00000000..df2f0b6e --- /dev/null +++ b/terraform/azurerm/private-cloud/master-cloud-config.yml.tpl @@ -0,0 +1,22 @@ +#cloud-config + +coreos: + etcd2: + # $private_ipv4 is populated by the cloud provider + # we don't have a $public_ipv4 in the private VPC + advertise-client-urls: http://$private_ipv4:2379,http://$private_ipv4:4001 + initial-advertise-peer-urls: http://$private_ipv4:2380 + # listen on both the official ports and the legacy ports + # legacy ports can be omitted if your application doesn't depend on them + listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001 + listen-peer-urls: http://$private_ipv4:2380,http://$private_ipv4:7001 + # Discovery is populated by Terraform + discovery: ${etcd_discovery_url} + fleet: + metadata: "role=master,region=${region}" + units: + - name: etcd2.service + command: start + update: + reboot-strategy: best-effort +manage_etc_hosts: localhost \ No newline at end of file diff --git a/terraform/azurerm/private-cloud/mesos-agents-availability-set.tf b/terraform/azurerm/private-cloud/mesos-agents-availability-set.tf new file mode 100644 index 00000000..8e5c8c46 --- /dev/null +++ b/terraform/azurerm/private-cloud/mesos-agents-availability-set.tf @@ -0,0 +1,11 @@ +# Create an availability set for agent servers +resource "azurerm_availability_set" "agent" { + name = "Agent_AvailabilitySet" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + location = "${var.region}" +} + +# Mesos agent availability set outputs +output "mesos_agent_virtual_machine_ids" { + value = "${azurerm_availability_set.agent.id}" +} \ No newline at end of file diff --git a/terraform/azurerm/private-cloud/mesos-agents.tf b/terraform/azurerm/private-cloud/mesos-agents.tf new file mode 100644 index 00000000..31349910 --- /dev/null +++ b/terraform/azurerm/private-cloud/mesos-agents.tf @@ -0,0 +1,129 @@ +# Create a network interface for agent server +resource "azurerm_network_interface" "agent_network_interface" { + name = "Agent_NetworkInterface-${count.index}" + count = "${var.agent_count}" + location = "${var.region}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + network_security_group_id = "${azurerm_network_security_group.network_security_group.id}" + + ip_configuration { + name = "agentipconfiguration-${count.index}" + subnet_id = "${element(azurerm_subnet.private.*.id, count.index)}" + private_ip_address_allocation = "dynamic" + } +} + +# User profile template +resource "template_file" "agent_cloud_init" { + template = "${file("agent-cloud-config.yml.tpl")}" + depends_on = ["template_file.etcd_discovery_url"] + vars { + etcd_discovery_url = "${file(var.etcd_discovery_url_file)}" + size = "${var.master_count}" + vpc_cidr_block = "${var.vpc_cidr_block}" + region = "${var.region}" + } +} + +# Agent server +resource "azurerm_virtual_machine" "mesos_agent" { + name = "apollo-mesos-agent-${count.index}" + count = "${var.agent_count}" + location = "${var.region}" + availability_set_id = "${azurerm_availability_set.agent.id}" + depends_on = ["azurerm_virtual_machine.bastion", "azurerm_virtual_machine.mesos_master"] + resource_group_name = "${azurerm_resource_group.resource_group.name}" + network_interface_ids = ["${element(azurerm_network_interface.agent_network_interface.*.id, count.index)}"] + vm_size = "${var.instance_type.agent}" + + storage_image_reference { + publisher = "${var.artifact_agent.publisher}" + offer = "${var.artifact_agent.offer}" + sku = "${var.artifact_agent.sku}" + version = "${var.artifact_agent.version}" + } + + storage_os_disk { + name = "agentdisk${count.index}" + vhd_uri = "${azurerm_storage_account.storage_account.primary_blob_endpoint}${azurerm_storage_container.storage_container.name}/agentdisk-${count.index}.vhd" + caching = "ReadWrite" + create_option = "FromImage" + } + + os_profile { + computer_name = "Mesos-Agent-${count.index}" + admin_username = "${var.agent_server_username}" + admin_password = "${var.agent_server_password}" + custom_data = "${base64encode(template_file.agent_cloud_init.rendered)}" + } + + os_profile_linux_config { + disable_password_authentication = true + + ssh_keys { + path = "/home/${var.agent_server_username}/.ssh/authorized_keys" + key_data = "${file("${var.ssh_public_key_file}")}" # openssh format + } + } + + tags = { + Name = "apollo-mesos-agent-${count.index}" + role = "mesos_agents" + } + + connection { + user = "${var.agent_server_username}" + host = "${element(azurerm_network_interface.agent_network_interface.*.private_ip_address, count.index)}" + private_key = "${file("${var.ssh_private_key_file}")}" # openssh format + bastion_host = "${azurerm_public_ip.bastion_publicip.ip_address}" + bastion_user = "${var.bastion_server_username}" + bastion_private_key = "${file("${var.ssh_private_key_file}")}" # openssh format + } + + # Do some early bootstrapping of the CoreOS machines. This will install + # python and pip so we can use as the ansible_python_interpreter in our playbooks + provisioner "file" { + source = "../../scripts/coreos" + destination = "/tmp" + } + + provisioner "remote-exec" { + inline = [ + "sudo chmod -R +x /tmp/coreos", + "/tmp/coreos/bootstrap.sh", + "~/bin/python /tmp/coreos/get-pip.py", + "sudo mv /tmp/coreos/runner ~/bin/pip && sudo chmod 0755 ~/bin/pip", + "sudo rm -rf /tmp/coreos" + ] + } +} + +# Mesos agent network interface outputs +output "mesos_agent_network_interface_ids" { + value = "${join(",", azurerm_network_interface.agent_network_interface.*.id)}" +} + +output "mesos_agent_network_interface_macaddresses" { + value = "${join(",", azurerm_network_interface.agent_network_interface.*.mac_address)}" +} + +output "mesos_agent_network_interface_privateipaddresses" { + value = "${join(",", azurerm_network_interface.agent_network_interface.*.private_ip_address)}" +} + +output "mesos_agent_network_interface_virtualmachineids" { + value = "${join(",", azurerm_network_interface.agent_network_interface.*.virtual_machine_id)}" +} + +output "mesos_agent_network_interface_applieddnsservers" { + value = "${join(",", azurerm_network_interface.agent_network_interface.*.applied_dns_servers)}" +} + +output "mesos_agent_network_interface_internalfqdns" { + value = "${join(",", azurerm_network_interface.agent_network_interface.*.internal_fqdn)}" +} + +# Mesos agent virtual machine outputs +output "mesos_agent_virtual_machine_ids" { + value = "${join(",", azurerm_virtual_machine.mesos_agent.*.id)}" +} \ No newline at end of file diff --git a/terraform/azurerm/private-cloud/mesos-masters-availability-set.tf b/terraform/azurerm/private-cloud/mesos-masters-availability-set.tf new file mode 100644 index 00000000..2f4f674a --- /dev/null +++ b/terraform/azurerm/private-cloud/mesos-masters-availability-set.tf @@ -0,0 +1,11 @@ +# Create an availability set for master servers +resource "azurerm_availability_set" "master" { + name = "Master_AvailabilitySet" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + location = "${var.region}" +} + +# Mesos master availability set outputs +output "mesos_master_virtual_machine_ids" { + value = "${azurerm_availability_set.master.id}" +} \ No newline at end of file diff --git a/terraform/azurerm/private-cloud/mesos-masters.tf b/terraform/azurerm/private-cloud/mesos-masters.tf new file mode 100644 index 00000000..24969243 --- /dev/null +++ b/terraform/azurerm/private-cloud/mesos-masters.tf @@ -0,0 +1,129 @@ +# Create a network interface for master server +resource "azurerm_network_interface" "master_network_interface" { + name = "Master_NetworkInterface-${count.index}" + count = "${var.master_count}" + location = "${var.region}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + network_security_group_id = "${azurerm_network_security_group.network_security_group.id}" + + ip_configuration { + name = "masteripconfiguration-${count.index}" + subnet_id = "${element(azurerm_subnet.private.*.id, count.index)}" + private_ip_address_allocation = "dynamic" + } +} + +# User profile template +resource "template_file" "master_cloud_init" { + template = "${file("master-cloud-config.yml.tpl")}" + depends_on = ["template_file.etcd_discovery_url"] + vars { + etcd_discovery_url = "${file(var.etcd_discovery_url_file)}" + size = "${var.master_count}" + vpc_cidr_block = "${var.vpc_cidr_block}" + region = "${var.region}" + } +} + +# Master server +resource "azurerm_virtual_machine" "mesos_master" { + name = "apollo-mesos-master-${count.index}" + count = "${var.master_count}" + location = "${var.region}" + availability_set_id = "${azurerm_availability_set.master.id}" + depends_on = ["azurerm_virtual_machine.bastion"] + resource_group_name = "${azurerm_resource_group.resource_group.name}" + network_interface_ids = ["${element(azurerm_network_interface.master_network_interface.*.id, count.index)}"] + vm_size = "${var.instance_type.master}" + + storage_image_reference { + publisher = "${var.artifact_master.publisher}" + offer = "${var.artifact_master.offer}" + sku = "${var.artifact_master.sku}" + version = "${var.artifact_master.version}" + } + + storage_os_disk { + name = "masterdisk${count.index}" + vhd_uri = "${azurerm_storage_account.storage_account.primary_blob_endpoint}${azurerm_storage_container.storage_container.name}/masterdisk-${count.index}.vhd" + caching = "ReadWrite" + create_option = "FromImage" + } + + os_profile { + computer_name = "apollo-mesos-master-${count.index}" + admin_username = "${var.master_server_username}" + admin_password = "${var.master_server_password}" + custom_data = "${base64encode(template_file.master_cloud_init.rendered)}" + } + + os_profile_linux_config { + disable_password_authentication = true + + ssh_keys { + path = "/home/${var.master_server_username}/.ssh/authorized_keys" + key_data = "${file("${var.ssh_public_key_file}")}" # openssh format + } + } + + tags { + Name = "apollo-mesos-master-${count.index}" + role = "mesos_masters" + } + + connection { + user = "${var.master_server_username}" + host = "${element(azurerm_network_interface.master_network_interface.*.private_ip_address, count.index)}" + private_key = "${file("${var.ssh_private_key_file}")}" # openssh format + bastion_host = "${azurerm_public_ip.bastion_publicip.ip_address}" + bastion_user = "${var.bastion_server_username}" + bastion_private_key = "${file("${var.ssh_private_key_file}")}" # openssh format + } + + # Do some early bootstrapping of the CoreOS machines. This will install + # python and pip so we can use as the ansible_python_interpreter in our playbooks + provisioner "file" { + source = "../../scripts/coreos" + destination = "/tmp" + } + + provisioner "remote-exec" { + inline = [ + "sudo chmod -R +x /tmp/coreos", + "/tmp/coreos/bootstrap.sh", + "~/bin/python /tmp/coreos/get-pip.py", + "sudo mv /tmp/coreos/runner ~/bin/pip && sudo chmod 0755 ~/bin/pip", + "sudo rm -rf /tmp/coreos" + ] + } +} + +# Mesos master network interface outputs +output "mesos_master_network_interface_ids" { + value = "${join(",", azurerm_network_interface.master_network_interface.*.id)}" +} + +output "mesos_master_network_interface_macaddresses" { + value = "${join(",", azurerm_network_interface.master_network_interface.*.mac_address)}" +} + +output "mesos_master_network_interface_privateipaddresses" { + value = "${join(",", azurerm_network_interface.master_network_interface.*.private_ip_address)}" +} + +output "mesos_master_network_interface_virtualmachineids" { + value = "${join(",", azurerm_network_interface.master_network_interface.*.virtual_machine_id)}" +} + +output "mesos_master_network_interface_applieddnsservers" { + value = "${join(",", azurerm_network_interface.master_network_interface.*.applied_dns_servers)}" +} + +output "mesos_master_network_interface_internalfqdns" { + value = "${join(",", azurerm_network_interface.master_network_interface.*.internal_fqdn)}" +} + +# Mesos master virtual machine outputs +output "mesos_master_virtual_machine_ids" { + value = "${join(",", azurerm_virtual_machine.mesos_master.*.id)}" +} \ No newline at end of file diff --git a/terraform/azurerm/private-cloud/network-security-group.tf b/terraform/azurerm/private-cloud/network-security-group.tf new file mode 100644 index 00000000..d77f322b --- /dev/null +++ b/terraform/azurerm/private-cloud/network-security-group.tf @@ -0,0 +1,39 @@ +#Create Network Security Group +resource "azurerm_network_security_group" "network_security_group" { + name = "AzureRM_BastionNetworkSecurityGroup" + location = "${var.region}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + + security_rule { + name = "AzureRM_SecurityRuleInbound" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "*" + source_port_range = "*" + destination_port_range = "*" + source_address_prefix = "*" + destination_address_prefix = "*" + } + + security_rule { + name = "AzureRM_SecurityRuleOutbound" + priority = 101 + direction = "Outbound" + access = "Allow" + protocol = "*" + source_port_range = "*" + destination_port_range = "*" + source_address_prefix = "*" + destination_address_prefix = "*" + } + + tags { + Name = "default-apollo-sg" + } +} + +#Output +output "network_security_group_id" { + value = "${azurerm_network_security_group.network_security_group.id}" +} diff --git a/terraform/azurerm/private-cloud/provider.tf b/terraform/azurerm/private-cloud/provider.tf new file mode 100644 index 00000000..57476fdd --- /dev/null +++ b/terraform/azurerm/private-cloud/provider.tf @@ -0,0 +1,9 @@ +/* Configure auth 2 resource manager authentication. This requires an aplication to be set up in Azure, see +https://www.terraform.io/docs/providers/azurerm/index.html fo details. */ + +provider "azurerm" { + subscription_id = "${var.subscription_id}" + client_id = "${var.client_id}" + client_secret = "${var.client_secret}" + tenant_id = "${var.tenant_id}" +} \ No newline at end of file diff --git a/terraform/azurerm/private-cloud/resource-group.tf b/terraform/azurerm/private-cloud/resource-group.tf new file mode 100644 index 00000000..32a6ac68 --- /dev/null +++ b/terraform/azurerm/private-cloud/resource-group.tf @@ -0,0 +1,10 @@ +# Create a resource group +resource "azurerm_resource_group" "resource_group" { + name = "AzureRM-Resource-Group" + location = "${var.region}" +} + +#Output +output "resource_group_id" { + value = "${azurerm_resource_group.resource_group.id}" +} \ No newline at end of file diff --git a/terraform/azurerm/private-cloud/storage.tf b/terraform/azurerm/private-cloud/storage.tf new file mode 100644 index 00000000..04b282e6 --- /dev/null +++ b/terraform/azurerm/private-cloud/storage.tf @@ -0,0 +1,66 @@ +# Create a storage account +resource "azurerm_storage_account" "storage_account" { + name = "${var.storage_account_name}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + location = "${var.region}" + account_type = "${var.storage_account_type}" +} + +# Create storage container +resource "azurerm_storage_container" "storage_container" { + name = "${var.storage_container_name}" + depends_on = ["azurerm_storage_account.storage_account"] + resource_group_name = "${azurerm_resource_group.resource_group.name}" + storage_account_name = "${azurerm_storage_account.storage_account.name}" + container_access_type = "private" +} + +# Storage Account Output +output "storage_account_id" { + value = "${azurerm_storage_account.storage_account.id}" +} + +output "storage_account_primary_location" { + value = "${azurerm_storage_account.storage_account.primary_location}" +} + +output "storage_account_secondary_location" { + value = "${azurerm_storage_account.storage_account.secondary_location}" +} + +output "storage_account_primary_blob_endpoint" { + value = "${azurerm_storage_account.storage_account.primary_blob_endpoint}" +} + +output "storage_account_secondary_blob_endpoint" { + value = "${azurerm_storage_account.storage_account.secondary_blob_endpoint}" +} + +output "storage_account_primary_queue_endpoint" { + value = "${azurerm_storage_account.storage_account.primary_queue_endpoint}" +} + +output "storage_account_secondary_queue_endpoint" { + value = "${azurerm_storage_account.storage_account.secondary_queue_endpoint}" +} + +output "storage_account_primary_table_endpoint" { + value = "${azurerm_storage_account.storage_account.primary_table_endpoint}" +} + +output "storage_account_secondary_table_endpoint" { + value = "${azurerm_storage_account.storage_account.secondary_table_endpoint}" +} + +output "storage_account_primary_file_endpoint" { + value = "${azurerm_storage_account.storage_account.primary_file_endpoint}" +} + +# Storage Container Output +output "storage_container_id" { + value = "${azurerm_storage_container.storage_container.id}" +} + +output "storage_container_properties" { + value = "${azurerm_storage_container.storage_container.properties}" +} diff --git a/terraform/azurerm/private-cloud/template_file.etcd_discovery_url.tf b/terraform/azurerm/private-cloud/template_file.etcd_discovery_url.tf new file mode 100644 index 00000000..631cc0bb --- /dev/null +++ b/terraform/azurerm/private-cloud/template_file.etcd_discovery_url.tf @@ -0,0 +1,12 @@ +# Generate an etcd URL for the cluster +resource "template_file" "etcd_discovery_url" { + # template = "${file(/dev/null)}" + provisioner "local-exec" { + command = "curl https://discovery.etcd.io/new?size=${var.master_count} > ${var.etcd_discovery_url_file}" + } + + # This will regenerate the discovery URL if the cluster size changes, we include the bastion here + vars { + size = "${var.master_count}" + } +} diff --git a/terraform/azurerm/private-cloud/variables.tf b/terraform/azurerm/private-cloud/variables.tf new file mode 100644 index 00000000..1d4a96ea --- /dev/null +++ b/terraform/azurerm/private-cloud/variables.tf @@ -0,0 +1,141 @@ +variable "subscription_id" { + description = "The Azure subscrition identifier (guid)." +} + +variable "client_id" { + description = "The oAuth 2 client id. " +} + +variable "client_secret" { + description = "The oAuth 2 client secret." +} + +variable "tenant_id" { + description = "The oAuth 2 tenant id." +} + +variable "region" { + description = "The deployment azure data centre location." + default = "North Europe" +} + +variable "vpc_cidr_block" { + description = "Cidr block for the VN." + default = "10.0.0.0/16" +} + +variable "private_subnet_cidr_block" { + description = "CIDR for private subnet" + default = "10.0.1.0/24,10.0.2.0/24,10.0.3.0/24" +} + +variable "public_subnet_cidr_block" { + description = "CIDR for public subnet" + default = "10.0.101.0/24,10.0.102.0/24,10.0.103.0/24" +} + +variable "storage_account_name" { + description = "Storage account name" + default = "mesosimages" +} + +# I.E. Standard_GRS +variable "storage_account_type" { + description = "Storage account type" +} + +variable "storage_container_name" { + description = "Storage container name" + default = "mesosimages-container" +} + +variable "artifact_bastion" { + default = { + publisher = "CoreOS" + offer ="CoreOS" + sku = "Stable" + version = "latest" + } +} + +variable "artifact_master" { + default = { + publisher = "CoreOS" + offer ="CoreOS" + sku = "Stable" + version = "latest" + } +} + +variable "artifact_agent" { + default = { + publisher = "CoreOS" + offer ="CoreOS" + sku = "Stable" + version = "latest" + } +} + +variable "instance_type" { + default = { + bastion = "Standard_A0" + master = "Standard_A0" + agent = "Standard_A0" + } +} + +variable "bastion_server_computername" { + description = "Username to access server" + default = "bastion" +} + +variable "bastion_server_username" { + description = "Username to access server" +} + +variable "bastion_server_password" { + description = "Password to access server" +} + +variable "master_server_username" { + description = "Username to access master server" +} + +variable "master_server_password" { + description = "Password to access master server" +} + +variable "agent_server_username" { + description = "Username to access agent server" +} + +variable "agent_server_password" { + description = "Password to access agent server" +} + +variable "master_count" { + description = "The number of masters." + default = "1" +} + +variable "agent_count" { + description = "The number of agents." + default = "1" +} + +variable "docker_version" { + description = "Docker version" + default = "1.9.0-0~trusty" +} + +variable "ssh_public_key_file" { + description = "Public key file path required to connect via ssh" +} + +variable "ssh_private_key_file" { + description = "Public key file path required to connect via ssh" +} + +variable "etcd_discovery_url_file" { + default = "etcd_discovery_url.txt" +} \ No newline at end of file diff --git a/terraform/azurerm/private-cloud/virtual-network-subnets.tf b/terraform/azurerm/private-cloud/virtual-network-subnets.tf new file mode 100644 index 00000000..669d6cf3 --- /dev/null +++ b/terraform/azurerm/private-cloud/virtual-network-subnets.tf @@ -0,0 +1,87 @@ +#Create public subnets +resource "azurerm_subnet" "public" { + name = "AzureRM_Public_Subnet-${count.index}" + count = "${length(compact(split(",", var.public_subnet_cidr_block)))}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + virtual_network_name = "${azurerm_virtual_network.virtual_network.name}" + address_prefix = "${element(split(",", var.public_subnet_cidr_block), count.index)}" + route_table_id = "${azurerm_route_table.public.id}" +} + +resource "azurerm_route_table" "public" { + name = "AzureRM_Public_Route_Table" + location = "${var.region}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + + route { + name = "AzureRM_Public_Route" + address_prefix = "0.0.0.0/0" + next_hop_type = "Internet" + } + + tags { + environment = "default-public" + } +} + +#Create private subnets +resource "azurerm_subnet" "private" { + name = "AzureRM_Private_Subnet-${count.index}" + count = "${length(compact(split(",", var.private_subnet_cidr_block)))}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + virtual_network_name = "${azurerm_virtual_network.virtual_network.name}" + address_prefix = "${element(split(",", var.private_subnet_cidr_block), count.index)}" + route_table_id = "${azurerm_route_table.private.id}" +} + +resource "azurerm_route_table" "private" { + name = "AzureRM_Private_Route_Table" + location = "${var.region}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + + route { + name = "AzureRM_Private_Route" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_in_ip_address = "${azurerm_network_interface.bastion_network_interface.private_ip_address}" + } + + tags { + environment = "default-private" + } +} + +# Public subnets output +output "subnet_ids" { + value = "${join(",", azurerm_subnet.public.*.id)}" +} + +output "subnet_ip_configurations" { + value = "${join(",", azurerm_subnet.public.ip_configurations)}" +} + +output "public_route_table_id" { + value = "${azurerm_route_table.public.id}" +} + +output "public_route_table_subnets" { + value = "${join(",", azurerm_route_table.public.subnets)}" +} + +# Private subnets output +output "subnet_ids" { + value = "${join(",", azurerm_subnet.private.*.id)}" +} + +output "subnet_ip_configurations" { + value = "${join(",", azurerm_subnet.private.ip_configurations)}" +} + +output "private_route_table_id" { + value = "${azurerm_route_table.private.id}" +} + +output "private_route_table_subnets" { + value = "${join(",", azurerm_route_table.private.subnets)}" +} + diff --git a/terraform/azurerm/private-cloud/virtual-network.tf b/terraform/azurerm/private-cloud/virtual-network.tf new file mode 100644 index 00000000..b7c3a0ff --- /dev/null +++ b/terraform/azurerm/private-cloud/virtual-network.tf @@ -0,0 +1,12 @@ +# Create a virtual network +resource "azurerm_virtual_network" "virtual_network" { + name = "AzureRM-Virtual-Network" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + address_space = ["${var.vpc_cidr_block}"] + location = "${var.region}" +} + +#Output +output "network_confirguration_id" { + value = "${azurerm_virtual_network.virtual_network.id}" +} \ No newline at end of file diff --git a/terraform/azurerm/public-cloud/agent-cloud-config.yml.tpl b/terraform/azurerm/public-cloud/agent-cloud-config.yml.tpl new file mode 100644 index 00000000..d47f1372 --- /dev/null +++ b/terraform/azurerm/public-cloud/agent-cloud-config.yml.tpl @@ -0,0 +1,48 @@ +#cloud-config + +coreos: + units: + - name: format-ebs-volume.service + command: start + content: | + [Unit] + Description=Formats the ebs volume if needed + Before=docker.service + [Service] + Type=oneshot + RemainAfterExit=yes + ExecStart=/bin/bash -c '(/usr/sbin/blkid -t TYPE=ext4 | grep /dev/xvdb) || (/usr/sbin/wipefs -fa /dev/xvdb && /usr/sbin/mkfs.ext4 /dev/xvdb)' + - name: var-lib-docker.mount + command: start + content: | + [Unit] + Description=Mount ephemeral to /var/lib/docker + Requires=format-ebs-volume.service + After=format-ebs-volume.service + [Mount] + What=/dev/xvdb + Where=/var/lib/docker + Type=ext4 + - name: docker.service + drop-ins: + - name: 10-wait-docker.conf + content: | + [Unit] + After=var-lib-docker.mount + Requires=var-lib-docker.mount + etcd2: + proxy: on + listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001 + discovery: ${etcd_discovery_url} + fleet: + metadata: "role=agent,region=${region}" + public-ip: "$public_ipv4" + etcd_servers: "http://localhost:2379" + locksmith: + endpoint: "http://localhost:2379" + units: + - name: etcd2.service + command: start + update: + reboot-strategy: best-effort +manage_etc_hosts: localhost \ No newline at end of file diff --git a/terraform/azurerm/public-cloud/master-cloud-config.yml.tpl b/terraform/azurerm/public-cloud/master-cloud-config.yml.tpl new file mode 100644 index 00000000..e1041f43 --- /dev/null +++ b/terraform/azurerm/public-cloud/master-cloud-config.yml.tpl @@ -0,0 +1,19 @@ +#cloud-config + +coreos: + etcd2: + # $public_ipv4 and $private_ipv4 are populated by the cloud provider + advertise-client-urls: http://$public_ipv4:2379 + initial-advertise-peer-urls: http://$private_ipv4:2380 + listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001 + listen-peer-urls: http://$private_ipv4:2380,http://$private_ipv4:7001 + discovery: ${etcd_discovery_url} + fleet: + metadata: "role=master,region=${region}" + public-ip: "$public_ipv4" + units: + - name: etcd2.service + command: start + update: + reboot-strategy: best-effort +manage_etc_hosts: localhost \ No newline at end of file diff --git a/terraform/azurerm/public-cloud/mesos-agent-publicips.tf b/terraform/azurerm/public-cloud/mesos-agent-publicips.tf new file mode 100644 index 00000000..f23960f8 --- /dev/null +++ b/terraform/azurerm/public-cloud/mesos-agent-publicips.tf @@ -0,0 +1,8 @@ +#Create Public IP Address for agent servers +resource "azurerm_public_ip" "agent_publicip" { + name = "AgentPublicIp-${count.index}" + count = "${var.agent_count}" + location = "${var.region}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + public_ip_address_allocation = "static" +} \ No newline at end of file diff --git a/terraform/azurerm/public-cloud/mesos-agents-availability-set.tf b/terraform/azurerm/public-cloud/mesos-agents-availability-set.tf new file mode 100644 index 00000000..8e5c8c46 --- /dev/null +++ b/terraform/azurerm/public-cloud/mesos-agents-availability-set.tf @@ -0,0 +1,11 @@ +# Create an availability set for agent servers +resource "azurerm_availability_set" "agent" { + name = "Agent_AvailabilitySet" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + location = "${var.region}" +} + +# Mesos agent availability set outputs +output "mesos_agent_virtual_machine_ids" { + value = "${azurerm_availability_set.agent.id}" +} \ No newline at end of file diff --git a/terraform/azurerm/public-cloud/mesos-agents.tf b/terraform/azurerm/public-cloud/mesos-agents.tf new file mode 100644 index 00000000..792db690 --- /dev/null +++ b/terraform/azurerm/public-cloud/mesos-agents.tf @@ -0,0 +1,104 @@ +# Create a network interface for agent server +resource "azurerm_network_interface" "agent_network_interface" { + name = "Agent_NetworkInterface-${count.index}" + count = "${var.agent_count}" + location = "${var.region}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + network_security_group_id = "${azurerm_network_security_group.network_security_group.id}" + + ip_configuration { + name = "agentipconfiguration-${count.index}" + subnet_id = "${element(azurerm_subnet.public.*.id, count.index)}" + private_ip_address_allocation = "dynamic" + public_ip_address_id = "${azurerm_public_ip.agent_publicip.id}" + } +} + +# User profile template +resource "template_file" "agent_cloud_init" { + template = "${file("agent-cloud-config.yml.tpl")}" + depends_on = ["template_file.etcd_discovery_url"] + vars { + etcd_discovery_url = "${file(var.etcd_discovery_url_file)}" + size = "${var.master_count}" + vpc_cidr_block = "${var.vpc_cidr_block}" + region = "${var.region}" + } +} + +# Agent server +resource "azurerm_virtual_machine" "mesos_agent" { + name = "apollo-mesos-agent-${count.index}" + count = "${var.agent_count}" + location = "${var.region}" + availability_set_id = "${azurerm_availability_set.agent.id}" + depends_on = ["azurerm_virtual_machine.mesos_master"] + resource_group_name = "${azurerm_resource_group.resource_group.name}" + network_interface_ids = ["${element(azurerm_network_interface.agent_network_interface.*.id, count.index)}"] + vm_size = "${var.instance_type.agent}" + + storage_image_reference { + publisher = "${var.artifact_agent.publisher}" + offer = "${var.artifact_agent.offer}" + sku = "${var.artifact_agent.sku}" + version = "${var.artifact_agent.version}" + } + + storage_os_disk { + name = "agentdisk${count.index}" + vhd_uri = "${azurerm_storage_account.storage_account.primary_blob_endpoint}${azurerm_storage_container.storage_container.name}/agentdisk-${count.index}.vhd" + caching = "ReadWrite" + create_option = "FromImage" + } + + os_profile { + computer_name = "Mesos-Agent-${count.index}" + admin_username = "${var.agent_server_username}" + admin_password = "${var.agent_server_password}" + custom_data = "${base64encode(template_file.agent_cloud_init.rendered)}" + } + + os_profile_linux_config { + disable_password_authentication = true + + ssh_keys { + path = "/home/${var.agent_server_username}/.ssh/authorized_keys" + key_data = "${file("${var.ssh_public_key_file}")}" # openssh format + } + } + + tags = { + Name = "apollo-mesos-agent-${count.index}" + role = "mesos_agents" + } +} + +# Mesos agent network interface outputs +output "mesos_agent_network_interface_ids" { + value = "${join(",", azurerm_network_interface.agent_network_interface.*.id)}" +} + +output "mesos_agent_network_interface_macaddresses" { + value = "${join(",", azurerm_network_interface.agent_network_interface.*.mac_address)}" +} + +output "mesos_agent_network_interface_privateipaddresses" { + value = "${join(",", azurerm_network_interface.agent_network_interface.*.private_ip_address)}" +} + +output "mesos_agent_network_interface_virtualmachineids" { + value = "${join(",", azurerm_network_interface.agent_network_interface.*.virtual_machine_id)}" +} + +output "mesos_agent_network_interface_applieddnsservers" { + value = "${join(",", azurerm_network_interface.agent_network_interface.*.applied_dns_servers)}" +} + +output "mesos_agent_network_interface_internalfqdns" { + value = "${join(",", azurerm_network_interface.agent_network_interface.*.internal_fqdn)}" +} + +# Mesos agent virtual machine outputs +output "mesos_agent_virtual_machine_ids" { + value = "${join(",", azurerm_virtual_machine.mesos_agent.*.id)}" +} \ No newline at end of file diff --git a/terraform/azurerm/public-cloud/mesos-master-publicips.tf b/terraform/azurerm/public-cloud/mesos-master-publicips.tf new file mode 100644 index 00000000..0cdb1286 --- /dev/null +++ b/terraform/azurerm/public-cloud/mesos-master-publicips.tf @@ -0,0 +1,8 @@ +#Create Public IP Address for master servers +resource "azurerm_public_ip" "master_publicip" { + name = "MasterPublicIp-${count.index}" + count = "${var.master_count}" + location = "${var.region}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + public_ip_address_allocation = "static" +} \ No newline at end of file diff --git a/terraform/azurerm/public-cloud/mesos-masters-availability-set.tf b/terraform/azurerm/public-cloud/mesos-masters-availability-set.tf new file mode 100644 index 00000000..2f4f674a --- /dev/null +++ b/terraform/azurerm/public-cloud/mesos-masters-availability-set.tf @@ -0,0 +1,11 @@ +# Create an availability set for master servers +resource "azurerm_availability_set" "master" { + name = "Master_AvailabilitySet" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + location = "${var.region}" +} + +# Mesos master availability set outputs +output "mesos_master_virtual_machine_ids" { + value = "${azurerm_availability_set.master.id}" +} \ No newline at end of file diff --git a/terraform/azurerm/public-cloud/mesos-masters.tf b/terraform/azurerm/public-cloud/mesos-masters.tf new file mode 100644 index 00000000..c61135f5 --- /dev/null +++ b/terraform/azurerm/public-cloud/mesos-masters.tf @@ -0,0 +1,103 @@ +# Create a network interface for master server +resource "azurerm_network_interface" "master_network_interface" { + name = "Master_NetworkInterface-${count.index}" + count = "${var.master_count}" + location = "${var.region}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + network_security_group_id = "${azurerm_network_security_group.network_security_group.id}" + + ip_configuration { + name = "masteripconfiguration-${count.index}" + subnet_id = "${element(azurerm_subnet.public.*.id, count.index)}" + private_ip_address_allocation = "dynamic" + public_ip_address_id = "${azurerm_public_ip.master_publicip.id}" + } +} + +# User profile template +resource "template_file" "master_cloud_init" { + template = "${file("master-cloud-config.yml.tpl")}" + depends_on = ["template_file.etcd_discovery_url"] + vars { + etcd_discovery_url = "${file(var.etcd_discovery_url_file)}" + size = "${var.master_count}" + vpc_cidr_block = "${var.vpc_cidr_block}" + region = "${var.region}" + } +} + +# Master server +resource "azurerm_virtual_machine" "mesos_master" { + name = "apollo-mesos-master-${count.index}" + count = "${var.master_count}" + location = "${var.region}" + availability_set_id = "${azurerm_availability_set.master.id}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + network_interface_ids = ["${element(azurerm_network_interface.master_network_interface.*.id, count.index)}"] + vm_size = "${var.instance_type.master}" + + storage_image_reference { + publisher = "${var.artifact_master.publisher}" + offer = "${var.artifact_master.offer}" + sku = "${var.artifact_master.sku}" + version = "${var.artifact_master.version}" + } + + storage_os_disk { + name = "masterdisk${count.index}" + vhd_uri = "${azurerm_storage_account.storage_account.primary_blob_endpoint}${azurerm_storage_container.storage_container.name}/masterdisk-${count.index}.vhd" + caching = "ReadWrite" + create_option = "FromImage" + } + + os_profile { + computer_name = "apollo-mesos-master-${count.index}" + admin_username = "${var.master_server_username}" + admin_password = "${var.master_server_password}" + custom_data = "${base64encode(template_file.master_cloud_init.rendered)}" + } + + os_profile_linux_config { + disable_password_authentication = true + + ssh_keys { + path = "/home/${var.master_server_username}/.ssh/authorized_keys" + key_data = "${file("${var.ssh_public_key_file}")}" # openssh format + } + } + + tags { + Name = "apollo-mesos-master-${count.index}" + role = "mesos_masters" + } +} + +# Mesos master network interface outputs +output "mesos_master_network_interface_ids" { + value = "${join(",", azurerm_network_interface.master_network_interface.*.id)}" +} + +output "mesos_master_network_interface_macaddresses" { + value = "${join(",", azurerm_network_interface.master_network_interface.*.mac_address)}" +} + +output "mesos_master_network_interface_privateipaddresses" { + value = "${join(",", azurerm_network_interface.master_network_interface.*.private_ip_address)}" +} + +output "mesos_master_network_interface_virtualmachineids" { + value = "${join(",", azurerm_network_interface.master_network_interface.*.virtual_machine_id)}" +} + +output "mesos_master_network_interface_applieddnsservers" { + value = "${join(",", azurerm_network_interface.master_network_interface.*.applied_dns_servers)}" +} + +output "mesos_master_network_interface_internalfqdns" { + value = "${join(",", azurerm_network_interface.master_network_interface.*.internal_fqdn)}" +} + +# Mesos master virtual machine outputs +output "mesos_master_virtual_machine_ids" { + value = "${join(",", azurerm_virtual_machine.mesos_master.*.id)}" +} \ No newline at end of file diff --git a/terraform/azurerm/public-cloud/network-security-group.tf b/terraform/azurerm/public-cloud/network-security-group.tf new file mode 100644 index 00000000..e0b56035 --- /dev/null +++ b/terraform/azurerm/public-cloud/network-security-group.tf @@ -0,0 +1,39 @@ +#Create Network Security Group +resource "azurerm_network_security_group" "network_security_group" { + name = "AzureRM_NetworkSecurityGroup" + location = "${var.region}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + + security_rule { + name = "AzureRM_SecurityRuleInbound" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "*" + source_port_range = "*" + destination_port_range = "*" + source_address_prefix = "*" + destination_address_prefix = "*" + } + + security_rule { + name = "AzureRM_SecurityRuleOutbound" + priority = 101 + direction = "Outbound" + access = "Allow" + protocol = "*" + source_port_range = "*" + destination_port_range = "*" + source_address_prefix = "*" + destination_address_prefix = "*" + } + + tags { + Name = "default-apollo-sg" + } +} + +#Output +output "network_security_group_id" { + value = "${azurerm_network_security_group.network_security_group.id}" +} diff --git a/terraform/azurerm/public-cloud/provider.tf b/terraform/azurerm/public-cloud/provider.tf new file mode 100644 index 00000000..66fd5ba1 --- /dev/null +++ b/terraform/azurerm/public-cloud/provider.tf @@ -0,0 +1,9 @@ +/* Configure auth 2 resource manager authentication. This requires an aplication to be set up in Azure, see +https://www.terraform.io/docs/providers/azurerm/index.html fo details. */ + +provider "azurerm" { + subscription_id = "${var.subscription_id}" + client_id = "${var.client_id}" + client_secret = "${var.client_secret}" + tenant_id = "${var.tenant_id}" +} \ No newline at end of file diff --git a/terraform/azurerm/public-cloud/resource-group.tf b/terraform/azurerm/public-cloud/resource-group.tf new file mode 100644 index 00000000..75be4923 --- /dev/null +++ b/terraform/azurerm/public-cloud/resource-group.tf @@ -0,0 +1,10 @@ +# Create a resource group +resource "azurerm_resource_group" "resource_group" { + name = "AzureRM-Resource-Group" + location = "${var.region}" +} + +#Output +output "resource_group_id" { + value = "${azurerm_resource_group.resource_group.id}" +} \ No newline at end of file diff --git a/terraform/azurerm/public-cloud/storage.tf b/terraform/azurerm/public-cloud/storage.tf new file mode 100644 index 00000000..3aa64e7b --- /dev/null +++ b/terraform/azurerm/public-cloud/storage.tf @@ -0,0 +1,66 @@ +# Create a storage account +resource "azurerm_storage_account" "storage_account" { + name = "${var.storage_account_name}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + location = "${var.region}" + account_type = "${var.storage_account_type}" +} + +# Create storage container +resource "azurerm_storage_container" "storage_container" { + name = "${var.storage_container_name}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + storage_account_name = "${azurerm_storage_account.storage_account.name}" + container_access_type = "private" + depends_on = ["azurerm_storage_account.storage_account"] +} + +# Storage Account Output +output "storage_account_id" { + value = "${azurerm_storage_account.storage_account.id}" +} + +output "storage_account_primary_location" { + value = "${azurerm_storage_account.storage_account.primary_location}" +} + +output "storage_account_secondary_location" { + value = "${azurerm_storage_account.storage_account.secondary_location}" +} + +output "storage_account_primary_blob_endpoint" { + value = "${azurerm_storage_account.storage_account.primary_blob_endpoint}" +} + +output "storage_account_secondary_blob_endpoint" { + value = "${azurerm_storage_account.storage_account.secondary_blob_endpoint}" +} + +output "storage_account_primary_queue_endpoint" { + value = "${azurerm_storage_account.storage_account.primary_queue_endpoint}" +} + +output "storage_account_secondary_queue_endpoint" { + value = "${azurerm_storage_account.storage_account.secondary_queue_endpoint}" +} + +output "storage_account_primary_table_endpoint" { + value = "${azurerm_storage_account.storage_account.primary_table_endpoint}" +} + +output "storage_account_secondary_table_endpoint" { + value = "${azurerm_storage_account.storage_account.secondary_table_endpoint}" +} + +output "storage_account_primary_file_endpoint" { + value = "${azurerm_storage_account.storage_account.primary_file_endpoint}" +} + +# Storage Container Output +output "storage_container_id" { + value = "${azurerm_storage_container.storage_container.id}" +} + +output "storage_container_properties" { + value = "${azurerm_storage_container.storage_container.properties}" +} diff --git a/terraform/azurerm/public-cloud/template_file.etcd_discovery_url.tf b/terraform/azurerm/public-cloud/template_file.etcd_discovery_url.tf new file mode 100644 index 00000000..631cc0bb --- /dev/null +++ b/terraform/azurerm/public-cloud/template_file.etcd_discovery_url.tf @@ -0,0 +1,12 @@ +# Generate an etcd URL for the cluster +resource "template_file" "etcd_discovery_url" { + # template = "${file(/dev/null)}" + provisioner "local-exec" { + command = "curl https://discovery.etcd.io/new?size=${var.master_count} > ${var.etcd_discovery_url_file}" + } + + # This will regenerate the discovery URL if the cluster size changes, we include the bastion here + vars { + size = "${var.master_count}" + } +} diff --git a/terraform/azurerm/public-cloud/variables.tf b/terraform/azurerm/public-cloud/variables.tf new file mode 100644 index 00000000..c3b62af8 --- /dev/null +++ b/terraform/azurerm/public-cloud/variables.tf @@ -0,0 +1,141 @@ +variable "subscription_id" { + description = "The Azure subscrition identifier (guid)." +} + +variable "client_id" { + description = "The oAuth 2 client id. " +} + +variable "client_secret" { + description = "The oAuth 2 client secret." +} + +variable "tenant_id" { + description = "The oAuth 2 tenant id." +} + +variable "region" { + description = "The deployment azure data centre location." + default = "North Europe" +} + +variable "vpc_cidr_block" { + description = "Cidr block for the VN." + default = "10.0.0.0/16" +} + +variable "private_subnet_cidr_block" { + description = "CIDR for private subnet" + default = "10.0.1.0/24,10.0.2.0/24,10.0.3.0/24" +} + +variable "public_subnet_cidr_block" { + description = "CIDR for public subnet" + default = "10.0.101.0/24,10.0.102.0/24,10.0.103.0/24" +} + +variable "storage_account_name" { + description = "Storage account name" + default = "mesosimages" +} + +# I.E. Standard_GRS +variable "storage_account_type" { + description = "Storage account type" +} + +variable "storage_container_name" { + description = "Storage container name" + default = "mesosimages-container" +} + +variable "artifact_bastion" { + default = { + publisher = "CoreOS" + offer ="CoreOS" + "sku" = "Stable" + "version" = "latest" + } +} + +variable "artifact_master" { + default = { + publisher = "CoreOS" + offer ="CoreOS" + "sku" = "Stable" + "version" = "latest" + } +} + +variable "artifact_agent" { + default = { + publisher = "CoreOS" + offer ="CoreOS" + "sku" = "Stable" + "version" = "latest" + } +} + +variable "instance_type" { + default = { + bastion = "Standard_A0" + master = "Standard_A0" + agent = "Standard_A0" + } +} + +variable "bastion_server_computername" { + description = "Username to access server" + default = "bastion" +} + +variable "bastion_server_username" { + description = "Username to access server" +} + +variable "bastion_server_password" { + description = "Password to access server" +} + +variable "master_server_username" { + description = "Username to access master server" +} + +variable "master_server_password" { + description = "Password to access master server" +} + +variable "agent_server_username" { + description = "Username to access agent server" +} + +variable "agent_server_password" { + description = "Password to access agent server" +} + +variable "master_count" { + description = "The number of masters." + default = "1" +} + +variable "agent_count" { + description = "The number of agents." + default = "1" +} + +variable "docker_version" { + description = "Docker version" + default = "1.9.0-0~trusty" +} + +variable "ssh_public_key_file" { + description = "Public key file path required to connect via ssh" +} + +variable "ssh_private_key_file" { + description = "Public key file path required to connect via ssh" +} + +variable "etcd_discovery_url_file" { + default = "etcd_discovery_url.txt" +} \ No newline at end of file diff --git a/terraform/azurerm/public-cloud/virtual-network-subnets.tf b/terraform/azurerm/public-cloud/virtual-network-subnets.tf new file mode 100644 index 00000000..2dd519ee --- /dev/null +++ b/terraform/azurerm/public-cloud/virtual-network-subnets.tf @@ -0,0 +1,17 @@ +#Create public subnets +resource "azurerm_subnet" "public" { + name = "AzureRM_Public_Subnet-${count.index}" + count = "${length(compact(split(",", var.public_subnet_cidr_block)))}" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + virtual_network_name = "${azurerm_virtual_network.virtual_network.name}" + address_prefix = "${element(split(",", var.public_subnet_cidr_block), count.index)}" +} + +# Public subnets output +output "subnet_ids" { + value = "${join(",", azurerm_subnet.public.*.id)}" +} + +output "subnet_ip_configurations" { + value = "${join(",", azurerm_subnet.public.ip_configurations)}" +} \ No newline at end of file diff --git a/terraform/azurerm/public-cloud/virtual-network.tf b/terraform/azurerm/public-cloud/virtual-network.tf new file mode 100644 index 00000000..b7c3a0ff --- /dev/null +++ b/terraform/azurerm/public-cloud/virtual-network.tf @@ -0,0 +1,12 @@ +# Create a virtual network +resource "azurerm_virtual_network" "virtual_network" { + name = "AzureRM-Virtual-Network" + resource_group_name = "${azurerm_resource_group.resource_group.name}" + address_space = ["${var.vpc_cidr_block}"] + location = "${var.region}" +} + +#Output +output "network_confirguration_id" { + value = "${azurerm_virtual_network.virtual_network.id}" +} \ No newline at end of file