terraform-bigip-ltm-nodemanages a singlebigip_ltm_nodeobject -- one addressable server IP (or FQDN-resolved node) on a BIG-IP TMOS >= v12.1.1 device, via theF5Networks/bigipTerraform provider~> 1.28.
- 🖥️ Creates one
bigip_ltm_node-- a static IP/hostname node, or an FQDN-resolved node whenvar.fqdnis set. - 🧵 Renders the single-item
fqdnnested block as adynamicblock so anull(the default) never forces a placeholder into the plan. - 🏷️ Emits
name(full path) as the primary cross-reference key, alongsideid,address,monitor,state, andsession. - 🚫 Owns nothing else -- no pool membership, no monitor. Both are consumed by full-path name from sibling modules.
- 🔓 Deliberately partition-agnostic in its own inputs:
partitionis folded intoname's full-path convention (/Partition/name), never a separate always-present variable.
💡 Why it matters: a BIG-IP node is the one LTM object most likely to be shared across several pools at once -- a single application server backing both an HTTP pool and a management pool, for example. Modeling it as its own standalone module (rather than an inline attribute of the pool that first references it) means the node's lifecycle -- and its administrative state (
user-up/user-down) -- is never accidentally tied to any one pool'sterraform apply.
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!
flowchart LR
MON["terraform-bigip-ltm-monitor"]
NODE["terraform-bigip-ltm-node (this module)"]
RES["bigip_ltm_node.this"]
POOL["terraform-bigip-ltm-pool"]
VS["terraform-bigip-ltm-virtual-server"]
MON -->|"monitor full-path name, optional"| NODE
NODE -->|"renders"| RES
RES -->|"name and address consumed by member map"| POOL
POOL -->|"pool name consumed by"| VS
style NODE fill:#E4002B,color:#ffffff
style RES fill:#000000,color:#ffffff
style MON fill:#ECECEC,color:#000000
style POOL fill:#ECECEC,color:#000000
style VS fill:#ECECEC,color:#000000
terraform-bigip-ltm-monitor is an optional upstream sibling -- most designs in this catalog put health checking on the pool rather than the node, so the monitor edge above is frequently unused. terraform-bigip-ltm-pool is the primary downstream consumer, referencing this module's name/address outputs from its member map. terraform-bigip-ltm-virtual-server is shown for full-chain context; it never references a node directly.
flowchart TB
subgraph Inputs["Inputs"]
NAME["var.name"]
ADDR["var.address"]
DESC["var.description"]
CONN["var.connection_limit"]
DYN["var.dynamic_ratio"]
RATIO["var.ratio"]
MONV["var.monitor"]
RATE["var.rate_limit"]
STATE["var.state"]
SESSION["var.session"]
FQDN["var.fqdn, optional block"]
end
RES["bigip_ltm_node.this"]
NAME --> RES
ADDR --> RES
DESC --> RES
CONN --> RES
DYN --> RES
RATIO --> RES
MONV --> RES
RATE --> RES
STATE --> RES
SESSION --> RES
FQDN -->|"dynamic block, zero or one"| RES
subgraph Outputs["Outputs"]
ONAME["name"]
OID["id"]
OADDR["address"]
OMON["monitor"]
OSTATE["state"]
OSESSION["session"]
end
RES --> ONAME
RES --> OID
RES --> OADDR
RES --> OMON
RES --> OSTATE
RES --> OSESSION
style RES fill:#E4002B,color:#ffffff
Resource inventory (1 resource, no children):
| Resource | Count | Notes |
|---|---|---|
bigip_ltm_node.this |
1 (single keystone) | The only resource this module manages. The fqdn nested block is rendered via dynamic, not a separate resource. |
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
F5Networks/bigip provider |
~> 1.28 (re-verify against the Registry before each new module wave) |
| Provider block | None -- the caller's root module configures address/username/password/token_value |
| BIG-IP TMOS | >= v12.1.1 (provider floor) |
Schema notes that bite:
addressisrequired: trueon the live schema unconditionally -- including for FQDN-based nodes. Do not assume it can be omitted whenfqdnis set; BIG-IP overwrites the resolved address at runtime, but Terraform still requires a value at plan time.fqdn.address_familyandfqdn.autopopulatedefaults disagree between the prose Argument Reference and the live provider schema (the schema documents"unspecified"foraddress_familywhere the prose says"all"/"ipv4"/"ipv6", and does not confirm an accepted value set forautopopulate). This module leaves bothnullunless the caller supplies them, so the provider's own computed default applies rather than this module guessing wrong.ratioisoptional, computedon the live schema but absent from the prose Argument Reference; confirmed present and defaulting to1.rate_limittakes the literal string"disabled"as its off-state, not a boolean or0-- passing a number as a string is required to set an actual limit.stateandsessionare each closed to exactly two values (user-up/user-downanduser-enabled/user-disabledrespectively) -- enforced here viavariablevalidationblocks, not left to the provider to reject at apply time.- Full-path identity (
/Partition/name) is partition-specific -- moving a node between partitions is a destroy/recreate, not an in-place rename.
Manager role scoped to the target partition is sufficient; Administrator is not required for this application-layer LTM object.
- iControl REST enabled and reachable on the target device.
- TMOS >= v12.1.1 (provider floor).
- Target partition must already exist.
- If
fqdnis set, the device must be able to resolve the configuredfqdn.namevia its configured DNS resolver for the node to populate a live address.
| File | Role |
|---|---|
providers.tf |
required_version >= 1.12.0, pinned F5Networks/bigip ~> 1.28 -- no provider {} block |
variables.tf |
1:1 bigip_ltm_node argument schema, including the fqdn object block, verified against the live provider schema |
main.tf |
Single keystone resource bigip_ltm_node.this; fqdn rendered as a dynamic block |
outputs.tf |
name, id, address, monitor, state, session |
SCOPE.md |
Lightweight standalone scope: consumed-by-name relationships, required role, F5 prerequisites |
README.md |
This document |
# Caller's root module configures the provider once -- never inside this module.
# provider "bigip" {
# address = var.bigip_address
# username = var.bigip_username
# password = var.bigip_password
# }
module "web01_node" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-node.git?ref=v1.0.0"
name = "/Common/web01-node"
address = "10.20.30.11"
}Consumes:
| Input | Type | Source module |
|---|---|---|
monitor (optional) |
string (full-path name) |
terraform-bigip-ltm-monitor |
Emits:
| Output | Description | Consumed by |
|---|---|---|
name |
Full-path node name (e.g. /Common/my-node) |
terraform-bigip-ltm-pool member map |
id |
Provider-internal id | (rarely consumed directly) |
address |
IP address or hostname configured for the node | terraform-bigip-ltm-pool member map (paired with name) |
monitor |
Full-path name of the node-level monitor, if any | (diagnostics / reporting only) |
state |
Administrative state (user-up / user-down) |
(diagnostics / reporting only) |
session |
Session-enablement state (user-enabled / user-disabled) |
(diagnostics / reporting only) |
1 · Minimal static node
The smallest real call -- only the two required arguments.
module "app01_node" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-node.git?ref=v1.0.0"
name = "/Common/app01-node"
address = "10.20.30.11"
}ℹ️ Every other argument falls back to the provider's own default (
connection_limit = 0,rate_limit = "disabled",state = "user-up",session = "user-enabled").
2 · Node with a description
module "app01_node" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-node.git?ref=v1.0.0"
name = "/Common/app01-node"
address = "10.20.30.11"
description = "App01 -- order-processing tier, rack 4"
}3 · Explicit connection limit (secure-by-default guardrail)
module "app01_node" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-node.git?ref=v1.0.0"
name = "/Common/app01-node"
address = "10.20.30.11"
connection_limit = 25000
}
⚠️ connection_limit = 0(unlimited) is the provider's own default and this module's default -- per this module suite's secure-by-default guidance, that default is accepted but surfaced as an explicit, guardrail-visible input. Set a real ceiling for production nodes rather than relying on the default.
4 · Dynamic-ratio load balancing weight
module "app01_node" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-node.git?ref=v1.0.0"
name = "/Common/app01-node"
address = "10.20.30.11"
dynamic_ratio = 3
}💡 Only relevant when a pool consuming this node uses a dynamic-ratio load-balancing mode.
5 · Static ratio weight
module "app02_node" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-node.git?ref=v1.0.0"
name = "/Common/app02-node"
address = "10.20.30.12"
ratio = 2
}💡 Only relevant when a pool consuming this node uses a ratio load-balancing mode.
6 · Node-level health monitor
module "app01_node" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-node.git?ref=v1.0.0"
name = "/Common/app01-node"
address = "10.20.30.11"
monitor = module.icmp_monitor.name # from terraform-bigip-ltm-monitor
}ℹ️ Node-level monitoring is optional and, in this catalog's design, typically secondary to pool-level
monitorslists -- one node commonly backs several pools, each with its own monitor.
7 · Explicit connections-per-second rate limit
module "app01_node" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-node.git?ref=v1.0.0"
name = "/Common/app01-node"
address = "10.20.30.11"
rate_limit = "1000"
}
⚠️ rate_limitis a string field -- the provider's off-state sentinel is the literal string"disabled"(this module's default), not a boolean or0.
8 · Administratively disabling a node
module "app01_node" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-node.git?ref=v1.0.0"
name = "/Common/app01-node"
address = "10.20.30.11"
state = "user-down"
}🔒 Sets the node administratively down without removing it from the configuration -- pools consuming it stop sending new traffic to it, existing connections are handled per the pool's own connection-draining behavior.
9 · Disabling new sessions only
module "app01_node" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-node.git?ref=v1.0.0"
name = "/Common/app01-node"
address = "10.20.30.11"
session = "user-disabled"
}ℹ️ Unlike
state = "user-down",session = "user-disabled"keeps the node "up" for monitor/health purposes while refusing new connections -- a softer drain than administratively disabling it.
10 · Minimal FQDN-resolved node
module "db_primary_node" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-node.git?ref=v1.0.0"
name = "/Common/db-primary-node"
address = "255.255.255.254" # placeholder; overwritten once BIG-IP resolves fqdn.name
fqdn = {
name = "db-primary.internal.example.org"
}
}
⚠️ addressis still required at plan time even for FQDN nodes -- the live schema requires it unconditionally. BIG-IP populates/overwrites the resolved address once DNS resolution occurs.
11 · FQDN node with a custom resolution interval
module "db_primary_node" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-node.git?ref=v1.0.0"
name = "/Common/db-primary-node"
address = "255.255.255.254"
fqdn = {
name = "db-primary.internal.example.org"
interval = "300"
}
}💡
interval(in seconds) defaults to"3600"; shorten it for records that change frequently (e.g. a database failover CNAME).
12 · FQDN node with a custom down-detection threshold
module "db_primary_node" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-node.git?ref=v1.0.0"
name = "/Common/db-primary-node"
address = "255.255.255.254"
fqdn = {
name = "db-primary.internal.example.org"
downinterval = 10
}
}ℹ️
downintervalis the number of failed resolution attempts before BIG-IP considers the FQDN name down; provider default is5.
13 · FQDN node with an explicit address family override
module "db_primary_node" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-node.git?ref=v1.0.0"
name = "/Common/db-primary-node"
address = "255.255.255.254"
fqdn = {
name = "db-primary.internal.example.org"
address_family = "ipv4"
}
}
⚠️ Leftnullby default because the prose Argument Reference and the live schema disagree on the provider's own default value -- only set this explicitly when the caller needs to force a specific address family.
14 · Multiple nodes via root-level `for_each`
This module models exactly one node per call (there is no in-module child collection to for_each over), so a caller creating several nodes wraps the module call itself at the root, keyed on a stable identifier -- never count.
locals {
nodes = {
"app01" = { address = "10.20.30.11" }
"app02" = { address = "10.20.30.12" }
"app03" = { address = "10.20.30.13" }
}
}
module "app_nodes" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-node.git?ref=v1.0.0"
for_each = local.nodes
name = "/Common/${each.key}-node"
address = each.value.address
}💡 Keying on
each.key(the stable node name) rather than a list index means removingapp02never forces Terraform to touchapp01's orapp03's state.
15 · 🏗️ End-to-end composition
A node feeding a monitor-backed pool feeding a virtual server -- the full chain this module participates in.
module "icmp_monitor" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-monitor.git?ref=v1.0.0"
name = "/Common/app-icmp-monitor"
#... monitor-specific arguments
}
module "app01_node" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-node.git?ref=v1.0.0"
name = "/Common/app01-node"
address = "10.20.30.11"
connection_limit = 25000
}
module "app02_node" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-node.git?ref=v1.0.0"
name = "/Common/app02-node"
address = "10.20.30.12"
connection_limit = 25000
}
module "app_pool" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-pool.git?ref=v1.0.0"
name = "/Common/app-pool"
monitors = [module.icmp_monitor.name]
members = {
"${module.app01_node.address}:8080" = {
node_name = module.app01_node.name
port = 8080
}
"${module.app02_node.address}:8080" = {
node_name = module.app02_node.name
port = 8080
}
}
}
module "app_virtual_server" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-virtual-server.git?ref=v1.0.0"
name = "/Common/app-vs"
destination = "10.20.30.100:443"
pool_name = module.app_pool.name
#... profile/persistence references
}🏗️ This module's contribution to the chain is narrow and intentional: it owns exactly the node record and nothing downstream. The pool decides how the node is health-checked (via its own
monitorslist) and load-balanced; the virtual server decides how traffic reaches the pool. Ordering matters -- nodes and monitors before the pool, the pool before the virtual server -- and Terraform's implicit dependency graph (viamodule.X.namereferences) enforces it automatically.
Summary:
| Variable | Type | Default | Required |
|---|---|---|---|
name |
string |
-- | ✅ |
address |
string |
-- | ✅ |
description |
string |
null |
-- |
connection_limit |
number |
0 |
-- |
dynamic_ratio |
number |
1 |
-- |
ratio |
number |
1 |
-- |
monitor |
string |
null |
-- |
rate_limit |
string |
"disabled" |
-- |
state |
string |
"user-up" |
-- |
session |
string |
"user-enabled" |
-- |
fqdn |
object(...) |
null |
-- |
Full object schema -- var.fqdn
variable "fqdn" {
type = object({
name = optional(string)
interval = optional(string, "3600")
address_family = optional(string)
autopopulate = optional(string)
downinterval = optional(number, 5)
})
default = null
}| Field | Type | Default | Notes |
|---|---|---|---|
name |
string |
unset | FQDN of the node. |
interval |
string |
"3600" |
Seconds between DNS queries. |
address_family |
string |
null (provider-computed) |
Prose docs and live schema disagree on the provider default -- left unset unless supplied. |
autopopulate |
string |
null (provider-computed) |
Accepted value set not confirmed against the live schema -- no closed-enum validation applied; left unset unless supplied. |
downinterval |
number |
5 |
Resolution attempts before the name is considered down. |
| Output | Description | Sensitive |
|---|---|---|
name |
Full-path name of the LTM node (e.g. /Common/my-node) -- primary cross-reference key |
No |
id |
Provider-internal id of the node resource | No |
address |
IP address or hostname configured for the node | No |
monitor |
Full-path name of the node-level monitor, if any | No |
state |
Administrative state (user-up / user-down) |
No |
session |
Session-enablement state (user-enabled / user-disabled) |
No |
- No child collections.
bigip_ltm_nodehas nofor_each-able sub-resource -- the only nested structure is the single-itemfqdnblock, rendered as adynamicblock so an absentvar.fqdnnever emits a null-block placeholder into the plan. addressis unconditionally required, including for FQDN nodes -- do not build calling code that tries to omit it whenfqdnis set.monitoris node-level and optional, distinct from a pool's requiredmonitorslist -- this catalog's default design puts health checking on the pool, since one node commonly backs several pools with different monitoring needs.stateandsessionare closed two-value enums, enforced viavariablevalidationblocks invariables.tfrather than left for the provider to reject at apply time -- a typo surfaces atterraform validate, before any HTTP call reaches the device.- Full-path identity is partition-specific.
namein the form/Partition/nameis the practical cross-reference key sibling modules use; moving a node between partitions is a destroy/recreate operation, not an in-place update. for_each, nevercount, at the call site. Because this module models one node per call, a caller creating multiple nodes wraps the module invocation itself withfor_eachover a keyed map (see Example 14) -- nevercount, so removing one node never re-indexes its siblings' state.
| Concern | Secure default in this module | Opt-out (caller must type extra) |
|---|---|---|
| Connection limits | connection_limit = 0 (unlimited) matches the provider's own default, but is surfaced as an explicit, guardrail-visible input rather than silently inherited |
Caller explicitly sets 0 to accept unlimited; production nodes should set a real ceiling |
| Health monitoring | Node-level monitor defaults to null -- by design, health checking is expected to live on the consuming pool's required monitors list, not silently assumed absent everywhere |
Caller sets monitor explicitly for node-level checks in addition to (or instead of) pool-level monitoring |
| Administrative state | state = "user-up" / session = "user-enabled" -- new nodes are live by default, matching the provider's own default; both are closed-enum validated |
Caller sets "user-down" / "user-disabled" explicitly to stage a node before it takes traffic |
| Partition scope | No implicit partition is invented -- partition is folded into the full-path name the caller supplies |
Caller includes the target partition segment in name for any non-/Common object |
| Secrets | Not applicable -- bigip_ltm_node has no secret-shaped attributes |
N/A |
cd C:\GitHubCode\newf5modules\bigip\terraform-bigip-ltm-node
terraform init -backend=false
terraform validate
terraform fmt -check
Remove-Item -Recurse -Force.terraform,.terraform.lock.hcl -ErrorAction SilentlyContinuePin consuming module calls to ?ref=v1.0.0 (or the current tagged release) rather than an unpinned branch reference.
This module's local proof gate is plan-only, schema-level validation -- terraform init -backend=false && terraform validate && terraform fmt -check confirm the HCL is syntactically valid, every required argument is present, and the fqdn object shape matches what main.tf expects. It does not exercise real device state: it cannot confirm the target partition exists, that fqdn.name actually resolves, or that the supplied address is reachable. A human runs terraform plan/apply against an actual BIG-IP from CI, never from this authoring process.
module.app01_node.bigip_ltm_node.this: Creating...
module.app01_node.bigip_ltm_node.this: Creation complete after 1s [id=/Common/app01-node]
Outputs:
address = "10.20.30.11"
id = "/Common/app01-node"
monitor = ""
name = "/Common/app01-node"
session = "user-enabled"
state = "user-up"
| Symptom | Cause | Fix |
|---|---|---|
terraform validate fails with a missing-argument error on address |
fqdn was set without also supplying address, on the assumption the provider computes it |
Supply an explicit address value even for FQDN nodes -- the live schema requires it unconditionally at plan time |
| Plan shows a full replace of the node instead of an in-place update | name's partition segment changed (e.g. /Common/x -> /Tenant-A/x) |
Expected -- full-path identity is partition-specific; moving partitions is a destroy/recreate, not a rename |
state/session rejected at terraform validate |
A value outside the two accepted enum members was supplied | Use exactly "user-up"/"user-down" for state and "user-enabled"/"user-disabled" for session |
Node stays reachable in monitor status after setting state = "user-down" |
state and session are independent controls -- user-down affects administrative/traffic state, not necessarily monitor-visible health |
Confirm which control the operational runbook actually needs; use session = "user-disabled" for a traffic-only drain |
Pool member plan shows an unexpected replace, not update, after changing this node's address |
The consuming pool's member map is keyed on ip:port (see terraform-bigip-ltm-pool) -- changing the node's address changes that key |
Expected per that module's for_each design; not a bug in this module |
connection refused on first apply against a cloud-deployed BIG-IP |
Management interface reachable only on a non-443 port (common on single-NIC AWS/Azure/GCP deployments) | Set the provider's port argument (env BIGIP_PORT) at the root module -- not a concern of this module |
F5Networks/bigipprovider registry docs- clouddocs.f5.com -- LTM Nodes
- This module's
SCOPE.md - Sibling modules:
terraform-bigip-ltm-monitor,terraform-bigip-ltm-pool,terraform-bigip-ltm-virtual-server