Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions terraform-modules/openstack-kubernetes-infra/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 10 additions & 5 deletions terraform-modules/openstack-kubernetes-infra/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,19 @@ Terraform Module for provisioning VMs on Openstack as a kubernetes cluster using
module "openstack_kubernetes_cluster" {
source = "../../modules/openstack-kubernetes-infra"
host_instances = [
{ name = "cogstack-k3s-server", is_controller = true },
{ name = "cogstack-k3s-server", is_controller = true, flavour = "m1.small" },
{
name = "cogstack-k3s-node-2"
flavour = "2cpu4ram"
flavour = "m1.small"
volume_size = 20
is_controller = false
},
]
network = {
name = "public"
}
allowed_ingress_ips_cidr = "0.0.0.0/24"
ubuntu_immage_name = "Ubuntu_Jammy""
ubuntu_immage_name = "ubuntu-22.04"
ssh_key_pair = {
private_key_file = "~/.ssh/id_rsa",
public_key_file = "~/.ssh/id_rsa.pub",
Expand All @@ -36,7 +39,7 @@ module "openstack_kubernetes_cluster" {
```

## Existing Network and Floating IPs
You can use this module with a custom OpenStack network by specifying the `network` variable. By default, it will use the network named `"external_4003"`, but you can override this with your own network name or network ID.
You can use this module with a custom OpenStack network by specifying the `network` variable with your own network name or network ID.

Using a custom OpenStack network with your own subnet allows you to improve security by keeping most nodes private. This ensures worker nodes and other internal resources are not directly exposed, reducing security risks. Only the controller node needs a floating IP to be accessible in order to use the k8s api server (eg for kubectl to work from outside the network). Using floating ips will also have the advantage of being stable, so you can destroy/create VMs without having to update anywhere the IP address is referenced by reassigning the floating ip.

Expand All @@ -49,13 +52,15 @@ host_instances = [
{
name = "controller"
is_controller = true
flavour = "m1.small"
floating_ip = {
use_floating_ip = true
address = "203.0.113.10" # Address of an existing floating_ip in openstack
}
},
{
name = "worker"
flavour = "m1.small"
# Optionally also assign a floating IP here, or leave blank to keep it internal to the network
}
network = {
Expand All @@ -79,7 +84,7 @@ resource "openstack_networking_subnet_v2" "example_subnet"{
resource "openstack_networking_router_v2" "example_router" {
name = "test-router"
admin_state_up = true
external_network_id = data.openstack_networking_network_v2.external_4003.id
external_network_id = data.openstack_networking_network_v2.public.id
}
```

Expand Down
10 changes: 4 additions & 6 deletions terraform-modules/openstack-kubernetes-infra/compute.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ resource "openstack_compute_instance_v2" "kubernetes_server" {
name = local.prefix != "" ? "${local.prefix}-${local.controller_host.name}" : local.controller_host.name
flavor_id = data.openstack_compute_flavor_v2.available_compute_flavors[local.controller_host.flavour].id
key_pair = openstack_compute_keypair_v2.compute_keypair.name
region = "RegionOne"

user_data = data.cloudinit_config.init_docker_controller.rendered

Expand Down Expand Up @@ -55,7 +54,6 @@ resource "openstack_compute_instance_v2" "kubernetes_nodes" {
name = local.prefix != "" ? "${local.prefix}-${each.value.name}" : each.value.name
flavor_id = data.openstack_compute_flavor_v2.available_compute_flavors[each.value.flavour].id
key_pair = openstack_compute_keypair_v2.compute_keypair.name
region = "RegionOne"

user_data = data.cloudinit_config.init_docker[each.key].rendered
security_groups = ["default",
Expand All @@ -81,10 +79,9 @@ resource "openstack_compute_instance_v2" "kubernetes_nodes" {
}

locals {
is_default_network = var.network != null && var.network == { name = "external_4003" }
nodes_with_exernal_ip = { for node in local.created_nodes : node.name => node if local.is_default_network || node.use_floating_ip }

nodes_with_exernal_ip = { for node in local.created_nodes : node.name => node if local.controller_host_has_floating_ip ? node.use_floating_ip : true }
}

resource "null_resource" "kubernetes_nodes_provisioner" {
# Provisioner is only used to check for node readiness. Skip for any nodes that are not in the external network,
# TODO: Filter this provisioner to only run on nodes that have a floating IP if the network is not default
Expand Down Expand Up @@ -159,7 +156,8 @@ data "openstack_compute_flavor_v2" "available_compute_flavors" {


data "openstack_networking_network_v2" "network" {
name = var.network != null && var.network.name != null ? var.network.name : "external_4003"
count = var.network.network_id == null ? 1 : 0
name = var.network.name
}

data "openstack_images_image_v2" "ubuntu" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
locals {
random_prefix = random_id.server.b64_url
prefix = var.prefix != null ? var.prefix : (var.generate_random_name_prefix ? local.random_prefix : "")
network_id = var.network != null && var.network.network_id != null ? var.network.network_id : data.openstack_networking_network_v2.network.id
network_id = var.network.network_id != null ? var.network.network_id : data.openstack_networking_network_v2.network[0].id
}


Expand Down
160 changes: 160 additions & 0 deletions terraform-modules/openstack-kubernetes-infra/tests/compute.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
mock_provider "openstack" {
override_during = plan

mock_data "openstack_networking_network_v2" {
defaults = { id = "net-1" }
}
}

mock_provider "random" {
override_during = plan

mock_resource "random_password" {
defaults = { result = "k3stoken" }
}

mock_resource "random_id" {
defaults = { b64_url = "rand" }
}
}

override_resource {
target = openstack_compute_instance_v2.kubernetes_server
override_during = plan
values = { access_ip_v4 = "10.0.0.1" }
}

run "server_and_node" {
command = plan

variables {
ubuntu_immage_name = "ubuntu"
generate_random_name_prefix = false
prefix = "test"
network = { name = "public" }
host_instances = [
{ name = "controller", is_controller = true, flavour = "m1.small" },
{ name = "worker", flavour = "m1.small" },
]
}

assert {
condition = openstack_compute_instance_v2.kubernetes_server.name == "test-controller" && openstack_compute_instance_v2.kubernetes_nodes["worker"].name == "test-worker"
error_message = "expected a controller and a worker"
}

assert {
condition = strcontains(data.cloudinit_config.init_docker_controller.part[0].content, "INSTALL_K3S_EXEC=\"server\"")
error_message = "server cloud-init should install k3s server"
}

assert {
condition = strcontains(data.cloudinit_config.init_docker["worker"].part[0].content, "K3S_URL=https://10.0.0.1:6443")
error_message = "node cloud-init should join the server"
}
}

run "required_only" {
command = plan

variables {
ubuntu_immage_name = "ubuntu"
network = { name = "public" }
host_instances = [
{ name = "controller", is_controller = true, flavour = "m1.small" },
{ name = "worker", flavour = "m1.small" },
]
}

assert {
condition = openstack_compute_instance_v2.kubernetes_server.name == "rand-controller" && openstack_compute_instance_v2.kubernetes_nodes["worker"].name == "rand-worker"
error_message = "required-only should still create a controller and worker"
}

assert {
condition = openstack_compute_instance_v2.kubernetes_server.block_device[0].volume_size == 20
error_message = "volume_size should default to 20"
}

assert {
condition = strcontains(data.cloudinit_config.init_docker["worker"].part[0].content, "K3S_URL=https://10.0.0.1:6443")
error_message = "node cloud-init should join the server"
}
}

run "all_variables" {
command = plan

variables {
ubuntu_immage_name = "ubuntu"
allowed_ingress_ips_cidr = "10.1.2.0/24"
k3s_version = "v1.32.2+k3s1"
generate_random_name_prefix = false
prefix = "full"
output_file_directory = "/tmp/cogstack-test"
network = {
name = "private"
network_id = "net-custom"
}
ssh_key_pair = {
private_key_file = "tests/fixtures/fake_key"
public_key_file = "tests/fixtures/fake_key.pub"
}
host_instances = [
{
name = "controller"
is_controller = true
flavour = "m1.large"
volume_size = 50
image_uuid = "img-controller"
floating_ip = {
use_floating_ip = true
address = "203.0.113.10"
}
},
{
name = "worker"
flavour = "m1.medium"
volume_size = 40
image_uuid = "img-worker"
floating_ip = {
use_floating_ip = true
address = "203.0.113.11"
}
},
]
}

assert {
condition = openstack_compute_instance_v2.kubernetes_server.name == "full-controller" && openstack_compute_instance_v2.kubernetes_nodes["worker"].name == "full-worker"
error_message = "prefix should be applied to host names"
}

assert {
condition = (
openstack_compute_instance_v2.kubernetes_server.block_device[0].volume_size == 50 &&
openstack_compute_instance_v2.kubernetes_server.block_device[0].uuid == "img-controller" &&
openstack_compute_instance_v2.kubernetes_nodes["worker"].block_device[0].volume_size == 40 &&
openstack_compute_instance_v2.kubernetes_nodes["worker"].block_device[0].uuid == "img-worker" &&
openstack_compute_instance_v2.kubernetes_server.network[0].uuid == "net-custom"
)
error_message = "optional host and network values should be used"
}

assert {
condition = (
strcontains(data.cloudinit_config.init_docker_controller.part[0].content, "TF_K3S_VERSION=\"v1.32.2+k3s1\"") &&
strcontains(data.cloudinit_config.init_docker_controller.part[0].content, "TF_K3S_TLS_SAN=203.0.113.10") &&
strcontains(data.cloudinit_config.init_docker["worker"].part[0].content, "TF_K3S_NODE_EXTERNAL_IP=203.0.113.11")
)
error_message = "cloud-init should include k3s version and floating IPs"
}

assert {
condition = (
openstack_networking_secgroup_rule_v2.cogstack_apps_port_rules["22-Expose SSH"].remote_ip_prefix == "10.1.2.0/24" &&
openstack_compute_keypair_v2.compute_keypair.public_key == file("tests/fixtures/fake_key.pub")
)
error_message = "ingress CIDR and ssh keypair should be used"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN TEST PRIVATE KEY-----
test
-----END TEST PRIVATE KEY-----
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC test@test
Loading
Loading