|
| 1 | +# Feature: Floating IP Support |
| 2 | + |
| 3 | +**Issue**: #413 |
| 4 | +**Parent Epic**: None |
| 5 | +**Related**: #405 - Deploy Hetzner Demo Tracker and Document the Process |
| 6 | + |
| 7 | +## Overview |
| 8 | + |
| 9 | +The deployer is not aware of **floating IPs** (also called static IPs, reserved IPs, |
| 10 | +or elastic IPs depending on the provider). A floating IP is an IP address owned |
| 11 | +independently of any specific server and can be reassigned between servers without |
| 12 | +changing DNS. |
| 13 | + |
| 14 | +During the Hetzner Demo deployment (#405) floating IPs were used deliberately to allow |
| 15 | +zero-downtime failover and maintenance: |
| 16 | + |
| 17 | +- **Instance IP**: `46.225.234.201` — the bare VM's IP, stored in the deployer's |
| 18 | + internal environment state after provisioning |
| 19 | +- **Floating IP**: `116.202.176.169` — the IP published in all DNS A records |
| 20 | + |
| 21 | +The deployer has no concept of floating IPs. It records the instance IP during |
| 22 | +`provision` and uses that IP as the expected DNS target in the `test` command's |
| 23 | +DNS checks. Because DNS points to the floating IP, every domain in the environment |
| 24 | +triggers a false-positive warning during `test`: |
| 25 | + |
| 26 | +```text |
| 27 | +⚠️ DNS check: api.torrust-tracker-demo.com resolves to [116.202.176.169] |
| 28 | + but expected 46.225.234.201 |
| 29 | +``` |
| 30 | + |
| 31 | +This is not an error. The deployment works correctly — traffic reaches the server |
| 32 | +through the floating IP. The deployer simply lacks the notion of a separate "public" |
| 33 | +IP that differs from the provisioned instance IP. |
| 34 | + |
| 35 | +The DNS setup for this deployment (floating IP assignment, VM network configuration, |
| 36 | +and DNS record creation) is documented in |
| 37 | +[docs/deployments/hetzner-demo-tracker/post-provision/dns-setup.md](../deployments/hetzner-demo-tracker/post-provision/dns-setup.md). |
| 38 | + |
| 39 | +## Goals |
| 40 | + |
| 41 | +- [ ] Allow users to specify a floating IP (or more generally, a public IP) that is |
| 42 | + separate from the instance IP in the environment configuration |
| 43 | +- [ ] Use the floating IP as the expected DNS target in `test` DNS checks when it |
| 44 | + is configured, eliminating false-positive warnings |
| 45 | +- [ ] Consider using the floating IP during provisioning to automatically assign it to |
| 46 | + the newly created instance (provider-dependent) |
| 47 | +- [ ] Expose the floating IP in `provision` and `Running` state output so operators |
| 48 | + can confirm which IP is serving traffic |
| 49 | + |
| 50 | +## Specifications |
| 51 | + |
| 52 | +### Motivation: Why Floating IPs? |
| 53 | + |
| 54 | +Floating IPs decouple the publicly announced address from the physical server, providing: |
| 55 | + |
| 56 | +1. **Zero-downtime failover**: If the primary server fails, the floating IP can be |
| 57 | + reassigned to a standby server in seconds. DNS TTLs are not a concern because the |
| 58 | + IP itself does not change. |
| 59 | +2. **Maintenance without downtime**: A new server can be fully provisioned and |
| 60 | + configured before traffic is cut over by reassigning the floating IP. |
| 61 | + |
| 62 | +Other providers use different terminology for the same concept: |
| 63 | + |
| 64 | +| Provider | Term | |
| 65 | +| ------------ | ---------------- | |
| 66 | +| Hetzner | Floating IP | |
| 67 | +| AWS | Elastic IP | |
| 68 | +| GCP | Reserved IP | |
| 69 | +| DigitalOcean | Reserved IP | |
| 70 | +| Azure | Static Public IP | |
| 71 | + |
| 72 | +### Current Behavior (Limitation) |
| 73 | + |
| 74 | +1. `provision` stores the instance IP in the environment state. |
| 75 | +2. `configure`, `release`, and `run` use the instance IP — no issue here since |
| 76 | + these commands operate on the instance directly. |
| 77 | +3. `test` resolves each domain via DNS and compares the result against the instance IP. |
| 78 | + When DNS points to a floating IP instead, the comparison fails and the deployer |
| 79 | + emits a warning for every domain. |
| 80 | +4. The deployer works correctly despite the warnings; the false positives are noise. |
| 81 | + |
| 82 | +### Proposed Configuration Change |
| 83 | + |
| 84 | +The environment config (or a provider-specific section) should accept an optional |
| 85 | +`public_ip` (or `floating_ip`) field: |
| 86 | + |
| 87 | +```json |
| 88 | +"provider": { |
| 89 | + "name": "hetzner", |
| 90 | + "instance_ip": "46.225.234.201", |
| 91 | + "public_ip": "116.202.176.169" |
| 92 | +} |
| 93 | +``` |
| 94 | + |
| 95 | +When `public_ip` is present: |
| 96 | + |
| 97 | +- The `test` command DNS checks compare resolved IPs against `public_ip`, not |
| 98 | + `instance_ip`. |
| 99 | +- The `provision` output includes `public_ip` alongside `instance_ip`. |
| 100 | +- Future: `provision` could automatically assign the floating IP to the instance |
| 101 | + via provider APIs. |
| 102 | + |
| 103 | +### Current Workaround |
| 104 | + |
| 105 | +Until this feature is implemented, `test` command DNS warnings can be safely ignored |
| 106 | +when floating IPs are in use. Verify manually that: |
| 107 | + |
| 108 | +1. Each domain resolves to the expected floating IP. |
| 109 | +2. Services are reachable through those domains. |
| 110 | + |
| 111 | +See [docs/deployments/hetzner-demo-tracker/commands/improvements.md](../deployments/hetzner-demo-tracker/commands/improvements.md) |
| 112 | +and [docs/deployments/hetzner-demo-tracker/post-provision/dns-setup.md](../deployments/hetzner-demo-tracker/post-provision/dns-setup.md) |
| 113 | +for the specific setup used in the Hetzner demo deployment. |
| 114 | + |
| 115 | +## Implementation Plan |
| 116 | + |
| 117 | +### Phase 1: Environment Config Schema |
| 118 | + |
| 119 | +- [ ] Add optional `public_ip` field to the environment config schema |
| 120 | + (`schemas/environment-config.json`) |
| 121 | +- [ ] Parse and store `public_ip` in the environment domain types |
| 122 | +- [ ] Validate: if `public_ip` is present it must be a valid IPv4/IPv6 address |
| 123 | + |
| 124 | +### Phase 2: `test` Command DNS Checks |
| 125 | + |
| 126 | +- [ ] When `public_ip` is configured, use it as the expected target in DNS checks |
| 127 | + instead of the instance IP |
| 128 | +- [ ] Update warning/success messages to indicate which IP was expected and which |
| 129 | + IP was found |
| 130 | +- [ ] Add unit tests for the DNS check logic covering the floating-IP case |
| 131 | + |
| 132 | +### Phase 3: `provision` Output |
| 133 | + |
| 134 | +- [ ] Include `public_ip` in the `provision` command output when configured |
| 135 | +- [ ] Include `public_ip` in the `Running` state output |
| 136 | + |
| 137 | +### Phase 4: Documentation |
| 138 | + |
| 139 | +- [ ] Update the environment config user guide to document the `public_ip` field |
| 140 | +- [ ] Add a note in the provider guide explaining floating IP support |
| 141 | + |
| 142 | +## Acceptance Criteria |
| 143 | + |
| 144 | +> **Note for Contributors**: These criteria define what the PR reviewer will check. |
| 145 | +> Use this as your pre-review checklist before submitting the PR to minimize |
| 146 | +> back-and-forth iterations. |
| 147 | +
|
| 148 | +**Quality Checks**: |
| 149 | + |
| 150 | +- [ ] Pre-commit checks pass: `./scripts/pre-commit.sh` |
| 151 | + |
| 152 | +**Task-Specific Criteria**: |
| 153 | + |
| 154 | +- [ ] `test` command emits no DNS warnings when `public_ip` is configured and DNS |
| 155 | + resolves to that IP |
| 156 | +- [ ] `provision` output includes `public_ip` when configured |
| 157 | +- [ ] Environment config schema validates `public_ip` as a valid IP address |
| 158 | +- [ ] Unit tests cover DNS check with and without `public_ip` |
| 159 | +- [ ] Documentation updated |
| 160 | + |
| 161 | +## Related Documentation |
| 162 | + |
| 163 | +- [docs/deployments/hetzner-demo-tracker/post-provision/dns-setup.md](../deployments/hetzner-demo-tracker/post-provision/dns-setup.md) — actual DNS setup using floating IPs |
| 164 | +- [docs/deployments/hetzner-demo-tracker/commands/improvements.md](../deployments/hetzner-demo-tracker/commands/improvements.md) — original field observation |
0 commit comments