Machine¶
The ghcr.io/captf-io/oci-machine image implements the machine role for a TerraformMachine on OCI: one compute instance per Machine, placed in the Machine’s failure domain on the cluster’s subnet and network security group and, for a control-plane machine, registered in the API load balancer.
What it creates¶
| Resource | Purpose | When |
|---|---|---|
oci_core_instance.node_instance | The node and its boot volume | Always |
oci_network_load_balancer_backend.api_backends | The instance in each API backend set (kube-apiserver, and rke2-supervisor with RKE2) | control_plane and a module-owned endpoint |
The instance is named machine_name, the first key the OCI cloud controller manager looks a node up by. It runs image_id on VM.Standard.E5.Flex with 2 OCPUs and 16 GB by default, on a 100 GiB boot volume that is encrypted at rest (with boot_volume_kms_key_id when set) and in transit, and that goes with the instance when it terminates. It has no public IP and no SSH key unless you ask, and instance metadata v1 is off. A control-plane machine carries the cluster’s control-plane defined tag, which the node dynamic group matches; a worker carries the worker tag.
Inputs¶
Contract inputs used: captf_cluster_outputs (or external_cluster_exports), captf_tags, machine_name, bootstrap_data, bootstrap_format, failure_domain and control_plane. captf_contract is validated; captf_cluster, captf_object and kubernetes_version are declared and unused: the image carries the version.
User variables, from variables.tf:
| Name | Type | Default | Description |
|---|---|---|---|
additional_nsg_ids | list(string) | [] | Extra network security groups for the VNIC, after the cluster’s; at most 4. |
additional_tags | map(string) | {} | Extra free-form tags for the instance and its VNIC; at most 4. |
boot_volume_kms_key_id | string | null | Vault key for the boot volume; null uses Oracle-managed keys. |
boot_volume_size_gib | number | 100 | Boot volume size, 50 to 32768. |
external_cluster_exports | any | null | The cluster’s exports when the TerraformCluster is externally managed. |
ignore_defined_tags | list(string) | [] | Tag-default keys (<namespace>.<key>) to leave alone. |
image_id | string | null | Required. Node image OCID in the cluster’s region. |
memory_gib | number | 16 | Memory of a flexible shape; ignored for fixed shapes. |
ocpus | number | 2 | OCPUs of a flexible shape (one OCPU is two vCPUs on x86); ignored for fixed shapes. |
preemptible | bool | false | Preemptible capacity; workers only. |
public_ip | bool | false | A public IP on the VNIC; needs a public subnet. |
pv_encryption_in_transit | bool | true | Encrypt boot volume traffic in transit. |
shape | string | "VM.Standard.E5.Flex" | Compute shape. |
ssh_authorized_keys | list(string) | [] | SSH public keys for the image’s default user. |
subnet_id | string | null | Subnet of the VNIC; null uses the cluster’s control-plane or worker subnet. |
The image’s io.captf.capacity label ({"cpu":"4","memory":"16Gi"}, amd64) describes the default shape, for Cluster Autoscaler scale from zero. A template that changes the shape needs an image built with matching labels.
Outputs¶
| Output | Value |
|---|---|
provider_id | oci://<instance OCID>, the format the OCI cloud controller manager writes (ProviderName() + "://" + InstanceID, ccm.go) |
addresses | InternalIP (the primary private IP) and ExternalIP (the public IP, when there is one), as the cloud controller manager reports them |
failure_domain | The requested failure domain, or the one picked from machine_name |
interruptible | true for a preemptible instance |
health | From the instance’s lifecycle state (below) |
A requested failure domain must be one of the cluster’s; the instance goes to its availability domain and, in fault-domain mode, its fault domain. Without one, the module picks from the sorted names by the sha256 of machine_name, so a new plan never moves the machine.
Health¶
| Instance state | Contract state | Reason |
|---|---|---|
PROVISIONING, STARTING | pending | InstanceProvisioning, InstanceStarting |
RUNNING, MOVING (live migration) | running | none |
CREATING_IMAGE | unknown | InstanceCreatingImage |
STOPPING, STOPPED | stopped | InstanceStopping, InstanceStopped |
TERMINATING, TERMINATED, or gone from state | terminated | InstanceNotFound |
| anything else | unknown | UnknownState |
The provider drops a TERMINATED instance from state when it reads it; the instance is counted, so the module still reads that as terminated.
Lifecycle¶
A machine never updates in place. A change to its bootstrap data or SSH keys replaces the instance (the provider forces it), which is what an immutable Machine expects; CAPI rolls machines through a new template instead. Destroying a control-plane machine removes its load balancer backends with it.
Bootstrap¶
- Delivery.
metadata.user_dataisbootstrap_dataunchanged, since OCI takes user data base64-encoded: cloud-config (CABPK’s Jinja header included), Ignition and gzipped cloud-config alike. Gzipped Ignition is refused. - Size. Instance metadata, user data and SSH keys together, may hold 32,000 bytes; a precondition stops a larger payload.
- Who can read it. The instance itself through its metadata service, and any principal that may read instances in the compartment through the OCI API. The cluster’s node policy grants that to control-plane instances only, which hold the cluster’s keys anyway. A control-plane payload holds the cluster’s CA keys; OCI has no store the module stages it in yet.
Limitations¶
- Preemptible capacity is refused for control-plane machines.
- Backend registration follows the instance once it is
RUNNING, a network load balancer work request that typically takes a minute or two; the backend takes traffic once its TCP health check passes. That is inside the timekubeadm initandjointake, but not yet verified.
Exceptions¶
display_nameismachine_namerather than a hashed name: the cloud controller manager matches nodes by it.- The control-plane payload is not staged in a secret store; it goes in instance metadata, as Bootstrap describes. OCI Vault secrets read by the instance principal could serve as the store.
Example¶
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
kind: TerraformMachineTemplate
metadata:
name: demo-md-0
spec:
template:
spec:
source:
image: ghcr.io/captf-io/oci-machine:v0.1.0-opentofu
variables:
image_id: ocid1.image.oc1.iad.<id>
boot_volume_size_gib: 200
Set provider-id: oci://{{ v1.instance_id }} and cloud-provider: external in the bootstrap configuration’s kubeletExtraArgs, as the repository’s example does.