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.
- checksums.yaml +7 -0
- data/.github/workflows/auto-bump.yml +11 -0
- data/.github/workflows/ci.yml +7 -0
- data/.github/workflows/release.yml +22 -0
- data/.gitignore +6 -0
- data/.rspec +3 -0
- data/AGENTS.md +3 -0
- data/CLAUDE.md +370 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +128 -0
- data/README.md +42 -0
- data/Rakefile +8 -0
- data/flake.lock +2144 -0
- data/flake.nix +30 -0
- data/gemset.nix +312 -0
- data/lib/pangea/kubernetes/architecture.rb +383 -0
- data/lib/pangea/kubernetes/backend_registry.rb +117 -0
- data/lib/pangea/kubernetes/backends/aws_eks.rb +203 -0
- data/lib/pangea/kubernetes/backends/aws_nixos.rb +1347 -0
- data/lib/pangea/kubernetes/backends/azure_aks.rb +145 -0
- data/lib/pangea/kubernetes/backends/azure_nixos.rb +275 -0
- data/lib/pangea/kubernetes/backends/base.rb +116 -0
- data/lib/pangea/kubernetes/backends/gcp_gke.rb +176 -0
- data/lib/pangea/kubernetes/backends/gcp_nixos.rb +240 -0
- data/lib/pangea/kubernetes/backends/hcloud_k3s.rb +181 -0
- data/lib/pangea/kubernetes/backends/nixos_base.rb +235 -0
- data/lib/pangea/kubernetes/bare_metal/cloud_init.rb +196 -0
- data/lib/pangea/kubernetes/bare_metal/cluster_reference.rb +72 -0
- data/lib/pangea/kubernetes/load_balancer.rb +157 -0
- data/lib/pangea/kubernetes/network_backend_registry.rb +54 -0
- data/lib/pangea/kubernetes/network_backends/base.rb +78 -0
- data/lib/pangea/kubernetes/network_backends/cilium.rb +105 -0
- data/lib/pangea/kubernetes/network_backends/vpc_cni.rb +36 -0
- data/lib/pangea/kubernetes/types/argocd_config.rb +55 -0
- data/lib/pangea/kubernetes/types/control_plane_config.rb +65 -0
- data/lib/pangea/kubernetes/types/etcd_config.rb +64 -0
- data/lib/pangea/kubernetes/types/firewall_config.rb +39 -0
- data/lib/pangea/kubernetes/types/k3s_config.rb +112 -0
- data/lib/pangea/kubernetes/types/kernel_config.rb +31 -0
- data/lib/pangea/kubernetes/types/kubernetes_config.rb +129 -0
- data/lib/pangea/kubernetes/types/persistent_state_config.rb +100 -0
- data/lib/pangea/kubernetes/types/pki_config.rb +48 -0
- data/lib/pangea/kubernetes/types/secrets_config.rb +41 -0
- data/lib/pangea/kubernetes/types/vpn_config.rb +188 -0
- data/lib/pangea/kubernetes/types/wait_for_dns_config.rb +35 -0
- data/lib/pangea/kubernetes/types.rb +521 -0
- data/lib/pangea-kubernetes/version.rb +5 -0
- data/lib/pangea-kubernetes.rb +43 -0
- data/pangea-kubernetes.gemspec +33 -0
- 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,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
data/.rspec
ADDED
data/AGENTS.md
ADDED
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
|