diff --git a/README.md b/README.md index 526e481..c798ed0 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,25 @@ worker_nodes = { Stateful apps currently pin retained local PVs to the `debian` node. Move or duplicate those PV manifests when you want storage on another node. +The website NodePort is reachable from the OCI jump box through the Raspberry Pi +Tailscale interface. `bootstrap/cluster` installs a persistent +`homelab-tailscale-nodeport.service` on the configured worker to restore the +route, rp_filter settings, and iptables rules after reboot. Override the +defaults through `tailscale_nodeport_access` when the jump-box IP, Pi Tailscale +IP, pod CIDR, or NodePort changes: + +```hcl +tailscale_nodeport_access = { + enabled = true + worker_key = "raspberrypi" + peer_ip = "100.118.255.19" + node_tailscale_ip = "100.77.80.72" + pod_cidr = "10.244.0.0/16" + node_port = 30080 + target_port = 80 +} +``` + For `./lab.sh nuke`, set `WORKER_SSH_TARGETS` to a space-separated list of remote SSH targets when more worker nodes exist. Set it to an empty string for a single-node rebuild. diff --git a/bootstrap/cluster/main.tf b/bootstrap/cluster/main.tf index 4595834..8d53c90 100644 --- a/bootstrap/cluster/main.tf +++ b/bootstrap/cluster/main.tf @@ -290,14 +290,21 @@ resource "null_resource" "kubeadm_worker" { depends_on = [data.external.kubeadm_join_command] triggers = { - node_name = each.value.node_name - host = each.value.host - user = each.value.user - ssh_key_path = each.value.ssh_key_path - registry_endpoint = var.registry_endpoint - registry_config_version = "6" - node_dns_servers = join(" ", var.node_dns_servers) - persistent_volume_dirs = join(",", var.persistent_volume_dirs) + node_name = each.value.node_name + host = each.value.host + user = each.value.user + ssh_key_path = each.value.ssh_key_path + registry_endpoint = var.registry_endpoint + registry_config_version = "6" + node_dns_servers = join(" ", var.node_dns_servers) + persistent_volume_dirs = join(",", var.persistent_volume_dirs) + tailscale_nodeport_version = "1" + tailscale_nodeport_enabled = var.tailscale_nodeport_access.enabled && each.key == var.tailscale_nodeport_access.worker_key ? "true" : "false" + tailscale_nodeport_peer_ip = var.tailscale_nodeport_access.peer_ip + tailscale_nodeport_node_tailscale_ip = var.tailscale_nodeport_access.node_tailscale_ip + tailscale_nodeport_pod_cidr = var.tailscale_nodeport_access.pod_cidr + tailscale_nodeport_node_port = tostring(var.tailscale_nodeport_access.node_port) + tailscale_nodeport_target_port = tostring(var.tailscale_nodeport_access.target_port) } connection { @@ -518,6 +525,82 @@ if ! getent hosts "${self.triggers.node_name}" >/dev/null; then printf '%s %s\n' "${self.triggers.host}" "${self.triggers.node_name}" | sudo tee -a /etc/hosts >/dev/null fi +configure_tailscale_nodeport_access() { + local enabled="$1" + local peer_ip="$2" + local node_tailscale_ip="$3" + local pod_cidr="$4" + local node_port="$5" + local target_port="$6" + + if [ "$enabled" != "true" ]; then + return 0 + fi + + sudo mkdir -p /usr/local/sbin /etc/sysctl.d + sudo tee /etc/sysctl.d/98-homelab-tailscale-nodeport.conf >/dev/null </dev/null </dev/null +sysctl -w net.ipv4.conf.tailscale0.rp_filter=0 >/dev/null 2>&1 || true + +if ! ip link show tailscale0 >/dev/null 2>&1; then + echo "tailscale0 is not present; skipping Tailscale NodePort routing" + exit 0 +fi + +ip route replace "$PEER_IP/32" dev tailscale0 src "$NODE_TAILSCALE_IP" + +iptables -C INPUT -i tailscale0 -p tcp --dport "$NODE_PORT" -j ACCEPT 2>/dev/null || + iptables -I INPUT 1 -i tailscale0 -p tcp --dport "$NODE_PORT" -j ACCEPT +iptables -C FORWARD -i tailscale0 -d "$POD_CIDR" -p tcp --dport "$TARGET_PORT" -j ACCEPT 2>/dev/null || + iptables -I FORWARD 1 -i tailscale0 -d "$POD_CIDR" -p tcp --dport "$TARGET_PORT" -j ACCEPT +iptables -C FORWARD -s "$POD_CIDR" -o tailscale0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || + iptables -I FORWARD 1 -s "$POD_CIDR" -o tailscale0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT +iptables -t nat -C POSTROUTING -s 100.64.0.0/10 -d "$POD_CIDR" -p tcp --dport "$TARGET_PORT" -m comment --comment tailscale-nodeport-to-pods -j MASQUERADE 2>/dev/null || + iptables -t nat -I POSTROUTING 1 -s 100.64.0.0/10 -d "$POD_CIDR" -p tcp --dport "$TARGET_PORT" -m comment --comment tailscale-nodeport-to-pods -j MASQUERADE +NODEPORT_SCRIPT_EOT + sudo chmod 0755 /usr/local/sbin/homelab-tailscale-nodeport.sh + + sudo tee /etc/systemd/system/homelab-tailscale-nodeport.service >/dev/null <<'NODEPORT_SERVICE_EOT' +[Unit] +Description=Homelab Tailscale NodePort routing +After=network-online.target tailscaled.service kubelet.service +Wants=network-online.target + +[Service] +Type=oneshot +ExecStart=/usr/local/sbin/homelab-tailscale-nodeport.sh +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target +NODEPORT_SERVICE_EOT + sudo systemctl daemon-reload + sudo systemctl enable homelab-tailscale-nodeport.service >/dev/null + sudo systemctl restart homelab-tailscale-nodeport.service +} + +configure_tailscale_nodeport_access \ + "${self.triggers.tailscale_nodeport_enabled}" \ + "${self.triggers.tailscale_nodeport_peer_ip}" \ + "${self.triggers.tailscale_nodeport_node_tailscale_ip}" \ + "${self.triggers.tailscale_nodeport_pod_cidr}" \ + "${self.triggers.tailscale_nodeport_node_port}" \ + "${self.triggers.tailscale_nodeport_target_port}" + configure_containerd_registry "${self.triggers.registry_endpoint}" pv_dirs="${self.triggers.persistent_volume_dirs}" diff --git a/bootstrap/cluster/variables.tf b/bootstrap/cluster/variables.tf index c38572a..6fe775d 100644 --- a/bootstrap/cluster/variables.tf +++ b/bootstrap/cluster/variables.tf @@ -61,3 +61,25 @@ variable "worker_nodes" { } } } + +variable "tailscale_nodeport_access" { + type = object({ + enabled = bool + worker_key = string + peer_ip = string + node_tailscale_ip = string + pod_cidr = string + node_port = number + target_port = number + }) + + default = { + enabled = true + worker_key = "raspberrypi" + peer_ip = "100.118.255.19" + node_tailscale_ip = "100.77.80.72" + pod_cidr = "10.244.0.0/16" + node_port = 30080 + target_port = 80 + } +}