diff --git a/.github/workflows/terraform.yml b/.github/workflows/terraform.yml index 0647b0b1..1ca37747 100644 --- a/.github/workflows/terraform.yml +++ b/.github/workflows/terraform.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: - provider: ['aws', 'azure', 'azure-windows', 'do', 'gcp'] + provider: ['aws', 'azure', 'azure-windows', 'do', 'gcp', 'openstack'] # Use the Bash shell regardless whether the GitHub Actions runner is ubuntu-latest, macos-latest, or windows-latest defaults: diff --git a/README.md b/README.md index 5a1dc468..b054a943 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ See [/vagrant](./vagrant) for details on usage and settings. ## Cloud quickstart -Quickstarts are provided for [**Amazon Web Services** (`aws`)](./aws), [**Microsoft Azure Cloud** (`azure`)](./azure), [**Microsoft Azure Cloud with Windows nodes** (`azure-windows`)](./azure-windows), [**DigitalOcean** (`do`)](./do), and [**Google Cloud Platform** (`gcp`)](./gcp). +Quickstarts are provided for [**Amazon Web Services** (`aws`)](./aws), [**Microsoft Azure Cloud** (`azure`)](./azure), [**Microsoft Azure Cloud with Windows nodes** (`azure-windows`)](./azure-windows), [**DigitalOcean** (`do`)](./do), [**Google Cloud Platform** (`gcp`)](./gcp) and [**OpenStack** (`openstack`)](./openstack). **You will be responsible for any and all infrastructure costs incurred by these resources.** diff --git a/openstack/README.md b/openstack/README.md new file mode 100644 index 00000000..987633bc --- /dev/null +++ b/openstack/README.md @@ -0,0 +1,108 @@ +# OpenStack Rancher Quickstart + +Two single-node RKE Kubernetes clusters will be created from two linux virtual instances running Ubuntu 18.04 and Docker. +Both instances will have wide-open security groups and will be accessible over SSH using the SSH keys +`id_rsa` and `id_rsa.pub`. + +## Variables + +###### `openstack_floating_ip_pool` +- **Required** +The name of the pool from which to obtain the floating IPs + +###### `openstack_auth_url` +- **Optional** +The OpenStack Identity authentication URL. +If omitted, the `OS_AUTH_URL` environment variable is used. + +###### `openstack_user_name` +- **Optional** +The Username to login with. +If omitted, the `OS_USERNAME` environment variable is used. + +###### `openstack_password` +- **Optional** +The Password to login with. +If omitted, the `OS_PASSWORD` environment variable is used. + +###### `openstack_region` +- **Optional** +The region of the OpenStack cloud to use. +If omitted, the `OS_REGION_NAME` environment variable is used. + +###### `openstack_domain_id` +- **Optional** +The Name of the Domain to scope to. +If omitted, the `OS_DOMAIN_ID` environment variable is checked. + +###### `openstack_domain_name` +- **Optional** +The Name of the Domain to scope to. +If omitted, the `OS_DOMAIN_NAME` environment variable is used. + +###### `openstack_project_id` +- **Optional** +The ID of the Project to login with. +If omitted, the `OS_TENANT_ID` or `OS_PROJECT_ID` environment variables are used. + +###### `openstack_project_name` +- **Optional** +The Name of the Project. +If omitted, the `OS_TENANT_ID` or `OS_PROJECT_ID` environment variables are used. + +###### `openstack_insecure` +- **Optional** +Trust self-signed SSL certificates. +If omitted, the `OS_INSECURE` environment variable is used. + +###### `openstack_cacert_file` +- **Optional** +Specify a custom CA certificate when communicating over SSL. +You can specify either a path to the file or the contents of the certificate. +If omitted, the `OS_CACERT` environment variable is used. + +###### `prefix` +- Default: **`"quickstart"`** +Prefix added to names of all resources + +###### `instance_type` +- Default: **`"m1.large"`** +Instance type used for all linux virtual instances + +###### `docker_version` +- Default: **`"19.03"`** +Docker version to install on nodes + +###### `rke_kubernetes_version` +- Default: **`"v1.18.3-rancher2-2"`** +Kubernetes version to use for Rancher server RKE cluster + +See `rancher-common` module variable `rke_kubernetes_version` for more details. + +###### `workload_kubernetes_version` +- Default: **`"v1.17.6-rancher2-2"`** +Kubernetes version to use for managed workload cluster + +See `rancher-common` module variable `workload_kubernetes_version` for more details. + +###### `cert_manager_version` +- Default: **`"0.12.0"`** +Version of cert-mananger to install alongside Rancher (format: 0.0.0) + +See `rancher-common` module variable `cert_manager_version` for more details. + +###### `rancher_version` +- Default: **`"v2.4.5"`** +Rancher server version (format v0.0.0) + +See `rancher-common` module variable `rancher_version` for more details. + +###### `rancher_server_admin_password` +- **Required** +Admin password to use for Rancher server bootstrap + +See `rancher-common` module variable `admin_password` for more details. + +###### `network_cidr` +- Default: **`"10.0.0.0/16"`** +The Network CIDR for the Rancher nodes diff --git a/openstack/files/userdata_quickstart_node.template b/openstack/files/userdata_quickstart_node.template new file mode 100644 index 00000000..2af9f1f9 --- /dev/null +++ b/openstack/files/userdata_quickstart_node.template @@ -0,0 +1,10 @@ +#!/bin/bash -x + +export DEBIAN_FRONTEND=noninteractive +curl -sL https://releases.rancher.com/install-docker/${docker_version}.sh | sh +sudo usermod -aG docker ${username} + +publicIP=$(curl -H "Metadata: true" http://169.254.169.254/latest/meta-data/public-ipv4) +privateIP=$(curl -H "Metadata: true" http://169.254.169.254/latest/meta-data/local-ipv4) + +${register_command} --address $publicIP --internal-address $privateIP --etcd --controlplane --worker diff --git a/openstack/infra.tf b/openstack/infra.tf new file mode 100644 index 00000000..1a12397a --- /dev/null +++ b/openstack/infra.tf @@ -0,0 +1,301 @@ +# OpenStack Infrastructure Resources + +resource "tls_private_key" "global_key" { + algorithm = "RSA" + rsa_bits = 2048 +} + +resource "local_file" "ssh_private_key_pem" { + filename = "${path.module}/id_rsa" + sensitive_content = tls_private_key.global_key.private_key_pem + file_permission = "0600" +} + +resource "local_file" "ssh_public_key_openssh" { + filename = "${path.module}/id_rsa.pub" + content = tls_private_key.global_key.public_key_openssh +} + +# Upload Ubuntu image +resource "openstack_images_image_v2" "rancher-ubuntu-image" { + name = "${var.prefix}-rancher-ubuntu-image" + image_source_url = "https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img" + container_format = "bare" + disk_format = "qcow2" + min_ram_mb = 4096 + min_disk_gb = 25 + visibility = "private" + + properties = { + os_type = "linux" + os_distro = "ubuntu" + os_version = "18.04-LTS" + } + + tags = ["rancher-quickstart"] +} + +# Upload SSH keypair +resource "openstack_compute_keypair_v2" "rancher-keypair" { + name = "${var.prefix}-rancher-keypair" + public_key = tls_private_key.global_key.public_key_openssh +} + +# OpenStack virtual network for quickstart resources +resource "openstack_networking_network_v2" "rancher-quickstart" { + name = "${var.prefix}-rancher-network" + + tags = ["rancher-quickstart"] +} + +# OpenStack internal subnet for quickstart resources +resource "openstack_networking_subnet_v2" "rancher-quickstart-internal" { + name = "${var.prefix}-rancher-quickstart-internal" + network_id = openstack_networking_network_v2.rancher-quickstart.id + cidr = var.network_cidr + + tags = ["rancher-quickstart"] +} + +# Get floating IP network +data "openstack_networking_network_v2" "rancher-quickstart-external" { + name = var.openstack_floating_ip_pool +} + +# OpenStack router to connect the internal subnet to the floating IP network +resource "openstack_networking_router_v2" "rancher-quickstart-router" { + name = "${var.prefix}-rancher-router" + external_network_id = data.openstack_networking_network_v2.rancher-quickstart-external.id + + tags = ["rancher-quickstart"] +} + +# OpenStack router interface to the internal subnet +resource "openstack_networking_router_interface_v2" "rancher-quickstart-router-interface" { + router_id = openstack_networking_router_v2.rancher-quickstart-router.id + subnet_id = openstack_networking_subnet_v2.rancher-quickstart-internal.id +} + +# Floating IP of Rancher server +resource "openstack_networking_floatingip_v2" "rancher-server-pip" { + pool = var.openstack_floating_ip_pool + + tags = ["rancher-quickstart"] +} + +# OpenStack network interface for quickstart resources +resource "openstack_networking_port_v2" "rancher-server-interface" { + name = "${var.prefix}-rancher-quickstart-interface" + network_id = openstack_networking_network_v2.rancher-quickstart.id + + fixed_ip { + subnet_id = openstack_networking_subnet_v2.rancher-quickstart-internal.id + } + + tags = ["rancher-quickstart"] +} + +# Associate Rancher server floating IP with the Rancher server port +resource "openstack_networking_floatingip_associate_v2" "rancher-server-pip-associate" { + floating_ip = openstack_networking_floatingip_v2.rancher-server-pip.address + port_id = openstack_networking_port_v2.rancher-server-interface.id +} + +# OpenStack security group +resource "openstack_networking_secgroup_v2" "rancher-quickstart-secgroup" { + name = "${var.prefix}-rancher-secgroup" +} + +# OpenStack security group rules +resource "openstack_networking_secgroup_rule_v2" "rancher-quickstart-secgroup-rule-v4-all" { + direction = "ingress" + ethertype = "IPv4" + remote_ip_prefix = "0.0.0.0/0" + security_group_id = openstack_networking_secgroup_v2.rancher-quickstart-secgroup.id +} + +resource "openstack_networking_secgroup_rule_v2" "rancher-quickstart-secgroup-rule-v6-all" { + direction = "ingress" + ethertype = "IPv6" + remote_ip_prefix = "0.0.0.0/0" + security_group_id = openstack_networking_secgroup_v2.rancher-quickstart-secgroup.id +} + +resource "openstack_networking_secgroup_rule_v2" "rancher-quickstart-secgroup-rule-self-v4" { + direction = "ingress" + ethertype = "IPv4" + remote_group_id = openstack_networking_secgroup_v2.rancher-quickstart-secgroup.id + security_group_id = openstack_networking_secgroup_v2.rancher-quickstart-secgroup.id +} + +resource "openstack_networking_secgroup_rule_v2" "rancher-quickstart-secgroup-rule-self-v6" { + direction = "ingress" + ethertype = "IPv6" + remote_group_id = openstack_networking_secgroup_v2.rancher-quickstart-secgroup.id + security_group_id = openstack_networking_secgroup_v2.rancher-quickstart-secgroup.id +} + +# Associate security group to Rancher server port +resource "openstack_networking_port_secgroup_associate_v2" "rancher-server-interface-secgroup" { + port_id = openstack_networking_port_v2.rancher-server-interface.id + security_group_ids = [openstack_networking_secgroup_v2.rancher-quickstart-secgroup.id] +} + +# Create CloudInit / User-Data for Rancher server +data "cloudinit_config" "rancher-server-cloudinit" { + part { + content_type = "text/x-shellscript" + content = templatefile( + join("/", [path.module, "../cloud-common/files/userdata_rancher_server.template"]), + { + docker_version = var.docker_version + username = local.node_username + } + ) + } +} + +# OpenStack instance for creating a single node RKE cluster and installing the Rancher Server +resource "openstack_compute_instance_v2" "rancher_server" { + name = "${var.prefix}-rancher-server" + flavor_name = var.instance_type + key_pair = openstack_compute_keypair_v2.rancher-keypair.name + user_data = data.cloudinit_config.rancher-server-cloudinit.rendered + + network { + port = openstack_networking_port_v2.rancher-server-interface.id + } + + block_device { + uuid = openstack_images_image_v2.rancher-ubuntu-image.id + source_type = "image" + volume_size = 25 + boot_index = 0 + destination_type = "volume" + delete_on_termination = true + } + + provisioner "remote-exec" { + inline = [ + "echo 'Waiting for cloud-init to complete...'", + "cloud-init status --wait > /dev/null", + "echo 'Completed cloud-init!'", + ] + + connection { + type = "ssh" + host = openstack_networking_floatingip_v2.rancher-server-pip.address + user = local.node_username + private_key = tls_private_key.global_key.private_key_pem + } + } + + tags = ["rancher-quickstart"] + +} + +# Rancher resources +module "rancher_common" { + source = "../rancher-common" + + node_public_ip = openstack_networking_floatingip_v2.rancher-server-pip.address + node_internal_ip = openstack_compute_instance_v2.rancher_server.access_ip_v4 + node_username = local.node_username + ssh_private_key_pem = tls_private_key.global_key.private_key_pem + rke_kubernetes_version = var.rke_kubernetes_version + + cert_manager_version = var.cert_manager_version + rancher_version = var.rancher_version + + rancher_server_dns = join(".", ["rancher", openstack_networking_floatingip_v2.rancher-server-pip.address, "xip.io"]) + + admin_password = var.rancher_server_admin_password + + workload_kubernetes_version = var.workload_kubernetes_version + workload_cluster_name = "${var.prefix}-quickstart-openstack-custom" +} + +# Floating IP of quickstart node +resource "openstack_networking_floatingip_v2" "quickstart-node-pip" { + pool = var.openstack_floating_ip_pool + + tags = ["rancher-quickstart"] +} + +# OpenStack network interface for quickstart resources +resource "openstack_networking_port_v2" "quickstart-node-interface" { + name = "${var.prefix}-quickstart-node-interface" + network_id = openstack_networking_network_v2.rancher-quickstart.id + + fixed_ip { + subnet_id = openstack_networking_subnet_v2.rancher-quickstart-internal.id + } + + tags = ["rancher-quickstart"] +} + +# Associate security group to Quickstart node port +resource "openstack_networking_port_secgroup_associate_v2" "quickstart-node-interface-secgroup" { + port_id = openstack_networking_port_v2.quickstart-node-interface.id + security_group_ids = [openstack_networking_secgroup_v2.rancher-quickstart-secgroup.id] +} + +# Associate Quickstart node floating IP with the Quickstart node port +resource "openstack_networking_floatingip_associate_v2" "quickstart-node-pip-associate" { + floating_ip = openstack_networking_floatingip_v2.quickstart-node-pip.address + port_id = openstack_networking_port_v2.quickstart-node-interface.id +} + +# Create CloudInit / User-Data for Quickstart node +data "cloudinit_config" "quickstart-node-cloudinit" { + part { + content_type = "text/x-shellscript" + content = templatefile( + join("/", [path.module, "files/userdata_quickstart_node.template"]), + { + docker_version = var.docker_version + username = local.node_username + register_command = module.rancher_common.custom_cluster_command + } + ) + } +} + +# OpenStack instance for creating a single node RKE cluster and installing the Rancher Server +resource "openstack_compute_instance_v2" "quickstart-node" { + name = "${var.prefix}-rancher-quickstart-node" + flavor_name = var.instance_type + key_pair = openstack_compute_keypair_v2.rancher-keypair.name + user_data = data.cloudinit_config.quickstart-node-cloudinit.rendered + + network { + port = openstack_networking_port_v2.quickstart-node-interface.id + } + + block_device { + uuid = openstack_images_image_v2.rancher-ubuntu-image.id + source_type = "image" + volume_size = 25 + boot_index = 0 + destination_type = "volume" + delete_on_termination = true + } + + provisioner "remote-exec" { + inline = [ + "echo 'Waiting for cloud-init to complete...'", + "cloud-init status --wait > /dev/null", + "echo 'Completed cloud-init!'", + ] + + connection { + type = "ssh" + host = openstack_networking_floatingip_v2.quickstart-node-pip.address + user = local.node_username + private_key = tls_private_key.global_key.private_key_pem + } + } + + tags = ["rancher-quickstart"] + +} diff --git a/openstack/output.tf b/openstack/output.tf new file mode 100644 index 00000000..5bfd53b9 --- /dev/null +++ b/openstack/output.tf @@ -0,0 +1,11 @@ +output "rancher_server_url" { + value = module.rancher_common.rancher_url +} + +output "rancher_node_ip" { + value = openstack_networking_floatingip_v2.rancher-server-pip.address +} + +output "workload_node_ip" { + value = openstack_networking_floatingip_v2.quickstart-node-pip.address +} diff --git a/openstack/provider.tf b/openstack/provider.tf new file mode 100644 index 00000000..4560416f --- /dev/null +++ b/openstack/provider.tf @@ -0,0 +1,18 @@ +provider "openstack" { + auth_url = var.openstack_auth_url + user_name = var.openstack_user_name + password = var.openstack_password + region = var.openstack_region + domain_id = var.openstack_domain_id + domain_name = var.openstack_domain_name + tenant_id = var.openstack_project_id + tenant_name = var.openstack_project_name + insecure = var.openstack_insecure + cacert_file = var.openstack_cacert_file +} + +provider "tls" { +} + +provider "cloudinit" { +} diff --git a/openstack/terraform.tfvars.example b/openstack/terraform.tfvars.example new file mode 100644 index 00000000..b6fb5975 --- /dev/null +++ b/openstack/terraform.tfvars.example @@ -0,0 +1,72 @@ +# Required variables +# - Fill in before beginning quickstart +# ========================================================== + +# Floating IP Pool +openstack_floating_ip_pool = "" + +# Password used to log in to the `admin` account on the new Rancher server +rancher_server_admin_password = "" + +# Use either the OS_* environment variables or the following variables +# to authenticate with OpenStack infrastructure. + +# OpenStack Identity authentication URL +# openstack_auth_url = "" + +# OpenStack User Name +# openstack_user_name = "" + +# OpenStack Password +# openstack_password = "" + +# OpenStack Region +# openstack_region = "" + +# OpenStack Domain ID +# openstack_domain_id = "" + +# OpenStack Domain Name +# openstack_domain_name = "" + +# OpenStacl Project ID +# openstack_project_id = "" + +# OpenStack Project Name +# openstack_project_name = "" + +# OpenStack Insecure +# openstack_insecure = "" + +# OpenStack CACert File +# openstack_cacert_file = "" + +# Optional variables, uncomment to customize the quickstart +# ---------------------------------------------------------- + +# Prefix for all resources created by quickstart +# prefix = "" + +# OpenStack virtual machine instance flavor of all created instances +# instance_type = "" + +# Docker version installed on target hosts +# - Must be a version supported by the Rancher install scripts +# docker_version = "" + +# Kubernetes version used for creating management server cluster +# - Must be supported by RKE terraform provider 1.0.1 +# rke_kubernetes_version = "" + +# Kubernetes version used for creating workload cluster +# - Must be supported by RKE terraform provider 1.0.1 +# workload_kubernetes_version = "" + +# Version of cert-manager to install, used in case of older Rancher versions +# cert_manager_version = "" + +# Version of Rancher to install +# rancher_version = "" + +# Rancher network CIDR +# network_cidr = "" diff --git a/openstack/variables.tf b/openstack/variables.tf new file mode 100644 index 00000000..7600b46d --- /dev/null +++ b/openstack/variables.tf @@ -0,0 +1,126 @@ +# Variables for OpenStack infrastructure module + +variable "openstack_floating_ip_pool" { + type = string + description = "The name of the pool from which to obtain the floating IPs." +} + +variable "openstack_auth_url" { + type = string + description = "The OpenStack Identity authentication URL" + default = null +} + +variable "openstack_user_name" { + type = string + description = "The Username to login with." + default = null +} + +variable "openstack_password" { + type = string + description = "The Password to login with." + default = null +} + +variable "openstack_region" { + type = string + description = "The region of the OpenStack cloud to use." + default = null +} + +variable "openstack_domain_id" { + type = string + description = "The Name of the Domain to scope to." + default = null +} + +variable "openstack_domain_name" { + type = string + description = "The Name of the Domain to scope to." + default = null +} + +variable "openstack_project_id" { + type = string + description = "The ID of the Project to login with." + default = null +} + +variable "openstack_project_name" { + type = string + description = "The Name of the Project." + default = null +} + +variable "openstack_insecure" { + type = bool + description = "Trust self-signed SSL certificates." + default = null +} + +variable "openstack_cacert_file" { + type = string + description = "Specify a custom CA certificate when communicating over SSL." + default = null +} + +variable "prefix" { + type = string + description = "Prefix added to names of all resources" + default = "quickstart" +} + +variable "instance_type" { + type = string + description = "Instance type used for all linux virtual machines" + default = "m1.large" +} + +variable "docker_version" { + type = string + description = "Docker version to install on nodes" + default = "19.03" +} + +variable "rke_kubernetes_version" { + type = string + description = "Kubernetes version to use for Rancher server RKE cluster" + default = "v1.18.6-rancher1-1" +} + +variable "workload_kubernetes_version" { + type = string + description = "Kubernetes version to use for managed workload cluster" + default = "v1.17.9-rancher1-1" +} + +variable "cert_manager_version" { + type = string + description = "Version of cert-mananger to install alongside Rancher (format: 0.0.0)" + default = "0.15.1" +} + +variable "rancher_version" { + type = string + description = "Rancher server version (format: v0.0.0)" + default = "v2.4.6" +} + +variable "network_cidr" { + type = string + description = "The Network CIDR for the Rancher nodes" + default = "10.0.0.0/16" +} + +# Required +variable "rancher_server_admin_password" { + type = string + description = "Admin password to use for Rancher server bootstrap" +} + + +# Local variables used to reduce repetition +locals { + node_username = "ubuntu" +} diff --git a/openstack/versions.tf b/openstack/versions.tf new file mode 100644 index 00000000..e9ab9576 --- /dev/null +++ b/openstack/versions.tf @@ -0,0 +1,20 @@ +terraform { + required_providers { + openstack = { + source = "terraform-providers/openstack" + version = "1.30.0" + } + local = { + source = "hashicorp/local" + } + tls = { + source = "hashicorp/tls" + version = "2.2.0" + } + cloudinit = { + source = "hashicorp/cloudinit" + version = "1.0.0" + } + } + required_version = ">= 0.13" +} diff --git a/test/e2e_test.go b/test/e2e_test.go index 9918d43f..b2ddeb75 100644 --- a/test/e2e_test.go +++ b/test/e2e_test.go @@ -34,6 +34,10 @@ func TestE2E_Gcp(t *testing.T) { runTerraformAndVerify(t, "../gcp") } +func TestE2E_Openstack(t *testing.T) { + runTerraformAndVerify(t, "../openstack") +} + func runTerraformAndVerify(t *testing.T, terraformDir string) { t.Parallel()