{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreih4u2hmsfjkqxnpgqlgf4lvwftndqs4bsf75gvxuzostbia3p6mie",
    "uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mpjryvaorcg2"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreiextwfvtarmgjxlfbyeqhw3duecso6jwq6q5k7ewimpevtz4w7rja"
    },
    "mimeType": "image/webp",
    "size": 79836
  },
  "path": "/vilan011/building-an-openshift-418-cluster-from-scratch-part-1-the-network-foundation-utilities-server-5g92",
  "publishedAt": "2026-06-30T19:21:50.000Z",
  "site": "https://dev.to",
  "tags": [
    "tutorial",
    "openshift",
    "infrastructure",
    "redhat",
    "OpenShift Container Platform 4.18 Installation Guide",
    "DO322 Course Page"
  ],
  "textContent": "> **Note:** This is **Part 1** of a multi-part series.\n>\n>   * **[Part 2: Generating Ignition Configs & VM Prep]** _(Coming Soon)_\n>   * **[Part 3: The Deployment Lifecycle & Troubleshooting]** _(Coming Soon)_\n>   * **[Part 4: Post-Install Hardening & User Management]** _(Coming Soon)_\n>\n\n\nIn this series, I walk through a full **OpenShift Container Platform 4.18 deployment from scratch**. While most tutorials target cloud providers or pre-provisioned infrastructure, this guide replicates a true **platform-agnostic (bare metal)** installation—executed within a lab of Virtual Machines hosted on **RHEL 10**.\n\nThis approach allows us to master the critical underlying infrastructure—DNS, DHCP, PXE booting, and load balancing—without the cost of physical hardware. By the end of Part 1, you will have a fully functional **Utilities Server** that acts as the command center for the entire cluster, handling:\n\n  * **DNS** : Internal resolution for all cluster nodes.\n  * **DHCP** : IP allocation and iPXE boot redirection via Kea.\n  * **TFTP & HTTP**: Serving bootloader images, RHCOS ISOs, and Ignition configs.\n  * **HAProxy** : Load balancing for the Kubernetes API and Ingress traffic.\n\n\n\nWe won't run _openshift-install_ just yet; first, we must build the foundation upon which the cluster relies.\n\n##  📚 References & Background\n\nThis lab strictly follows the **Platform-Agnostic** installation methodology defined by Red Hat. The setup was heavily inspired by the **DO322: Red Hat OpenShift Installation Lab** training course, ensuring we cover the same critical edge cases and network requirements found in production environments.\n\n  1. OpenShift Container Platform 4.18 Installation Guide\n  2. DO322 Course Page\n\n\n\n**Why use VMs for a \"Bare Metal\" guide**? While running on virtual machines provides speed and portability for this lab, the network stack (DNS/DHCP/PXE) and the Ignition configuration flow are **identical** to what you would encounter on physical hardware. Following the DO322 methodology ensures this tutorial remains relevant for real-world bare-metal deployments.\n\nLet's get the foundation laid.\n\n##  🛠 Lab Environment Overview\n\nBefore diving in, here is the topology I'm working with. All these VMs are hosted on a RHEL 10 hypervisor.\n\nRole | VM Name | IP Address | Specs\n---|---|---|---\nUtilities Server | utilities | 192.168.110.20 | 4 vCPU, 8GB RAM, 250GB Disk\nBootstrap Node | bootstos | 192.168.110.100 | 4 vCPU, 16GB RAM, 100GB NVMe\nControl Plane | masteros01-03 | .101 to .103 | 4 vCPU, 24GB RAM, 100GB NVMe\nCompute Nodes | workeros01-03 | .111 to .113 | 2 vCPU, 8GB RAM, 100GB SSD\n\n##  1. Setting Up the Utilities Server\n\nWe start with a fresh RHEL 10 installation. Once installed, we register the system and ensure all packages are up to date.\n\n\n\n    $ sudo subscription-manager register --username <YOUR_USER> --password <PASSWORD>\n    $ sudo dnf update redhat-release\n    $ sudo dnf upgrade -y\n    $ sudo reboot\n\n\nAfter reboot, we verify connectivity and prepare for the services.\n\n####  Network Configuration\n\nThe utilities server has two interfaces. I configured them via _nmtui_ to assign static IPs:\n\n  * enp1s0: 192.168.1.20 (Management/Internet Gateway)\n  * enp7s0: 192.168.110.20 (Internal Cluster Network)\n\n\n\nand then, add two extra IP addresses to interface enp7s0, 192.168.110.21 and 192.168.110.22, to be used by OpenShift services.\n\n\n\n    $ nmcli\n    enp1s0: connected to enp1s0\n            \"Red Hat Virtio 1.0\"\n            ethernet (virtio_net), 52:54:00:C5:C1:9E, hw, mtu 1500\n            ip4 default\n            inet4 192.168.1.20/24\n            route4 default via 192.168.1.1 metric 100\n            route4 192.168.1.0/24 metric 100\n            inet6 fe80::5054:ff:fec5:c19e/64\n            route6 fe80::/64 metric 1024\n\n    enp7s0: connected to enp7s0\n            \"Red Hat Virtio 1.0\"\n            ethernet (virtio_net), 52:54:00:E6:D1:A4, hw, mtu 1500\n            inet4 192.168.110.22/24\n            inet4 192.168.110.21/24\n            inet4 192.168.110.20/24\n            route4 192.168.110.0/24 metric 101\n            route4 192.168.110.0/24 metric 101\n            route4 192.168.110.0/24 metric 101\n            inet6 fe80::5054:ff:fee6:d1a4/64\n            route6 fe80::/64 metric 1024\n    ...\n\n\n##  2. Installing & Configuring DNS (BIND)\n\nOpenShift relies heavily on proper DNS. We need records for the API VIPs, ingress VIP, and every node.\n\n###  Installation\n\n\n    $ sudo dnf install bind bind-utils -y\n\n\n###  Configuration\n\nI modified the default config (/etc/named.conf) to listen on both interfaces and allow recursion only for our internal subnets. Crucially, we define our zones here.\n\n\n\n    # /etc/named.conf snippet\n    options {\n        listen-on port 53 { 127.0.0.1; 192.168.1.20; 192.168.110.20; };\n        allow-query { localhost; 192.168.1.0/24; 192.168.110.0/24; };\n        allow-recursion { localhost; 192.168.1.0/24; 192.168.110.0/24; };\n        forwarders { 8.8.8.8; 8.8.4.4; }; # Or your ISP DNS\n        recursion yes;\n    };\n\n    zone \"internal.local\" {\n        type master;\n        file \"internal.local.zone\";\n        allow-query { any; };\n    };\n\n    zone \"110.168.192.in-addr.arpa\" {\n        type master;\n        file \"110.168.192.in-addr.arpa.zone\";\n        allow-query { any; };\n    };\n\n\n###  Zone Files\n\nWe create the forward and reverse lookup zones. Notice the VIPs for the API (192.168.110.21) and Ingress (192.168.110.22). These point to our HAProxy server (which runs on the utilities node).\n\n/var/named/internal.local.zone:\n\n\n\n    $TTL 8h\n    @ IN SOA ns1.internal.local. hostmaster.internal.local. (\n        2026042601 ; serial\n        1d         ; refresh\n        3h         ; retry\n        3d         ; expire\n        3h )       ; minimum\n\n    IN NS ns1.internal.local.\n\n    ntp               IN A    192.168.110.20\n    dns               IN A    192.168.110.20\n\n    ; Static infrastructure\n    ;hostname IN A 192.168.110.x\n    utilities.internal.local.  IN A    192.168.110.20\n\n    ; OpenShift VIPs\n    api.internal.local.       IN A    192.168.110.21\n    api-int.internal.local.   IN A    192.168.110.21\n    *.apps.internal.local.    IN A    192.168.110.22\n\n    ; Bootstrap Node\n    bootstos.internal.local.     IN A 192.168.110.100\n\n    ; Master Nodes\n    masteros01.internal.local. IN A 192.168.110.101\n    masteros02.internal.local. IN A 192.168.110.102\n    masteros03.internal.local. IN A 192.168.110.103\n\n    ; Worker Nodes\n    workeros01.internal.local. IN A 192.168.110.111\n    workeros02.internal.local. IN A 192.168.110.112\n    workeros03.internal.local. IN A 192.168.110.113\n\n\n/var/named/110.168.192.in-addr.arpa.zone:\n\n\n\n    $TTL 8h\n    @ IN SOA ns1.internal.local. hostmaster.internal.local. (\n              2026041901 ; serial number\n              1d         ; refresh period\n              3h         ; retry period\n              3d         ; expire time\n              3h )       ; minimum TTL\n\n     IN NS   ns1.internal.local.\n\n    20                IN PTR  ns1.internal.local.\n    20                IN PTR  utilities.internal.local.\n\n    21    IN PTR api.internal.local.\n    21    IN PTR api-int.internal.local.\n\n    100  IN PTR bootstos.internal.local.\n    101  IN PTR masteros01.internal.local.\n    102  IN PTR masteros02.internal.local.\n    103  IN PTR masteros03.internal.local.\n\n    111  IN PTR workeros01.internal.local.\n    112  IN PTR workeros02.internal.local.\n    113  IN PTR workeros03.internal.local.\n\n\n###  Check configuration and start the service\n\nChange ownership and permissions, and check the configuration:\n\n\n\n    $ sudo named-checkconf\n    $ sudo chown root:named /var/named/internal.local.zone\n    $ sudo chown root:named /var/named/110.168.192.in-addr.arpa.zone\n    $ sudo chmod 640 /var/named/internal.local.zone\n    $ sudo chmod 640 /var/named/110.168.192.in-addr.arpa.zone\n    $ sudo named-checkzone internal.local /var/named/internal.local.zone\n    zone internal.local/IN: loaded serial 2026041901\n    OK\n    $ sudo named-checkzone 110.168.192.in-addr.arpa /var/named/110.168.192.in-addr.arpa.zone\n    zone 110.168.192.in-addr.arpa/IN: loaded serial 2026041901\n    OK\n    $\n\n\nEnable and start the service\n\n\n\n    $ sudo systemctl enable --now named\n\n\nand allow the service through the firewall.\n\n\n\n    $ sudo firewall-cmd --permanent --add-service=dns\n    $ sudo firewall-cmd --reload\n\n\n##  3. Time Synchronization (Chrony)\n\nKubernetes clusters are extremely sensitive to time skew. If the clocks drift, certificates and authentication will fail immediately.\n\nWe install chrony (it's already present in RHEL, but we configure it):\n\n\n\n    # /etc/chrony.conf snippet\n    pool 2.rhel.pool.ntp.org iburst\n    allow 192.168.1.0/24\n    allow 192.168.110.0/24\n    local stratum 10\n\n\nThis allows the utility server to act as a local NTP source for all nodes while syncing itself with external sources.\n\nEnable and start the service\n\n\n\n    $ sudo systemctl enable --now chronyd\n    Created symlink '/etc/systemd/system/multi-user.target.wants/chronyd.service' → '/usr/lib/systemd/system/chronyd.service'.\n    $\n\n\nand allow the service through the firewall.\n\n\n\n    $ sudo firewall-cmd --add-service=ntp --permanent\n    $ sudo firewall-cmd --reload\n\n\n##  4. DHCP with Kea\n\nInstead of the legacy ISC DHCP server, I used Kea, which is the recommended choice for RHEL 10.\n\n###  Installation & Config\n\n\n    sudo dnf install kea -y\n\n\nThe configuration /etc/kea/kea-dhcp4.conf is where the magic happens for PXE booting. We define client classes to distinguish between iPXE clients and standard UEFI/BIOS PXE clients.\n\n\n\n    {\n      \"Dhcp4\": {\n        \"interfaces-config\": {\n          \"interfaces\": [ \"enp7s0/192.168.110.20\" ]\n        },\n\n        \"client-classes\": [\n            {\n                \"name\": \"iPXE Clients\",\n                \"test\": \"option[175].exists\",\n                \"option-data\": [\n                   { \"name\": \"tftp-server-name\", \"data\": \"192.168.110.20\" },\n                   { \"name\": \"boot-file-name\", \"data\": \"http://192.168.110.20/boot.ipxe\" }\n                ]\n            },\n            {\n                \"name\": \"UEFI PXE Clients\",\n                \"test\": \"option[93].hex == 0x0007 and not option[175].exists\",\n                \"next-server\": \"192.168.110.20\",\n                \"boot-file-name\": \"ipxe-snponly-x86_64.efi\"\n            },\n            {\n                \"name\": \"BIOS PXE Clients\",\n                \"test\": \"option[93].hex == 0x0000 and not option[175].exists\",\n                \"next-server\": \"192.168.110.20\",\n                \"boot-file-name\": \"undionly.kpxe\"\n            }\n        ],\n\n        \"subnet4\": [\n          {\n            \"id\": 1,\n            \"subnet\": \"192.168.110.0/24\",\n            \"pools\": [\n              {\n                \"pool\": \"192.168.110.200 - 192.168.110.250\"\n              }\n            ],\n\n            \"reservations\": [\n              { \"hw-address\": \"AA:BB:CC:DD:EE:01\", \"ip-address\": \"192.168.110.100\", \"hostname\": \"bootstos.oc41827.internal.local\" },\n\n              { \"hw-address\": \"AA:BB:CC:DD:EE:02\", \"ip-address\": \"192.168.110.101\", \"hostname\": \"masteros01.oc41827.internal.local\" },\n              { \"hw-address\": \"AA:BB:CC:DD:EE:03\", \"ip-address\": \"192.168.110.102\", \"hostname\": \"masteros02.oc41827.internal.local\" },\n              { \"hw-address\": \"AA:BB:CC:DD:EE:04\", \"ip-address\": \"192.168.110.103\", \"hostname\": \"masteros03.oc41827.internal.local\" },\n\n              { \"hw-address\": \"AA:BB:CC:DD:EE:05\", \"ip-address\": \"192.168.110.111\", \"hostname\": \"workeros01.oc41827.internal.local\" },\n              { \"hw-address\": \"AA:BB:CC:DD:EE:06\", \"ip-address\": \"192.168.110.112\", \"hostname\": \"workeros02.oc41827.internal.local\" },\n              { \"hw-address\": \"AA:BB:CC:DD:EE:07\", \"ip-address\": \"192.168.110.113\", \"hostname\": \"workeros03.oc41827.internal.local\" }\n            ],\n\n            \"option-data\": [\n              { \"name\": \"routers\", \"code\": 3, \"data\": \"192.168.110.1\" },\n              { \"name\": \"domain-name-servers\", \"code\": 6, \"space\": \"dhcp4\", \"data\": \"192.168.110.20\" },\n              { \"name\": \"domain-name\", \"data\": \"internal.local\" },\n              { \"name\": \"domain-search\", \"data\": \"oc41827.internal.local internal.local\" }\n            ]\n\n          },\n        ]\n      }\n    }\n\n\n**Note** : Ensure you replace the MAC addresses with the actual ones assigned to your VMs.\n\n###  Check the configuration and enable Kea\n\n\n    $ sudo kea-dhcp4 -t /etc/kea/kea-dhcp4.conf\n\n\nYou might see some warnings, you might ignore them (but do not ignore any error), see this as an example:\n\n\n\n    $ sudo kea-dhcp4 -t /etc/kea/kea-dhcp4.conf\n    2026-04-21 20:52:38.159 WARN  [kea-dhcp4.dhcpsrv/6313.140336065439872] DHCPSRV_MT_DISABLED_QUEUE_CONTROL disabling dhcp queue control when multi-threading is enabled.\n    2026-04-21 20:52:38.159 WARN  [kea-dhcp4.dhcp4/6313.140336065439872] DHCP4_RESERVATIONS_LOOKUP_FIRST_ENABLED Multi-threading is enabled and host reservations lookup is always performed first.\n    2026-04-21 20:52:38.160 INFO  [kea-dhcp4.dhcpsrv/6313.140336065439872] DHCPSRV_CFGMGR_NEW_SUBNET4 a new subnet has been added to configuration: 192.168.110.0/24 with params: valid-lifetime=7200\n    2026-04-21 20:52:38.160 INFO  [kea-dhcp4.dhcpsrv/6313.140336065439872] DHCPSRV_CFGMGR_SOCKET_TYPE_SELECT using socket type raw\n    2026-04-21 20:52:38.160 INFO  [kea-dhcp4.dhcpsrv/6313.140336065439872] DHCPSRV_CFGMGR_USE_ADDRESS listening on address 192.168.110.20, on interface enp7s0\n    2026-04-21 20:52:38.160 INFO  [kea-dhcp4.dhcpsrv/6313.140336065439872] DHCPSRV_CFGMGR_SOCKET_TYPE_DEFAULT \"dhcp-socket-type\" not specified , using default socket type raw\n    2026-04-21 20:52:38.160 INFO  [kea-dhcp4.dhcpsrv/6313.140336065439872] DHCPSRV_LEASE_MGR_BACKENDS_REGISTERED the following lease backend types are available: memfile\n    2026-04-21 20:52:38.160 INFO  [kea-dhcp4.hosts/6313.140336065439872] HOSTS_BACKENDS_REGISTERED the following host backend types are available:\n    2026-04-21 20:52:38.160 INFO  [kea-dhcp4.dhcpsrv/6313.140336065439872] DHCPSRV_FORENSIC_BACKENDS_REGISTERED the following forensic backend types are available:\n    2026-04-21 20:52:38.160 INFO  [kea-dhcp4.database/6313.140336065439872] CONFIG_BACKENDS_REGISTERED the following config backend types are available:\n    $\n\n\nEnable and start the service:\n\n\n\n    $ sudo systemctl enable --now kea-dhcp4\n\n\nand allow the service through the firewall.\n\n\n\n    $ sudo firewall-cmd --add-service=dhcp --permanent\n    $ sudo firewall-cmd --reload\n\n\n##  5. Web Services & iPXE Boot\n\nOpenShift uses a live-boot mechanism. We need an HTTP server to serve the RHCOS kernel, rootfs, and ignition files, plus TFTP for the initial bootloader.\n\n###  Apache Setup\n\nInstall Apache\n\n\n\n    $ sudo dnf install httpd -y\n\n\nTo prevent interferences with haproxy, change the listen port to include the IP address:\n\n\n\n    $ sudo nano /etc/httpd/conf/httpd.conf\n\n\nFrom\n\n\n\n    Listen 80\n\n\nto\n\n\n\n    Listen 192.168.110.20:80\n\n\nAllow the service through the firewall.\n\n\n\n    $ sudo firewall-cmd --add-service=http --permanent\n    $ sudo firewall-cmd --reload\n\n\nand enable and start the service:\n\n\n\n    $ sudo systemctl enable --now httpd.service\n\n\n###  TFTP Setup\n\nInstall TFTP:\n\n\n\n    $ sudo dnf install tftp-server -y\n\n\nEnable and start the service:\n\n\n\n    $ sudo systemctl enable --now tftp.socket\n\n\nand allow the service through the firewall.\n\n\n\n    $ sudo firewall-cmd --add-service=tftp --permanent\n    $ sudo firewall-cmd --reload\n\n\n###  iPXE Setup\n\nWe are going to deploy the OpenShift nodes using ipxe and tftp-server. The key is the boot.ipxe script, which we will discuss in the next blog. For now, we prepare iPXE.\n\nInstall iPXE\n\n\n\n    $ sudo dnf install ipxe-bootimgs\n\n\n\nand prepare the basic structure.\n\n\n\n    $ sudo cp /usr/share/ipxe/undionly.kpxe /var/lib/tftpboot/\n    $ sudo cp /usr/share/ipxe/ipxe-snponly-x86_64.efi /var/lib/tftpboot/\n    $ sudo restorecon -Rv /var/lib/tftpboot\n\n\n\nIt can be later used to install other OS, like RHEL or Ubuntu.\n\n##  6. Load Balancing with HAProxy\n\nThis is critical for High Availability. HAProxy listens on the API VIP (192.168.110.21) and Ingress VIP (192.168.110.22) and forwards traffic to the backend nodes.\n\n###  Install HAProxy\n\n\n    $ sudo dnf install haproxy -y\n\n\n###  HAProxy Configuration for Openshift\n\nUse nano or vim to update the configuration file (/etc/haproxy/haproxy.cfg). See below the changes I made:\n\n\n\n    # /etc/haproxy/haproxy.cfg snippet\n        chroot      /var/lib/haproxy\n        #pidfile     /var/run/haproxy.pid\n\n    defaults\n        mode                    tcp\n        log                     global\n        option                  tcplog\n        retries                 3\n        timeout connect         10s\n        timeout client          1m\n        timeout server          1m\n        maxconn                 3000\n\n    #---------------------------------------------------------------------\n    # round robin balancing for RHOCP Kubernetes API Server\n    #---------------------------------------------------------------------\n     listen api-server-6443\n       bind 192.168.110.21:6443\n       mode tcp\n       option httpchk GET /readyz HTTP/1.0\n       option log-health-checks\n       balance roundrobin\n       server bootstos bootstos.oc41827.internal.local:6443 verify none check check-ssl inter 10s fall 2 rise 3 backup\n       server masteros01 masteros01.oc41827.internal.local:6443 weight 1 verify none check check-ssl inter 10s fall 2 rise 3\n       server masteros02 masteros02.oc41827.internal.local:6443 weight 1 verify none check check-ssl inter 10s fall 2 rise 3\n       server masteros03 masteros03.oc41827.internal.local:6443 weight 1 verify none check check-ssl inter 10s fall 2 rise 3\n\n    # ---------------------------------------------------------------------\n    # round robin balancing for RHOCP Machine Config Server\n    # ---------------------------------------------------------------------\n     listen machine-config-server-22623\n       bind 192.168.110.21:22623\n       mode tcp\n       server bootstos bootstos.oc41827.internal.local:22623 check inter 1s backup\n       server masteros01 masteros01.oc41827.internal.local:22623 check inter 1s\n       server masteros02 masteros02.oc41827.internal.local:22623 check inter 1s\n       server masteros03 masteros03.oc41827.internal.local:22623 check inter 1s\n\n    # ---------------------------------------------------------------------\n    # round robin balancing for RHOCP Ingress Insecure Port\n    # ---------------------------------------------------------------------\n     listen ingress-router-80\n       bind 192.168.110.22:80\n       mode tcp\n       balance source\n       server workeros01 workeros01.oc41827.internal.local:80 check inter 1s\n       server workeros02 workeros02.oc41827.internal.local:80 check inter 1s\n       server workeros03 workeros03.oc41827.internal.local:80 check inter 1s\n\n    # ---------------------------------------------------------------------\n    # round robin balancing for RHOCP Ingress Secure Port\n    # ---------------------------------------------------------------------\n     listen ingress-router-443\n       bind 192.168.110.22:443\n       mode tcp\n       balance source\n       server workeros01 workeros01.oc41827.internal.local:443 check inter 1s\n       server workeros02 workeros02.oc41827.internal.local:443 check inter 1s\n       server workeros03 workeros03.oc41827.internal.local:443 check inter 1s\n\n\n###  Updating firewalld and SELinux\n\nWe need to allow the tcp ports used by OpenShift through firewalld\n\n\n\n    $ sudo firewall-cmd --permanent --add-service=https\n    $ sudo firewall-cmd --permanent --add-port=6443/tcp\n    $ sudo firewall-cmd --permanent --add-port=22623/tcp\n    $ sudo firewall-cmd --reload\n\n\nand also ensure SELinux allows those ports to be used as HTTP ports\n\n\n\n    $ sudo semanage port -a -t http_port_t -p tcp 6443\n    $ sudo semanage port -m -t http_port_t -p tcp 6443\n    $ sudo semanage port -a -t http_port_t -p tcp 22623\n    $ sudo semanage port -m -t http_port_t -p tcp 22623\n\n\n###  Configuration check and enabling the HAProxy service\n\nCheck the configuration file:\n\n\n\n    $ sudo haproxy -c -f /etc/haproxy/haproxy.cfg\n\n\nNote that no output from the previous command is good news!\n\nand enable and start the service.\n\n\n\n    $ sudo systemctl enable --now haproxy\n\n\n##  🔜 Coming Up in Part 2\n\nNow that the network is breathing correctly, we need to generate the Ignition files.\n\n  * Downloading RHCOS and OpenShift CLI tools.\n  * Creating the install-config.yaml.\n  * Running openshift-install to generate manifests.\n  * Critical Step: Disabling Secure Boot on VMs (the \"secret\" to getting iPXE to work).\n  * Powering on the nodes and watching the magic happen.\n\n\n\nStay tuned!\n\nDid you find this guide helpful? Let me know in the comments if you'd prefer using Ansible for the setup instead of manual commands! Also, subscribe to follow the rest of the series.",
  "title": "Building an OpenShift 4.18 Cluster from Scratch: Part 1 – The Network Foundation & Utilities Server"
}