diff --git a/terraform/examples/multi-node-cluster/README.md b/terraform/examples/multi-node-cluster/README.md new file mode 100644 index 0000000..cda8038 --- /dev/null +++ b/terraform/examples/multi-node-cluster/README.md @@ -0,0 +1,7 @@ +# Multi Node HA Cluster Example + +This directory shows an example Terraform configuration that uses all of the [modules](../../modules) to deploy NS1 in a more complex, multi-node pattern with 3 HA Data containers. This topology provides the foundation of a scalable, hub and spoke topology as well as high availability of the data nodes. The HA Data nodes are expected to be on 3 separate hosts, labeled `data01`, `data02` and `data03`. The "hub" is refered to in this configuration as the `control01` node and the "spoke" is refered to as the `edge01` node. + +In this example, the Docker images have already been loaded on the `control` node's Docker daemon, while the `edge` node will be download the images from a Docker registry. *Note* that in this example Docker Hub is used as the registry, but this cannot be used in production as the images are not publically available on Docker Hub. + +This example will also create dedicated `ns1` Docker networks on all of the `data` Docker hosts as well as the `control` and `edge` Docker hosts. diff --git a/terraform/examples/multi-node-cluster/main.tf b/terraform/examples/multi-node-cluster/main.tf new file mode 100644 index 0000000..ce38ef3 --- /dev/null +++ b/terraform/examples/multi-node-cluster/main.tf @@ -0,0 +1,262 @@ +# --------------------------------------------------------------------------------------------------------------------- +# DEPLOY NS1 eDDI CLUSTER ON MULTIPLE DOCKER HOSTS +# This configuration deploys NS1 containers on two hosts. The Data cluster is deployed on 3 separate hosts. +# The Core and XFR containers are deployed together on the "control" host, while the DHCP, DNS and Dist containers +# are deployed on the "edge" host. In a "hub and spoke" pattern, "control" can be thought of as the hub, while "edge" +# would be a sigle spoke. +# --------------------------------------------------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------------------------------------------------- +# REQUIRE A SPECIFIC TERRAFORM VERSION OR HIGHER +# This module has been updated with 0.12 syntax, which means it is no longer compatible with any versions below 0.12. +# ---------------------------------------------------------------------------------------------------------------------- +terraform { + required_version = ">= 0.12" +} + +# ---------------------------------------------------------------------------------------------------------------------- +# DEPLOY DEDICATED NETWORKS ON DOCKER HOSTS +# A dedicated Docker network should be deployed on both hosts for the NS1 containers to join. +# ----------------------------------------------------------------------------------------------------------------------- + +provider "docker" { + alias = "data01" + host = "${var.docker_protocol}${var.data01_host}" +} + +provider "docker" { + alias = "data02" + host = "${var.docker_protocol}${var.data02_host}" +} + +provider "docker" { + alias = "data03" + host = "${var.docker_protocol}${var.data03_host}" +} + +provider "docker" { + alias = "control01" + host = "${var.docker_protocol}${var.control01_host}" +} + +provider "docker" { + alias = "edge01" + host = "${var.docker_protocol}${var.edge01_host}" +} + +resource "docker_network" "data01" { + provider = "docker.data01" + name = "ns1" + driver = "bridge" + ipam_driver = "default" + attachable = true + + ipam_config { + subnet = "172.18.12.0/24" + } +} + +resource "docker_network" "data02" { + provider = "docker.data02" + name = "ns1" + driver = "bridge" + ipam_driver = "default" + attachable = true + + ipam_config { + subnet = "172.18.12.0/24" + } +} + +resource "docker_network" "data03" { + provider = "docker.data03" + name = "ns1" + driver = "bridge" + ipam_driver = "default" + attachable = true + + ipam_config { + subnet = "172.18.12.0/24" + } +} + +resource "docker_network" "control01" { + provider = "docker.control01" + name = "ns1" + driver = "bridge" + ipam_driver = "default" + attachable = true + + ipam_config { + subnet = "172.18.12.0/24" + } +} + +resource "docker_network" "edge01" { + provider = "docker.edge01" + name = "ns1" + driver = "bridge" + ipam_driver = "default" + attachable = true + + ipam_config { + subnet = "172.18.12.0/24" + } +} + +# --------------------------------------------------------------------------------------------------------------------- +# DEPLOY THE CONTAINERS ON THE DATA HOSTS +# This configuration assumes the containers have already been loaded on the Docker host. +# If they have not been loaded, it will attempt to download them from Docker Hub and fail. +# --------------------------------------------------------------------------------------------------------------------- + +module "data01" { + source = "../../modules/data" + docker_host = "${var.docker_protocol}${var.data01_host}" + docker_network = docker_network.data01.name + docker_image_username = var.docker_image_username + docker_image_repository = "${var.docker_image_repository}_data" + docker_image_tag = var.docker_image_tag + docker_registry_address = var.docker_registry_address + docker_registry_username = var.docker_registry_username + docker_registry_password = var.docker_registry_password + pop_id = var.data01_pop_id + server_id = var.data01_host + primary = false + cluster_id = 1 + cluster_size = 3 + data_peers = [var.data02_host, var.data03_host] + telegraf_output_elasticsearch_data_host = var.elasticsearch_data_host + telegraf_output_elasticsearch_index = var.elasticsearch_index +} + +module "data02" { + source = "../../modules/data" + docker_host = "${var.docker_protocol}${var.data02_host}" + docker_network = docker_network.data02.name + docker_image_username = var.docker_image_username + docker_image_repository = "${var.docker_image_repository}_data" + docker_image_tag = var.docker_image_tag + docker_registry_address = var.docker_registry_address + docker_registry_username = var.docker_registry_username + docker_registry_password = var.docker_registry_password + pop_id = var.data02_pop_id + server_id = var.data02_host + primary = false + cluster_id = 2 + cluster_size = 3 + data_peers = [var.data01_host, var.data03_host] + telegraf_output_elasticsearch_data_host = var.elasticsearch_data_host + telegraf_output_elasticsearch_index = var.elasticsearch_index +} + +module "data03" { + source = "../../modules/data" + docker_host = "${var.docker_protocol}${var.data03_host}" + docker_network = docker_network.data03.name + docker_image_username = var.docker_image_username + docker_image_repository = "${var.docker_image_repository}_data" + docker_image_tag = var.docker_image_tag + docker_registry_address = var.docker_registry_address + docker_registry_username = var.docker_registry_username + docker_registry_password = var.docker_registry_password + pop_id = var.data03_pop_id + server_id = var.data03_host + primary = false + cluster_id = 3 + cluster_size = 3 + data_peers = [var.data01_host, var.data02_host] + telegraf_output_elasticsearch_data_host = var.elasticsearch_data_host + telegraf_output_elasticsearch_index = var.elasticsearch_index +} + +# --------------------------------------------------------------------------------------------------------------------- +# DEPLOY THE CONTAINERS ON THE CONTROL HOST +# This configuration assumes the containers have already been loaded on the Docker host. +# If they have not been loaded, it will attempt to download them from Docker Hub and fail. +# --------------------------------------------------------------------------------------------------------------------- + +module "core" { + source = "../../modules/core" + docker_host = "${var.docker_protocol}${var.control01_host}" + docker_network = docker_network.control01.name + docker_image_username = var.docker_image_username + docker_image_repository = "${var.docker_image_repository}_core" + docker_image_tag = var.docker_image_tag + docker_registry_address = var.docker_registry_address + docker_registry_username = var.docker_registry_username + docker_registry_password = var.docker_registry_password + bootstrappable = false + pop_id = var.control01_pop_id + server_id = var.control01_host + data_hosts = [var.data01_host,var.data02_host,var.data03_host] + api_fqdn = var.api_fqdn + portal_fqdn = var.portal_fqdn + nameservers = var.nameservers + hostmaster_email = var.hostmaster_email +} + +module "xfr" { + source = "../../modules/xfr" + docker_host = "${var.docker_protocol}${var.control01_host}" + docker_network = docker_network.control01.name + docker_image_username = var.docker_image_username + docker_image_repository = "${var.docker_image_repository}_xfr" + docker_image_tag = var.docker_image_tag + docker_registry_address = var.docker_registry_address + docker_registry_username = var.docker_registry_username + docker_registry_password = var.docker_registry_password + pop_id = var.control01_pop_id + server_id = var.control01_host +} + +# --------------------------------------------------------------------------------------------------------------------- +# DEPLOY THE CONTAINERS ON THE EDGE HOST +# The configuration of these modules provides an example of using the moddules with a Docker registry. +# The NS1 Docker containers should already be loaded into the registry at run time and the registry must be reachable +# from the Docker host. +# --------------------------------------------------------------------------------------------------------------------- +module "dns" { + source = "../../modules/dns" + docker_host = "${var.docker_protocol}${var.edge01_host}" + docker_network = docker_network.edge01.name + docker_image_username = var.docker_image_username + docker_image_repository = "${var.docker_image_repository}_dns" + docker_image_tag = var.docker_image_tag + docker_registry_address = var.docker_registry_address + docker_registry_username = var.docker_registry_username + docker_registry_password = var.docker_registry_password + pop_id = var.edge01_pop_id + server_id = var.edge01_host +} + +module "dhcp" { + source = "../../modules/dhcp" + docker_host = "${var.docker_protocol}${var.edge01_host}" + docker_network = docker_network.edge01.name + docker_image_username = var.docker_image_username + docker_image_repository = "${var.docker_image_repository}_dhcp" + docker_image_tag = var.docker_image_tag + docker_registry_address = var.docker_registry_address + docker_registry_username = var.docker_registry_username + docker_registry_password = var.docker_registry_password + pop_id = var.edge01_pop_id + server_id = var.edge01_host +} + +module "dist" { + source = "../../modules/dist" + docker_host = "${var.docker_protocol}${var.edge01_host}" + docker_network = docker_network.edge01.name + docker_image_username = var.docker_image_username + docker_image_repository = "${var.docker_image_repository}_dist" + docker_image_tag = var.docker_image_tag + docker_registry_address = var.docker_registry_address + docker_registry_username = var.docker_registry_username + docker_registry_password = var.docker_registry_password + # This transforms the user defined URL defined for the control host into + # a FQDN or IP, which is expected by the `core_hosts` argument + core_hosts = [element(split("@", var.control01_host), 1)] + pop_id = var.edge01_pop_id + server_id = var.edge01_host +} diff --git a/terraform/examples/multi-node-cluster/variables.tf b/terraform/examples/multi-node-cluster/variables.tf new file mode 100644 index 0000000..52d7514 --- /dev/null +++ b/terraform/examples/multi-node-cluster/variables.tf @@ -0,0 +1,106 @@ +variable "docker_registry_username" { + description = "Username for authentication to Docker registry." +} + +variable "docker_registry_password" { + description = "Password for authentication to Docker registry." +} + +variable "docker_registry_address" { + description = "The absolute URL of the Docker registry (i.e. 'https://registry.hub.docker.com') to pull the container images from." +} + +variable "docker_image_tag" { + default = "2.1.1" + description = "The image tag of the Docker image. Defaults to the latest GA version number." +} + +variable "docker_image_username" { + default = "ns1inc" + description = "The username used in the Docker image name. This should not need to be changed." +} + +variable "docker_image_repository" { + default = "privatedns_data" + description = "The repository name used in the Docker image name. This should not need to be changed." +} + +variable "docker_protocol" { + default = "ssh://" + description = "The protocol to use when connecting to the docker host" +} + +variable "data01_host" { + description = "The address of the Docker host to deploy the first Data container on (i.e. 'ssh://user@remote-host'). Both ssh:// and tcp:// are supported. See https://www.terraform.io/docs/providers/docker/index.html for more details" +} + +variable "data02_host" { + description = "The address of the Docker host to deploy the second Data container on (i.e. 'ssh://user@remote-host'). Both ssh:// and tcp:// are supported. See https://www.terraform.io/docs/providers/docker/index.html for more details" +} + +variable "data03_host" { + description = "The address of the Docker host to deploy the third Data container on (i.e. 'ssh://user@remote-host'). Both ssh:// and tcp:// are supported. See https://www.terraform.io/docs/providers/docker/index.html for more details" +} + +variable "control01_host" { + description = "The address of the Docker host to deploy the Data, Core and XFR containers on (i.e. 'ssh://user@remote-host'). Both ssh:// and tcp:// are supported. See https://www.terraform.io/docs/providers/docker/index.html for more details" +} + +variable "edge01_host" { + description = "The address of the Docker host to deploy the DHCP, DNS and Dist containers on (i.e. 'ssh://user@remote-host'). Both ssh:// and tcp:// are supported. See https://www.terraform.io/docs/providers/docker/index.html for more details" +} + +variable "data01_pop_id" { + description = "The pop id for data01" + default = "dc1" +} + +variable "data02_pop_id" { + description = "The pop id for data02" + default = "dc1" +} + +variable "data03_pop_id" { + description = "The pop id for data03" + default = "dc1" +} + +variable "control01_pop_id" { + description = "The pop id for control01" + default = "dc1" +} + +variable "edge01_pop_id" { + description = "The pop id for edge01" + default = "dc2" +} + +variable "elasticsearch_data_host" { + default = null + description = "The elasticsearch host to export metrics" +} + +variable "elasticsearch_index" { + default = null + description = "The elasticsearch index to use when exporting metrics" +} + +variable "api_fqdn" { + default = "api.mycompany.net" + description = "FQDN to use for the api and feed URLs" +} + +variable "portal_fqdn" { + default = "portal.mycompany.net" + description = "FQDN to use for the portal URL" +} + +variable "nameservers" { + default = "ns1.mycompany.net" + description = "Nameservers used in SOA records" +} + +variable "hostmaster_email" { + default = "hostmaster@mycompany.net" + description = "Hostmaster email address used in SOA records" +} diff --git a/terraform/modules/data/main.tf b/terraform/modules/data/main.tf index 3a5a35d..12ea6c9 100644 --- a/terraform/modules/data/main.tf +++ b/terraform/modules/data/main.tf @@ -16,6 +16,22 @@ ${join(",", var.data_peers)} ${var.enable_ops_metrics} --expose_ops_metrics ${var.expose_ops_metrics~} +%{if var.telegraf_output_elasticsearch_data_host != null} +--telegraf_output_elasticsearch_data_host +${var.telegraf_output_elasticsearch_data_host~} +%{endif~} +%{if var.telegraf_output_elasticsearch_index != null} +--telegraf_output_elasticsearch_index +${var.telegraf_output_elasticsearch_index~} +%{endif~} +%{if var.cluster_id != null} +--cluster_id +${var.cluster_id~} +%{endif~} +%{if var.cluster_size != null} +--cluster_size +${var.cluster_size~} +%{endif~} EOT env_template = <