Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: introduce VLAN for private networking #38

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
9 changes: 8 additions & 1 deletion main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ module "sshkey" {
cluster_name = var.cluster_name
}

module "network" {
source = "./modules/network"

project_id = var.metal_project_id
metal_metro = var.metal_metro
}

module "bastion" {
source = "./modules/bastion"
depends_on = [module.sshkey]
Expand All @@ -21,7 +28,7 @@ module "bastion" {
cluster_basedomain = var.cluster_basedomain
ocp_version = var.ocp_version
ocp_version_zstream = var.ocp_version_zstream
//depends = [module.prepare_openshift.finished]
vlan = var.metal_vlan
}

module "dns_lb" {
Expand Down
2 changes: 2 additions & 0 deletions modules/bastion/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
# Nginx via Terraform on Equinix Metal

This is a very basic [Terraform](http://terraform.io) template that will deploy [Nginx](http://nginx.org) on [Equinix Metal](https://metal.equinix.com) baremetal.

Nginx will host IPXE and Ignition config files for each node defined.
59 changes: 59 additions & 0 deletions modules/bastion/assets/cloudinit_centos_8.cfg.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
## template: jinja
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: create an equivalent RHEL 7 config

#cloud-config
package_update: true
package_upgrade: true
package_reboot_if_required: true
write_files:
- path: /etc/sysctl.d/10-ip-forwarding.conf
content: |
net.ipv4.ip_forward=1
- path: /etc/iptables/rules.v4
content: |
# Generated by iptables-save v1.8.4 on Tue Aug 24 19:16:37 2021
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT
# Completed on Tue Aug 24 19:16:37 2021
# Generated by iptables-save v1.8.4 on Tue Aug 24 19:16:37 2021
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -o bond0 -j MASQUERADE
COMMIT
# Completed on Tue Aug 24 19:16:37 2021
- path: /etc/iptables/rules.v6
content: |
# Generated by ip6tables-save v1.8.4 on Tue Aug 24 19:16:37 2021
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT
- path: /etc/network/interfaces
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is certainly not correct for non-Debian based systems

Copy link
Member Author

@displague displague Jul 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a starting point for changes to consider, ChatGPT suggests:

...
- path: /etc/sysconfig/iptables
  content: |
    ... same
- path: /etc/sysconfig/ip6tables
  content: |
    ... same
- path: /etc/sysconfig/network-scripts/ifcfg-bond0.${metal_vlan_id}
  content: |
    DEVICE=bond0.${metal_vlan_id}
    BOOTPROTO=static
    ONBOOT=yes
    IPADDR=${address}
    NETMASK=${netmask}
    VLAN=yes
...

Copy link
Member Author

@displague displague Jul 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With a little more prodding (again, this is just to serve as a pointer in the general direction of what may be needed):

- path: /etc/sysconfig/network-scripts/ifcfg-bond0.${metal_vlan_id}
  content: |
    DEVICE=bond0.${metal_vlan_id}
    NAME=bond0.${metal_vlan_id}
    TYPE=Bond
    BONDING_MASTER=yes
    IPADDR=${address}
    NETMASK=${netmask}
    ONBOOT=yes
    BOOTPROTO=none
    BONDING_OPTS="mode=1 miimon=100"
    NM_CONTROLLED="no"
- path: /etc/sysconfig/network-scripts/ifcfg-eth0
  content: |
    DEVICE=eth0
    NAME=bond0-slave
    TYPE=Ethernet
    ONBOOT=yes
    BOOTPROTO=none
    MASTER=bond0
    SLAVE=yes
    NM_CONTROLLED="no"
- path: /etc/sysconfig/network-scripts/ifcfg-eth1
  content: |
    DEVICE=eth1
    NAME=bond0-slave
    TYPE=Ethernet
    ONBOOT=yes
    BOOTPROTO=none
    MASTER=bond0
    SLAVE=yes
    NM_CONTROLLED="no"
...
runcmd:
- modprobe bonding
- ifup eth0
- ifup eth1
- ifup bond0.${metal_vlan_id}
...

Copy link
Member Author

@displague displague Jul 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if we'll have eth0/eth1 names. On the bootstrap node this may be tricky unless we can disable predictable names for the first boot (Arch guidance may have relevance: https://wiki.archlinux.org/title/Network_configuration#Revert_to_traditional_interface_names)

We'll also need to add kernel command line args for the IPXE scripts to have them come up bonded, which may also need to include net.ifnames=0 (bootloader --location=mbr --append="net.ifnames=0 biosdevname=0")

Copy link
Member Author

@displague displague Jul 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, enp0sX naming may work fine if we document the server plans (c3.medium, etc) that these names are known to work within.

append: true
content: |

auto bond0.${metal_vlan_id}
iface bond0.${metal_vlan_id} inet static
pre-up sleep 5
address ${address}
netmask ${netmask}
vlan-raw-device bond0
- path: /etc/dnsmasq.d/openshift.config
append: true
content: |
bind-interfaces
interface=bond0.${metal_vlan_id}
dhcp-range=${host_dhcp_start},${host_dhcp_end},${lease_time}
packages:
- iptables
- iptables-services
- dnsmasq
runcmd:
- sysctl -p /etc/sysctl.d/10-ip-forwarding.conf
- systemctl restart networking
- systemctl restart dnsmasq
2 changes: 1 addition & 1 deletion modules/bastion/assets/ipxe.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ set coreos-img $${coreos-url}/rhcos-$${release}.$${zstream}-$${arch}-live-rootfs
set console console=ttyS1,115200n8

kernel $${coreos-url}/rhcos-$${release}.$${zstream}-$${arch}-live-kernel-$${arch} initrd=main coreos.live.rootfs_url=$${coreos-img} coreos.inst.ignition_url=http://${ bastion_ip }:8080/${ node_type }-append.ign random.trust_cpu=on rd.luks.options=discard console=tty1 console=ttyS1,115200n8 coreos.inst.persistent-kargs="console=tty1 console=ttyS1,115200n8" coreos.inst.install_dev=/dev/sda
initrd --name main $${coreos-url}/rhcos-$${release}.$${zstream}-$${arch}-live-initramfs.$${arch}.img
initrd --name main $${coreos-url}/rhcos-$${release}.$${zstream}-$${arch}-live-initramfs.$${arch}.img
boot
3 changes: 2 additions & 1 deletion modules/bastion/assets/user_data_centos_8.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ systemctl enable nfs-server.service
systemctl start nfs-server.service

mkdir -p /mnt/nfs/ocp
chmod -R 777 /mnt/nfs/ocp
chmod -R 777 /mnt/nfs/ocp

37 changes: 34 additions & 3 deletions modules/bastion/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,38 @@ resource "equinix_metal_device" "lb" {
operating_system = var.operating_system
billing_cycle = var.billing_cycle
project_id = var.project_id
user_data = file("${path.module}/assets/user_data_${var.operating_system}.sh")
user_data = data.cloudinit_config.lb.rendered
}


resource "equinix_metal_port" "lb_bond0" {
port_id = [for p in equinix_metal_device.lb.ports : p.id if p.name == "bond0"][0]
layer2 = false
bonded = true
vlan_ids = [var.vlan]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename to metal_vlan for consistency

}


data "cloudinit_config" "lb" {
gzip = false
base64_encode = false

part {
filename = "cloud-config.cfg"
content = templatefile("${path.module}/assets/cloudinit_${var.operating_system}.cfg.tpl",
{
metal_vlan_id = 1000,
address = cidrhost(var.cluster_subnet, 2),
netmask = cidrnetmask(var.cluster_subnet),
host_dhcp_start = cidrhost(var.cluster_subnet, 3),
host_dhcp_end = cidrhost(var.cluster_subnet, 15),
lease_time = "infinite",
})
}
part {
filename = "user-data"
content = file("${path.module}/assets/user_data_${var.operating_system}.sh")
}
}

resource "null_resource" "dircheck" {
Expand Down Expand Up @@ -72,7 +103,7 @@ resource "null_resource" "ipxe_files" {

content = templatefile("${path.module}/assets/ipxe.tpl", {
node_type = each.value
bastion_ip = equinix_metal_device.lb.access_public_ipv4
bastion_ip = equinix_metal_device.lb.access_private_ipv4
Copy link
Member Author

@displague displague Jul 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

access_private_ipv4 needs to be replaced with an address from the VRF range, 192.168.100.2
(every mention of access_private_ipv4 should be replaced since we are not using the EM private management network, using VRF over VLAN 1000 instead)

ocp_version = var.ocp_version
ocp_version_zstream = var.ocp_version_zstream
})
Expand Down Expand Up @@ -107,7 +138,7 @@ resource "null_resource" "ignition_append_files" {

content = templatefile("${path.module}/assets/ignition-append.json.tpl", {
node_type = each.value
bastion_ip = equinix_metal_device.lb.access_public_ipv4
bastion_ip = equinix_metal_device.lb.access_private_ipv4
cluster_name = var.cluster_name
cluster_basedomain = var.cluster_basedomain
})
Expand Down
4 changes: 4 additions & 0 deletions modules/bastion/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ output "lb_ip" {
value = equinix_metal_device.lb.access_public_ipv4
}

output "node_ip" {
value = equinix_metal_device.lb.access_private_ipv4
}

output "finished" {
depends_on = [
null_resource.ipxe_files,
Expand Down
12 changes: 12 additions & 0 deletions modules/bastion/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,15 @@ variable "nodes" {
type = list(string)
default = ["bootstrap", "master", "worker"]
}

variable "vlan" {
description = "VLAN to use"
type = number
default = 1000
}

variable "cluster_subnet" {
description = "The subnet to use for the cluster"
type = string
default = "192.168.100.0/22"
}
60 changes: 60 additions & 0 deletions modules/network/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
locals {
vlan_id = var.create_vlan ? element(equinix_metal_vlan.openshift[*].id, 0) : element(data.equinix_metal_vlan.openshift[*].id, 0)
vxlan = var.create_vlan ? element(equinix_metal_vlan.openshift[*].vxlan, 0) : element(data.equinix_metal_vlan.openshift[*].vxlan, 0)
vrf_id = var.create_vrf ? element(equinix_metal_vrf.openshift[*].id, 0) : element(data.equinix_metal_vrf.openshift[*].id, 0)
}


# This generates a random suffix to avoid VRF name
# collisions when multiple clusters are deployed to
# an existing Metal project
resource "random_string" "vrf_name_suffix" {
length = 5
special = false
}


resource "equinix_metal_vlan" "openshift" {
count = var.create_vlan ? 1 : 0
project_id = var.project_id
description = var.metal_vlan_description
metro = var.metal_metro
}


data "equinix_metal_vlan" "openshift" {
count = var.create_vlan ? 0 : 1
project_id = var.project_id
vxlan = var.metal_vlan_id
}

resource "equinix_metal_vrf" "openshift" {
count = var.create_vrf ? 1 : 0
description = "VRF with ASN 65000 and a pool of address space that includes 192.168.100.0/25"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
description = "VRF with ASN 65000 and a pool of address space that includes 192.168.100.0/25"
description = "VRF with ASN 65000 and a pool of address space"

name = "openshift-vrf-${random_string.vrf_name_suffix.result}"
metro = var.metal_metro
local_asn = "65000"
ip_ranges = [var.cluster_subnet]
project_id = var.project_id
}

data "equinix_metal_vrf" "openshift" {
count = var.create_vrf ? 0 : 1
vrf_id = var.vrf_id
}

resource "equinix_metal_reserved_ip_block" "openshift" {
description = "Reserved IP block (${var.cluster_subnet}) taken from on of the ranges in the VRF's pool of address space."
project_id = var.project_id
metro = var.metal_metro
type = "vrf"
vrf_id = local.vrf_id
cidr = split("/", var.cluster_subnet)[1]
network = cidrhost(var.cluster_subnet, 0)
}

resource "equinix_metal_gateway" "gateway" {
project_id = var.project_id
vlan_id = local.vlan_id
ip_reservation_id = equinix_metal_reserved_ip_block.openshift.id
}
11 changes: 11 additions & 0 deletions modules/network/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
output "vrf_id" {
Copy link
Member Author

@displague displague Jul 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entire module needs a terraform fmt -recursive, for existing code too.
Opting to do that in a separate PR to avoid noise.

value = local.vrf_id
}

output "vlan_id" {
value = local.vlan_id
}

output "reserved_ip_block_id" {
value = equinix_metal_reserved_ip_block.openshift.id
}
28 changes: 28 additions & 0 deletions modules/network/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
variable "cluster_subnet" { default = "192.168.100.0/22" }
variable "metal_metro" { default = "da" }
variable "create_vrf" { default = true }
variable "project_id" {}

variable "create_vlan" {
type = bool
default = true
description = "Whether to create a new VLAN for this project."
}
variable "metal_vlan_id" {
type = number
default = null
description = "ID of the VLAN you wish to use."
}

variable "metal_vlan_description" {
type = string
default = "openshift-demo"
description = "Description to add to created VLAN."
}


variable "vrf_id" {
type = string
default = null
description = "ID of the VRF you wish to use."
}
15 changes: 15 additions & 0 deletions modules/network/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
terraform {
required_providers {
equinix = {
source = "equinix/equinix"
version = "~> 1.14"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider bumping all Equinix provider version deps to ~> 2

}
null = {
source = "hashicorp/null"
}
}
required_version = ">= 1.0.0"
provider_meta "equinix" {
module_name = "equinix-metal-openshift-on-baremetal/bastion"
}
}
4 changes: 4 additions & 0 deletions modules/node/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,9 @@ resource "equinix_metal_device" "node" {
count = var.node_count
billing_cycle = "hourly"
project_id = var.project_id
ip_address {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be removed. There was a starting thought that all nodes could use the EM Private IPs, but DHCP management needs, configuring a gateway, meant that VRF and DHCP services running on the bastion would be a better fit.

type = "private_ipv4"
cidr = 30
}
}

2 changes: 1 addition & 1 deletion modules/node/outputs.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
output "node_ip" {
value = equinix_metal_device.node.*.access_public_ipv4
value = equinix_metal_device.node.*.access_private_ipv4
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may pose a problem. access_private_ipv4 should certainly be removed, but we can not depend on access_public_ipv4 either since the nodes will not have this address. The node_ip is only used for DNS (that I recall at the moment) and this is used within the cluster. Either we don't need to assign these addresses to DNS (they may be optional in OpenShift install guidance) or we'll need to pin the DHCP assigned addresses such that we can predict the DHCP assigned IP for assignment to the DNS name.

}

output "finished" {
Expand Down
5 changes: 5 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ variable "metal_project_id" {
description = "Your Equinix Metal Project ID"
}

variable "metal_vlan" {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll want to allow for existing VLANs to be plumbed in. The network module already supports that.

description = "Your Equinix Metal VLAN (ex. 1000)"
default = 1000
}

variable "bastion_operating_system" {
description = "Your preferred bastion operating systems (RHEL or CentOS)"
default = "rhel_7"
Expand Down