{"id":235,"date":"2024-10-09T13:24:37","date_gmt":"2024-10-09T11:24:37","guid":{"rendered":"https:\/\/blog.openshift.one\/?p=235"},"modified":"2024-10-09T15:16:39","modified_gmt":"2024-10-09T13:16:39","slug":"scaling-openshift-compute-worker-baremetal-nodes-part-2","status":"publish","type":"post","link":"https:\/\/blog.openshift.one\/index.php\/2024\/10\/09\/scaling-openshift-compute-worker-baremetal-nodes-part-2\/","title":{"rendered":"Scaling OpenShift compute (worker) BareMetal nodes &#8211; Part 2"},"content":{"rendered":"\n<p>In the first post from this series &#8211; <a href=\"https:\/\/blog.openshift.one\/index.php\/2024\/10\/07\/scaling-openshift-compute-worker-baremetal-nodes-part-1\/\">Scaling OpenShift compute (worker) BareMetal nodes &#8211; Part 1<\/a> &#8211; I explained the fully automated process of <a href=\"https:\/\/www.redhat.com\/en\/technologies\/cloud-computing\/openshift\">OpenShift<\/a> baremetal node bootstrap using out-of-band-management (OOBM) interface and virtual media functionality. While it is very convenient and time efficient, sometimes it does not fit to the environment where the cluster is running. For an instance there may be a firewall between the cluster and OOBM network which prevents communication between <a href=\"https:\/\/www.redhat.com\/en\/technologies\/cloud-computing\/openshift\">OpenShift<\/a>&#8216;s <a href=\"https:\/\/metal3.io\/\">Metal3<\/a> components and baremetal servers&#8217; management consoles. In such case <a href=\"https:\/\/metal3.io\/\">Metal3<\/a> duties in regard to servers power management are delegated to&#8230; a human being \ud83d\ude09<\/p>\n\n\n\n<p>Luckily OpenShift can provide valuable help in getting new node provisioned even manually. However the documentation about it is a bit unstructured and somewhat hard to follow, unless you know what you&#8217;re looking for. And for that reason I decided to gather all the information I gained and put it into a single blog post. <\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"471\" src=\"https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-6-1024x471.png\" alt=\"\" class=\"wp-image-280\" srcset=\"https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-6-1024x471.png 1024w, https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-6-300x138.png 300w, https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-6-768x353.png 768w, https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-6-1536x706.png 1536w, https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-6-2048x942.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Few assumptions.<\/h2>\n\n\n\n<p>I assume the cluster is installed with Agent Based Installer (<a href=\"https:\/\/docs.openshift.com\/container-platform\/4.16\/installing\/installing_with_agent_based_installer\/preparing-to-install-with-agent-based-installer.html\">https:\/\/docs.openshift.com\/container-platform\/4.16\/installing\/installing_with_agent_based_installer\/preparing-to-install-with-agent-based-installer.html<\/a>) with BareMetal platform type. Clusters with user-provisioned infrastructure require additional validation and configuration to use the Machine API.<\/p>\n\n\n\n<p>Clusters with the infrastructure platform type&nbsp;<code>none<\/code>&nbsp;cannot use the Machine API. This limitation applies even if the compute machines that are attached to the cluster are installed on a platform that supports the feature. To view the platform type for your cluster, run the following command:<\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># oc get infrastructure cluster -o jsonpath='{.status.platform}'\nBareMetal<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>I also assume the environment is connected to Internet, however performing this exercise in disconnected\/air-gapped environment should be similar.<\/p>\n\n\n\n<p>The last but not least I need to get <code>coreos-installer<\/code> package installed:<\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># dnf install -y coreos-installer<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Scaling-out the compute nodes using manually crafted RHCOS ISO image.<\/h2>\n\n\n\n<p>This scenario covers the case where fully automated node provisioning cannot be performed. The Red Hat CoreOS ISO bootstrap image can be written on DVD, USB or simply mounted with VirtualMedia if your OOBM interface allows to boot the server from it. In my lab I use <a href=\"https:\/\/github.com\/openstack\/sushy-tools\">Sushy tools<\/a> to emulate Redfish interface for baremetal management. <\/p>\n\n\n\n<p>The initial state of the cluster looks as follow:<\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># oc get nodes,machines,machinesets,bmh\nNAME            STATUS   ROLES                         AGE   VERSION\nnode\/master-0   Ready    control-plane,master,worker   3d   v1.30.4\nnode\/master-1   Ready    control-plane,master,worker   3d   v1.30.4\nnode\/master-2   Ready    control-plane,master,worker   3d   v1.30.4\n\nNAME                                               PHASE     TYPE   REGION   ZONE   AGE\nmachine.machine.openshift.io\/ocp4-l2k5k-master-0   Running                          3d\nmachine.machine.openshift.io\/ocp4-l2k5k-master-1   Running                          3d\nmachine.machine.openshift.io\/ocp4-l2k5k-master-2   Running                          3d\n\nNAME                                                  DESIRED   CURRENT   READY   AVAILABLE   AGE\nmachineset.machine.openshift.io\/ocp4-l2k5k-worker-0   0         0                             3d\n\nNAME                               STATE       CONSUMER              ONLINE   ERROR   AGE\nbaremetalhost.metal3.io\/master-0   unmanaged   ocp4-l2k5k-master-0   true             3d\nbaremetalhost.metal3.io\/master-1   unmanaged   ocp4-l2k5k-master-1   true             3d\nbaremetalhost.metal3.io\/master-2   unmanaged   ocp4-l2k5k-master-2   true             3d<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>As you can see it is three nodes compact cluster where control plane and workloads are hosted on the same nodes. This deployment type allows you to benefit from highly available platform with minimal hardware footprint. <\/p>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Setting network configuration for the new node<\/h3>\n\n\n\n<p>If your node has a single NIC and the IP configuration is obtained via DHCP that\u2019s easy and you can skip to the next paragraph. However for the most of scenarios that\u2019s not the case. Very often there are multiple NICs which are supposed to be configured as bonds, use of the tagged VLANs or there is no DHCP in place and IP configuration has to be statically applied. When you were installing your cluster using Agent Based Installer, you probably came across <a href=\"https:\/\/docs.openshift.com\/container-platform\/4.16\/installing\/installing_with_agent_based_installer\/preparing-to-install-with-agent-based-installer.html#static-networking\">agent-config.yaml<\/a> file which contains configuration of all nodes you\u2019re deploying at the cluster installation time, including network configuration written in a NMState format. However now, since deployment is already done, the file isn\u2019t useful anymore and you have to provide network configuration individually for the each node the other way. Luckily&nbsp;<code>BareMetalHost<\/code>&nbsp;resource allows to reference Secret with NMState configuration for the new node.<\/p>\n\n\n\n<p>The full list of options including examples you can find at the official NMState project page:&nbsp;<a href=\"https:\/\/nmstate.io\/\">https:\/\/nmstate.io\/<\/a>. For the demo purposes I will configure bond interface with two physical NICs, using active-backup (Mode 1) bonding which does not require any integration at the switch side. Additionaly IP configuration is static, therefore <strong>each new node will require individual Secret with NMState configuration<\/strong> (<strong><mark style=\"background-color:#ffe2c7\" class=\"has-inline-color has-foreground-color\">WARNING: overlapping IP addresses ahead!<\/mark><\/strong>). For more information about bonding and OpenShift, please refer to:&nbsp;<a href=\"https:\/\/docs.openshift.com\/container-platform\/4.16\/networking\/k8s_nmstate\/k8s-nmstate-updating-node-network-config.html#virt-example-bond-nncp_k8s_nmstate-updating-node-network-config\">https:\/\/docs.openshift.com\/container-platform\/4.16\/networking\/k8s_nmstate\/k8s-nmstate-updating-node-network-config.html#virt-example-bond-nncp_k8s_nmstate-updating-node-network-config<\/a><\/p>\n\n\n\n<p>Bellow is the content of Secret containing NMState configuration for my new baremetal node:<\/p>\n\n\n\n<pre class=\"wp-block-code has-foreground-color has-tertiary-background-color has-text-color has-background\"><code>apiVersion: v1<br>kind: Secret<br>metadata:<br>  name: compute-0-network-config-secret<br>  namespace: openshift-machine-api<br>type: Opaque<br>stringData:<br>  nmstate: |<br>    interfaces:<br>      - name: bond0<br>        type: bond<br>        state: up<br>        link-aggregation:<br>          mode: active-backup<br>          options:<br>            primary: enp1s0<br>          port:<br>            - enp1s0<br>            - enp2s0<br>        ipv4:<br>          dhcp: false<br>          enabled: true<br>          address:<br>            - ip: 192.168.232.103<br>              prefix-length: 24<br>      - name: enp1s0<br>        type: ethernet<br>        state: up<br>        ipv4:<br>          enabled: false<br>          dhcp: false<br>      - name: enp2s0<br>        type: ethernet<br>        state: up<br>        ipv4:<br>          enabled: false<br>          dhcp: false<br>      - name: enp3s0<br>        type: ethernet<br>        state: up<br>        ipv4:<br>          enabled: false<br>          dhcp: false<br>      - name: enp4s0<br>        type: ethernet<br>        state: up<br>        ipv4:<br>          enabled: false<br>          dhcp: false<br>    routes:<br>      config:<br>        - destination: 0.0.0.0\/0<br>          next-hop-address: 192.168.232.1<br>          next-hop-interface: bond0<br>          table-id: 254<br>    dns-resolver:<br>      config:<br>        server:<br>          - 192.168.232.1<br><br><\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>The command to create it is as follow:<\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># oc -n openshift-machine-api create -f compute-0.nmstate.secret.yaml\nsecret\/compute-0-network-config-secret created<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Creating BareMetalHost resource (and labeling it)<\/h3>\n\n\n\n<p>The BareMetalHost resource defines a physical host and its properties. For more information about it please refer to&nbsp;<a href=\"https:\/\/docs.openshift.com\/container-platform\/4.16\/post_installation_configuration\/post-install-bare-metal-configuration.html\">Bare-metal configuration<\/a> documentation. The resource definition looks as follow:<\/p>\n\n\n\n<pre class=\"wp-block-code has-foreground-color has-tertiary-background-color has-text-color has-background\"><code>apiVersion: metal3.io\/v1alpha1\nkind: BareMetalHost\nmetadata:\n  name: compute-0\n  namespace: openshift-machine-api\nspec:\n  automatedCleaningMode: metadata\n  bmc:\n    address: \"\"\n    credentialsName: \"\"\n  bootMACAddress: de:ad:be:ef:66:04\n  bootMode: UEFI\n  customDeploy:\n    method: install_coreos\n  externallyProvisioned: true\n  hardwareProfile: unknown\n  online: true\n  userData:\n    name: worker-user-data-managed\n    namespace: openshift-machine-api<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>The comprehensive description of BareMetalHost resource can be found at <a href=\"https:\/\/docs.openshift.com\/container-platform\/4.16\/post_installation_configuration\/post-install-bare-metal-configuration.html#the-baremetalhost-spec\">The BareMetalHost spec<\/a>,&nbsp;but for now please check the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>spec.bootMACAddress<\/code>&nbsp;\u2013 this configures the MAC address that system is supposed to boot from. Since we\u2019re not booting over network, this mac address is being used to confirm we\u2019re working on the right server. If the NIC with the matching mac address won\u2019t be found, the node will not be bootstrapped.<\/li>\n<\/ul>\n\n\n\n<p><strong>Please note<\/strong> there is no <code>spec.bmc.address<\/code> nor <code>spec.bmc.credentialsName<\/code> configured as the node won&#8217;t be managed by OpenShift&#8217;s Metal3 component. Furthermore I don&#8217;t specify here <code>spec.networkData<\/code>, <code>spec.preprovisioningNetworkDataName<\/code> and <code>spec.rootDeviceHints<\/code> as well. These parameters will be configured later at the time of RHCOS ISO image creation.<\/p>\n\n\n\n<p>Now I will create the <code>BareMetalHost<\/code> resource for compute-0. Because it isn&#8217;t managed by the cluster it will remain in unmanaged state, there will be no automated introspection nor provisioning. Let&#8217;s also check labels of the all <code>BareMetalHost<\/code> I have configured so far:<\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># oc create -f compute-0.bmh.no-virtmedia.yaml\nbaremetalhost.metal3.io\/compute-0 created\n\n# oc get bmh --show-labels\nNAME        STATE       CONSUMER              ONLINE   ERROR   AGE   LABELS\ncompute-0   unmanaged                         true             44s   \nmaster-0    unmanaged   ocp4-l2k5k-master-0   true             3d    installer.openshift.io\/role=control-plane\nmaster-1    unmanaged   ocp4-l2k5k-master-1   true             3d    installer.openshift.io\/role=control-plane\nmaster-2    unmanaged   ocp4-l2k5k-master-2   true             3d    installer.openshift.io\/role=control-plane<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>The <code>BareMetalHost<\/code> I&#8217;ve added has no label while the other control-plane nodes have <code>installer.openshift.io\/role=control-plane<\/code> configured. This label is important later to associate the right <code>BareMetalHost<\/code> with the related <code>Machine<\/code> and <code>Node<\/code> resources, so let me label compute-0 with worker role:<\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># oc label bmh compute-0 installer.openshift.io\/role=worker\nbaremetalhost.metal3.io\/compute-0 labeled\n\n# oc get bmh --show-labels\nNAME        STATE       CONSUMER              ONLINE   ERROR   AGE   LABELS\ncompute-0   unmanaged                         true             54s   installer.openshift.io\/role=worker\nmaster-0    unmanaged   ocp4-l2k5k-master-0   true             3d    installer.openshift.io\/role=control-plane\nmaster-1    unmanaged   ocp4-l2k5k-master-1   true             3d    installer.openshift.io\/role=control-plane\nmaster-2    unmanaged   ocp4-l2k5k-master-2   true             3d    installer.openshift.io\/role=control-plane<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Obtaining RHCOS ISO image<\/h3>\n\n\n\n<p>To bootstrap the host manually I need to get Red Hat CoreOS image first. Since each OpenShift version may use different image, the best way to obtain the exact needed image version is query OpenShift for it:<\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># oc -n openshift-machine-config-operator get configmap\/coreos-bootimages -o jsonpath='{.data.stream}' | jq -r '.architectures.x86_64.artifacts.metal.formats.iso.disk.location'\nhttps:&#47;&#47;rhcos.mirror.openshift.com\/art\/storage\/prod\/streams\/4.17-9.4\/builds\/417.94.202408270355-0\/x86_64\/rhcos-417.94.202408270355-0-live.x86_64.iso\n\n# curl -LO https:\/\/rhcos.mirror.openshift.com\/art\/storage\/prod\/streams\/4.17-9.4\/builds\/417.94.202408270355-0\/x86_64\/rhcos-417.94.202408270355-0-live.x86_64.iso\n  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n                                 Dload  Upload   Total   Spent    Left  Speed\n100 1173M  100 1173M    0     0  97.7M      0  0:00:11  0:00:11 --:--:-- 75.9M\n\n# file rhcos-417.94.202408270355-0-live.x86_64.iso\nrhcos-417.94.202408270355-0-live.x86_64.iso: ISO 9660 CD-ROM filesystem data (DOS\/MBR boot sector) 'rhcos-417.94.202408270355-0' (bootable)<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>I could now boot this ISO straightaway but then it would take me to the shell and I had to run all necessary installation commands manually through the console. For this exercise I look for automated installation process. Luckily all the necessary information is already existing in the cluster.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Getting Ignition file for a compute node<\/h3>\n\n\n\n<p>Let&#8217;s start first with the Ignition file for worker (compute) nodes. I can get generic one from <code>worker-user-data-managed<\/code> Secret resource stored in <code>openshift-machine-api<\/code> project: <\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># oc -n openshift-machine-api extract secret\/worker-user-data-managed --keys userData --to=- &gt; worker.userData.ign<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Creating NMState file<\/h3>\n\n\n\n<p>If my new node would have just a single NIC connected to network with DHCP server I&#8217;d skip this step and move to the next paragraph. However since I have multiple NICs, have no DHCP server in the network and on the top of everything I&#8217;d like to get configured bonded logical interface, I have to provide network configuration in advance, so when the system boots from the ISO it will get it properly configured. Fortunately <code>coreos-installer<\/code> can include network configuration in NMState format so let&#8217;s prepare the file right now. In my case the content of file is as follows:<\/p>\n\n\n\n<pre class=\"wp-block-code has-foreground-color has-tertiary-background-color has-text-color has-background\"><code>interfaces:\n  - name: bond0\n    type: bond\n    state: up\n    link-aggregation:\n      mode: active-backup\n      options:\n        primary: enp1s0\n      port:\n        - enp1s0\n        - enp2s0\n    ipv4:\n      dhcp: false\n      enabled: true\n      address:\n        - ip: 192.168.232.103\n          prefix-length: 24\n  - name: enp1s0\n    type: ethernet\n    state: up\n    ipv4:\n      enabled: false\n      dhcp: false\n  - name: enp2s0\n    type: ethernet\n    state: up\n    ipv4:\n      enabled: false\n      dhcp: false\n  - name: enp3s0\n    type: ethernet\n    state: up\n    ipv4:\n      enabled: false\n      dhcp: false\n  - name: enp4s0\n    type: ethernet\n    state: up\n    ipv4:\n      enabled: false\n      dhcp: false\nroutes:\n  config:\n    - destination: 0.0.0.0\/0\n      next-hop-address: 192.168.232.1\n      next-hop-interface: bond0\n      table-id: 254\ndns-resolver:\n  config:\n    server:\n      - 192.168.232.1<\/code><\/pre>\n\n\n\n<p>The full list of options including examples is available at the official NMState project page:&nbsp;<a href=\"https:\/\/nmstate.io\/\">https:\/\/nmstate.io\/<\/a>.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Customising RHCOS ISO image for an individual node<\/h3>\n\n\n\n<p>Each node requires unique hostname and IP address assigned. Since I don&#8217;t run DHCP in my lab I have to somehow pass this information to the bootstrap process in the ISO image.<\/p>\n\n\n\n<p>Hostname configuration cannot be passed via NMState and therefore I need to slightly modify the Ignition file. The command below will create new, personalised Ignition file which will create \/etc\/hostname file upon system bootstrap containing the expected hostname value:<\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># export NEW_HOSTNAME=compute-0\n\n# cat worker.userData.ign  | jq '. += {\"storage\": {\"files\": &#91;{\"path\": \"\/etc\/hostname\", \"contents\": {\"source\": \"data:,'${NEW_HOSTNAME}'\"}, \"mode\": 420}]}}' &gt; ${NEW_HOSTNAME}.userData.ign<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>And just do double-check:<\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># cat compute-0.userData.ign |  jq  .storage\n{\n  \"files\": &#91;\n    {\n      \"path\": \"\/etc\/hostname\",\n      \"contents\": {\n        \"source\": \"data:,compute-0\"\n      },\n      \"mode\": 420\n    }\n  ]\n}<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>So now I should have the following files prepared:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>compute-0.bmh.yaml<\/code> &#8211; BareMetalHost definition for the new node, this resource has been already created in the previous step.<\/li>\n\n\n\n<li><code>compute-0.userData.ign <\/code>&#8211; customized userData file for the new node, it includes \/etc\/hostname file content and should be used exclusively with compute-0 node<\/li>\n\n\n\n<li><code>compute-0.nmstate.yaml<\/code> &#8211; NMState config file for compute-0 node, includes static IP configuration and bonded interfaces<\/li>\n\n\n\n<li><code>rhcos-417.94.202408270355-0-live.x86_64.iso<\/code> &#8211; vanilla RHCOS ISO image<\/li>\n\n\n\n<li><code>worker.userData.ign<\/code> &#8211; generic userData for worker nodes which I used to create compute-0.userData.ign from it<\/li>\n<\/ul>\n\n\n\n<p>The command bellow will create the ISO file to bootstrap the server and use \/<code>dev\/sda<\/code> device as root device. <mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\"><strong>Please note the warning message &#8211; it will overwrite <code>\/dev\/sda<\/code> disk without any warning nor confirmation<\/strong><\/mark> once booted!<\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># export NEW_HOSTNAME=compute-0\n# coreos-installer iso customize --network-nmstate compute-0.nmstate.yaml --dest-device \/dev\/sda --dest-ignition ${NEW_HOSTNAME}.userData.ign rhcos-417.94.202408270355-0-live.x86_64.iso -o ${NEW_HOSTNAME}.rhcos.iso\nBoot media will automatically install to \/dev\/sda without confirmation.<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>The output file <code>compute-0.rhcos.iso<\/code> is ready to be used to bootstrap compute-0 node (and only this node as it has hardcoded IP and hostname configurations).<\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># file compute-0.rhcos.iso\ncompute-0.rhcos.iso: ISO 9660 CD-ROM filesystem data (DOS\/MBR boot sector) 'rhcos-417.94.202408270355-0' (bootable)<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Bootstrapping the new server with the customised ISO image<\/h3>\n\n\n\n<p>Depends on the hardware configuration I could burn this ISO on DVD, create bootable USB stick or use Virtual Media feature of my out-of-band management interface. Since in my lab I have Sushy tools emulating Redfish interface, I will use it to boot the ISO remotely from http server.<\/p>\n\n\n\n<p>Once I copied the ISO to the system where I run accessible from the OOBM network http server, I just want to verify all the permissions are set right to avoid unnecessary troubleshooting later: <\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># curl -I http:\/\/192.168.232.128\/compute-0.rhcos.iso\nHTTP\/1.1 200 OK\nServer: nginx\/1.20.1\nContent-Type: application\/octet-stream\nContent-Length: 1229979648\nConnection: keep-alive\nAccept-Ranges: bytes<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>Now please note that the commands I run here and outputs depends on the OOBM interface implementation. This is how it looks like for Redfish provided by Sushy tools on virtualised systems pretending to be bare metals. In case of any other management interfaces, including these supporting Redfish it will most likely look differently.<\/p>\n\n\n\n<p>The first POST request with a JSON payload made with curl is to mount the ISO from http server:<\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># curl -k -d '{\"Image\":\"http:\/\/192.168.232.128\/compute-0.rhcos.iso\", \"Inserted\": true}' -H \"Content-Type: application\/json\" -X POST https:\/\/192.168.232.1:8000\/redfish\/v1\/Managers\/80db3a4f-930c-4f5f-b4d0-cf18356fe9a5\/VirtualMedia\/Cd\/Actions\/VirtualMedia.InsertMedia<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>To verify it is successfully mounted I run another curl GET request:<\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># curl -k https:\/\/192.168.232.1:8000\/redfish\/v1\/Managers\/80db3a4f-930c-4f5f-b4d0-cf18356fe9a5\/VirtualMedia\/Cd\/ | jq .\n{\n  \"@odata.type\": \"#VirtualMedia.v1_1_0.VirtualMedia\",\n  \"Id\": \"Cd\",\n  \"Name\": \"Virtual CD\",\n  \"MediaTypes\": &#91;\n    \"CD\",\n    \"DVD\"\n  ],\n  \"Image\": \"compute-0.rhcos.iso\",\n  \"ImageName\": \"\",\n  \"ConnectedVia\": \"URI\",\n  \"Inserted\": true,\n  \"WriteProtected\": false,\n  \"Actions\": {\n    \"#VirtualMedia.EjectMedia\": {\n      \"target\": \"\/redfish\/v1\/Managers\/80db3a4f-930c-4f5f-b4d0-cf18356fe9a5\/VirtualMedia\/Cd\/Actions\/VirtualMedia.EjectMedia\"\n    },\n    \"#VirtualMedia.InsertMedia\": {\n      \"target\": \"\/redfish\/v1\/Managers\/80db3a4f-930c-4f5f-b4d0-cf18356fe9a5\/VirtualMedia\/Cd\/Actions\/VirtualMedia.InsertMedia\"\n    },\n    \"Oem\": {}\n  },\n  \"@odata.context\": \"\/redfish\/v1\/$metadata#VirtualMedia.VirtualMedia\",\n  \"@odata.id\": \"\/redfish\/v1\/Managers\/80db3a4f-930c-4f5f-b4d0-cf18356fe9a5\/VirtualMedia\/Cd\",\n  \"@Redfish.Copyright\": \"Copyright 2014-2017 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http:\/\/www.dmtf.org\/about\/policies\/copyright.\"<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p><strong><mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">Important:<\/mark><\/strong> Before I turn on the server I need to ensure it has configured correct boot order. Ideally it should first attempt to boot from (empty) HDD then try with DVD. This may not work if I have previously installed system still on the disk. Then I&#8217;d need to enforce system to temporarily boot from the DVD and later, once bootstrap will be completed and system reboot on itself, it will start from HDD. Failing to configure it properly may result in either DVD boot loop (and each result will bootstrap the node) or booting previously installed system. This may be quite funny to troubleshoot if the previously installed system was RHCOS for another OpenShift deployment. System will boot like regular, properly provisioned node but it won&#8217;t register with the cluster. I&#8217;m telling you &#8211; troubleshooting it is quite funny experience \ud83d\ude09<\/p>\n\n\n\n<p>Since I ensured the right boot order is set and disk drive on the new node is empty I can turn on the system and let it bootstrap itself:<\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># curl -k -X POST -H \"Content-Type: application\/json\" -d '{\"ResetType\":\"On\"}' https:\/\/192.168.232.1:8000\/redfish\/v1\/Systems\/80db3a4f-930c-4f5f-b4d0-cf18356fe9a\/Actions\/ComputerSystem.Reset<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>To ensure it really boots from the ISO I opened the console. &#8220;RHEL CoreOS (Live)&#8221; GRUB menu item below confirms it starts from the Live ISO and not from the local disk drive:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"745\" src=\"https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-1-1024x745.png\" alt=\"\" class=\"wp-image-252\" srcset=\"https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-1-1024x745.png 1024w, https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-1-300x218.png 300w, https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-1-768x558.png 768w, https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-1-1536x1117.png 1536w, https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-1.png 1942w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Once booted it will start with the installation. I can observe its progress on the console as well:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"729\" src=\"https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-2-1024x729.png\" alt=\"\" class=\"wp-image-256\" srcset=\"https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-2-1024x729.png 1024w, https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-2-300x214.png 300w, https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-2-768x547.png 768w, https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-2-1536x1094.png 1536w, https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-2-2048x1458.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>During the bootstrap process system may reboot twice. First time to boot from the local drive using RHCOS installed from the live ISO &#8211; it will install the lates version of RHCOS, second boot is the final one &#8211; from the local drive but with the recent RHCOS version as I can see below (please note the 2nd entry in the GRUB menu is the previous version of RHCOS installed from the Live ISO, the 1st entry is the newest one that will start shortly):<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"717\" src=\"https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-3-1024x717.png\" alt=\"\" class=\"wp-image-257\" srcset=\"https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-3-1024x717.png 1024w, https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-3-300x210.png 300w, https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-3-768x538.png 768w, https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-3-1536x1075.png 1536w, https:\/\/blog.openshift.one\/wp-content\/uploads\/2024\/10\/image-3-2048x1434.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Before the new node will join the cluster, I need to approve three &#8220;Certificate Signing Requests&#8221; as bellow. They will occur one after another so it is convenient to run <code>oc get csr -w<\/code> command in one terminal and approve them in the other when occurred.<\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background has-small-font-size\"><code># oc get csr -w\nNAME        AGE   SIGNERNAME                                    REQUESTOR                                                                   REQUESTEDDURATION   CONDITION\ncsr-n7n5q   0s    kubernetes.io\/kube-apiserver-client-kubelet   system:serviceaccount:openshift-machine-config-operator:node-bootstrapper   &lt;none&gt;              Pending\ncsr-r6jtp   0s    kubernetes.io\/kube-apiserver-client-kubelet   system:serviceaccount:openshift-machine-config-operator:node-bootstrapper   &lt;none&gt;              Pending\ncsr-n7n5q   17s   kubernetes.io\/kube-apiserver-client-kubelet   system:serviceaccount:openshift-machine-config-operator:node-bootstrapper   &lt;none&gt;              Approved\ncsr-n7n5q   17s   kubernetes.io\/kube-apiserver-client-kubelet   system:serviceaccount:openshift-machine-config-operator:node-bootstrapper   &lt;none&gt;              Approved,Issued\ncsr-r6jtp   16s   kubernetes.io\/kube-apiserver-client-kubelet   system:serviceaccount:openshift-machine-config-operator:node-bootstrapper   &lt;none&gt;              Approved\ncsr-r6jtp   16s   kubernetes.io\/kube-apiserver-client-kubelet   system:serviceaccount:openshift-machine-config-operator:node-bootstrapper   &lt;none&gt;              Approved,Issued\ncsr-wwpp2   0s    kubernetes.io\/kubelet-serving                 system:node:compute-0                                                       &lt;none&gt;              Pending\ncsr-wwpp2   13s   kubernetes.io\/kubelet-serving                 system:node:compute-0                                                       &lt;none&gt;              Approved\ncsr-wwpp2   13s   kubernetes.io\/kubelet-serving                 system:node:compute-0                                                       &lt;none&gt;              Approved,Issued<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background has-small-font-size\"><code># oc adm certificate approve csr-n7n5q\ncertificatesigningrequest.certificates.k8s.io\/csr-n7n5q approved\n\n# oc adm certificate approve csr-r6jtp\ncertificatesigningrequest.certificates.k8s.io\/csr-r6jtp approved\n\n# oc adm certificate approve csr-wwpp2\ncertificatesigningrequest.certificates.k8s.io\/csr-wwpp2 approved<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>Once all required CSR have been approved the new node will appear on the nodes list and hopefully turn into &#8220;Ready&#8221; state<\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># oc get nodes -w\nNAME       STATUS   ROLES                         AGE   VERSION\nmaster-0   Ready    control-plane,master,worker   3d   v1.30.4\nmaster-1   Ready    control-plane,master,worker   3d   v1.30.4\nmaster-2   Ready    control-plane,master,worker   3d   v1.30.4\ncompute-0   NotReady   worker                        0s    v1.30.4\ncompute-0   NotReady   worker                        53s   v1.30.4\ncompute-0   Ready      worker                        64s   v1.30.4<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background has-small-font-size\"><code># oc get nodes,machines,machinesets,bmh,mcp\n\nNAME             STATUS   ROLES                         AGE   VERSION\nnode\/compute-0   Ready    worker                        10m   v1.30.4\nnode\/master-0    Ready    control-plane,master,worker   3d    v1.30.4\nnode\/master-1    Ready    control-plane,master,worker   3d    v1.30.4\nnode\/master-2    Ready    control-plane,master,worker   3d    v1.30.4\n\nNAME                                               PHASE     TYPE   REGION   ZONE   AGE\nmachine.machine.openshift.io\/ocp4-l2k5k-master-0   Running                          3d\nmachine.machine.openshift.io\/ocp4-l2k5k-master-1   Running                          3d\nmachine.machine.openshift.io\/ocp4-l2k5k-master-2   Running                          3d\n\nNAME                                                  DESIRED   CURRENT   READY   AVAILABLE   AGE\nmachineset.machine.openshift.io\/ocp4-l2k5k-worker-0   0         0                             3d\n\nNAME                                STATE       CONSUMER              ONLINE   ERROR   AGE\nbaremetalhost.metal3.io\/compute-0   unmanaged                         true             20m\nbaremetalhost.metal3.io\/master-0    unmanaged   ocp4-l2k5k-master-0   true             3d\nbaremetalhost.metal3.io\/master-1    unmanaged   ocp4-l2k5k-master-1   true             3d\nbaremetalhost.metal3.io\/master-2    unmanaged   ocp4-l2k5k-master-2   true             3d\n\nNAME     CONFIG                                             UPDATED   UPDATING   DEGRADED   MACHINECOUNT   READYMACHINECOUNT   UPDATEDMACHINECOUNT   DEGRADEDMACHINECOUNT   AGE\nmaster   rendered-master-3980d9fd20abd3edb06d23c72c4ee7e1   True      False      False      3              3                   3                     0                      3d\nworker   rendered-worker-f2e82a9776085629a65e45a6a2751456   True      False      False      1              1                   1                     0                      3d<\/code><\/pre>\n\n\n\n<p>The node is now fully functional and recognised by the cluster, including MachineConfig operator. New workloads can be scheduled on it already.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Missing bits<\/h2>\n\n\n\n<p>While the node is up and running and I could stop now, there are two bits missing on the outputs above:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>BareMetalHost<\/code> compute-0 has no consumer defined<\/li>\n\n\n\n<li>There is no <code>Machine<\/code> resource for compute-0 node<\/li>\n<\/ul>\n\n\n\n<p>They would be normally configured by the automation as happened in <a href=\"https:\/\/blog.openshift.one\/index.php\/2024\/10\/07\/scaling-openshift-compute-worker-baremetal-nodes-part-1\/\">Scaling OpenShift compute (worker) BareMetal nodes &#8211; Part 1<\/a> exercise, but since in this case I&#8217;m responsible for &#8220;automating&#8217; things I have to create them manually.<\/p>\n\n\n\n<p>Firstly I need to get UID of the <code>BareMetalHost<\/code> compute-0:<\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># oc -n openshift-machine-api get bmh compute-0 -o json | jq -r .metadata.uid\ne8933748-c06e-4e35-9bab-02b7a3f3619a<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>Now with that information I will reference <code>Node<\/code> <em>compute-0<\/em> to <code>BareMetalHost<\/code> <em>compute-0<\/em>:<\/p>\n\n\n\n<p><mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\"><strong>Please note how <code>providerID<\/code> path is being build<\/strong><\/mark>: it is in <em>baremetalhost:\/\/\/openshift-machine-api\/${<strong>BMH_NAME<\/strong>}\/${<strong>BMH_UID<\/strong>}<\/em> format<\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># oc patch node compute-0 -p '{\"spec\": {\"providerID\": \"baremetalhost:\/\/\/openshift-machine-api\/<strong>compute-0<\/strong>\/<strong>e8933748-c06e-4e35-9bab-02b7a3f3619a<\/strong>\"}}'\nnode\/compute-0 patched<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>The last missing resource is a <code>Machine<\/code>. I will create it from the following template:<\/p>\n\n\n\n<pre class=\"wp-block-code has-foreground-color has-tertiary-background-color has-text-color has-background\"><code>apiVersion: machine.openshift.io\/v1beta1\nkind: Machine\nmetadata:\n  name: compute-0\n  annotations:\n    machine.openshift.io\/instance-state: unmanaged\n    metal3.io\/BareMetalHost: openshift-machine-api\/compute-0\n  labels:\n    machine.openshift.io\/cluster-api-machine-role: worker\n    machine.openshift.io\/cluster-api-machine-type: worker\nspec:\n  metadata: {}\n  providerSpec:\n    value:\n      apiVersion: baremetal.cluster.k8s.io\/v1alpha1\n      customDeploy:\n        method: install_coreos\n      hostSelector: {}\n      image:\n        checksum: ''\n        url: ''\n      kind: BareMetalMachineProviderSpec\n      metadata:\n        creationTimestamp: null\n      userData:\n        name: worker-user-data-managed<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># oc create -f compute-0.machine.yaml\nmachine.machine.openshift.io\/compute-0 created<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>Now if I review the status of all the resources I&#8217;ve been tinkering with, it looks much more elegant:<\/p>\n\n\n\n<p>Please note <code>MachineSet<\/code> does not include this newly added compute-0 node as it does not manage it.<\/p>\n\n\n\n<pre class=\"wp-block-code has-background-color has-foreground-background-color has-text-color has-background\"><code># oc get nodes,machines,machinesets,bmh,mcp\nNAME             STATUS   ROLES                         AGE   VERSION\nnode\/compute-0   Ready    worker                        21m   v1.30.4\nnode\/master-0    Ready    control-plane,master,worker   3d    v1.30.4\nnode\/master-1    Ready    control-plane,master,worker   3d    v1.30.4\nnode\/master-2    Ready    control-plane,master,worker   3d    v1.30.4\n\nNAME                                               PHASE     TYPE   REGION   ZONE   AGE\nmachine.machine.openshift.io\/compute-0             Running                          4m3s\nmachine.machine.openshift.io\/ocp4-l2k5k-master-0   Running                          3d\nmachine.machine.openshift.io\/ocp4-l2k5k-master-1   Running                          3d\nmachine.machine.openshift.io\/ocp4-l2k5k-master-2   Running                          3d\n\nNAME                                                  DESIRED   CURRENT   READY   AVAILABLE   AGE\nmachineset.machine.openshift.io\/ocp4-l2k5k-worker-0   0         0                             3d\n\nNAME                                STATE       CONSUMER              ONLINE   ERROR   AGE\nbaremetalhost.metal3.io\/compute-0   unmanaged   compute-0             true             42m\nbaremetalhost.metal3.io\/master-0    unmanaged   ocp4-l2k5k-master-0   true             3d\nbaremetalhost.metal3.io\/master-1    unmanaged   ocp4-l2k5k-master-1   true             3d\nbaremetalhost.metal3.io\/master-2    unmanaged   ocp4-l2k5k-master-2   true             3d\n\nNAME                                                         CONFIG                                             UPDATED   UPDATING   DEGRADED   MACHINECOUNT   READYMACHINECOUNT   UPDATEDMACHINECOUNT   DEGRADEDMACHINECOUNT   AGE\nmachineconfigpool.machineconfiguration.openshift.io\/master   rendered-master-3980d9fd20abd3edb06d23c72c4ee7e1   True      False      False      3              3                   3                     0                      3d\nmachineconfigpool.machineconfiguration.openshift.io\/worker   rendered-worker-f2e82a9776085629a65e45a6a2751456   True      False      False      1              1                   1                     0                      3d<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p>In this exercise I&#8217;ve demonstrated how to manually add new compute node to the existing OpenShift cluster. While it isn&#8217;t very straightforward process compared to the fully automated one presented in part 1, all the necessary bits are available in the platform. The only challenge is to know what and where they are.<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the first post from this series &#8211; Scaling OpenShift compute (worker) BareMetal nodes &#8211; Part 1 &#8211; I explained the fully automated process of OpenShift baremetal node bootstrap using out-of-band-management (OOBM) interface and virtual media functionality. While it is very convenient and time efficient, sometimes it does not fit to the environment where the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[24,6],"tags":[25,9],"class_list":["post-235","post","type-post","status-publish","format-standard","hentry","category-baremetal","category-openshift","tag-baremetal","tag-openshift"],"_links":{"self":[{"href":"https:\/\/blog.openshift.one\/index.php\/wp-json\/wp\/v2\/posts\/235","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.openshift.one\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.openshift.one\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.openshift.one\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.openshift.one\/index.php\/wp-json\/wp\/v2\/comments?post=235"}],"version-history":[{"count":31,"href":"https:\/\/blog.openshift.one\/index.php\/wp-json\/wp\/v2\/posts\/235\/revisions"}],"predecessor-version":[{"id":283,"href":"https:\/\/blog.openshift.one\/index.php\/wp-json\/wp\/v2\/posts\/235\/revisions\/283"}],"wp:attachment":[{"href":"https:\/\/blog.openshift.one\/index.php\/wp-json\/wp\/v2\/media?parent=235"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.openshift.one\/index.php\/wp-json\/wp\/v2\/categories?post=235"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.openshift.one\/index.php\/wp-json\/wp\/v2\/tags?post=235"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}