← Control PlaneCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Control Plane
Snapshot Sep 30, 2026 · 23:00 UTC · version 1.0.1
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"name": "ipset-load-balancing",
"description": "Static IPs and load balancers on Control Plane. Use when the user asks about IP sets, fixed IPs, direct or dedicated load balancers, exposing raw TCP/UDP ports, IP allowlisting, geo headers, or egress IPs.",
"included_files": [],
"skill_md_contents": "---\nname: ipset-load-balancing\ndescription: \"Static IPs and load balancers on Control Plane. Use when the user asks about IP sets, fixed IPs, direct or dedicated load balancers, exposing raw TCP/UDP ports, IP allowlisting, geo headers, or egress IPs.\"\n---\n\n# IP Sets & Load Balancing\n\nAn IP set reserves one static public IPv4 address per location and attaches it to a **direct** (per-workload) or **dedicated** (per-GVC) load balancer. The linking is bidirectional, and the recurring failure is configuring only one side: the IP set's `spec.link` must point at the workload/GVC AND that target's load balancer must reference the IP set back — otherwise addresses sit `unbound` and the IP set carries `status.warning: Cross-link misconfiguration`. The `workload` skill is primary for the LB-type picker and routing basics; this skill carries the full configuration.\n\n## Load balancer types\n\n| Type | Scope | What it adds | Cost |\n|---|---|---|---|\n| Default (shared) | every workload | HTTP/HTTPS on 80/443, nothing to configure | included |\n| Direct | one workload | raw TCP/UDP on external ports 22-32768, static IPs, TLS passthrough | charged while enabled |\n| Dedicated | whole GVC | domain custom ports and TCP routing, wildcard and accept-all hosts, redirects, trusted proxies, static IPs | per location (multiZone adds cross-zone charges) |\n\nToggling the `direct` block (workload) or `dedicated` flag (GVC) requires the **`configureLoadBalancer` permission** on that resource — `edit` does not imply it (403 \"Not allowed to change loadBalancer configuration\"); `manage` covers it.\n\n## IP sets\n\n```yaml\nkind: ipset\nname: partner-ips\nspec:\n link: //gvc/GVC/workload/WORKLOAD # or //gvc/GVC for a dedicated LB\n locations:\n - name: //location/aws-us-west-2\n retentionPolicy: keep # keep | free\n```\n\nHow allocation actually works:\n\n- **IPs are allocated in the locations of the linked GVC** (for a workload link, the workload's GVC). No `spec.link`, no allocation — `spec.locations` alone does nothing.\n- `spec.locations` pins a per-location `retentionPolicy`; unlisted locations behave as `keep` while in the GVC. Workload links require the GVC segment — `//workload/WORKLOAD` without it is rejected.\n- `keep` (default) allocates eagerly and holds the IP through unlinking, GVC location removal, and target deletion (state drops to `unbound`, billing continues until the IP set is deleted). `free` allocates only while bound and releases once the location leaves the GVC or the link/target goes away.\n- **Flipping `keep` to `free` does not release an IP whose location is still active in the GVC.** To stop charges: detach the binding (`update_ipset` with `removeLink: true`) so `free` locations release, then delete the IP set to release the rest.\n- `state: bound` means both sides point at each other; `unbound` means allocated but unused. Delete is **blocked with 400 while any address is bound** — remove the back-link first. Re-adding a location later does NOT return the same IP.\n- Supported on AWS (Elastic IP), GCP (static external address, STANDARD network tier), and Azure (static public IPv4), including BYOK on those clouds. Other providers fail with `status.error` \"provider not configured to use IpSets\"; cloud IP-quota errors also land in `status.error`.\n\n## Direct load balancer (per workload)\n\nOne cloud L4 load balancer per location running the workload, with `externalTrafficPolicy: Local` so the client IP reaches the workload. No TLS termination — the workload owns its certificates. No domain registration needed: each location's address is published on the workload's canonical endpoint DNS with latency-based geo routing, and `status.canonicalEndpoint` switches to the **first** port's `scheme://HOST:externalPort`. Custom hostnames can CNAME to that endpoint. Inbound firewall CIDRs still apply — they become cloud-level source ranges on the LB.\n\n```yaml\nspec:\n loadBalancer:\n direct:\n enabled: true\n ipSet: //ipset/partner-ips # optional static IPs; that IP set must link back to this workload\n ports:\n - externalPort: 5432 # 22-32768\n protocol: TCP # TCP or UDP\n containerPort: 5432 # plain number 80-65535; reserved: 8012, 8022, 9090, 9091, 15000, 15001, 15006, 15020, 15021, 15090, 41000\n - externalPort: 443\n protocol: TCP\n scheme: https # display-only (http|tcp|https|ws|wss): sets the URL scheme shown in UI/status\n```\n\nSet with `configure_workload_load_balancer` — it replaces the whole `spec.loadBalancer` block (`remove: true` clears it) and rolls a new deployment (about a minute).\n\n### Geo location headers (`spec.loadBalancer.geoLocation`)\n\nInjects MaxMind GeoLite2 client-location headers on inbound HTTP requests — works with any LB type, no effect on non-HTTP ports. Set `enabled: true` plus `headers` naming at least one of `asn`/`city`/`country`/`region` (names unique, max 128 chars each). Matching client-sent headers are replaced, so apps can trust the values; the country header carries the two-letter ISO code. Filtering on these headers (geo blocking) lives in the `firewall-networking` skill.\n\n### Replica direct (`spec.loadBalancer.replicaDirect: true`)\n\nStateful workloads only (rejected for other types, including `vm`), capped by a separate quota of **6 replicas per workload**. Each replica becomes addressable as `replica-INDEX.` on the workload's endpoints; internal names appear in `status.replicaInternalNames`. Per-replica custom-domain routing is in the `domain` skill; replica identities and database patterns in `stateful-storage`.\n\n## Dedicated load balancer (per GVC)\n\nA GVC setting — set with `update_gvc` (the `loadBalancer` object is replaced wholesale):\n\n```yaml\nspec:\n loadBalancer:\n dedicated: true\n ipSet: //ipset/gvc-ips # optional; that IP set must link back to //gvc/GVC\n trustedProxies: 0 # 0 (default) source client IP | 1 last X-Forwarded-For address | 2 second-to-last; sets the logged IP and X-Envoy-External-Address\n multiZone: { enabled: false } # cross-zone load balancing, extra charges\n redirect:\n class:\n status5xx: https://errors.example.com # any 500-level response (must be a valid URI)\n status401: https://auth.example.com/login?return_to=%REQ(:path)% # supports Envoy format strings\n```\n\nRequired before domains can use custom ports or the TCP protocol (without it those deploy as warnings and never route) and for wildcard / accept-all hosts — details in the `domain` skill. Enabling or disabling it can cause a brief connectivity blip while DNS propagates. Its access logs are queryable as `{gvc=\"GVC\", workload=\"_loadbalancer\"}`.\n\n## Verify\n\n- `get_resource` (kind=\"ipset\") — every `status.ipAddresses[].state` is `bound`, and no `status.warning` (cross-link) or `status.error` (provider/quota). Share the `ip` values only once bound.\n- `list_deployments` — all locations ready after an LB change; the workload's `status.canonicalEndpoint` reflects the direct-LB scheme and port.\n- CLI fallback (CI/CD): `CPLN_TOKEN` + `cpln ipset get NAME --org ORG -o yaml`.\n\n## Troubleshooting\n\n| Symptom | Cause and fix |\n|---|---|\n| IPs stay `unbound`, warning `Cross-link misconfiguration: /org/...` | Only one side is linked — the object named in the warning points here without a matching `spec.link` (or vice versa); configure both sides |\n| No IPs allocated at all | `spec.link` missing (locations alone allocate nothing), or the linked GVC has no locations |\n| Delete fails 400 \"one or more ip addresses are bound\" | Remove the workload/GVC back-link or pass `removeLink: true` to `update_ipset`, wait for `unbound`, delete again |\n| Still billed after setting `free` | The location is still active in the GVC — `free` releases only when it leaves the GVC or the IP set is unlinked |\n| `status.error` \"provider not configured to use IpSets\" | That location's cloud has no IP-set support (AWS, GCP, Azure only — including BYOK on them) |\n| `status.error` AddressLimitExceeded / QUOTA_EXCEEDED / PublicIPCountLimitReached | Cloud-account IP quota exhausted in that region — request an increase from the provider |\n| 403 \"not granted [configureLoadBalancer]\" | Toggling direct/dedicated needs that permission — `edit` alone is not enough |\n| API rejects `containerPort` | It is a plain number (80-65535 minus reserved ports); the docs' `containerPort: {port: N}` object form is wrong |\n| Deploy warning \"TCP access can only be restricted to specific ip addresses when using a custom domain and the GVC has dedicated loadBalancer enabled\" | Inbound CIDR rules on a TCP port need the dedicated LB (custom domain) or a direct LB — the shared LB cannot enforce them |\n\n## Quick reference\n\n| Tool | Purpose |\n|---|---|\n| `create_ipset` | Create with optional `link` and `locations[]` (`retentionPolicy` defaults to `keep`); friendly location names resolve server-side |\n| `update_ipset` | Description, tags, replace `link`, or `removeLink: true` to detach |\n| `add_ipset_location` | Add locations or overwrite an existing location's `retentionPolicy` |\n| `remove_ipset_location` | Drop location entries (releases only IPs whose location is no longer active in the GVC) |\n| `list_resources` / `get_resource` / `delete_resource` (kind=\"ipset\") | Read, and delete (releases every IP; blocked while bound) |\n| `configure_workload_load_balancer` | Workload side: `direct`, `geoLocation`, `replicaDirect` (`remove: true` clears) |\n| `update_gvc` | GVC side: `loadBalancer` (dedicated, ipSet, trustedProxies, multiZone, redirect) |\n\nCLI fallback: `cpln ipset create --name NAME --link LINK --location LOC,POLICY`, plus `add-location` / `update-location` / `remove-location REF --location ...` and `get` / `delete`. `cpln gvc update --set` cannot reach `spec.loadBalancer` — use `cpln gvc edit` or `cpln apply`.\n\n### Related skills\n\n- **workload** — the primary skill: LB-type picker, container ports, endpoints, the `configure_workload_*` tools.\n- **domain** — custom domains, custom ports and TCP routes on the dedicated LB, per-replica routing.\n- **firewall-networking** — inbound/outbound CIDR rules, header filtering on geo headers.\n- **workload-security** — TLS on the workload behind a direct LB, JWT auth, mTLS.\n- **stateful-storage** — replica identities and replica-direct with databases.\n\n## Documentation\n\n- [IP Set Reference](https://docs.controlplane.com/reference/ipset.md)\n- [Load Balancing Reference](https://docs.controlplane.com/reference/workload/load-balancing.md)\n- [GVC Reference (Dedicated LB)](https://docs.controlplane.com/reference/gvc.md)\n- [Domain Reference](https://docs.controlplane.com/reference/domain.md)\n"
}SHA-256: 7b468961d1d4168f1f83d38fbe700e0148933903a21feadc2a68d7801a46b570