pangea-kubernetes 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/auto-bump.yml +11 -0
  3. data/.github/workflows/ci.yml +7 -0
  4. data/.github/workflows/release.yml +22 -0
  5. data/.gitignore +6 -0
  6. data/.rspec +3 -0
  7. data/AGENTS.md +3 -0
  8. data/CLAUDE.md +370 -0
  9. data/Gemfile +9 -0
  10. data/Gemfile.lock +128 -0
  11. data/README.md +42 -0
  12. data/Rakefile +8 -0
  13. data/flake.lock +2144 -0
  14. data/flake.nix +30 -0
  15. data/gemset.nix +312 -0
  16. data/lib/pangea/kubernetes/architecture.rb +383 -0
  17. data/lib/pangea/kubernetes/backend_registry.rb +117 -0
  18. data/lib/pangea/kubernetes/backends/aws_eks.rb +203 -0
  19. data/lib/pangea/kubernetes/backends/aws_nixos.rb +1347 -0
  20. data/lib/pangea/kubernetes/backends/azure_aks.rb +145 -0
  21. data/lib/pangea/kubernetes/backends/azure_nixos.rb +275 -0
  22. data/lib/pangea/kubernetes/backends/base.rb +116 -0
  23. data/lib/pangea/kubernetes/backends/gcp_gke.rb +176 -0
  24. data/lib/pangea/kubernetes/backends/gcp_nixos.rb +240 -0
  25. data/lib/pangea/kubernetes/backends/hcloud_k3s.rb +181 -0
  26. data/lib/pangea/kubernetes/backends/nixos_base.rb +235 -0
  27. data/lib/pangea/kubernetes/bare_metal/cloud_init.rb +196 -0
  28. data/lib/pangea/kubernetes/bare_metal/cluster_reference.rb +72 -0
  29. data/lib/pangea/kubernetes/load_balancer.rb +157 -0
  30. data/lib/pangea/kubernetes/network_backend_registry.rb +54 -0
  31. data/lib/pangea/kubernetes/network_backends/base.rb +78 -0
  32. data/lib/pangea/kubernetes/network_backends/cilium.rb +105 -0
  33. data/lib/pangea/kubernetes/network_backends/vpc_cni.rb +36 -0
  34. data/lib/pangea/kubernetes/types/argocd_config.rb +55 -0
  35. data/lib/pangea/kubernetes/types/control_plane_config.rb +65 -0
  36. data/lib/pangea/kubernetes/types/etcd_config.rb +64 -0
  37. data/lib/pangea/kubernetes/types/firewall_config.rb +39 -0
  38. data/lib/pangea/kubernetes/types/k3s_config.rb +112 -0
  39. data/lib/pangea/kubernetes/types/kernel_config.rb +31 -0
  40. data/lib/pangea/kubernetes/types/kubernetes_config.rb +129 -0
  41. data/lib/pangea/kubernetes/types/persistent_state_config.rb +100 -0
  42. data/lib/pangea/kubernetes/types/pki_config.rb +48 -0
  43. data/lib/pangea/kubernetes/types/secrets_config.rb +41 -0
  44. data/lib/pangea/kubernetes/types/vpn_config.rb +188 -0
  45. data/lib/pangea/kubernetes/types/wait_for_dns_config.rb +35 -0
  46. data/lib/pangea/kubernetes/types.rb +521 -0
  47. data/lib/pangea-kubernetes/version.rb +5 -0
  48. data/lib/pangea-kubernetes.rb +43 -0
  49. data/pangea-kubernetes.gemspec +33 -0
  50. metadata +192 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cde4e2e8e9f1ea2cb7a8104d2383229a7b350cae14bf703e3c0471b5f6c3740e
4
+ data.tar.gz: 5bea4b200a23f14e71b7d42978fef3eb959eae64da946087aef0d76612c65b10
5
+ SHA512:
6
+ metadata.gz: 9ecfb4b6966a31ec67075e8fd35e729e910264cc173a26707beb347a15cc7b85f0d5d1615a3e8a787b35c18b4d09ab0522aba3cc7ec066bf3de7fb3ace2502bc
7
+ data.tar.gz: e72989f6bcc36ce970bb60fc743ee61911aa73cfec03d25cdb63f6e46891de243da66cf7b987d85a169ec2020588314dd5bd01e362b0406a4d537eb38ed27b34
@@ -0,0 +1,11 @@
1
+ name: auto-bump
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ jobs:
8
+ bump:
9
+ uses: pleme-io/substrate/.github/workflows/gem-auto-bump.yml@main
10
+ secrets:
11
+ BOT_PAT: ${{ secrets.BOT_PAT }}
@@ -0,0 +1,7 @@
1
+ name: ci
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ check:
7
+ uses: pleme-io/substrate/.github/workflows/gem-ci.yml@main
@@ -0,0 +1,22 @@
1
+ name: release
2
+
3
+ on:
4
+ push:
5
+ tags: ['v*']
6
+ workflow_dispatch:
7
+ inputs:
8
+ version:
9
+ description: 'Version to publish (informational; lib/pangea-kubernetes/version.rb is source of truth)'
10
+ required: false
11
+
12
+ permissions:
13
+ contents: write
14
+
15
+ jobs:
16
+ gem:
17
+ name: Build & push to rubygems.org
18
+ uses: pleme-io/substrate/.github/workflows/gem-release.yml@main
19
+ with:
20
+ gem-name: pangea-kubernetes
21
+ secrets:
22
+ RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ /.gems/
2
+ /.bundle/
3
+ /coverage/
4
+ /pkg/
5
+ *.gem
6
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --format progress
3
+ --color
data/AGENTS.md ADDED
@@ -0,0 +1,3 @@
1
+ # Agents
2
+
3
+ See [CLAUDE.md](./CLAUDE.md) for guidance on this repository.
data/CLAUDE.md ADDED
@@ -0,0 +1,370 @@
1
+ # pangea-kubernetes
2
+
3
+ > **★★★ CSE / Knowable Construction.** This repo operates under **Constructive Substrate Engineering** — canonical specification at [`pleme-io/theory/CONSTRUCTIVE-SUBSTRATE-ENGINEERING.md`](https://github.com/pleme-io/theory/blob/main/CONSTRUCTIVE-SUBSTRATE-ENGINEERING.md). The Compounding Directive (operational rules: solve once, load-bearing fixes only, idiom-first, models stay current, direction beats velocity) is in the org-level pleme-io/CLAUDE.md ★★★ section. Read both before non-trivial changes.
4
+
5
+
6
+ <!-- Blackmatter alignment: pillars 5, 7 -->
7
+ <!-- See ~/code/github/pleme-io/BLACKMATTER.md for pillar definitions. -->
8
+
9
+ ## Blackmatter pillars upheld
10
+
11
+ - **Pillar 5** (Infrastructure declaration): cloud-agnostic Kubernetes abstractions for Pangea — one typed declaration maps to EKS / GKE / AKS / K3s. Same shape, 8 backends.
12
+ - **Pillar 7** (Kubernetes control): Pangea Ruby → Kubernetes manifests, HelmReleases, Kustomization CRDs. Provider gems (EKS/GKE/AKS/K3s) inherit the pattern.
13
+
14
+ Cloud-agnostic Kubernetes abstractions for the Pangea infrastructure DSL.
15
+ Provides `kubernetes_cluster()` and `kubernetes_node_pool()` functions that
16
+ compile to provider-specific Terraform JSON via backend modules. All code
17
+ is **hand-written** (not auto-generated).
18
+
19
+ ## Structure
20
+
21
+ ```
22
+ lib/
23
+ pangea-kubernetes.rb # Entry point (requires all modules)
24
+ pangea-kubernetes/version.rb # Version constant
25
+ pangea/
26
+ kubernetes/
27
+ architecture.rb # kubernetes_cluster() / kubernetes_node_pool() API
28
+ backend_registry.rb # Lazy-loading backend resolver
29
+ load_balancer.rb # Elastic LB tier composition
30
+ types.rb # Core types (ClusterConfig, NodePoolConfig, etc.)
31
+ types/
32
+ control_plane_config.rb # Control plane tuning
33
+ etcd_config.rb # etcd configuration
34
+ firewall_config.rb # Firewall rules
35
+ k3s_config.rb # K3s distribution options
36
+ kernel_config.rb # Kernel parameters
37
+ kubernetes_config.rb # Vanilla K8s distribution options
38
+ pki_config.rb # PKI / certificate configuration
39
+ secrets_config.rb # Secrets (sops-nix paths)
40
+ wait_for_dns_config.rb # DNS readiness checks
41
+ backends/
42
+ base.rb # Backend contract interface (Base module)
43
+ nixos_base.rb # Shared NixOS backend logic
44
+ aws_eks.rb # AWS EKS (managed)
45
+ aws_nixos.rb # AWS EC2 + NixOS k3s
46
+ gcp_gke.rb # GCP GKE (managed)
47
+ gcp_nixos.rb # GCP GCE + NixOS k3s
48
+ azure_aks.rb # Azure AKS (managed)
49
+ azure_nixos.rb # Azure VM + NixOS k3s
50
+ hcloud_k3s.rb # Hetzner Cloud + NixOS k3s
51
+ bare_metal/
52
+ cloud_init.rb # Cloud-init user_data generator for NixOS k3s
53
+ cluster_reference.rb # External cluster reference type
54
+ spec/
55
+ spec_helper.rb
56
+ support/ # Shared test helpers
57
+ architecture/ # Architecture integration tests
58
+ backend_registry/ # Registry resolution tests
59
+ backends/ # Per-backend unit tests
60
+ aws_eks/ aws_nixos/ gcp_gke/ gcp_nixos/
61
+ azure_aks/ azure_nixos/ hcloud_k3s/ nixos_base/
62
+ base_spec.rb load_provider_spec.rb
63
+ bare_metal/ # Cloud-init and cluster ref tests
64
+ cross_backend/ # Cross-backend compatibility tests
65
+ load_balancer/ # Elastic LB tier tests
66
+ types/ # Dry::Struct type validation tests
67
+ ```
68
+
69
+ ## Architecture
70
+
71
+ ```
72
+ kubernetes_cluster(:name, config)
73
+ |
74
+ v
75
+ BackendRegistry.resolve(config.backend)
76
+ |
77
+ +-- Managed backends (delegate to cloud-native K8s)
78
+ | aws -> AwsEks (EKS)
79
+ | gcp -> GcpGke (GKE)
80
+ | azure -> AzureAks (AKS)
81
+ |
82
+ +-- NixOS backends (k3s/k8s on NixOS VMs via blackmatter-kubernetes)
83
+ aws_nixos -> AwsNixos (LT+ASG+NLB, NixOS)
84
+ gcp_nixos -> GcpNixos (GCE + NixOS)
85
+ azure_nixos -> AzureNixos (Azure VM + NixOS)
86
+ hcloud -> HcloudK3s (Hetzner + NixOS)
87
+ ```
88
+
89
+ ### Phase pipeline
90
+
91
+ Each `kubernetes_cluster()` call executes four phases:
92
+ 1. **Network** -- VPC/VNet/network creation (if `config.network` is set)
93
+ 2. **IAM** -- Roles, service accounts, managed identities
94
+ 3. **Cluster** -- The K8s control plane (EKS/GKE/AKS or NixOS server)
95
+ 4. **Node Pools** -- Worker nodes (ASGs, instance groups, or Hetzner servers)
96
+
97
+ Returns an `ArchitectureResult` with `.cluster`, `.network`, `.iam`, `.node_pools`.
98
+
99
+ ## Backend contract
100
+
101
+ Every backend implements `Pangea::Kubernetes::Backends::Base`:
102
+
103
+ | Method | Purpose |
104
+ |--------|---------|
105
+ | `backend_name` | Symbol identifier (`:aws`, `:hcloud`, etc.) |
106
+ | `managed_kubernetes?` | `true` for EKS/GKE/AKS, `false` for NixOS |
107
+ | `required_gem` | Provider gem name (`pangea-aws`, `pangea-hcloud`, etc.) |
108
+ | `load_provider!` | Require the provider gem |
109
+ | `create_network(synth, name, config, tags)` | Phase 1 |
110
+ | `create_iam(synth, name, config, tags)` | Phase 2 |
111
+ | `create_cluster(synth, name, config, result, tags)` | Phase 3 |
112
+ | `create_node_pool(synth, name, cluster_ref, pool_config, tags)` | Phase 4 |
113
+
114
+ NixOS backends share logic via `NixosBase` -- cloud-init generation, FluxCD
115
+ bootstrap, sops-nix secrets, blackmatter-kubernetes profile selection.
116
+
117
+ ### AwsNixos compute pattern (ASG-based)
118
+
119
+ All AWS NixOS compute uses ASGs — no raw `aws_instance` resources. The
120
+ `create_cluster` method produces:
121
+
122
+ ```
123
+ aws_launch_template (AMI, instance type, cloud-init, IMDSv2, encrypted gp3, IAM profile, SG)
124
+ aws_autoscaling_group (min/max/desired from system_node_pool, LT ref, subnets)
125
+ aws_lb (internal NLB — stable endpoint for workers)
126
+ aws_lb_target_group (TCP 6443, health check)
127
+ aws_lb_listener (TCP 6443 → target group)
128
+ aws_autoscaling_attachment (ASG → target group)
129
+ ```
130
+
131
+ Returns a `ControlPlaneRef` struct that:
132
+ - `ipv4_address` → delegates to `nlb.dns_name` (used by `build_agent_cloud_init`)
133
+ - Carries `subnet_ids`, `sg_id`, `instance_profile_name`, `ami_id`, `key_name`
134
+ so `create_worker_pool` reads infra context from the CP ref, not tags
135
+
136
+ Workers use the same LT+ASG pattern. `create_worker_pool` reads IAM/SG/subnet
137
+ from the `ControlPlaneRef` to ensure parity with the control plane.
138
+
139
+ ### GitOps operator selection
140
+
141
+ `ClusterConfig.gitops_operator` selects the GitOps bootstrap mechanism:
142
+ - `:fluxcd` (default) — FluxCDConfig passed to cloud-init, manifests auto-deployed
143
+ - `:argocd` — ArgocdConfig passed to cloud-init, ArgoCD bootstrapped at boot
144
+ - `:none` — no GitOps operator, manual cluster management
145
+
146
+ Cloud-init writes the operator config to `/etc/pangea/cluster-config.json`.
147
+ The NixOS module (blackmatter-kubernetes) reads the JSON and writes operator
148
+ manifests to the k3s auto-deploy directory. Credentials are created by a
149
+ separate systemd service after the API is ready.
150
+
151
+ ### Karpenter IAM (opt-in)
152
+
153
+ `ClusterConfig.karpenter_enabled = true` creates a Karpenter IRSA IAM role
154
+ and instance profile at Terraform time. Karpenter itself is deployed
155
+ post-cluster via the GitOps repo (HelmRelease).
156
+
157
+ ### Parked mode
158
+
159
+ System pool `min_size: 0` sets the CP ASG to 0 instances. All infrastructure
160
+ (VPC, IAM, NLB, LTs, key pairs) remains — only instances are terminated.
161
+ Credentials stay static across park/unpark cycles.
162
+
163
+ ### Bootstrap chain
164
+
165
+ ```
166
+ Layer 0 (Terraform): VPC, IAM, ASG, NLB, Karpenter IAM (opt-in)
167
+ Layer 1 (Cloud-init): NixOS reads config JSON → k3s + GitOps operator
168
+ Layer 2 (GitOps): Karpenter, workloads, everything else
169
+ ```
170
+
171
+ The akeyless-k8s GitOps repo (`pleme-io/akeyless-k8s`) follows the standard
172
+ FluxCD structure: `clusters/{name}/{flux-system,infrastructure,apps}`.
173
+
174
+ ## Key types
175
+
176
+ | Type | Purpose |
177
+ |------|---------|
178
+ | `ClusterConfig` | Top-level cluster configuration (backend, version, region, node pools, FluxCD, NixOS) |
179
+ | `NodePoolConfig` | Node pool sizing, instance types, labels, taints |
180
+ | `NetworkConfig` | VPC CIDR, pod/service CIDR, subnet IDs, endpoint visibility |
181
+ | `FluxCDConfig` | GitOps bootstrap (source URL, auth, SOPS, reconciliation) |
182
+ | `NixOSConfig` | NixOS image, flake URL, k3s/kubernetes options, secrets |
183
+ | `LoadBalancerConfig` | Elastic LB tier (HAProxy fleet, BGP/VRRP for bare metal) |
184
+ | `DeploymentContext` | Environment metadata (production/staging/development) |
185
+
186
+ ## NixOS / bare metal specifics
187
+
188
+ - **Cloud-init**: `BareMetal::CloudInit.generate()` produces cloud-init user_data
189
+ that writes `/etc/pangea/cluster-config.json` on NixOS servers
190
+ - **Profiles**: Maps to blackmatter-kubernetes profiles (`cilium-standard`,
191
+ `flannel-production`, `calico-hardened`, `istio-mesh`, etc.)
192
+ - **Distributions**: `:k3s` (default) or `:kubernetes` (vanilla)
193
+ - **Secrets**: sops-nix decrypted paths for age keys, SSH keys, tokens
194
+ - **FluxCD**: Full GitOps bootstrap with SSH or token auth, SOPS decryption
195
+
196
+ ## Load balancer tier
197
+
198
+ Two-tier architecture for production:
199
+ - **Tier 1 (External)**: Fleet of NixOS HAProxy VMs behind cloud LB
200
+ - **Tier 2 (In-Cluster)**: Cilium eBPF (L4) + Istio Gateway (L7)
201
+
202
+ Bare metal mode adds BGP/VRRP (HAProxy + BIRD) with virtual IPs.
203
+
204
+ ## VPN Validation
205
+
206
+ `ClusterConfig` includes optional VPN configuration via `Types::VpnConfig`. The
207
+ VPN config is validated before `ClusterConfig` coercion -- if a VPN hash is present
208
+ but malformed, a clear error is raised rather than a cryptic Dry::Struct failure.
209
+ VPN-enabled clusters synthesize an additional NLB listener for WireGuard UDP traffic.
210
+
211
+
212
+ ## Dynamic node_index via IMDSv2
213
+
214
+ Worker nodes use EC2 Instance Metadata Service v2 (IMDSv2) to dynamically
215
+ determine their `node_index` at boot time rather than relying on static
216
+ Terraform indices. This solves the ASG replacement problem where Terraform's
217
+ `count.index` becomes stale after instance recycling.
218
+
219
+ The cloud-init template queries IMDSv2 for the instance ID, then uses a
220
+ tag-based lookup to resolve the node's position in the cluster:
221
+
222
+ ```ruby
223
+ # In BareMetal::CloudInit.generate()
224
+ # IMDSv2 token acquisition + instance identity
225
+ imdsv2_token = "$(curl -s -X PUT 'http://169.254.169.254/latest/api/token' -H 'X-aws-ec2-metadata-token-ttl-seconds: 60')"
226
+ instance_id = "$(curl -s -H 'X-aws-ec2-metadata-token: #{imdsv2_token}' http://169.254.169.254/latest/meta-data/instance-id)"
227
+ ```
228
+
229
+ This replaces the previous pattern where `node_index` was baked into the launch
230
+ template at Terraform plan time. With IMDSv2, the index is resolved at boot,
231
+ making ASG scaling and replacement deterministic.
232
+
233
+ ## terraform_base64encode() Fix
234
+
235
+ The `NixosBase` backend previously used `base64encode()` for cloud-init
236
+ user_data, which is a Terraform built-in that operates on string literals. For
237
+ dynamic content (interpolated variables, template references), this caused
238
+ plan-time errors because the string wasn't fully resolved yet.
239
+
240
+ The fix uses `terraform_base64encode()` from `pangea-core`'s expression helpers,
241
+ which wraps the content in Terraform's native `base64encode()` function call
242
+ rather than evaluating it at synthesis time:
243
+
244
+ ```ruby
245
+ # Before (broken for dynamic content):
246
+ user_data = Base64.strict_encode64(cloud_init_content)
247
+
248
+ # After (works with Terraform interpolation):
249
+ user_data = Pangea::Core::Expressions.terraform_base64encode(cloud_init_content)
250
+ ```
251
+
252
+ This generates `base64encode(...)` in the Terraform JSON output, letting
253
+ Terraform handle the encoding at apply time when all variables are resolved.
254
+
255
+ ## Dependencies
256
+
257
+ - pangea-core ~> 0.2
258
+ - terraform-synthesizer ~> 0.0.28
259
+ - dry-types ~> 1.7
260
+ - dry-struct ~> 1.6
261
+ - Provider gems loaded lazily per backend:
262
+ - `pangea-aws` (aws, aws_nixos)
263
+ - `pangea-gcp` (gcp, gcp_nixos)
264
+ - `pangea-azure` (azure, azure_nixos)
265
+ - `pangea-hcloud` (hcloud)
266
+
267
+ ## Testing
268
+
269
+ ```sh
270
+ bundle exec rspec # Run all tests
271
+ bundle exec rspec spec/backends/ # Backend tests only
272
+ bundle exec rspec spec/types/ # Type validation tests
273
+ bundle exec rspec spec/architecture/ # Integration tests
274
+ bundle exec rspec spec/bare_metal/ # Cloud-init tests
275
+ bundle exec rspec spec/cross_backend/ # Cross-backend compat
276
+ bundle exec rspec spec/load_balancer/ # LB tier tests
277
+ ```
278
+
279
+ ## Adding a new backend
280
+
281
+ 1. Create `lib/pangea/kubernetes/backends/new_backend.rb`
282
+ 2. Include `Pangea::Kubernetes::Backends::Base` (or extend `NixosBase`)
283
+ 3. Implement all contract methods (`create_cluster`, `create_node_pool`, etc.)
284
+ 4. Register in `BackendRegistry::BACKEND_MAP`
285
+ 5. Add specs under `spec/backends/new_backend/`
286
+ 6. Add cross-backend specs if the new backend has unique constraints
287
+
288
+ ## Typed Result Classes
289
+
290
+ Backend phase methods return typed result structs from `Pangea::Contracts` (pangea-core):
291
+
292
+ | Contract | Phase | Key fields |
293
+ |----------|-------|------------|
294
+ | `NetworkResult` | `create_network` | vpc, subnets, security_groups, nat |
295
+ | `IamResult` | `create_iam` | roles, policies, instance_profiles |
296
+ | `ClusterResult` | `create_cluster` | control_plane, endpoint, certificate_authority |
297
+ | `ArchitectureResult` | Full return | network, iam, cluster, node_pools |
298
+
299
+ These contracts enforce that all backends return the same typed shape regardless
300
+ of cloud provider. Templates and architectures can rely on `result.network.vpc`
301
+ rather than provider-specific hash keys.
302
+
303
+ ## TypedSynthesizerContext
304
+
305
+ `spec/support/typed_synthesizer_context.rb` provides a test helper that wraps
306
+ `TerraformSynthesizer` with real `Dry::Struct` type validation. Tests using
307
+ `TypedSynthesizerContext` verify that resource function calls pass type checks
308
+ at synthesis time, catching type mismatches before deployment.
309
+
310
+ ```ruby
311
+ # spec/typed_synthesizer_context_spec.rb
312
+ RSpec.describe 'TypedSynthesizerContext' do
313
+ let(:ctx) { TypedSynthesizerContext.new }
314
+
315
+ it 'validates types on resource calls' do
316
+ expect { ctx.aws_vpc(:test, cidr_block: 123) }.to raise_error(Dry::Struct::Error)
317
+ end
318
+ end
319
+ ```
320
+
321
+ ## Typed Backend Contract (shared examples)
322
+
323
+ `spec/support/shared_examples/typed_backend_contract.rb` defines shared RSpec
324
+ examples that all backends must pass. Ensures each backend returns the correct
325
+ contract types from each phase method:
326
+
327
+ ```ruby
328
+ RSpec.shared_examples 'typed backend contract' do
329
+ it 'returns NetworkResult from create_network' do
330
+ result = backend.create_network(synth, name, config, tags)
331
+ expect(result).to be_a(Pangea::Contracts::NetworkResult)
332
+ end
333
+ end
334
+ ```
335
+
336
+ ## 3-Tier Subnet Architecture
337
+
338
+ Network phase creates a 3-tier x 3-AZ subnet layout with explicit routing:
339
+
340
+ | Tier | AZs | Routing | Purpose |
341
+ |------|-----|---------|---------|
342
+ | public | a, b, c | IGW (direct internet) | ALB, NAT gateway, bastion |
343
+ | web | a, b, c | NAT gateway (egress only, via public-a) | App servers, K3s nodes |
344
+ | data | a, b, c | VPC-local only (no internet) | RDS, ElastiCache, etcd |
345
+
346
+ - NAT gateway lives in `public-a`; web-tier route tables point to it
347
+ - Data tier has no NAT/IGW routes — fully isolated
348
+ - Each tier gets its own route table; no shared routes between tiers
349
+
350
+ ## Configurable Load Balancers
351
+
352
+ Load balancers are created in the backend, not hand-crafted in templates.
353
+ Three LB types, each independently toggleable:
354
+
355
+ | LB | Type | Default | Config key | Purpose |
356
+ |----|------|---------|------------|---------|
357
+ | ALB | Application | off | `alb_enabled` | HTTP/HTTPS ingress (web traffic) |
358
+ | VPN NLB | Network | off | `vpn.enabled` | WireGuard UDP (VPN tunnel) |
359
+ | K8s API NLB | Network | always on | — | Internal stable endpoint for kubelet/workers |
360
+
361
+ - ALB supports HTTP (80) and HTTPS (443) listeners
362
+ - VPN NLB uses UDP on the configured WireGuard port
363
+ - K8s API NLB is internal-only, TCP 6443, created by every NixOS backend
364
+
365
+ ## Etcd Backup Toggle
366
+
367
+ `ClusterConfig.etcd_backup_enabled` (default: `true` in production profile,
368
+ `false` in dev profile) controls whether S3 etcd backup resources are created.
369
+ When disabled, no S3 bucket, IAM policy, or CronJob is synthesized — reducing
370
+ cost and complexity for ephemeral dev clusters.
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source %(https://rubygems.org)
2
+ gemspec
3
+
4
+ # Local development overrides
5
+ gem 'pangea-core', path: '../pangea-core'
6
+ gem 'pangea-aws', path: '../pangea-aws'
7
+ gem 'pangea-gcp', path: '../pangea-gcp'
8
+ gem 'pangea-azure', path: '../pangea-azure'
9
+ gem 'pangea-hcloud', path: '../pangea-hcloud'
data/Gemfile.lock ADDED
@@ -0,0 +1,128 @@
1
+ PATH
2
+ remote: ../pangea-aws
3
+ specs:
4
+ pangea-aws (0.2.0)
5
+ dry-struct (~> 1.6)
6
+ dry-types (~> 1.7)
7
+ pangea-core (~> 0.2)
8
+ terraform-synthesizer (~> 0.0.28)
9
+
10
+ PATH
11
+ remote: ../pangea-azure
12
+ specs:
13
+ pangea-azure (0.1.0)
14
+ dry-struct (~> 1.6)
15
+ dry-types (~> 1.7)
16
+ pangea-core (~> 0.2)
17
+ terraform-synthesizer (~> 0.0.28)
18
+
19
+ PATH
20
+ remote: ../pangea-core
21
+ specs:
22
+ pangea-core (0.3.0)
23
+ base64
24
+ dry-struct (~> 1.6)
25
+ dry-types (~> 1.7)
26
+ terraform-synthesizer (>= 0.0.28)
27
+
28
+ PATH
29
+ remote: ../pangea-gcp
30
+ specs:
31
+ pangea-gcp (0.1.0)
32
+ dry-struct (~> 1.6)
33
+ dry-types (~> 1.7)
34
+ pangea-core (~> 0.2)
35
+ terraform-synthesizer (~> 0.0.28)
36
+
37
+ PATH
38
+ remote: ../pangea-hcloud
39
+ specs:
40
+ pangea-hcloud (0.1.0)
41
+ dry-struct (~> 1.6)
42
+ dry-types (~> 1.7)
43
+ pangea-core (~> 0.2)
44
+ terraform-synthesizer (~> 0.0.28)
45
+
46
+ PATH
47
+ remote: .
48
+ specs:
49
+ pangea-kubernetes (0.1.0)
50
+ dry-struct (~> 1.6)
51
+ dry-types (~> 1.7)
52
+ pangea-core (~> 0.2)
53
+ terraform-synthesizer (~> 0.0.28)
54
+
55
+ GEM
56
+ remote: https://rubygems.org/
57
+ specs:
58
+ abstract-synthesizer (0.0.15)
59
+ base64 (0.3.0)
60
+ bigdecimal (4.0.1)
61
+ concurrent-ruby (1.3.6)
62
+ diff-lcs (1.6.2)
63
+ docile (1.4.1)
64
+ dry-core (1.2.0)
65
+ concurrent-ruby (~> 1.0)
66
+ logger
67
+ zeitwerk (~> 2.6)
68
+ dry-inflector (1.3.1)
69
+ dry-logic (1.6.0)
70
+ bigdecimal
71
+ concurrent-ruby (~> 1.0)
72
+ dry-core (~> 1.1)
73
+ zeitwerk (~> 2.6)
74
+ dry-struct (1.8.0)
75
+ dry-core (~> 1.1)
76
+ dry-types (~> 1.8, >= 1.8.2)
77
+ ice_nine (~> 0.11)
78
+ zeitwerk (~> 2.6)
79
+ dry-types (1.9.1)
80
+ bigdecimal (>= 3.0)
81
+ concurrent-ruby (~> 1.0)
82
+ dry-core (~> 1.0)
83
+ dry-inflector (~> 1.0)
84
+ dry-logic (~> 1.4)
85
+ zeitwerk (~> 2.6)
86
+ ice_nine (0.11.2)
87
+ logger (1.7.0)
88
+ rake (13.3.1)
89
+ rspec (3.13.2)
90
+ rspec-core (~> 3.13.0)
91
+ rspec-expectations (~> 3.13.0)
92
+ rspec-mocks (~> 3.13.0)
93
+ rspec-core (3.13.6)
94
+ rspec-support (~> 3.13.0)
95
+ rspec-expectations (3.13.5)
96
+ diff-lcs (>= 1.2.0, < 2.0)
97
+ rspec-support (~> 3.13.0)
98
+ rspec-mocks (3.13.7)
99
+ diff-lcs (>= 1.2.0, < 2.0)
100
+ rspec-support (~> 3.13.0)
101
+ rspec-support (3.13.7)
102
+ simplecov (0.22.0)
103
+ docile (~> 1.1)
104
+ simplecov-html (~> 0.11)
105
+ simplecov_json_formatter (~> 0.1)
106
+ simplecov-html (0.13.2)
107
+ simplecov_json_formatter (0.1.4)
108
+ terraform-synthesizer (0.0.28)
109
+ abstract-synthesizer
110
+ zeitwerk (2.7.5)
111
+
112
+ PLATFORMS
113
+ arm64-darwin-24
114
+ ruby
115
+
116
+ DEPENDENCIES
117
+ pangea-aws!
118
+ pangea-azure!
119
+ pangea-core!
120
+ pangea-gcp!
121
+ pangea-hcloud!
122
+ pangea-kubernetes!
123
+ rake (~> 13.0)
124
+ rspec (~> 3.12)
125
+ simplecov (~> 0.22)
126
+
127
+ BUNDLED WITH
128
+ 2.5.22
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # pangea-kubernetes
2
+
3
+ Cloud-agnostic Kubernetes abstractions for the Pangea infrastructure DSL.
4
+
5
+ ## Overview
6
+
7
+ Provides a unified `kubernetes_cluster` and `kubernetes_node_pool` API that compiles
8
+ to provider-specific Terraform JSON via pluggable backend modules (AWS EKS, GCP GKE,
9
+ Azure AKS, Hetzner k3s-on-NixOS). Includes bare-metal cloud-init support, elastic
10
+ load balancer abstractions, and a backend registry for extensibility. Built on pangea-core.
11
+
12
+ ## Installation
13
+
14
+ ```ruby
15
+ gem 'pangea-kubernetes', '~> 0.1'
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```ruby
21
+ require 'pangea-kubernetes'
22
+ require 'pangea-hcloud' # backend provider
23
+
24
+ cluster = Pangea::Kubernetes::Architecture.new(
25
+ backend: :hcloud_nixos,
26
+ name: "my-cluster",
27
+ version: "1.31"
28
+ )
29
+ ```
30
+
31
+ Backends are lazy-loaded -- only require the provider gem for the backend you use.
32
+
33
+ ## Development
34
+
35
+ ```bash
36
+ nix develop
37
+ bundle exec rspec
38
+ ```
39
+
40
+ ## License
41
+
42
+ Apache-2.0
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require %(bundler/gem_tasks)
4
+ require %(rspec/core/rake_task)
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec