Skip to content

Commit e0c2323

Browse files
robo-caphyder
authored andcommitted
add support for compute-cluster
1 parent 492bada commit e0c2323

File tree

11 files changed

+376
-28
lines changed

11 files changed

+376
-28
lines changed

docs/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
- [Instance](./guide/workers_mode_instance.md)
2323
- [Instance Pool](./guide/workers_mode_instancepool.md)
2424
- [Cluster Network](./guide/workers_mode_clusternetwork.md)
25+
- [Compute Clusters](./guide/workers_mode_computecluster.md)
2526
- [Network](./guide/workers_network.md)
2627
- [Image](./guide/workers_image.md)
2728
- [Cloud-Init](./guide/workers_cloudinit.md)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Workers / Mode: Compute Clusters
2+
3+
<p>
4+
5+
Create self-managed HPC Compute Clusters.
6+
7+
A compute cluster is a group of high performance computing (HPC), GPU, or optimized instances that are connected with a high-bandwidth, ultra low-latency network.
8+
9+
[Supported shapes](https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/compute-clusters.htm#compute-cluster-shapes):
10+
* BM.GPU.A100-v2.8
11+
* BM.GPU.H100.8
12+
* BM.GPU4.8
13+
* BM.HPC2.36
14+
* BM.Optimized3.36
15+
16+
Configured with `mode = "compute-cluster"` on a `worker_pools` entry, or with `worker_pool_mode = "compute-cluster"` to use as the default for all pools unless otherwise specified.
17+
</p>
18+
19+
Compute clusters shared by multiple worker groups must be created using the variable `worker_compute_clusters` and should be referenced by the key in the `compute_cluster` attribute of the worker group.
20+
If the `worker_compute_clusters` is not specified, the module will create a compute cluster per each worker group.
21+
22+
## Usage
23+
24+
```javascript
25+
{{#include ../../../examples/workers/vars-workers-computecluster.auto.tfvars:4:}}
26+
```
27+
28+
Instance agent configuration:
29+
```javascript
30+
{{#include ../../../examples/workers/vars-workers-agent.auto.tfvars:4:}}
31+
```
32+
33+
## References
34+
* [Compute Clusters](https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/compute-clusters.htm)
35+
* [Large Clusters, Lowest Latency: Cluster Networking on Oracle Cloud Infrastructure](https://blogs.oracle.com/cloud-infrastructure/post/large-clusters-lowest-latency-cluster-networking-on-oracle-cloud-infrastructure)
36+
* [First principles: Building a high-performance network in the public cloud](https://blogs.oracle.com/cloud-infrastructure/post/building-high-performance-network-in-the-cloud)
37+
* [Running Applications on Oracle Cloud Using Cluster Networking](https://blogs.oracle.com/cloud-infrastructure/post/running-applications-on-oracle-cloud-using-cluster-networking)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Copyright (c) 2025 Oracle Corporation and/or its affiliates.
2+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl
3+
4+
worker_compute_clusters = { # Use this variable to define a compute cluster you intend to use with multiple-nodepools.
5+
"shared" = {
6+
placement_ad = 1
7+
}
8+
}
9+
10+
worker_pools = {
11+
oke-vm-standard = {
12+
description = "Managed node pool for operational workloads without GPU toleration"
13+
mode = "node-pool",
14+
size = 1,
15+
shape = "VM.Standard.E4.Flex",
16+
ocpus = 2,
17+
memory = 16,
18+
boot_volume_size = 50,
19+
},
20+
21+
compute-cluster-group-1 = {
22+
shape = "BM.HPC2.36",
23+
boot_volume_size = 100,
24+
image_id = "ocid1.image.oc1..."
25+
image_type = "custom"
26+
mode = "compute-cluster"
27+
compute_cluster = "shared"
28+
instance_ids = ["1", "2", "3"] # List of instance IDs in the compute cluster. Each instance ID corresponds to a separate node in the cluster.
29+
placement_ad = "1"
30+
cloud_init = [
31+
{
32+
content = <<-EOT
33+
#!/usr/bin/env bash
34+
echo "Pool-specific cloud_init using shell script"
35+
EOT
36+
},
37+
],
38+
secondary_vnics = {
39+
"vnic-display-name" = {
40+
nic_index = 1,
41+
subnet_id = "ocid1.subnet..."
42+
},
43+
},
44+
}
45+
46+
compute-cluster-group-2 = {
47+
shape = "BM.HPC2.36",
48+
boot_volume_size = 100,
49+
image_id = "ocid1.image.oc1..."
50+
image_type = "custom"
51+
mode = "compute-cluster"
52+
compute_cluster = "shared"
53+
instance_ids = ["a", "b", "c"] # List of instance IDs in the compute cluster. Each instance ID corresponds to a separate node in the cluster.
54+
placement_ad = "1"
55+
cloud_init = [
56+
{
57+
content = <<-EOT
58+
#!/usr/bin/env bash
59+
echo "Pool-specific cloud_init using shell script"
60+
EOT
61+
},
62+
],
63+
secondary_vnics = {
64+
"vnic-display-name" = {
65+
nic_index = 1,
66+
subnet_id = "ocid1.subnet..."
67+
},
68+
},
69+
}
70+
71+
compute-cluster-group-3 = {
72+
shape = "BM.HPC2.36",
73+
boot_volume_size = 100,
74+
image_id = "ocid1.image.oc1..."
75+
image_type = "custom"
76+
mode = "compute-cluster"
77+
instance_ids = ["001", "002", "003"] # List of instance IDs in the compute cluster. Each instance ID corresponds to a separate node in the cluster.
78+
placement_ad = "1"
79+
cloud_init = [
80+
{
81+
content = <<-EOT
82+
#!/usr/bin/env bash
83+
echo "Pool-specific cloud_init using shell script"
84+
EOT
85+
},
86+
],
87+
}
88+
}

module-workers.tf

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ module "workers" {
3838
cluster_type = var.cluster_type
3939
kubernetes_version = var.kubernetes_version
4040

41+
# Compute clusters
42+
compute_clusters = var.worker_compute_clusters
43+
4144
# Worker pools
4245
worker_pool_mode = var.worker_pool_mode
4346
worker_pool_size = var.worker_pool_size
@@ -103,4 +106,4 @@ output "worker_pool_ids" {
103106
output "worker_pool_ips" {
104107
description = "Created worker instance private IPs by pool for available modes ('node-pool', 'instance')."
105108
value = local.worker_count_expected > 0 ? try(one(module.workers[*].worker_pool_ips), null) : null
106-
}
109+
}

modules/operator/cloudinit.tf

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ locals {
1010

1111
baserepo = "ol${var.operator_image_os_version}"
1212
developer_EPEL = "${local.baserepo}_developer_EPEL"
13-
olcne18 = "${local.baserepo}_olcne18"
13+
olcne19 = "${local.baserepo}_olcne19"
1414
developer_olcne = "${local.baserepo}_developer_olcne"
1515
arch_amd = "amd64"
1616
arch_arm = "aarch64"
@@ -45,9 +45,9 @@ data "cloudinit_config" "operator" {
4545
gpgcheck = true
4646
enabled = true
4747
}
48-
"${local.olcne18}" = {
48+
"${local.olcne19}" = {
4949
name = "Oracle Linux Cloud Native Environment 1.8 ($basearch)"
50-
baseurl = "https://yum$ociregion.$ocidomain/repo/OracleLinux/OL${var.operator_image_os_version}/olcne18/$basearch/"
50+
baseurl = "https://yum$ociregion.$ocidomain/repo/OracleLinux/OL${var.operator_image_os_version}/olcne19/$basearch/"
5151
gpgkey = "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-oracle"
5252
gpgcheck = true
5353
enabled = true

modules/workers/computecluster.tf

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Copyright (c) 2022, 2025 Oracle Corporation and/or its affiliates.
2+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl
3+
4+
# Create the shared compute clusters defined in workers_compute_clusters
5+
6+
resource "oci_core_compute_cluster" "shared" {
7+
# Create an OCI Compute Cluster resource for each enabled entry of the worker_pools map with that mode.
8+
for_each = var.compute_clusters
9+
compartment_id = lookup(each.value, "compartment_id", var.compartment_id)
10+
display_name = each.key
11+
defined_tags = merge(
12+
var.defined_tags,
13+
lookup(each.value, "defined_tags", {})
14+
)
15+
freeform_tags = merge(
16+
var.freeform_tags,
17+
lookup(each.value, "freeform_tags", {})
18+
)
19+
20+
availability_domain = lookup(var.ad_numbers_to_names, lookup(each.value, "placement_ad", 1))
21+
22+
lifecycle {
23+
ignore_changes = [
24+
display_name, defined_tags, freeform_tags,
25+
]
26+
}
27+
}
28+
29+
# Dynamic resource block for Compute Cluster groups defined in worker_pools
30+
resource "oci_core_compute_cluster" "workers" {
31+
# Create an OCI Compute Cluster resource for each enabled entry of the worker_pools map with that mode.
32+
for_each = { for k, v in local.enabled_compute_clusters : k => v if length(lookup(v, "instance_ids", [])) > 0 && lookup(v, "compute_cluster", null) == null }
33+
compartment_id = each.value.compartment_id
34+
display_name = each.key
35+
defined_tags = each.value.defined_tags
36+
freeform_tags = each.value.freeform_tags
37+
availability_domain = lookup(each.value, "placement_ad", null) != null ? lookup(var.ad_numbers_to_names, lookup(each.value, "placement_ad")) : element(each.value.availability_domains, 0)
38+
39+
lifecycle {
40+
ignore_changes = [
41+
display_name, defined_tags, freeform_tags,
42+
]
43+
}
44+
}
45+
46+
resource "oci_core_instance" "compute_cluster_workers" {
47+
for_each = local.compute_cluster_instance_map
48+
49+
availability_domain = (lookup(oci_core_compute_cluster.shared, lookup(each.value, "compute_cluster", ""), null) != null ?
50+
oci_core_compute_cluster.shared[lookup(each.value, "compute_cluster", "")].availability_domain :
51+
lookup(each.value, "placement_ad", null) != null ? lookup(var.ad_numbers_to_names, lookup(each.value, "placement_ad")) : element(each.value.availability_domains, 0)
52+
)
53+
fault_domain = try(each.value.placement_fds[0], null)
54+
compartment_id = each.value.compartment_id
55+
display_name = format("%s-%s", element(split("###", each.key), 0), element(split("###", each.key), 1))
56+
preserve_boot_volume = false
57+
shape = each.value.shape
58+
59+
defined_tags = each.value.defined_tags
60+
freeform_tags = each.value.freeform_tags
61+
extended_metadata = each.value.extended_metadata
62+
capacity_reservation_id = each.value.capacity_reservation_id
63+
compute_cluster_id = (lookup(oci_core_compute_cluster.shared, lookup(each.value, "compute_cluster", ""), null) != null ?
64+
oci_core_compute_cluster.shared[lookup(each.value, "compute_cluster", "")].id :
65+
(lookup(oci_core_compute_cluster.workers, element(split("###", each.key), 0), null) != null ?
66+
oci_core_compute_cluster.workers[element(split("###", each.key), 0)].id :
67+
lookup(each.value, "compute_cluster", "")
68+
)
69+
)
70+
71+
dynamic "platform_config" {
72+
for_each = each.value.platform_config != null ? [1] : []
73+
content {
74+
type = lookup(
75+
# Attempt lookup against data source for the associated 'type' of configured worker shape
76+
lookup(local.platform_config_by_shape, each.value.shape, {}), "type",
77+
# Fall back to 'type' on pool with custom platform_config, or INTEL_VM default
78+
lookup(each.value.platform_config, "type", "INTEL_VM")
79+
)
80+
# Remaining parameters as configured, validated by instance/instance config resource
81+
are_virtual_instructions_enabled = lookup(each.value.platform_config, "are_virtual_instructions_enabled", null)
82+
is_access_control_service_enabled = lookup(each.value.platform_config, "is_access_control_service_enabled", null)
83+
is_input_output_memory_management_unit_enabled = lookup(each.value.platform_config, "is_input_output_memory_management_unit_enabled", null)
84+
is_measured_boot_enabled = lookup(each.value.platform_config, "is_measured_boot_enabled", null)
85+
is_memory_encryption_enabled = lookup(each.value.platform_config, "is_memory_encryption_enabled", null)
86+
is_secure_boot_enabled = lookup(each.value.platform_config, "is_secure_boot_enabled", null)
87+
is_symmetric_multi_threading_enabled = lookup(each.value.platform_config, "is_symmetric_multi_threading_enabled", null)
88+
is_trusted_platform_module_enabled = lookup(each.value.platform_config, "is_trusted_platform_module_enabled", null)
89+
numa_nodes_per_socket = lookup(each.value.platform_config, "numa_nodes_per_socket", null)
90+
percentage_of_cores_enabled = lookup(each.value.platform_config, "percentage_of_cores_enabled", null)
91+
}
92+
}
93+
94+
agent_config {
95+
are_all_plugins_disabled = each.value.agent_config.are_all_plugins_disabled
96+
is_management_disabled = each.value.agent_config.is_management_disabled
97+
is_monitoring_disabled = each.value.agent_config.is_monitoring_disabled
98+
dynamic "plugins_config" {
99+
for_each = merge(
100+
{
101+
"Compute HPC RDMA Authentication" : "ENABLED",
102+
"Compute HPC RDMA Auto-Configuration" : "ENABLED"
103+
},
104+
each.value.agent_config.plugins_config
105+
)
106+
content {
107+
name = plugins_config.key
108+
desired_state = plugins_config.value
109+
}
110+
}
111+
}
112+
113+
create_vnic_details {
114+
assign_private_dns_record = var.assign_dns
115+
assign_public_ip = each.value.assign_public_ip
116+
nsg_ids = each.value.nsg_ids
117+
subnet_id = each.value.subnet_id
118+
defined_tags = each.value.defined_tags
119+
freeform_tags = each.value.freeform_tags
120+
}
121+
122+
instance_options {
123+
are_legacy_imds_endpoints_disabled = false
124+
}
125+
126+
metadata = merge(
127+
{
128+
apiserver_host = var.apiserver_private_host
129+
cluster_ca_cert = var.cluster_ca_cert
130+
oke-k8version = var.kubernetes_version
131+
oke-kubeproxy-proxy-mode = var.kubeproxy_mode
132+
oke-tenancy-id = var.tenancy_id
133+
oke-initial-node-labels = join(",", [for k, v in each.value.node_labels : format("%v=%v", k, v)])
134+
secondary_vnics = jsonencode(lookup(each.value, "secondary_vnics", {}))
135+
ssh_authorized_keys = var.ssh_public_key
136+
user_data = lookup(lookup(data.cloudinit_config.workers, element(split("###", each.key), 0), {}), "rendered", "")
137+
},
138+
139+
# Add labels required for NPN CNI.
140+
var.cni_type == "npn" ? {
141+
oke-native-pod-networking = true
142+
oke-max-pods = var.max_pods_per_node
143+
pod-subnets = coalesce(var.pod_subnet_id, var.worker_subnet_id, "none")
144+
pod-nsgids = join(",", each.value.pod_nsg_ids)
145+
} : {},
146+
147+
# Only provide cluster DNS service address if set explicitly; determined automatically in practice.
148+
coalesce(var.cluster_dns, "none") == "none" ? {} : { kubedns_svc_ip = var.cluster_dns },
149+
150+
# Extra user-defined fields merged last
151+
var.node_metadata, # global
152+
lookup(each.value, "node_metadata", {}), # pool-specific
153+
)
154+
155+
source_details {
156+
boot_volume_size_in_gbs = each.value.boot_volume_size
157+
boot_volume_vpus_per_gb = each.value.boot_volume_vpus_per_gb
158+
source_id = each.value.image_id
159+
source_type = "image"
160+
}
161+
162+
lifecycle {
163+
precondition {
164+
condition = coalesce(each.value.image_id, "none") != "none"
165+
error_message = <<-EOT
166+
Missing image_id; check provided value if image_type is 'custom', or image_os/image_os_version if image_type is 'oke' or 'platform'.
167+
pool: ${element(split("###", each.key), 0)}
168+
image_type: ${coalesce(each.value.image_type, "none")}
169+
image_id: ${coalesce(each.value.image_id, "none")}
170+
EOT
171+
}
172+
173+
ignore_changes = [
174+
agent_config, # TODO Not updateable; remove when supported
175+
defined_tags, freeform_tags, display_name,
176+
metadata["cluster_ca_cert"], metadata["user_data"],
177+
create_vnic_details[0].defined_tags,
178+
create_vnic_details[0].freeform_tags,
179+
]
180+
}
181+
}

modules/workers/instance.tf

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -74,20 +74,24 @@ resource "oci_core_instance" "workers" {
7474

7575
metadata = merge(
7676
{
77-
apiserver_host = var.apiserver_private_host
78-
cluster_ca_cert = var.cluster_ca_cert
79-
oke-k8version = var.kubernetes_version
80-
oke-kubeproxy-proxy-mode = var.kubeproxy_mode
81-
oke-tenancy-id = var.tenancy_id
82-
oke-initial-node-labels = join(",", [for k, v in each.value.node_labels : format("%v=%v", k, v)])
83-
secondary_vnics = jsonencode(lookup(each.value, "secondary_vnics", {}))
84-
ssh_authorized_keys = var.ssh_public_key
85-
user_data = lookup(lookup(data.cloudinit_config.workers, lookup(each.value, "key", ""), {}), "rendered", "")
86-
oke-native-pod-networking = var.cni_type == "npn" ? true : false
77+
apiserver_host = var.apiserver_private_host
78+
cluster_ca_cert = var.cluster_ca_cert
79+
oke-k8version = var.kubernetes_version
80+
oke-kubeproxy-proxy-mode = var.kubeproxy_mode
81+
oke-tenancy-id = var.tenancy_id
82+
oke-initial-node-labels = join(",", [for k, v in each.value.node_labels : format("%v=%v", k, v)])
83+
secondary_vnics = jsonencode(lookup(each.value, "secondary_vnics", {}))
84+
ssh_authorized_keys = var.ssh_public_key
85+
user_data = lookup(lookup(data.cloudinit_config.workers, lookup(each.value, "key", ""), {}), "rendered", "")
86+
},
87+
88+
# Add labels required for NPN CNI.
89+
var.cni_type == "npn" ? {
90+
oke-native-pod-networking = true
8791
oke-max-pods = var.max_pods_per_node
8892
pod-subnets = coalesce(var.pod_subnet_id, var.worker_subnet_id, "none")
89-
pod-nsgids = var.cni_type == "npn" ? join(",", each.value.pod_nsg_ids) : null
90-
},
93+
pod-nsgids = join(",", each.value.pod_nsg_ids)
94+
} : {},
9195

9296
# Only provide cluster DNS service address if set explicitly; determined automatically in practice.
9397
coalesce(var.cluster_dns, "none") == "none" ? {} : { kubedns_svc_ip = var.cluster_dns },

0 commit comments

Comments
 (0)