Skip to content

Commit 5b0ce31

Browse files
authored
Customizable userdata for PXE boot (#31)
Customizable metalman userdata
1 parent f4f27c6 commit 5b0ce31

12 files changed

Lines changed: 805 additions & 65 deletions

File tree

api/v1alpha3/machine_types.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,38 @@ type PXESpec struct {
191191
// Redfish configures optional Redfish BMC access.
192192
// +optional
193193
Redfish *RedfishSpec `json:"redfish,omitempty"`
194+
195+
// CloudInit contains optional cloud-init customization for PXE-booted
196+
// machines.
197+
// +optional
198+
CloudInit *CloudInitSpec `json:"cloudInit,omitempty"`
199+
}
200+
201+
// CloudInitSpec defines cloud-init customization for PXE-booted machines.
202+
// Cloud-init merges vendor-data (managed by unbounded-kube) with user-data
203+
// (managed by the cluster operator). This spec controls the user-data
204+
// portion, allowing operators to configure SSH keys, install packages,
205+
// and perform other host-level customization.
206+
type CloudInitSpec struct {
207+
// UserDataConfigMapRef references a ConfigMap containing custom
208+
// cloud-init user-data. The referenced key (default "user-data")
209+
// must contain a valid cloud-init configuration (e.g. a
210+
// #cloud-config YAML document).
211+
// +optional
212+
UserDataConfigMapRef *ConfigMapKeySelector `json:"userDataConfigMapRef,omitempty"`
213+
}
214+
215+
// ConfigMapKeySelector selects a key from a ConfigMap.
216+
type ConfigMapKeySelector struct {
217+
// Name of the ConfigMap.
218+
Name string `json:"name"`
219+
220+
// Namespace of the ConfigMap.
221+
Namespace string `json:"namespace"`
222+
223+
// Key within the ConfigMap.
224+
// +kubebuilder:default=user-data
225+
Key string `json:"key,omitempty"`
194226
}
195227

196228
// KubernetesSpec defines Kubernetes-specific configuration for a Machine.

api/v1alpha3/zz_generated.deepcopy.go

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deploy/machina/06-metalman-rbac.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ rules:
117117
- apiGroups: [""]
118118
resources: ["secrets"]
119119
verbs: ["get"]
120+
# ConfigMaps (cloud-init user-data)
121+
- apiGroups: [""]
122+
resources: ["configmaps"]
123+
verbs: ["get", "list", "watch"]
120124
# Nodes (cloud provider detection)
121125
- apiGroups: [""]
122126
resources: ["nodes"]

deploy/machina/crd/unbounded-kube.io_machines.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,33 @@ spec:
132132
pxe:
133133
description: PXE contains PXE boot configuration for the machine.
134134
properties:
135+
cloudInit:
136+
description: |-
137+
CloudInit contains optional cloud-init customization for PXE-booted
138+
machines.
139+
properties:
140+
userDataConfigMapRef:
141+
description: |-
142+
UserDataConfigMapRef references a ConfigMap containing custom
143+
cloud-init user-data. The referenced key (default "user-data")
144+
must contain a valid cloud-init configuration (e.g. a
145+
#cloud-config YAML document).
146+
properties:
147+
key:
148+
default: user-data
149+
description: Key within the ConfigMap.
150+
type: string
151+
name:
152+
description: Name of the ConfigMap.
153+
type: string
154+
namespace:
155+
description: Namespace of the ConfigMap.
156+
type: string
157+
required:
158+
- name
159+
- namespace
160+
type: object
161+
type: object
135162
dhcpLeases:
136163
description: DHCPLeases defines static DHCP leases for PXE booting.
137164
items:

docs/content/concepts/bare-metal.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,15 @@ For PXE-provisioned machines, the `Machine` resource includes:
8989
assignment for each interface.
9090
- **`spec.pxe.redfish`** -- Optional BMC connection details (endpoint, username,
9191
password secret) for remote power management.
92+
- **`spec.pxe.cloudInit`** -- Optional cloud-init customization. References a
93+
ConfigMap containing user-data that is merged with the vendor-data managed by
94+
unbounded-kube.
9295

93-
### Pool Isolation
96+
### Site Isolation
9497

9598
In environments with multiple metalman instances (e.g., different racks or
96-
sites), the `--pool` flag scopes each instance to machines labeled with
97-
`unbounded-kube.io/pool=<name>`. This prevents one metalman from interfering
99+
sites), the `--site` flag scopes each instance to machines labeled with
100+
`unbounded-kube.io/site=<name>`. This prevents one metalman from interfering
98101
with another's machines.
99102

100103
### TPM 2.0 Attestation

docs/content/guides/pxe.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,57 @@ spec:
9696
9797
Store BMC passwords in a Secret referenced by `passwordRef`. See the [CRD Reference]({{< relref "/reference/machina-crd" >}}) for all fields.
9898

99+
## Cloud-Init Customization
100+
101+
Cloud-init on PXE-booted machines uses two data sources that are merged at boot:
102+
103+
- **Vendor-data** (managed by unbounded-kube) -- Contains the agent configuration, bootstrap scripts, and system defaults required for the node to join the cluster. This is not user-editable.
104+
- **User-data** (managed by the cluster operator) -- Optional customization such as SSH keys, additional packages, or host-level configuration.
105+
106+
When no user-data is configured, metalman serves a minimal `#cloud-config` document. To supply custom user-data, create a ConfigMap and reference it from the Machine spec:
107+
108+
```yaml
109+
apiVersion: v1
110+
kind: ConfigMap
111+
metadata:
112+
name: my-cloud-init
113+
namespace: unbounded-kube
114+
data:
115+
user-data: |
116+
#cloud-config
117+
ssh_authorized_keys:
118+
- ssh-rsa AAAA...
119+
packages:
120+
- vim
121+
- htop
122+
```
123+
124+
Then reference the ConfigMap in the Machine:
125+
126+
```yaml
127+
apiVersion: unbounded-kube.io/v1alpha3
128+
kind: Machine
129+
metadata:
130+
name: server-01
131+
spec:
132+
pxe:
133+
image: ghcr.io/azure/images/host-ubuntu2404:v1
134+
dhcpLeases:
135+
- ipv4: "10.10.0.50"
136+
mac: "aa:bb:cc:dd:ee:ff"
137+
subnetMask: "255.255.255.0"
138+
gateway: "10.10.0.1"
139+
dns: ["8.8.8.8"]
140+
cloudInit:
141+
userDataConfigMapRef:
142+
name: my-cloud-init
143+
namespace: unbounded-kube
144+
```
145+
146+
The `key` field defaults to `user-data` but can be overridden to select a different key from the ConfigMap. Both `data` and `binaryData` entries are supported.
147+
148+
If the referenced ConfigMap does not exist, metalman falls back to the default minimal cloud-config. If the ConfigMap exists but the referenced key is not found, metalman returns an error and the machine will not receive user-data.
149+
99150
## Boot Flow
100151

101152
1. **Machine CR created.** The Redfish reconciler sets the boot device to PXE and power-cycles the server (ForceOff → On).

docs/content/reference/machina-crd.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ PXE boot configuration consumed by the metalman controller.
6464
| `pxe.redfish.username` | string | Yes || Redfish username. |
6565
| `pxe.redfish.deviceID` | string | No | `"1"` | Redfish system device ID. |
6666
| `pxe.redfish.passwordRef` | SecretKeySelector | Yes || Secret containing the Redfish password. |
67+
| `pxe.cloudInit` | CloudInitSpec | No || Optional cloud-init customization for PXE-booted machines. |
68+
| `pxe.cloudInit.userDataConfigMapRef` | ConfigMapKeySelector | No || Reference to a ConfigMap containing custom cloud-init user-data. |
69+
| `pxe.cloudInit.userDataConfigMapRef.name` | string | Yes || ConfigMap name. |
70+
| `pxe.cloudInit.userDataConfigMapRef.namespace` | string | Yes || ConfigMap namespace. |
71+
| `pxe.cloudInit.userDataConfigMapRef.key` | string | No | `"user-data"` | Key within the ConfigMap. |
6772

6873
### spec.kubernetes
6974

@@ -212,6 +217,10 @@ spec:
212217
passwordRef:
213218
name: bmc-password
214219
namespace: unbounded-kube
220+
cloudInit:
221+
userDataConfigMapRef:
222+
name: my-cloud-init
223+
namespace: unbounded-kube
215224
kubernetes:
216225
version: v1.34.0
217226
bootstrapTokenRef:

images/host-ubuntu2404/Containerfile

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,6 @@ RUN if [ "${TARGETARCH}" = "amd64" ]; then \
129129
echo "dhcpBootImageName: shimaa64.efi" > /disk/metadata.yaml; \
130130
fi
131131

132-
# User-data is now a Go template rendered per-node by metalman.
133-
# The unbounded-agent handles kubelet/containerd/TPM attestation
134-
# internally, so no attest script injection or kubelet configs are needed.
135-
COPY images/host-ubuntu2404/assets/user-data.tmpl /disk/cloud-init/user-data.tmpl
136-
137132
# ── Download and convert Ubuntu cloud image to raw+gzip ─────────────
138133
# Cloud image URL is parameterized by architecture.
139134
ARG UBUNTU_CLOUDIMG_URL=https://cloud-images.ubuntu.com/releases/noble/release/ubuntu-24.04-server-cloudimg-${TARGETARCH}.img

images/host-ubuntu2404/assets/user-data.tmpl

Lines changed: 0 additions & 32 deletions
This file was deleted.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,36 @@
11
#cloud-config
2+
manage_etc_hosts: true
3+
growpart:
4+
mode: auto
5+
devices:
6+
- /
7+
resize_rootfs: true
8+
swap:
9+
size: 0
10+
users:
11+
- name: unbounded-metal
12+
lock_passwd: true
13+
shell: /bin/bash
14+
sudo: ALL=(ALL) NOPASSWD:ALL
15+
ssh_pwauth: false
216
reporting:
317
unbounded:
418
type: webhook
519
endpoint: {{ .ServeURL }}/cloudinit/log
20+
write_files:
21+
- path: /etc/unbounded-agent/config.json
22+
permissions: '0600'
23+
content: |
24+
{{ .AgentConfigJSON }}
25+
- path: /etc/cloud/cloud-init.disabled
26+
defer: true
27+
content: ''
28+
runcmd:
29+
- - /bin/bash
30+
- -c
31+
- |
32+
set -eo pipefail
33+
export UNBOUNDED_AGENT_CONFIG_FILE=/etc/unbounded-agent/config.json
34+
echo "Running unbounded-agent start..."
35+
/usr/local/bin/unbounded-agent start
36+
echo "Node bootstrap complete."

0 commit comments

Comments
 (0)