A standalone module that manages a single vsphere_resource_pool — a CPU/memory
allocation container nested under a compute cluster's root pool, a standalone host's root
pool, or another resource pool.
Why it matters: A resource pool is how you carve a slice of an existing cluster or host into a governed, named allocation — reservations that guarantee capacity, limits that cap it, and shares that set scheduling priority under contention. This module wraps exactly one pool as the keystone resource (
this), exposes the provider's flatcpu_*/memory_*arguments as two typedobjectinputs, and enforces the share-level/share-count invariant beforeterraform validateever reaches vCenter. The parent pool is consumed by MOID — the module never looks up or creates the cluster, host, or parent it hangs from.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
Render the source below via the Mermaid Chart MCP (validated
flowchart). This module is highlighted in#607078. It hangs from aparent_resource_pool_id(the root pool of aterraform-vsphere-compute-cluster, or another pool) and takestagsfromterraform-vsphere-tag; itsidflows out as theresource_pool_ida VM runs in, the parent of a nested child pool, or a vApp's parent.
graph LR
cc["terraform-vsphere-compute-cluster<br/>parent_resource_pool_id"]
parent["parent resource pool (another pool)<br/>parent_resource_pool_id"]
tag["terraform-vsphere-tag<br/>tags"]
rp["terraform-vsphere-resource-pool"]
child["terraform-vsphere-resource-pool<br/>(nested child pool)"]
vm["terraform-vsphere-virtual-machine<br/>resource_pool_id"]
vapp["terraform-vsphere-vapp<br/>parent_resource_pool_id"]
cc -->|parent_resource_pool_id| rp
parent -->|parent_resource_pool_id| rp
tag -->|tags| rp
rp -->|id| child
rp -->|id| vm
rp -->|id| vapp
style rp fill:#607078,color:#fff,stroke:#333,stroke-width:2px
Keystone node in
#00A1E0; the universal tail in#607078. This module wraps exactly one resource —vsphere_resource_pool.this— hung from a parent pool (consumed by ID) and carrying CPU + memory reservations, limits, and shares.
graph TD
parent["parent_resource_pool_id (MOID)<br/>cluster root pool or another pool<br/>consumed by ID"]
this["vsphere_resource_pool.this<br/>(keystone — one resource pool)<br/>CPU + memory reservations / limits / shares"]
tail["universal tail<br/>tags (list) + custom_attributes (map)"]
parent -->|parent_resource_pool_id| this
this --> tail
style this fill:#00A1E0,color:#fff,stroke:#333,stroke-width:2px
style tail fill:#607078,color:#fff
Grant the Terraform integration account the least-privilege set below — never the built-in
Administrator role. Privileges are granted on the parent object referenced by
parent_resource_pool_id (the cluster, host, or parent pool) and must propagate to children
so the created pool can later be modified, renamed, or removed. Validate the final set with
vCenter's Check Privileges feature before handing the account to a pipeline.
Core — always required (create / update settings / destroy):
| Privilege ID | vCenter display name | Why this module needs it |
|---|---|---|
Resource.CreatePool |
Resource → Create resource pool | Create the pool under the parent. |
Resource.EditPool |
Resource → Modify resource pool | Change CPU/memory allocation (share_level, shares, reservation, expandable, limit) and scale_descendants_shares. |
Resource.DeletePool |
Resource → Remove resource pool | Destroy the pool on terraform destroy / removal. |
Conditional — required only when the corresponding input changes:
| Privilege ID | vCenter display name | Required when |
|---|---|---|
Resource.RenamePool |
Resource → Rename resource pool | name is changed after creation (rename in place). |
Resource.MovePool |
Resource → Move resource pool | parent_resource_pool_id is changed (the pool is re-parented / moved). |
InventoryService.Tagging.AttachTag |
vSphere Tagging → Assign or Unassign vSphere Tag | tags is non-empty. |
Global.SetCustomField |
Global → Set custom attribute | custom_attributes is non-empty (sets values on existing attributes — defining attributes is the caller's job and is out of scope). |
- Provider:
vmware/vsphere~> 2.0(VMware by Broadcom). Do not usehashicorp/vsphere. - Terraform:
>= 1.12.0. - vCenter Server: any version supported by the
~> 2.0provider (7.0 / 8.0). A non-defaultscale_descendants_shares(i.e.scaleCpuAndMemoryShares) requires vCenter 7.0 or higher — it fails on older endpoints. - Licensing / DRS: a resource pool on a cluster requires that cluster to have DRS enabled, which requires vSphere Enterprise Plus licensing. A pool on a standalone host's root pool does not require DRS.
- Pre-existing parent: the MOID passed as
parent_resource_pool_idmust already exist and be resolvable by the caller (a cluster/host root pool, or another pool). The module performs no lookups of its own. - Connectivity & auth: the caller configures the
provider "vsphere"block; credentials are supplied out of band (preferVSPHERE_SERVER/VSPHERE_USER/VSPHERE_PASSWORDenvironment variables). This module defines no connection variables.
Source is pinned to a release tag (
?ref=v1.0.0) — never a branch. The provider is configured by the caller; no credentials appear in module inputs. Plan-only — a human reviews the plan and runsapply.
# Provider is caller-configured; credentials come from the environment
# (VSPHERE_SERVER / VSPHERE_USER / VSPHERE_PASSWORD).
provider "vsphere" {}
# Resolve the parent by ID — the module never looks this up.
data "vsphere_datacenter" "dc" {
name = "dc-01"
}
data "vsphere_compute_cluster" "cluster" {
name = "cluster-01"
datacenter_id = data.vsphere_datacenter.dc.id
}
# Minimal pool: an empty cpu/memory config produces a neutral, non-disruptive pool
# (normal shares, zero reservation, expandable, unlimited limit).
module "team_pool" {
source = "git::https://github.com/microsoftexpert/terraform-vsphere-resource-pool?ref=v1.0.0"
name = "team-apps"
parent_resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
}A fuller example — custom CPU shares, reservations, a high memory priority, and a tag:
module "team_pool_custom" {
source = "git::https://github.com/microsoftexpert/terraform-vsphere-resource-pool?ref=v1.0.0"
name = "team-bravo"
parent_resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
cpu = {
share_level = "custom" # shares are honored ONLY when level is "custom"
shares = 8000
reservation = 4000 # MHz guaranteed to the pool
limit = 16000 # MHz ceiling
expandable = false # do not borrow beyond the reservation
}
memory = {
share_level = "high"
reservation = 8192 # MB guaranteed
limit = -1 # unlimited
}
# tags is a LIST of vsphere_tag MOIDs (not a key/value map).
tags = [module.tf_mod_vsphere_tag.tag_ids["environment"]]
}See examples/main.tf for a complete, runnable example that also nests a
child pool under another pool.
1 · Minimal — no CPU or memory constraints (unlimited)
module "resource_pool_prod" {
source = "git::https://github.com/microsoftexpert/terraform-vsphere-resource-pool?ref=v1.0.0"
name = "rp-prod-workloads"
parent_resource_pool_id = data.vsphere_compute_cluster.prod.resource_pool_id
}2 · CPU reservation with expandable default
module "resource_pool_reserved" {
source = "git::https://github.com/microsoftexpert/terraform-vsphere-resource-pool?ref=v1.0.0"
name = "rp-prod-reserved"
parent_resource_pool_id = data.vsphere_compute_cluster.prod.resource_pool_id
cpu = {
reservation = 4000 # 4 GHz reserved for this pool
expandable = true # may borrow additional unreserved parent capacity
}
}3 · Memory reservation for latency-sensitive workloads
module "resource_pool_db" {
source = "git::https://github.com/microsoftexpert/terraform-vsphere-resource-pool?ref=v1.0.0"
name = "rp-prod-databases"
parent_resource_pool_id = data.vsphere_compute_cluster.prod.resource_pool_id
memory = {
reservation = 65536 # 64 GB guaranteed to database VMs
expandable = false # strict reservation — no balloon
share_level = "high"
}
}4 · Fixed CPU limit (cap at specific MHz)
module "resource_pool_dev" {
source = "git::https://github.com/microsoftexpert/terraform-vsphere-resource-pool?ref=v1.0.0"
name = "rp-dev-capped"
parent_resource_pool_id = data.vsphere_compute_cluster.dev.resource_pool_id
cpu = {
limit = 8000 # dev pool capped at 8 GHz total
}
}5 · Fixed memory limit
module "resource_pool_sandbox" {
source = "git::https://github.com/microsoftexpert/terraform-vsphere-resource-pool?ref=v1.0.0"
name = "rp-sandbox"
parent_resource_pool_id = data.vsphere_compute_cluster.dev.resource_pool_id
memory = {
limit = 131072 # 128 GB memory ceiling for sandbox
}
}6 · High-priority pool with custom shares
module "resource_pool_priority" {
source = "git::https://github.com/microsoftexpert/terraform-vsphere-resource-pool?ref=v1.0.0"
name = "rp-prod-high-priority"
parent_resource_pool_id = data.vsphere_compute_cluster.prod.resource_pool_id
cpu = {
share_level = "high"
}
memory = {
share_level = "high"
}
}7 · Custom shares (explicit share count)
module "resource_pool_custom_shares" {
source = "git::https://github.com/microsoftexpert/terraform-vsphere-resource-pool?ref=v1.0.0"
name = "rp-custom-shares"
parent_resource_pool_id = data.vsphere_compute_cluster.prod.resource_pool_id
cpu = {
share_level = "custom"
shares = 8000 # valid only when share_level = "custom"
}
memory = {
share_level = "custom"
shares = 163840 # explicit memory share count
}
}8 · Strict (non-expandable) CPU + memory reservation
module "resource_pool_strict" {
source = "git::https://github.com/microsoftexpert/terraform-vsphere-resource-pool?ref=v1.0.0"
name = "rp-prod-strict"
parent_resource_pool_id = data.vsphere_compute_cluster.prod.resource_pool_id
cpu = {
reservation = 8000
expandable = false # hard reservation — never borrow from parent
}
memory = {
reservation = 32768 # 32 GB
expandable = false
}
}9 · Low-priority dev/test pool
module "resource_pool_dev_low" {
source = "git::https://github.com/microsoftexpert/terraform-vsphere-resource-pool?ref=v1.0.0"
name = "rp-dev-low-priority"
parent_resource_pool_id = data.vsphere_compute_cluster.dev.resource_pool_id
cpu = { share_level = "low" }
memory = { share_level = "low" }
}10 · Nested child pool (inside another resource pool)
module "resource_pool_parent" {
source = "git::https://github.com/microsoftexpert/terraform-vsphere-resource-pool?ref=v1.0.0"
name = "rp-prod-tier1"
parent_resource_pool_id = data.vsphere_compute_cluster.prod.resource_pool_id
cpu = { reservation = 16000 }
memory = { reservation = 131072 }
}
module "resource_pool_child" {
source = "git::https://github.com/microsoftexpert/terraform-vsphere-resource-pool?ref=v1.0.0"
name = "rp-prod-tier1-web"
parent_resource_pool_id = module.resource_pool_parent.id # ← child of above
cpu = { reservation = 4000 }
memory = { reservation = 32768 }
}11 · Multiple pools with for_each
locals {
resource_pools = {
web = { share = "high", cpu_limit = -1, mem_limit = -1 }
app = { share = "normal", cpu_limit = -1, mem_limit = -1 }
dev = { share = "low", cpu_limit = 4000, mem_limit = 16384 }
}
}
module "resource_pools" {
source = "git::https://github.com/microsoftexpert/terraform-vsphere-resource-pool?ref=v1.0.0"
for_each = local.resource_pools
name = "rp-${each.key}"
parent_resource_pool_id = data.vsphere_compute_cluster.prod.resource_pool_id
cpu = { share_level = each.value.share, limit = each.value.cpu_limit }
memory = { share_level = each.value.share, limit = each.value.mem_limit }
}12 · With tags and custom attributes
module "resource_pool_tagged" {
source = "git::https://github.com/microsoftexpert/terraform-vsphere-resource-pool?ref=v1.0.0"
name = "rp-prod-tagged"
parent_resource_pool_id = data.vsphere_compute_cluster.prod.resource_pool_id
cpu = { share_level = "high" }
memory = { share_level = "high" }
tags = [
module.tf_mod_vsphere_tag.tag_ids["environment"], # "production"
module.tf_mod_vsphere_tag.tag_ids["tier"], # "tier-1"
]
custom_attributes = {
(data.vsphere_custom_attribute.cost_center.id) = "PROD-OPS"
(data.vsphere_custom_attribute.owner.id) = "platform-team"
}
}13 · Scale descendants shares (vSphere 7.0+)
module "resource_pool_scale" {
source = "git::https://github.com/microsoftexpert/terraform-vsphere-resource-pool?ref=v1.0.0"
name = "rp-prod-scalable"
parent_resource_pool_id = data.vsphere_compute_cluster.prod.resource_pool_id
cpu = { share_level = "high" }
memory = { share_level = "high" }
scale_descendants_shares = "scaleCpuAndMemoryShares"
# Requires vSphere 7.0+; child pool shares scale proportionally when this pool's shares change
}14 · Import an existing resource pool
import {
to = module.resource_pool_existing.vsphere_resource_pool.this
id = "resgroup-123" # MOID — obtain from vCenter or `terraform show`
}
module "resource_pool_existing" {
source = "git::https://github.com/microsoftexpert/terraform-vsphere-resource-pool?ref=v1.0.0"
name = "rp-prod-existing"
parent_resource_pool_id = data.vsphere_compute_cluster.prod.resource_pool_id
}15 · End-to-end: compute cluster → resource pool → VM placement
# The cluster root pool is resolved by the caller
data "vsphere_compute_cluster" "prod" {
name = "Cluster-Prod-01"
datacenter_id = module.datacenter.id
}
module "resource_pool_web" {
source = "git::https://github.com/microsoftexpert/terraform-vsphere-resource-pool?ref=v1.0.0"
name = "rp-prod-web"
parent_resource_pool_id = data.vsphere_compute_cluster.prod.resource_pool_id
cpu = { share_level = "high" }
memory = { reservation = 16384 }
}
output "resource_pool_id" {
value = module.resource_pool_web.id # wire into terraform-vsphere-virtual-machine
}| Name | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string |
yes | — | Display name of the pool in vCenter. Validated non-empty. Mutable — a change renames the pool in place. |
parent_resource_pool_id |
string |
yes | — | MOID of the parent pool: a cluster root pool (data.vsphere_compute_cluster.*.resource_pool_id), a host root pool, or another pool's id. A change re-parents (moves) the pool — see gotchas. |
cpu |
object (below) |
no | {} |
CPU allocation. Empty object → provider defaults. |
memory |
object (below) |
no | {} |
Memory allocation. Empty object → provider defaults. |
scale_descendants_shares |
string |
no | "disabled" |
disabled or scaleCpuAndMemoryShares. Non-default requires vCenter 7.0+. |
tags |
list(string) |
no | [] |
List of vsphere_tag MOIDs to attach (e.g. from terraform-vsphere-tag.tag_ids). A list, not a map. |
custom_attributes |
map(string) |
no | {} |
Map of custom-attribute MOID → value string. Attributes must already exist. |
Both inputs share the same shape (units differ: CPU in MHz, memory in MB):
cpu = {
share_level = optional(string, "normal") # low | normal | high | custom
shares = optional(number, null) # explicit share count; valid ONLY when share_level = "custom"
reservation = optional(number, 0) # guaranteed capacity (MHz for cpu, MB for memory); must be >= 0
expandable = optional(bool, true) # allow the reservation to borrow unreserved parent capacity
limit = optional(number, -1) # ceiling (MHz/MB); -1 = unlimited
}| Field | Type | Default | Notes |
|---|---|---|---|
share_level |
string |
"normal" |
One of low / normal / high / custom. With low/normal/high, vCenter derives the share count and any supplied shares is ignored. |
shares |
number |
null |
Must be set when share_level = "custom" and must be null otherwise (validated). When set, must be > 0. |
reservation |
number |
0 |
Capacity guaranteed to the pool. Must be >= 0. |
expandable |
bool |
true |
When true, the reservation may grow into the parent's unreserved capacity. |
limit |
number |
-1 |
Hard ceiling. -1 = unlimited; otherwise must be >= -1. |
Validations enforced by the module (fail at terraform validate, not at apply):
nameandparent_resource_pool_idmust be non-empty.cpu/memoryshare_level∈ {low,normal,high,custom}.sharesis set iffshare_level = "custom"(loud failure instead of a silently-ignored value).reservation >= 0,limit >= -1, andshares(when set)> 0.scale_descendants_shares∈ {disabled,scaleCpuAndMemoryShares}.
| Output | Type | Description |
|---|---|---|
id |
string |
MOID of the resource pool. Wire to parent_resource_pool_id of a child pool, or to resource_pool_id on a vsphere_virtual_machine. |
name |
string |
Display name of the pool. |
cpu_shares |
number |
Effective CPU shares. Derived by vCenter from share_level when it is low/normal/high; the supplied value when custom. Computed — appears as known after apply on first create and whenever share_level is non-custom. |
memory_shares |
number |
Effective memory shares, same semantics as cpu_shares. |
The empty call produces the safe, neutral pool: it guarantees itself nothing, caps nothing, skews no scheduling priority, and carries no governance metadata. Every risky or opinionated choice is an explicit opt-out the caller must type.
| Principle | Secure / neutral default | Explicit opt-out |
|---|---|---|
| Reserve nothing by default | cpu.reservation / memory.reservation = 0 — creating the pool cannot starve sibling pools or trip admission control. |
Set a positive reservation to guarantee capacity. |
| No artificial ceiling | cpu.limit / memory.limit = -1 (unlimited) — the pool never throttles its own workloads. |
Set a positive limit (MHz / MB) to cap consumption. |
| Neutral priority | share_level = "normal" — no scheduling advantage under contention. |
low / high, or custom with an explicit shares count. |
| Fail loud on ignored shares | shares must be null unless share_level = "custom" (validated). |
Set share_level = "custom" together with a shares value. |
| Elastic but bounded | expandable = true — a reservation may borrow unused parent capacity rather than fail. |
expandable = false to hard-bound the pool at its reservation. |
| No implicit cascade | scale_descendants_shares = "disabled" — resizing this pool does not silently re-scale child pools; also keeps the module working on pre-7.0 vCenter. |
scaleCpuAndMemoryShares (requires vCenter 7.0+). |
| Explicit placement | parent_resource_pool_id is required — the module never guesses where the pool hangs. |
— (always caller-supplied). |
| Opt-in governance metadata | tags = [], custom_attributes = {}. |
Supply tag MOIDs / attribute values. |
Plan-only. This module system never runs terraform apply — a human reviews the plan and
applies it after review. From the module or example directory:
# 1. Initialize without a backend (validation/formatting only — no state, no apply).
terraform init -backend=false
# 2. Validate types, schema, and the module's validation {} rules.
terraform validate
# 3. Confirm canonical formatting (use `terraform fmt` to fix, then review the diff).
terraform fmt -check
# A human reviews the resulting plan and runs `terraform apply` against vCenter.
# Never run `terraform apply` from this module system.Pin the module source to the release tag (?ref=v1.0.0) — never a branch.
| Symptom | Cause | Resolution |
|---|---|---|
cpu.shares must be set when cpu.share_level is "custom"... at validate |
You set shares without share_level = "custom", or selected custom without a shares value. |
Either set share_level = "custom" and a shares count, or remove shares and use low/normal/high. |
cpu_shares / memory_shares show known after apply on a no-op plan |
share_level is low/normal/high, so the share count is computed by vCenter, not by you. |
Expected — it is a computed attribute, not drift. It stabilizes after apply; use custom if you need a fixed, plan-visible value. |
Re-parent fails when parent_resource_pool_id changes |
The old and new parents do not share a common root resource pool, or the account lacks Resource.MovePool. |
A pool can only move within the same root pool tree. To relocate across clusters/hosts, recreate the pool. Grant Resource.MovePool if the move is legitimate. |
scaleCpuAndMemoryShares rejected by vCenter |
scale_descendants_shares other than disabled requires vCenter 7.0+. |
Set scale_descendants_shares = "disabled", or target a 7.0+ vCenter. |
Permission to perform this operation was denied on create |
The integration account is missing Resource.CreatePool on the parent object, or the privilege does not propagate. |
Grant the privileges in 🔑 Required vCenter Privileges on the parent with propagation; confirm via vCenter Check Privileges. |
Insufficient resources / admission-control failure on apply |
The requested reservation exceeds the parent's unreserved capacity, especially with expandable = false. |
Lower the reservation, set expandable = true, or free capacity in the parent pool/cluster. |
SCOPE.md— design contract: in-scope vs consumed-by-ID, privileges, prerequisites, emits, gotchas.examples/main.tf— runnable example (minimal pool, custom pool, nested child pool).- Provider docs —
vmware/vsphere·vsphere_resource_pool. - Sibling modules —
terraform-vsphere-compute-cluster(emits the cluster rootresource_pool_id),terraform-vsphere-tag(emitstag_idsfortags),terraform-vsphere-virtual-machine(consumesidasresource_pool_id).