Skip to content

Instantly share code, notes, and snippets.

@ag4ve
Last active December 1, 2025 19:30
Show Gist options
  • Select an option

  • Save ag4ve/2acea14d9f7a691023df8b8db1561b5b to your computer and use it in GitHub Desktop.

Select an option

Save ag4ve/2acea14d9f7a691023df8b8db1561b5b to your computer and use it in GitHub Desktop.
OCP/OKD deployment makefile
SHELL := /usr/bin/bash -eu -o pipefail
.ONESHELL:
.DEFAULT_GOAL := help
# =============================================================================
# EXPECTED FILE LAYOUT
#
# project-root/
# Makefile # <--- this file
# bin/ # created by prereqs/okd-download
# assets/ # created by prereqs
# clusters/
# hp-proliant/
# node.env # cluster-wide environment (this cluster)
# nodes.mk # node inventory for this cluster
# Makefile # optional stub: forwards to ../Makefile
# dell-r740/
# node.env
# nodes.mk
# Makefile
#
# Typical usage:
# # HP / OCP / IPv4 / iLO cluster:
# sudo make -C clusters/hp-proliant all
#
# # Dell / OKD / IPv6 / DRAC cluster:
# sudo make -C clusters/dell-r740 all
#
# The cluster-local Makefile (in clusters/<name>/Makefile) usually just runs:
# $(MAKE) -f ../Makefile ENV_FILE=node.env NODES_FILE=nodes.mk MGMT_TYPE=...
#
# You can also run from project root:
# sudo make ENV_FILE=clusters/hp-proliant/node.env \
# NODES_FILE=clusters/hp-proliant/nodes.mk \
# all
#
# =============================================================================
# FEATURE CHECKLIST
# [x] nftables-only (no firewalld), exact-string teardown
# [x] radvd (SLAAC+RDNSS, IPv6 mode)
# [x] dnsmasq DHCPv6 with STATIC reservations (DUID/IAID, IPv6 mode)
# [x] Pure iPXE (.ipxe) with MAC-based auto role selection from mapping files
# [x] FCOS PXE artifacts via coreos-installer (kernel/initramfs/rootfs)
# [x] Cluster assets (openshift-install/oc), distro-neutral via RELEASE_BASE_URL
# [x] Authoritative DNS (BIND): forward api / *.apps + reverse ip6.arpa for /48
# [x] Zone generator from node.env (forward + reverse)
# [x] Bootstrap node runs locally (KVM VM) on install host
# [x] nftables rules for bootstrap phase (6443, 22623, 80, 443) + auto teardown
# [x] Optional Cilium helpers (ENABLE_CILIUM=1)
# [x] Backups + restore on teardown; all heredocs use <<-
# [x] Node inventory (nodes.mk) with per-node:
# - role (bootstrap/master/worker)
# - PXE MAC
# - install_dev (e.g. RAID virtual disk)
# - optional management info (ilo/drac host/user)
# [x] HP iLO helpers (raw + generic node-* interface)
# [x] Dell DRAC helpers (raw + generic node-* interface)
# [x] MGMT_TYPE=ilo|drac switch for node-level power/pxe recipes
# [x] PXE discovery environment (FCOS live + SSH via Ignition)
# [x] ipxe-discovery-maps target to point MACs at discovery boot
# [x] raid-discovery target: SSH into discovery env, run lsblk, print *_install_dev lines
# =============================================================================
# -----------------------------------------------------------------------------
# Cluster environment and node inventory
# -----------------------------------------------------------------------------
# These are intended to be overridden when running in a cluster dir:
# make -C clusters/<name> ENV_FILE=node.env NODES_FILE=nodes.mk ...
# -----------------------------------------------------------------------------
ENV_FILE ?= ./node.env
include $(ENV_FILE)
export
NODES_FILE ?= ./nodes.mk
-include $(NODES_FILE)
# -----------------------------------------------------------------------------
# Network / CNI / distro configuration
# NETWORK_STACK : ipv6 | ipv4
# NETWORK_TYPE : OVNKubernetes | Other
# ENABLE_CILIUM : 0 | 1
# DISTRO : okd | ocp (informational)
# RELEASE_BASE_URL : base URL where openshift-install/oc tarballs are hosted
# -----------------------------------------------------------------------------
NETWORK_STACK ?= ipv6
NETWORK_TYPE ?= OVNKubernetes
ENABLE_CILIUM ?= 0
DISTRO ?= okd
# RELEASE_BASE_URL must be set in node.env, e.g.:
# For OCP: https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest
# For OKD: https://github.com/okd-project/okd/releases/download/<release-tag>
RELEASE_BASE_URL ?=
# -----------------------------------------------------------------------------
# Paths & constants
# -----------------------------------------------------------------------------
BIN_DIR := $(PWD)/bin
ASSETS_DIR := $(PWD)/assets
FCOS_DIR := $(ASSETS_DIR)/fcos
HTTP_CLUSTER_DIR := $(HTTP_DIR)
PXE_HTTP_DIR := $(HTTP_CLUSTER_DIR)/pxe
# Provisioning host URL prefix (handles IPv4 vs IPv6 syntax)
PROVISION_URL_PREFIX_v6 = http://[$(PROVISION_HOST)]
PROVISION_URL_PREFIX_v4 = http://$(PROVISION_HOST)
PROVISION_URL_PREFIX = $(if $(filter $(NETWORK_STACK),ipv6),$(PROVISION_URL_PREFIX_v6),$(PROVISION_URL_PREFIX_v4))
# nftables
NFT_MAIN_CONF := /etc/nftables.conf
NFT_CLUSTER_FILE := /etc/nftables.d/okd-nat66.nft
NFT_SVC_FILE := /etc/nftables.d/okd-svc.nft
NFT_CLUSTER_TABLE := okd_nat66
NFT_SVC_TABLE := okd_svc
# dnsmasq (DHCPv6 + iPXE HTTP bootfile-url)
DNSMASQ_FILE := /etc/dnsmasq.d/okd-pxe.conf
# radvd
RADVD_FILE := /etc/radvd.conf
# BIND/named
NAMED_CONF := /etc/named.conf
NAMED_DIR := /var/named
FWD_ZONE_FILE := $(NAMED_DIR)/$(CLUSTER_NAME).$(BASE_DOMAIN).zone
REV_ZONE_FILE := $(NAMED_DIR)/$(CLUSTER_NAME).$(BASE_DOMAIN).ip6.arpa.zone
# Backups
BACKUP_SUFFIX := .bak.okd
NFT_MAIN_BAK := $(NFT_MAIN_CONF)$(BACKUP_SUFFIX)
DNSMASQ_BAK := $(DNSMASQ_FILE)$(BACKUP_SUFFIX)
RADVD_BAK := $(RADVD_FILE)$(BACKUP_SUFFIX)
NFT_CLUSTER_BAK := $(NFT_CLUSTER_FILE)$(BACKUP_SUFFIX)
NFT_SVC_BAK := $(NFT_SVC_FILE)$(BACKUP_SUFFIX)
NAMED_CONF_BAK := $(NAMED_CONF)$(BACKUP_SUFFIX)
FWD_ZONE_BAK := $(FWD_ZONE_FILE)$(BACKUP_SUFFIX)
REV_ZONE_BAK := $(REV_ZONE_FILE)$(BACKUP_SUFFIX)
# Bootstrap VM
BOOTSTRAP_VM ?= okd-bootstrap
# Management defaults
MGMT_TYPE ?= ilo # ilo | drac
ILO_DEFAULT_USER ?= Administrator
DRAC_USER ?= root
help:
@echo "Targets:"
@echo " prereqs - Install packages; prep dirs"
@echo " okd-download - Download openshift-install & oc (via RELEASE_BASE_URL)"
@echo " install-config - Generate install-config.yaml (NETWORK_TYPE aware)"
@echo " create-assets - Create manifests/ignitions; stage to HTTP"
@echo " pxe-stage - FCOS PXE images (kernel/initramfs/rootfs)"
@echo " ipxe-stage - iPXE scripts + MAC-based mapping (nodes.mk aware) + discovery"
@echo " ipxe-discovery-maps - Point all NODES MACs at discovery boot"
@echo " raid-discovery - SSH to NODES in discovery env, run lsblk, print *_install_dev lines"
@echo " pxe-up - dnsmasq (DHCPv6 + iPXE), httpd"
@echo " radvd-up - Router Advertisements (IPv6 only)"
@echo " dns-generate - Generate forward + reverse (ip6.arpa /48) zones (IPv6 only)"
@echo " dns-up - Write named.conf & start bind (IPv6 only)"
@echo " nat66-up - IPv6 forwarding + NAT66 table (IPv6 only)"
@echo " svc-allow-bootstrap - Open inbound nft rules for bootstrap/API/Ignition (IPv6 only)"
@echo " svc-allow-bootstrap-down - Remove those rules immediately"
@echo " bootstrap-host-up - Launch local KVM bootstrap VM"
@echo " bootstrap-host-down - Stop local bootstrap VM"
@echo " wait-bootstrap - Wait for bootstrap-complete, then auto svc-rules teardown"
@echo " cilium-olm - Install Cilium OLM operator (when ENABLE_CILIUM=1)"
@echo " cilium-migrate - Disable CNO safely; apply CiliumConfig; checks"
@echo " bond-howto - Emit nmcli LACP bonding helper"
@echo " ilo-show-nodes - Show node+iLO inventory from nodes.mk"
@echo " node-power-on-<node> - Power on a node via MGMT_TYPE=ilo|drac"
@echo " node-power-off-<node>- Power off a node via MGMT_TYPE=ilo|drac"
@echo " node-reset-<node> - Reset a node via MGMT_TYPE=ilo|drac"
@echo " node-set-pxe-<node> - Set one-time PXE via MGMT_TYPE=ilo|drac"
@echo " drac-power-* / ilo-* - Raw vendor helpers (direct iLO/DRAC)"
@echo " teardown - Stop services; delete nft tables; restore backups"
@echo " all - Full prep up to nat66 + svc rules (IPv6 mode only)"
# -----------------------------------------------------------------------------
# Prereqs
# -----------------------------------------------------------------------------
.PHONY: prereqs
prereqs:
@test $$(id -u) -eq 0 || { echo "Run as root"; exit 1; }
dnf -y install \
dnsmasq radvd \
coreos-installer podman jq curl unzip \
httpd tftp-server \
nftables policycoreutils-python-utils \
bind bind-utils \
qemu-kvm libvirt-daemon-kvm virt-install \
ipxe-bootimgs
systemctl enable --now nftables || true
mkdir -p $(BIN_DIR) $(ASSETS_DIR) $(FCOS_DIR) $(HTTP_CLUSTER_DIR) $(PXE_HTTP_DIR)
semanage fcontext -a -t httpd_sys_content_t "$(HTTP_CLUSTER_DIR)(/.*)?"
restorecon -Rv $(HTTP_CLUSTER_DIR)
systemctl disable --now dnsmasq || true
systemctl disable --now httpd || true
systemctl disable --now radvd || true
systemctl disable --now named || true
# -----------------------------------------------------------------------------
# Download installer & clients (distro-neutral)
# -----------------------------------------------------------------------------
.PHONY: okd-download
okd-download:
@if [ -z "$(RELEASE_BASE_URL)" ]; then \
echo "RELEASE_BASE_URL is not set."; \
echo " Example for OCP: https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest"; \
echo " Example for OKD: https://github.com/okd-project/okd/releases/download/<release-tag>"; \
exit 1; \
fi
mkdir -p $(BIN_DIR) $(ASSETS_DIR)
echo "Downloading openshift-install/oc from $(RELEASE_BASE_URL)"
curl -fsSL -o $(ASSETS_DIR)/openshift-install-linux.tar.gz $(RELEASE_BASE_URL)/openshift-install-linux.tar.gz
curl -fsSL -o $(ASSETS_DIR)/openshift-client-linux.tar.gz $(RELEASE_BASE_URL)/openshift-client-linux.tar.gz
tar -C $(BIN_DIR) -xzf $(ASSETS_DIR)/openshift-install-linux.tar.gz openshift-install
tar -C $(BIN_DIR) -xzf $(ASSETS_DIR)/openshift-client-linux.tar.gz oc kubectl || true
chmod +x $(BIN_DIR)/*
$(BIN_DIR)/openshift-install version
# -----------------------------------------------------------------------------
# Cluster assets (NETWORK_TYPE-aware)
# -----------------------------------------------------------------------------
.PHONY: install-config
install-config:
@test -f "$(PULL_SECRET_FILE)" || { echo "Missing PULL_SECRET_FILE $(PULL_SECRET_FILE)"; exit 1; }
@test -f "$(SSH_PUB_KEY_FILE)" || { echo "Missing SSH_PUB_KEY_FILE $(SSH_PUB_KEY_FILE)"; exit 1; }
mkdir -p $(ASSETS_DIR)/cluster
cat > $(ASSETS_DIR)/cluster/install-config.yaml <<-EOF
apiVersion: v1
baseDomain: $(BASE_DOMAIN)
compute:
- hyperthreading: Enabled
name: worker
replicas: 2
controlPlane:
hyperthreading: Enabled
name: master
replicas: 3
metadata:
name: $(CLUSTER_NAME)
networking:
networkType: $(NETWORK_TYPE)
clusterNetwork:
- cidr: $(CLUSTER_NETWORK_CIDR)
hostPrefix: $(CLUSTER_NETWORK_HOSTPREFIX)
machineNetwork:
- cidr: $(MACHINE_NETWORK_CIDR)
serviceNetwork:
- $(SERVICE_NETWORK_CIDR)
platform:
none: {}
fips: false
pullSecret: '$(shell jq -c . $(PULL_SECRET_FILE))'
sshKey: '$(shell cat $(SSH_PUB_KEY_FILE))'
additionalTrustBundlePolicy: Proxyonly
EOF
@echo "install-config.yaml written (NETWORK_TYPE=$(NETWORK_TYPE))."
.PHONY: create-assets
create-assets:
cd $(ASSETS_DIR)/cluster
$(OPENSHIFT_INSTALL) create manifests --dir=$(ASSETS_DIR)/cluster
$(OPENSHIFT_INSTALL) create ignition-configs --dir=$(ASSETS_DIR)/cluster
cp -av $(ASSETS_DIR)/cluster/*.ign $(HTTP_CLUSTER_DIR)/
restorecon -Rv $(HTTP_CLUSTER_DIR)
# -----------------------------------------------------------------------------
# FCOS PXE artifacts
# -----------------------------------------------------------------------------
.PHONY: pxe-stage
pxe-stage:
cd $(FCOS_DIR)
podman run --rm --pull=always --security-opt label=disable \
-v $(FCOS_DIR):/data -w /data quay.io/coreos/coreos-installer:release \
download -s $(FCOS_STREAM) -f pxe
ln -sf *-live-kernel-x86_64 kernel-x86_64
ln -sf *-live-initramfs.x86_64.img initramfs.x86_64.img
ln -sf *-live-rootfs.x86_64.img rootfs.x86_64.img
mkdir -p $(HTTP_CLUSTER_DIR)/fcos
cp -av $(FCOS_DIR)/kernel-x86_64 $(FCOS_DIR)/initramfs.x86_64.img $(FCOS_DIR)/rootfs.x86_64.img $(HTTP_CLUSTER_DIR)/fcos/
restorecon -Rv $(HTTP_CLUSTER_DIR)
# -----------------------------------------------------------------------------
# iPXE stage: entrypoint + per-MAC mapping files + discovery
# -----------------------------------------------------------------------------
.PHONY: ipxe-stage
ipxe-stage:
mkdir -p $(PXE_HTTP_DIR) $(PXE_HTTP_DIR)/mac
# Discovery Ignition: FCOS live + SSH enabled for user "core"
@test -f "$(SSH_PUB_KEY_FILE)" || { echo "Missing SSH_PUB_KEY_FILE $(SSH_PUB_KEY_FILE) for discovery SSH access"; exit 1; }
cat > $(HTTP_CLUSTER_DIR)/discovery.ign <<-IGN
{
"ignition": { "version": "3.4.0" },
"passwd": {
"users": [
{
"name": "core",
"sshAuthorizedKeys": [ "$(shell cat $(SSH_PUB_KEY_FILE))" ]
}
]
}
}
IGN
restorecon -Rv $(HTTP_CLUSTER_DIR)
# index.ipxe
cat > $(PXE_HTTP_DIR)/index.ipxe <<-IPXE
#!ipxe
set base $(PROVISION_URL_PREFIX)/okd
chain \${base}/pxe/mac/\${net0/mac}.ipxe || chain \${base}/pxe/unknown.ipxe
IPXE
# Role-based legacy scripts (still available)
cat > $(PXE_HTTP_DIR)/bootstrap.ipxe <<-IPXE
#!ipxe
echo Bootstrapping cluster bootstrap (iPXE)
kernel $(PROVISION_URL_PREFIX)/okd/fcos/kernel-x86_64 initrd=initramfs \
ip=dhcp rd.neednet=1 ignition.firstboot ignition.platform.id=metal \
coreos.live.rootfs_url=$(PROVISION_URL_PREFIX)/okd/fcos/rootfs.x86_64.img \
coreos.inst.ignition_url=$(PROVISION_URL_PREFIX)/okd/bootstrap.ign
initrd $(PROVISION_URL_PREFIX)/okd/fcos/initramfs.x86_64.img
boot
IPXE
cat > $(PXE_HTTP_DIR)/master.ipxe <<-IPXE
#!ipxe
echo Bootstrapping cluster master (iPXE)
kernel $(PROVISION_URL_PREFIX)/okd/fcos/kernel-x86_64 initrd=initramfs \
ip=dhcp rd.neednet=1 ignition.firstboot ignition.platform.id=metal \
coreos.live.rootfs_url=$(PROVISION_URL_PREFIX)/okd/fcos/rootfs.x86_64.img \
coreos.inst.ignition_url=$(PROVISION_URL_PREFIX)/okd/master.ign
initrd $(PROVISION_URL_PREFIX)/okd/fcos/initramfs.x86_64.img
boot
IPXE
cat > $(PXE_HTTP_DIR)/worker.ipxe <<-IPXE
#!ipxe
echo Bootstrapping cluster worker (iPXE)
kernel $(PROVISION_URL_PREFIX)/okd/fcos/kernel-x86_64 initrd=initramfs \
ip=dhcp rd.neednet=1 ignition.firstboot ignition.platform.id=metal \
coreos.live.rootfs_url=$(PROVISION_URL_PREFIX)/okd/fcos/rootfs.x86_64.img \
coreos.inst.ignition_url=$(PROVISION_URL_PREFIX)/okd/worker.ign
initrd $(PROVISION_URL_PREFIX)/okd/fcos/initramfs.x86_64.img
boot
IPXE
# Discovery iPXE script
cat > $(PXE_HTTP_DIR)/discover.ipxe <<-IPXE
#!ipxe
echo Booting discovery environment (FCOS live, SSH enabled)
kernel $(PROVISION_URL_PREFIX)/okd/fcos/kernel-x86_64 initrd=initramfs \
ip=dhcp rd.neednet=1 ignition.firstboot ignition.platform.id=metal \
coreos.live.rootfs_url=$(PROVISION_URL_PREFIX)/okd/fcos/rootfs.x86_64.img \
ignition.config.url=$(PROVISION_URL_PREFIX)/okd/discovery.ign
initrd $(PROVISION_URL_PREFIX)/okd/fcos/initramfs.x86_64.img
boot
IPXE
# Unknown MAC fallback
cat > $(PXE_HTTP_DIR)/unknown.ipxe <<-IPXE
#!ipxe
echo Unknown MAC \${net0/mac}; falling back to worker
chain $(PROVISION_URL_PREFIX)/okd/pxe/worker.ipxe
IPXE
# Per-MAC mapping: prefer NODES inventory, else legacy okd.env mapping.
if [ -n "$(strip $(NODES))" ]; then \
echo "ipxe-stage: using node inventory (NODES=$(NODES))"; \
for n in $(NODES); do \
mac_var="$${n}_mac"; role_var="$${n}_role"; dev_var="$${n}_install_dev"; \
eval mac=\$${mac_var}; eval role=\$${role_var}; eval dev=\$${dev_var}; \
if [ -z "$$mac" ] || [ -z "$$role" ]; then \
echo " WARN: node $$n missing mac or role, skipping"; \
continue; \
fi; \
ign="$(PROVISION_URL_PREFIX)/okd/$$role.ign"; \
dev_arg=""; \
if [ -n "$$dev" ]; then \
dev_arg=" coreos.inst.install_dev=$$dev"; \
fi; \
f="$(PXE_HTTP_DIR)/mac/$$mac.ipxe"; \
echo " generating $$f for node=$$n role=$$role dev=$$dev"; \
cat > "$$f" <<-E
#!ipxe
echo Bootstrapping node $$n (role=$$role) install_dev=$$dev
kernel $(PROVISION_URL_PREFIX)/okd/fcos/kernel-x86_64 initrd=initramfs \\
ip=dhcp rd.neednet=1 ignition.firstboot ignition.platform.id=metal \\
coreos.live.rootfs_url=$(PROVISION_URL_PREFIX)/okd/fcos/rootfs.x86_64.img \\
coreos.inst.ignition_url=$$ign$$dev_arg
initrd $(PROVISION_URL_PREFIX)/okd/fcos/initramfs.x86_64.img
boot
E
done; \
else \
echo "ipxe-stage: no NODES inventory; you may still use legacy mapping from node.env if defined."; \
fi
restorecon -Rv $(PXE_HTTP_DIR)
# -----------------------------------------------------------------------------
# iPXE discovery maps
# -----------------------------------------------------------------------------
.PHONY: ipxe-discovery-maps
ipxe-discovery-maps:
@if [ -z "$(strip $(NODES))" ]; then \
echo "ipxe-discovery-maps: NODES is empty (nodes.mk missing or no nodes defined)."; \
exit 1; \
fi
@for n in $(NODES); do \
mac_var="$${n}_mac"; eval mac=\$${mac_var}; \
if [ -z "$$mac" ]; then \
echo " WARN: node $$n has no _mac defined, skipping"; \
continue; \
fi; \
f="$(PXE_HTTP_DIR)/mac/$$mac.ipxe"; \
echo " discovery mapping $$n ($$mac) -> discover.ipxe"; \
cat > "$$f" <<-E
#!ipxe
echo Discovery boot for node $$n (MAC $$mac)
chain $(PROVISION_URL_PREFIX)/okd/pxe/discover.ipxe
E
done
restorecon -Rv $(PXE_HTTP_DIR)
# -----------------------------------------------------------------------------
# raid-discovery: SSH into discovery env and suggest *_install_dev values
# -----------------------------------------------------------------------------
.PHONY: raid-discovery
raid-discovery:
@if [ -z "$(strip $(NODES))" ]; then \
echo "raid-discovery: NODES is empty (nodes.mk missing or no nodes defined)."; \
exit 1; \
fi
@echo "# Suggested *_install_dev assignments based on discovery environment:"
@for n in $(NODES); do \
ssh_host_var="$${n}_ssh_host"; \
eval ssh_host=\$${ssh_host_var}; \
if [ -z "$$ssh_host" ]; then ssh_host="$$n"; fi; \
echo "# Node $$n: probing $$ssh_host ..."; \
dev_path=$$(ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
core@$$ssh_host ' \
disk=$$(lsblk -dn -o NAME,TYPE,SIZE | awk '"'"'$2=="disk"{print $1,$3}'"'"' | sort -k2 -h | tail -1 | cut -d" " -f1); \
if [ -z "$$disk" ]; then \
echo ""; \
exit 0; \
fi; \
id=$$(ls -1 /dev/disk/by-id | grep "$$disk$$" | head -1); \
if [ -z "$$id" ]; then \
echo ""; \
exit 0; \
fi; \
echo "/dev/disk/by-id/$$id" \
' || true); \
if [ -z "$$dev_path" ]; then \
echo "# WARN: could not determine install device for $$n (ssh or lsblk failed)"; \
else \
echo "$${n}_install_dev := $$dev_path"; \
fi; \
done
# -----------------------------------------------------------------------------
# pxe-up (IPv6 DHCPv6 only)
# -----------------------------------------------------------------------------
.PHONY: pxe-up
pxe-up: pxe-dnsmasq pxe-httpd
.PHONY: pxe-dnsmasq
pxe-dnsmasq:
@if [ "$(NETWORK_STACK)" != "ipv6" ]; then \
echo "pxe-dnsmasq: IPv6-specific (DHCPv6). Skipped because NETWORK_STACK=$(NETWORK_STACK)."; \
exit 0; \
fi
@if [ -f "$(DNSMASQ_FILE)" ] && [ ! -f "$(DNSMASQ_BAK)" ]; then cp -a "$(DNSMASQ_FILE)" "$(DNSMASQ_BAK)"; fi
install -d -m 0755 $$(dirname "$(DNSMASQ_FILE)")
cat > $(DNSMASQ_FILE) <<-EOF
# Cluster iPXE over HTTP (DHCPv6), static DUID/IAID reservations
interface=$(DHCP_INTERFACE)
bind-interfaces
dhcp-range=$(DHCP_RANGE_START),$(DHCP_RANGE_END),64,12h
# Static reservations (DUID/IAID -> fixed IPv6 + hostname)
$(if $(BOOTSTRAP_DUID),dhcp-host=$(BOOTSTRAP_IAID),id:$(BOOTSTRAP_DUID),[$(BOOTSTRAP_IP6)],bootstrap,infinite)
$(if $(MASTER0_DUID),dhcp-host=$(MASTER0_IAID),id:$(MASTER0_DUID),[$(MASTER0_IP6)],master0,infinite)
$(if $(MASTER1_DUID),dhcp-host=$(MASTER1_IAID),id:$(MASTER1_DUID),[$(MASTER1_IP6)],master1,infinite)
$(if $(MASTER2_DUID),dhcp-host=$(MASTER2_IAID),id:$(MASTER2_DUID),[$(MASTER2_IP6)],master2,infinite)
$(if $(WORKER0_DUID),dhcp-host=$(WORKER0_IAID),id:$(WORKER0_DUID),[$(WORKER0_IP6)],worker0,infinite)
$(if $(WORKER1_DUID),dhcp-host=$(WORKER1_IAID),id:$(WORKER1_DUID),[$(WORKER1_IP6)],worker1,infinite)
# All PXE clients get the same iPXE entry script; that script selects role by MAC
dhcp-option=option6:bootfile-url,[http://[$(INSTALL_HOST_IP6)]/okd/pxe/index.ipxe]
EOF
systemctl enable --now dnsmasq
systemctl status --no-pager dnsmasq || true
.PHONY: pxe-httpd
pxe-httpd:
systemctl enable --now httpd
@echo "Ignitions: $(PROVISION_URL_PREFIX)/okd/{bootstrap,master,worker}.ign"
@echo "Discovery Ignition: $(PROVISION_URL_PREFIX)/okd/discovery.ign"
@echo "iPXE entry: $(PROVISION_URL_PREFIX)/okd/pxe/index.ipxe"
# -----------------------------------------------------------------------------
# radvd (IPv6 only)
# -----------------------------------------------------------------------------
.PHONY: radvd-up
radvd-up:
@if [ "$(NETWORK_STACK)" != "ipv6" ]; then \
echo "radvd-up: skipped (NETWORK_STACK=$(NETWORK_STACK), IPv6 only)."; \
exit 0; \
fi
@if [ -f "$(RADVD_FILE)" ] && [ ! -f "$(RADVD_BAK)" ]; then cp -a "$(RADVD_FILE)" "$(RADVD_BAK)"; fi
cat > $(RADVD_FILE) <<-EOF
interface $(DHCP_INTERFACE)
{
AdvSendAdvert on;
MaxRtrAdvInterval 30;
AdvManagedFlag off;
AdvOtherConfigFlag on;
prefix $(MACHINE_NETWORK_CIDR)
{
AdvOnLink on;
AdvAutonomous on;
};
RDNSS $(INSTALL_HOST_IP6)
{
AdvRDNSSLifetime 600;
};
};
EOF
systemctl enable --now radvd
systemctl status --no-pager radvd || true
# -----------------------------------------------------------------------------
# DNS (IPv6-only ip6.arpa helper)
# -----------------------------------------------------------------------------
.PHONY: dns-generate
dns-generate:
@if [ "$(NETWORK_STACK)" != "ipv6" ]; then \
echo "dns-generate: IPv6-only implementation (ip6.arpa). Skipped because NETWORK_STACK=$(NETWORK_STACK)."; \
exit 0; \
fi
@if [ -f "$(FWD_ZONE_FILE)" ] && [ ! -f "$(FWD_ZONE_BAK)" ]; then cp -a "$(FWD_ZONE_FILE)" "$(FWD_ZONE_BAK)"; fi
@if [ -f "$(REV_ZONE_FILE)" ] && [ ! -f "$(REV_ZONE_BAK)" ]; then cp -a "$(REV_ZONE_FILE)" "$(REV_ZONE_BAK)"; fi
install -d -m 0755 $(NAMED_DIR)
cat > $(FWD_ZONE_FILE) <<-EOF
\$TTL 60
@ IN SOA ns1.$(CLUSTER_NAME).$(BASE_DOMAIN). hostmaster.$(BASE_DOMAIN). (
$$(date +%Y%m%d%H) ; serial
60 ; refresh
30 ; retry
120 ; expire
60) ; minimum
IN NS ns1.$(CLUSTER_NAME).$(BASE_DOMAIN).
ns1 IN AAAA $(INSTALL_HOST_IP6)
api IN AAAA $(API_VIP6)
*.apps IN AAAA $(APPS_VIP6)
bootstrap IN AAAA $(BOOTSTRAP_IP6)
master0 IN AAAA $(MASTER0_IP6)
master1 IN AAAA $(MASTER1_IP6)
master2 IN AAAA $(MASTER2_IP6)
worker0 IN AAAA $(WORKER0_IP6)
worker1 IN AAAA $(WORKER1_IP6)
EOF
PREFIX_HEX=$$(echo "$(MACHINE_NETWORK_CIDR)" | cut -d/ -f1 | sed 's/://g' | tr '[:lower:]' '[:upper:]')
PREFIX_LEN=$$(echo "$(MACHINE_NETWORK_CIDR)" | cut -d/ -f2)
@if [ "$$PREFIX_LEN" != "48" ]; then echo "dns-generate expects /48, got /$$PREFIX_LEN"; exit 1; fi
ZONE_SUFFIX=$$(echo "$$PREFIX_HEX" | cut -c1-12 | rev | sed 's/./&./g')ip6.arpa
cat > $(REV_ZONE_FILE) <<-EOF
\$TTL 60
@ IN SOA ns1.$(CLUSTER_NAME).$(BASE_DOMAIN). hostmaster.$(BASE_DOMAIN). (
$$(date +%Y%m%d%H) ; serial
60 ; refresh
30 ; retry
120 ; expire
60) ; minimum
IN NS ns1.$(CLUSTER_NAME).$(BASE_DOMAIN).
EOF
emit_ptr() { ip=$$1; host=$$2; \
full=$$(python3 - <<PY
import ipaddress,sys
print(ipaddress.IPv6Address(sys.argv[1]).exploded)
PY
"$$ip"); \
hex=$$(echo $$full | tr -d ':' | tr '[:lower:]' '[:upper:]'); \
tail=$$(echo $$hex | cut -c13-32 | rev | sed 's/./&./g'); \
echo "$$tail IN PTR $$host.$(CLUSTER_NAME).$(BASE_DOMAIN)." >> $(REV_ZONE_FILE); }
emit_ptr "$(BOOTSTRAP_IP6)" "bootstrap"
emit_ptr "$(MASTER0_IP6)" "master0"
emit_ptr "$(MASTER1_IP6)" "master1"
emit_ptr "$(MASTER2_IP6)" "master2"
emit_ptr "$(WORKER0_IP6)" "worker0"
emit_ptr "$(WORKER1_IP6)" "worker1"
@echo "Forward: $(FWD_ZONE_FILE)"
@echo "Reverse: $(REV_ZONE_FILE)"
.PHONY: dns-up
dns-up:
@if [ "$(NETWORK_STACK)" != "ipv6" ]; then \
echo "dns-up: IPv6-only named.conf helper (ip6.arpa). Skipped because NETWORK_STACK=$(NETWORK_STACK)."; \
exit 0; \
fi
@if [ -f "$(NAMED_CONF)" ] && [ ! -f "$(NAMED_CONF_BAK)" ]; then cp -a "$(NAMED_CONF)" "$(NAMED_CONF_BAK)"; fi
cat > $(NAMED_CONF) <<-EOF
options {
directory "$(NAMED_DIR)";
listen-on-v6 { any; };
allow-query { any; };
};
zone "$(CLUSTER_NAME).$(BASE_DOMAIN)" IN {
type master; file "$(FWD_ZONE_FILE)"; allow-update { none; };
};
zone "$(shell HEX=$$(echo "$(MACHINE_NETWORK_CIDR)"|cut -d/ -f1|sed 's/://g'|tr a-z A-Z); echo $$HEX|cut -c1-12|rev|sed 's/./&./g')ip6.arpa" IN {
type master; file "$(REV_ZONE_FILE)"; allow-update { none; };
};
EOF
restorecon -Rv $(NAMED_DIR) $(NAMED_CONF)
systemctl enable --now named
systemctl status --no-pager named || true
# -----------------------------------------------------------------------------
# NAT66 + svc-allow-bootstrap (IPv6-only)
# -----------------------------------------------------------------------------
.PHONY: nat66-up
nat66-up:
@if [ "$(NETWORK_STACK)" != "ipv6" ]; then \
echo "nat66-up: NAT66 is IPv6-only. Skipped because NETWORK_STACK=$(NETWORK_STACK)."; \
exit 0; \
fi
sysctl -w net.ipv6.conf.all.forwarding=1
sysctl -w net.ipv6.conf.default.forwarding=1
@if [ -f "$(NFT_MAIN_CONF)" ] && [ ! -f "$(NFT_MAIN_BAK)" ]; then cp -a "$(NFT_MAIN_CONF)" "$(NFT_MAIN_BAK)"; fi
@if [ -f "$(NFT_CLUSTER_FILE)" ] && [ ! -f "$(NFT_CLUSTER_BAK)" ]; then cp -a "$(NFT_CLUSTER_FILE)" "$(NFT_CLUSTER_BAK)"; fi
cat > $(NFT_CLUSTER_FILE) <<-EOF
table ip6 $(NFT_CLUSTER_TABLE) {
chain POSTROUTING {
type nat hook postrouting priority srcnat; policy accept;
oifname "$(NAT66_UPLINK)" masquerade
}
}
EOF
if ! grep -qF 'include "$(NFT_CLUSTER_FILE)"' "$(NFT_MAIN_CONF)"; then
cat >> "$(NFT_MAIN_CONF)" <<-EOF
include "$(NFT_CLUSTER_FILE)"
EOF
fi
systemctl reload nftables || systemctl restart nftables
nft list tables ip6 | sed -n '1,120p'
.PHONY: svc-allow-bootstrap
svc-allow-bootstrap:
@if [ "$(NETWORK_STACK)" != "ipv6" ]; then \
echo "svc-allow-bootstrap: IPv6-only helper (ip6 tables). Skipped because NETWORK_STACK=$(NETWORK_STACK)."; \
exit 0; \
fi
@if [ -f "$(NFT_SVC_FILE)" ] && [ ! -f "$(NFT_SVC_BAK)" ]; then cp -a "$(NFT_SVC_FILE)" "$(NFT_SVC_BAK)"; fi
cat > $(NFT_SVC_FILE) <<-EOF
table ip6 $(NFT_SVC_TABLE) {
set okd_ports { type inet_service; flags interval; elements = { 80, 443, 6443, 22623 } }
chain INPUT {
type filter hook input priority 0; policy accept;
ip6 saddr $(MACHINE_NETWORK_CIDR) tcp dport @okd_ports accept
}
}
EOF
if ! grep -qF 'include "$(NFT_SVC_FILE)"' "$(NFT_MAIN_CONF)"; then
cat >> "$(NFT_MAIN_CONF)" <<-EOF
include "$(NFT_SVC_FILE)"
EOF
fi
systemctl reload nftables || systemctl restart nftables
nft list tables ip6 | sed -n '1,200p'
.PHONY: svc-allow-bootstrap-down
svc-allow-bootstrap-down:
if nft list tables ip6 | grep -q '^table ip6 $(NFT_SVC_TABLE)$$'; then
nft delete table ip6 $(NFT_SVC_TABLE) || true
fi
if [ -f "$(NFT_MAIN_CONF)" ]; then
sed -i '\|include "$(NFT_SVC_FILE)"|d' "$(NFT_MAIN_CONF)" || true
fi
@if [ -f "$(NFT_SVC_BAK)" ]; then cp -af "$(NFT_SVC_BAK)" "$(NFT_SVC_FILE)"; else rm -f "$(NFT_SVC_FILE)"; fi
systemctl reload nftables || systemctl restart nftables || true
@echo "Bootstrap svc-allow rules removed."
# -----------------------------------------------------------------------------
# Bootstrap local VM
# -----------------------------------------------------------------------------
.PHONY: bootstrap-host-up
bootstrap-host-up:
qemu-img create -f qcow2 $(ASSETS_DIR)/bootstrap.qcow2 40G
qemu-system-x86_64 \
-enable-kvm -m 8192 -smp 4 -name $(BOOTSTRAP_VM) -cpu host \
-drive file=$(ASSETS_DIR)/bootstrap.qcow2,if=virtio \
-netdev user,id=net0,ipv6=on -device virtio-net-pci,netdev=net0 \
-kernel $(FCOS_DIR)/kernel-x86_64 \
-initrd $(FCOS_DIR)/initramfs.x86_64.img \
-append "ip=dhcp rd.neednet=1 ignition.firstboot ignition.platform.id=metal coreos.live.rootfs_url=$(PROVISION_URL_PREFIX)/okd/fcos/rootfs.x86_64.img coreos.inst.ignition_url=$(PROVISION_URL_PREFIX)/okd/bootstrap.ign console=ttyS0" \
-nographic & echo $$! > $(ASSETS_DIR)/bootstrap.pid
@echo "Bootstrap VM started (PID $$(cat $(ASSETS_DIR)/bootstrap.pid))."
.PHONY: bootstrap-host-down
bootstrap-host-down:
@if [ -f "$(ASSETS_DIR)/bootstrap.pid" ]; then kill $$(cat $(ASSETS_DIR)/bootstrap.pid) || true; rm -f $(ASSETS_DIR)/bootstrap.pid; fi
@echo "Bootstrap VM stopped."
# -----------------------------------------------------------------------------
# Wait for bootstrap
# -----------------------------------------------------------------------------
.PHONY: wait-bootstrap
wait-bootstrap:
@echo "Waiting for bootstrap-complete ..."
$(OPENSHIFT_INSTALL) --dir=$(ASSETS_DIR)/cluster wait-for bootstrap-complete --log-level=info
@echo "Bootstrap complete. Tearing down bootstrap service rules (if IPv6)."
-$(MAKE) svc-allow-bootstrap-down
# -----------------------------------------------------------------------------
# Cilium helpers (gated by ENABLE_CILIUM)
# -----------------------------------------------------------------------------
.PHONY: cilium-olm
cilium-olm:
@if [ "$(ENABLE_CILIUM)" != "1" ]; then \
echo "cilium-olm: disabled (ENABLE_CILIUM=$(ENABLE_CILIUM))."; \
exit 0; \
fi
cat > $(ASSETS_DIR)/cilium-olm.yaml <<-YAML
apiVersion: v1
kind: Namespace
metadata: { name: cilium }
---
apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata: { name: cilium-og, namespace: cilium }
spec: { targetNamespaces: [ "cilium" ] }
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata: { name: cilium-enterprise, namespace: cilium }
spec:
channel: stable
name: cilium-olm
source: redhat-operators
sourceNamespace: openshift-marketplace
YAML
$(OC) apply -f $(ASSETS_DIR)/cilium-olm.yaml
.PHONY: cilium-migrate
cilium-migrate:
@if [ "$(ENABLE_CILIUM)" != "1" ]; then \
echo "cilium-migrate: disabled (ENABLE_CILIUM=$(ENABLE_CILIUM))."; \
exit 0; \
fi
@echo "== Cilium migration (review vendor docs to confirm steps/versions) =="
$(OC) -n openshift-network-operator scale deploy network-operator --replicas=0 || true
cat > $(ASSETS_DIR)/network.operator.patch.yaml <<-YAML
apiVersion: operator.openshift.io/v1
kind: Network
metadata:
name: cluster
spec:
defaultNetwork:
type: Other
disableMultiNetwork: true
YAML
$(OC) apply -f $(ASSETS_DIR)/network.operator.patch.yaml || true
cat > $(ASSETS_DIR)/ciliumconfig.yaml <<-YAML
apiVersion: cilium.io/v1alpha1
kind: CiliumConfig
metadata:
name: cilium
namespace: cilium
spec:
cluster:
name: $(CLUSTER_NAME)
cni:
exclusive: true
ipam:
mode: kubernetes
k8sServiceHost: api.$(CLUSTER_NAME).$(BASE_DOMAIN)
k8sServicePort: 6443
routingMode: tunnel
enableIPv6: true
enableIPv4: false
kubeProxyReplacement: strict
YAML
$(OC) apply -f $(ASSETS_DIR)/ciliumconfig.yaml
@echo "Check cilium pods and cluster networking per vendor guidance."
# -----------------------------------------------------------------------------
# Bonding helper
# -----------------------------------------------------------------------------
.PHONY: bond-howto
bond-howto:
cat > /root/postinstall_bond.sh <<-EOS
#!/usr/bin/bash -eu
nmcli con add type bond con-name bond0 ifname bond0 mode 802.3ad
nmcli con modify bond0 bond.options "mode=802.3ad,miimon=100,xmit_hash_policy=layer2+3"
for i in 1 2 3 4; do nmcli con add type ethernet con-name bond0-slave\${i} ifname eno\${i} master bond0; done
nmcli con show bond0
EOS
chmod +x /root/postinstall_bond.sh
@echo "Wrote /root/postinstall_bond.sh"
# -----------------------------------------------------------------------------
# HPE iLO helpers (raw)
# -----------------------------------------------------------------------------
.PHONY: ilo-show-nodes
ilo-show-nodes:
@if [ -z "$(strip $(NODES))" ]; then \
echo "ilo-show-nodes: no NODES defined (nodes.mk missing or empty)."; \
exit 1; \
fi
@for n in $(NODES); do \
ilo_ip_var="$${n}_ilo_ip"; ilo_user_var="$${n}_ilo_user"; role_var="$${n}_role"; dev_var="$${n}_install_dev"; mac_var="$${n}_mac"; \
eval ilo_ip=\$${ilo_ip_var}; eval ilo_user=\$${ilo_user_var}; eval role=\$${role_var}; eval dev=\$${dev_var}; eval mac=\$${mac_var}; \
if [ -z "$$ilo_ip" ]; then \
echo "$$n: role=$$role mac=$$mac install_dev=$$dev (no iLO IP set)"; \
else \
if [ -z "$$ilo_user" ]; then ilo_user="$(ILO_DEFAULT_USER)"; fi; \
echo "$$n: role=$$role mac=$$mac install_dev=$$dev iLO=$$ilo_ip user=$$ilo_user"; \
fi; \
done
.PHONY: ilo-power-on-% ilo-power-off-% ilo-reset-% ilo-set-pxe-%
ilo-power-on-%:
@node="$*"; \
ilo_ip_var="$${node}_ilo_ip"; ilo_user_var="$${node}_ilo_user"; \
eval ilo_ip=\$${ilo_ip_var}; eval ilo_user=\$${ilo_user_var}; \
if [ -z "$$ilo_ip" ]; then \
echo "ilo-power-on-$$node: missing $${node}_ilo_ip"; exit 1; \
fi; \
if [ -z "$$ilo_user" ]; then ilo_user="$(ILO_DEFAULT_USER)"; fi; \
echo "ilo-power-on-$$node: $$ilo_user@$$ilo_ip"; \
ssh "$$ilo_user@$$ilo_ip" "power on"
ilo-power-off-%:
@node="$*"; \
ilo_ip_var="$${node}_ilo_ip"; ilo_user_var="$${node}_ilo_user"; \
eval ilo_ip=\$${ilo_ip_var}; eval ilo_user=\$${ilo_user_var}; \
if [ -z "$$ilo_ip" ]; then \
echo "ilo-power-off-$$node: missing $${node}_ilo_ip"; exit 1; \
fi; \
if [ -z "$$ilo_user" ]; then ilo_user="$(ILO_DEFAULT_USER)"; fi; \
echo "ilo-power-off-$$node: $$ilo_user@$$ilo_ip"; \
ssh "$$ilo_user@$$ilo_ip" "power off force"
ilo-reset-%:
@node="$*"; \
ilo_ip_var="$${node}_ilo_ip"; ilo_user_var="$${node}_ilo_user"; \
eval ilo_ip=\$${ilo_ip_var}; eval ilo_user=\$${ilo_user_var}; \
if [ -z "$$ilo_ip" ]; then \
echo "ilo-reset-$$node: missing $${node}_ilo_ip"; exit 1; \
fi; \
if [ -z "$$ilo_user" ]; then ilo_user="$(ILO_DEFAULT_USER)"; fi; \
echo "ilo-reset-$$node: $$ilo_user@$$ilo_ip"; \
ssh "$$ilo_user@$$ilo_ip" "power reset"
ilo-set-pxe-%:
@node="$*"; \
ilo_ip_var="$${node}_ilo_ip"; ilo_user_var="$${node}_ilo_user"; \
eval ilo_ip=\$${ilo_ip_var}; eval ilo_user=\$${ilo_user_var}; \
if [ -z "$$ilo_ip" ]; then \
echo "ilo-set-pxe-$$node: missing $${node}_ilo_ip"; exit 1; \
fi; \
if [ -z "$$ilo_user" ]; then ilo_user="$(ILO_DEFAULT_USER)"; fi; \
echo "ilo-set-pxe-$$node (one-time network boot): $$ilo_user@$$ilo_ip"; \
ssh "$$ilo_user@$$ilo_ip" "echo 'set /system1/bootconfig1/bootsource1 bootorder=network' | some_ilo_cli_placeholder"
# -----------------------------------------------------------------------------
# DRAC helpers (raw, cluster-global)
# -----------------------------------------------------------------------------
DRAC_HOST ?=
.PHONY: drac-power-on drac-power-off drac-reset drac-set-pxe
drac-power-on:
@if [ -z "$(DRAC_HOST)" ]; then echo "DRAC_HOST not set"; exit 1; fi
@echo "DRAC power on: $(DRAC_USER)@$(DRAC_HOST)"
@ssh $(DRAC_USER)@$(DRAC_HOST) "racadm serveraction powerup"
drac-power-off:
@if [ -z "$(DRAC_HOST)" ]; then echo "DRAC_HOST not set"; exit 1; fi
@echo "DRAC power off: $(DRAC_USER)@$(DRAC_HOST)"
@ssh $(DRAC_USER)@$(DRAC_HOST) "racadm serveraction powerdown"
drac-reset:
@if [ -z "$(DRAC_HOST)" ]; then echo "DRAC_HOST not set"; exit 1; fi
@echo "DRAC reset: $(DRAC_USER)@$(DRAC_HOST)"
@ssh $(DRAC_USER)@$(DRAC_HOST) "racadm serveraction powercycle"
drac-set-pxe:
@if [ -z "$(DRAC_HOST)" ]; then echo "DRAC_HOST not set"; exit 1; fi
@echo "DRAC one-time PXE boot: $(DRAC_USER)@$(DRAC_HOST)"
@ssh $(DRAC_USER)@$(DRAC_HOST) "racadm config -g cfgServerInfo -o cfgServerBootOnce 1"
@ssh $(DRAC_USER)@$(DRAC_HOST) "racadm config -g cfgServerInfo -o cfgServerFirstBootDevice PXE"
# -----------------------------------------------------------------------------
# Generic node-level management (MGMT_TYPE=ilo|drac)
# -----------------------------------------------------------------------------
.PHONY: node-power-on-% node-power-off-% node-reset-% node-set-pxe-%
node-power-on-%:
@node="$*"; \
if [ "$(MGMT_TYPE)" = "ilo" ]; then \
ilo_ip_var="$${node}_ilo_ip"; ilo_user_var="$${node}_ilo_user"; \
eval ilo_ip=\$${ilo_ip_var}; eval ilo_user=\$${ilo_user_var}; \
if [ -z "$$ilo_ip" ]; then echo "node-power-on-$$node: missing $${node}_ilo_ip"; exit 1; fi; \
if [ -z "$$ilo_user" ]; then ilo_user="$(ILO_DEFAULT_USER)"; fi; \
echo "node-power-on-$$node via iLO: $$ilo_user@$$ilo_ip"; \
ssh "$$ilo_user@$$ilo_ip" "power on"; \
elif [ "$(MGMT_TYPE)" = "drac" ]; then \
drac_host_var="$${node}_drac_host"; drac_user_var="$${node}_drac_user"; \
eval drac_host=\$${drac_host_var}; eval drac_user=\$${drac_user_var}; \
if [ -z "$$drac_host" ]; then echo "node-power-on-$$node: missing $${node}_drac_host"; exit 1; fi; \
if [ -z "$$drac_user" ]; then drac_user="$(DRAC_USER)"; fi; \
echo "node-power-on-$$node via DRAC: $$drac_user@$$drac_host"; \
ssh "$$drac_user@$$drac_host" "racadm serveraction powerup"; \
else \
echo "node-power-on-$$node: unsupported MGMT_TYPE=$(MGMT_TYPE) (expected ilo or drac)"; \
exit 1; \
fi
node-power-off-%:
@node="$*"; \
if [ "$(MGMT_TYPE)" = "ilo" ]; then \
ilo_ip_var="$${node}_ilo_ip"; ilo_user_var="$${node}_ilo_user"; \
eval ilo_ip=\$${ilo_ip_var}; eval ilo_user=\$${ilo_user_var}; \
if [ -z "$$ilo_ip" ]; then echo "node-power-off-$$node: missing $${node}_ilo_ip"; exit 1; fi; \
if [ -z "$$ilo_user" ]; then ilo_user="$(ILO_DEFAULT_USER)"; fi; \
echo "node-power-off-$$node via iLO: $$ilo_user@$$ilo_ip"; \
ssh "$$ilo_user@$$ilo_ip" "power off force"; \
elif [ "$(MGMT_TYPE)" = "drac" ]; then \
drac_host_var="$${node}_drac_host"; drac_user_var="$${node}_drac_user"; \
eval drac_host=\$${drac_host_var}; eval drac_user=\$${drac_user_var}; \
if [ -z "$$drac_host" ]; then echo "node-power-off-$$node: missing $${node}_drac_host"; exit 1; fi; \
if [ -z "$$drac_user" ]; then drac_user="$(DRAC_USER)"; fi; \
echo "node-power-off-$$node via DRAC: $$drac_user@$$drac_host"; \
ssh "$$drac_user@$$drac_host" "racadm serveraction powerdown"; \
else \
echo "node-power-off-$$node: unsupported MGMT_TYPE=$(MGMT_TYPE)"; \
exit 1; \
fi
node-reset-%:
@node="$*"; \
if [ "$(MGMT_TYPE)" = "ilo" ]; then \
ilo_ip_var="$${node}_ilo_ip"; ilo_user_var="$${node}_ilo_user"; \
eval ilo_ip=\$${ilo_ip_var}; eval ilo_user=\$${ilo_user_var}; \
if [ -z "$$ilo_ip" ]; then echo "node-reset-$$node: missing $${node}_ilo_ip"; exit 1; fi; \
if [ -z "$$ilo_user" ]; then ilo_user="$(ILO_DEFAULT_USER)"; fi; \
echo "node-reset-$$node via iLO: $$ilo_user@$$ilo_ip"; \
ssh "$$ilo_user@$$ilo_ip" "power reset"; \
elif [ "$(MGMT_TYPE)" = "drac" ]; then \
drac_host_var="$${node}_drac_host"; drac_user_var="$${node}_drac_user"; \
eval drac_host=\$${drac_host_var}; eval drac_user=\$${drac_user_var}; \
if [ -z "$$drac_host" ]; then echo "node-reset-$$node: missing $${node}_drac_host"; exit 1; fi; \
if [ -z "$$drac_user" ]; then drac_user="$(DRAC_USER)"; fi; \
echo "node-reset-$$node via DRAC: $$drac_user@$$drac_host"; \
ssh "$$drac_user@$$drac_host" "racadm serveraction powercycle"; \
else \
echo "node-reset-$$node: unsupported MGMT_TYPE=$(MGMT_TYPE)"; \
exit 1; \
fi
node-set-pxe-%:
@node="$*"; \
if [ "$(MGMT_TYPE)" = "ilo" ]; then \
ilo_ip_var="$${node}_ilo_ip"; ilo_user_var="$${node}_ilo_user"; \
eval ilo_ip=\$${ilo_ip_var}; eval ilo_user=\$${ilo_user_var}; \
if [ -z "$$ilo_ip" ]; then echo "node-set-pxe-$$node: missing $${node}_ilo_ip"; exit 1; fi; \
if [ -z "$$ilo_user" ]; then ilo_user="$(ILO_DEFAULT_USER)"; fi; \
echo "node-set-pxe-$$node via iLO (one-time network boot): $$ilo_user@$$ilo_ip"; \
ssh "$$ilo_user@$$ilo_ip" "echo 'set /system1/bootconfig1/bootsource1 bootorder=network' | some_ilo_cli_placeholder"; \
elif [ "$(MGMT_TYPE)" = "drac" ]; then \
drac_host_var="$${node}_drac_host"; drac_user_var="$${node}_drac_user"; \
eval drac_host=\$${drac_host_var}; eval drac_user=\$${drac_user_var}; \
if [ -z "$$drac_host" ]; then echo "node-set-pxe-$$node: missing $${node}_drac_host"; exit 1; fi; \
if [ -z "$$drac_user" ]; then drac_user="$(DRAC_USER)"; fi; \
echo "node-set-pxe-$$node via DRAC (one-time PXE boot): $$drac_user@$$drac_host"; \
ssh "$$drac_user@$$drac_host" "racadm config -g cfgServerInfo -o cfgServerBootOnce 1"; \
ssh "$$drac_user@$$drac_host" "racadm config -g cfgServerInfo -o cfgServerFirstBootDevice PXE"; \
else \
echo "node-set-pxe-$$node: unsupported MGMT_TYPE=$(MGMT_TYPE)"; \
exit 1; \
fi
# -----------------------------------------------------------------------------
# Teardown
# -----------------------------------------------------------------------------
.PHONY: teardown
teardown:
systemctl disable --now dnsmasq || true
systemctl disable --now httpd || true
systemctl disable --now radvd || true
systemctl disable --now named || true
-$(MAKE) bootstrap-host-down
if nft list tables ip6 | grep -q '^table ip6 $(NFT_SVC_TABLE)$$'; then nft delete table ip6 $(NFT_SVC_TABLE) || true; fi
if nft list tables ip6 | grep -q '^table ip6 $(NFT_CLUSTER_TABLE)$$'; then nft delete table ip6 $(NFT_CLUSTER_TABLE) || true; fi
if [ -f "$(NFT_MAIN_CONF)" ]; then \
sed -i '\|include "$(NFT_SVC_FILE)"|d' "$(NFT_MAIN_CONF)" || true; \
sed -i '\|include "$(NFT_CLUSTER_FILE)"|d' "$(NFT_MAIN_CONF)" || true; \
fi
@if [ -f "$(DNSMASQ_BAK)" ]; then cp -af "$(DNSMASQ_BAK)" "$(DNSMASQ_FILE)"; else rm -f "$(DNSMASQ_FILE)"; fi
@if [ -f "$(RADVD_BAK)" ]; then cp -af "$(RADVD_BAK)" "$(RADVD_FILE)"; else rm -f "$(RADVD_FILE)"; fi
@if [ -f "$(NFT_CLUSTER_BAK)" ]; then cp -af "$(NFT_CLUSTER_BAK)" "$(NFT_CLUSTER_FILE)"; else rm -f "$(NFT_CLUSTER_FILE)"; fi
@if [ -f "$(NFT_SVC_BAK)" ]; then cp -af "$(NFT_SVC_BAK)" "$(NFT_SVC_FILE)"; else rm -f "$(NFT_SVC_FILE)"; fi
@if [ -f "$(NAMED_CONF_BAK)" ]; then cp -af "$(NAMED_CONF_BAK)" "$(NAMED_CONF)"; fi
@if [ -f "$(FWD_ZONE_BAK)" ]; then cp -af "$(FWD_ZONE_BAK)" "$(FWD_ZONE_FILE)"; else rm -f "$(FWD_ZONE_FILE)"; fi
@if [ -f "$(REV_ZONE_BAK)" ]; then cp -af "$(REV_ZONE_BAK)" "$(REV_ZONE_FILE)"; else rm -f "$(REV_ZONE_FILE)"; fi
systemctl reload nftables || systemctl restart nftables || true
@echo "Teardown complete."
# -----------------------------------------------------------------------------
# Convenience
# -----------------------------------------------------------------------------
.PHONY: all
all: prereqs okd-download install-config create-assets pxe-stage ipxe-stage pxe-up radvd-up dns-generate dns-up nat66-up svc-allow-bootstrap
# -----------------------------------------------------------------------------
# Cluster environment (generic, vendor-agnostic)
# -----------------------------------------------------------------------------
# Cluster identity
CLUSTER_NAME=hp-ocp
BASE_DOMAIN=lab.example.com
# Network stack: ipv4 or ipv6
NETWORK_STACK=ipv4
# CNI
NETWORK_TYPE=OVNKubernetes
# Machine & service networks (IPv4 example)
MACHINE_NETWORK_CIDR=192.168.90.0/24
CLUSTER_NETWORK_CIDR=10.128.0.0/14
CLUSTER_NETWORK_HOSTPREFIX=23
SERVICE_NETWORK_CIDR=172.30.0.0/16
# IPv6 placeholders (used only if NETWORK_STACK=ipv6)
MACHINE_NETWORK_IPV6=2602:f6ae:aa::/48
CLUSTER_NETWORK_IPV6=fd00:10:64::/48
SERVICE_NETWORK_IPV6=fd00:10:96::/112
# VIPs
API_VIP4=192.168.90.10
APPS_VIP4=192.168.90.11
API_VIP6=2602:f6ae:aa::50
APPS_VIP6=2602:f6ae:aa::60
# Provisioning host (this machine’s IP on the provisioning network)
# For IPv4 clusters:
PROVISION_HOST=192.168.90.5
# For IPv6 clusters, you’d use:
# PROVISION_HOST=2602:f6ae:aa::10
# HTTP root for cluster artifacts
HTTP_DIR=/var/www/html/okd
# Optional TFTP root
TFTP_DIR=/var/lib/tftpboot
# PXE/DHCP interface on provisioning host
DHCP_INTERFACE=eno1
# NAT uplink (used only in IPv6 NAT66)
NAT66_UPLINK=eno2
# DHCPv6 range (only used when NETWORK_STACK=ipv6)
DHCP_RANGE_START=2602:f6ae:aa::200
DHCP_RANGE_END=2602:f6ae:aa::2ff
# IPv6 installer host address (for radvd/DNS in IPv6 mode)
INSTALL_HOST_IP6=2602:f6ae:aa::10
# Example static IPv6s for named helpers (only used if you actually run IPv6 mode)
BOOTSTRAP_IP6=2602:f6ae:aa::101
MASTER0_IP6=2602:f6ae:aa::111
MASTER1_IP6=2602:f6ae:aa::112
MASTER2_IP6=2602:f6ae:aa::113
WORKER0_IP6=2602:f6ae:aa::121
WORKER1_IP6=2602:f6ae:aa::122
# -----------------------------------------------------------------------------
# Distro + release
# -----------------------------------------------------------------------------
# DISTRO is informational; the Makefile only cares that RELEASE_BASE_URL points
# to a location containing:
# - openshift-install-linux.tar.gz
# - openshift-client-linux.tar.gz
DISTRO=ocp
# For fully licensed OpenShift (OCP), default to the "latest" client:
RELEASE_BASE_URL=https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest
# For OKD, you’d instead set, e.g.:
# DISTRO=okd
# RELEASE_BASE_URL=https://github.com/okd-project/okd/releases/download/4.20.0-okd-scos.4
# Paths to installer and client after download
OPENSHIFT_INSTALL=${PWD}/bin/openshift-install
OC=${PWD}/bin/oc
# -----------------------------------------------------------------------------
# Auth / SSH
# -----------------------------------------------------------------------------
# Pull secret: download from:
# https://console.redhat.com/openshift/install/pull-secret
# Save as secret.json in this cluster dir or project root.
PULL_SECRET_FILE=${PWD}/secret.json
# SSH key for core user on nodes + discovery env
SSH_PUB_KEY_FILE=${HOME}/.ssh/id_rsa.pub
# Optional trust bundle PEM file for internal registries (can be empty)
ADDITIONAL_TRUST_BUNDLE_FILE=
# FCOS stream
FCOS_STREAM=stable
# -----------------------------------------------------------------------------
# Cilium toggle
# -----------------------------------------------------------------------------
ENABLE_CILIUM=0
# -----------------------------------------------------------------------------
# Node management type
# -----------------------------------------------------------------------------
# MGMT_TYPE controls generic node-* targets:
# ilo -> uses <node>_ilo_ip / <node>_ilo_user
# drac -> uses <node>_drac_host / <node>_drac_user
MGMT_TYPE=ilo
# Default management users (can be overridden per node)
ILO_DEFAULT_USER=Administrator
DRAC_USER=root
# -----------------------------------------------------------------------------
# Node inventory (vendor-agnostic)
# One nodes.mk per cluster.
# -----------------------------------------------------------------------------
# All nodes in this cluster
NODES := node01 node02 node03 node04 node05 node06
# ---- Roles ------------------------------------------------------------------
node01_role := master
node02_role := master
node03_role := master
node04_role := worker
node05_role := worker
node06_role := worker
# ---- PXE MACs ---------------------------------------------------------------
# MAC addresses of the NICs that will PXE boot from the provisioning network.
node01_mac := aa:bb:cc:00:00:01
node02_mac := aa:bb:cc:00:00:02
node03_mac := aa:bb:cc:00:00:03
node04_mac := aa:bb:cc:00:00:04
node05_mac := aa:bb:cc:00:00:05
node06_mac := aa:bb:cc:00:00:06
# ---- Install devices (RAID/BOSS/whatever) -----------------------------------
# Populate after discovery:
# make ipxe-discovery-maps
# power nodes to PXE into discovery env
# make raid-discovery
# Then paste the suggested *_install_dev lines here.
#
# Examples:
# node01_install_dev := /dev/disk/by-id/scsi-3600508e000000000000000001
# node02_install_dev := /dev/disk/by-id/scsi-3600508e000000000000000002
# node03_install_dev := /dev/disk/by-id/scsi-3600508e000000000000000003
# node04_install_dev := /dev/disk/by-id/scsi-3600508e000000000000000004
# node05_install_dev := /dev/disk/by-id/scsi-3600508e000000000000000005
# node06_install_dev := /dev/disk/by-id/scsi-3600508e000000000000000006
# ---- Management endpoints ---------------------------------------------------
# Used by generic node-* targets depending on MGMT_TYPE in node.env:
#
# MGMT_TYPE=ilo -> <node>_ilo_ip / optional <node>_ilo_user
# MGMT_TYPE=drac -> <node>_drac_host / optional <node>_drac_user
#
# Example: HP iLO nodes (when MGMT_TYPE=ilo)
node01_ilo_ip := 10.0.10.11
node02_ilo_ip := 10.0.10.12
node03_ilo_ip := 10.0.10.13
node04_ilo_ip := 10.0.10.14
node05_ilo_ip := 10.0.10.15
node06_ilo_ip := 10.0.10.16
# Optional custom per-node user:
# node01_ilo_user := Administrator
# If you have a DRAC-based cluster instead, you’d use:
# node01_drac_host := 10.0.20.11
# node02_drac_host := 10.0.20.12
# node03_drac_host := 10.0.20.13
# node04_drac_host := 10.0.20.14
# node05_drac_host := 10.0.20.15
# node06_drac_host := 10.0.20.16
# node01_drac_user := root
# ---- Discovery SSH endpoints (optional) -------------------------------------
# raid-discovery will ssh as:
# ssh core@<node> (default)
# or, if defined:
# ssh core@<node>_ssh_host
#
# Use these if DNS doesn’t resolve node01/node02 to the temporary discovery IPs.
# node01_ssh_host := 192.168.90.21
# node02_ssh_host := 192.168.90.22
# node03_ssh_host := 192.168.90.23
# node04_ssh_host := 192.168.90.24
# node05_ssh_host := 192.168.90.25
# node06_ssh_host := 192.168.90.26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment