Module: Pangea::Kubernetes::Backends::NixosBase
- Included in:
- AwsNixos, AzureNixos, GcpNixos, HcloudK3s
- Defined in:
- lib/pangea/kubernetes/backends/nixos_base.rb
Overview
Template method module for NixOS backends. Extracts shared logic for all 4 NixOS backends (AWS, GCP, Azure, Hetzner).
Shared methods (implemented here):
- create_cluster: firewall + control plane server loop + cloud-init
- create_node_pool: worker cloud-init + scaling group delegation
- build_server_cloud_init: full option passthrough from config.nixos
- build_agent_cloud_init: worker cloud-init with join_server
- base_firewall_ports: cloud-agnostic port definitions
- build_secrets_hash: extracts path references from config
Template hooks (subclasses implement):
- create_compute_instance(ctx, resource_name, config, result, cloud_init, index, )
- create_worker_pool(ctx, name, cluster_ref, pool_config, cloud_init, )
- create_firewall_resources(ctx, name, config, network_result, )
- resolve_image(config)
- post_create_instance(ctx, name, server, result, index, )
Constant Summary collapse
- COMMON_PORTS =
Kubernetes port definitions shared across all NixOS backends
{ ssh: { port: 22, protocol: :tcp, public: true, description: 'SSH' }, http: { port: 80, protocol: :tcp, public: true, description: 'HTTP' }, https: { port: 443, protocol: :tcp, public: true, description: 'HTTPS' }, api: { port: 6443, protocol: :tcp, public: true, description: 'K8s API' }, kubelet: { port: 10_250, protocol: :tcp, public: false, description: 'Kubelet' }, etcd: { port: '2379-2380', protocol: :tcp, public: false, description: 'etcd' }, vxlan: { port: 8472, protocol: :udp, public: false, description: 'VXLAN' }, wireguard: { port: 51_820, protocol: :udp, public: false, description: 'WireGuard VPN' } }.freeze
- VANILLA_K8S_PORTS =
Additional ports for vanilla Kubernetes
{ controller_manager: { port: 10_257, protocol: :tcp, public: false, description: 'controller-manager' }, scheduler: { port: 10_259, protocol: :tcp, public: false, description: 'scheduler' } }.freeze
- JOIN_SERVER_PLACEHOLDER =
Placeholder used in agent cloud-init for the join server address. Backends that defer user_data encoding to Terraform (e.g., AWS with terraform_base64encode) replace this with the actual Terraform expression at synthesis time via replace().
'__PANGEA_JOIN_SERVER__'- AGENT_BOOTSTRAP_KEYS =
Extract only the secrets workers need to join the cluster. Workers receive:
-
k3s_server_token: cluster join authentication
-
nix_github_token: access private flake inputs during nixos-rebuild
They do NOT receive flux tokens, SOPS keys, VPN keys, or admin passwords.
-
%i[k3s_server_token nix_github_token].freeze
Instance Method Summary collapse
-
#base_firewall_ports(distribution) ⇒ Object
Returns all firewall ports for the given distribution.
- #build_agent_bootstrap_secrets(config) ⇒ Object
-
#build_agent_cloud_init(name, tags, cluster_ref, node_index: 'dynamic', use_join_placeholder: false) ⇒ Object
Build cloud-init for a worker/agent node.
-
#build_bootstrap_secrets(config) ⇒ Object
Extract bootstrap secrets from config for cloud-init delivery.
-
#build_secrets_hash(config) ⇒ Object
Extract secrets path references from config.
-
#build_server_cloud_init(name, config, index, result) ⇒ Object
Build cloud-init for a control plane server with full option passthrough.
-
#create_compute_instance(_ctx, _name, _config, _result, _cloud_init, _index, _tags) ⇒ Object
Create a single compute instance.
-
#create_worker_pool(_ctx, _name, _cluster_ref, _pool_config, _cloud_init, _tags) ⇒ Object
Create a worker pool (ASG, MIG, VMSS, or server loop).
-
#nixos_create_cluster(ctx, name, config, result, tags) ⇒ Object
Create control plane server(s) via template hooks.
-
#nixos_create_node_pool(ctx, name, cluster_ref, pool_config, tags) ⇒ Object
Create worker node pool via template hooks.
-
#post_create_instance(_ctx, _name, _server, _result, _index, _tags) ⇒ Object
Post-instance creation hook (e.g., Hetzner network attachment).
Instance Method Details
#base_firewall_ports(distribution) ⇒ Object
Returns all firewall ports for the given distribution
59 60 61 62 63 |
# File 'lib/pangea/kubernetes/backends/nixos_base.rb', line 59 def base_firewall_ports(distribution) ports = COMMON_PORTS.dup ports.merge!(VANILLA_K8S_PORTS) if distribution.to_sym == :kubernetes ports end |
#build_agent_bootstrap_secrets(config) ⇒ Object
206 207 208 209 210 211 212 213 214 |
# File 'lib/pangea/kubernetes/backends/nixos_base.rb', line 206 def build_agent_bootstrap_secrets(config) bs = config.bootstrap_secrets return nil unless bs.is_a?(Hash) agent_secrets = AGENT_BOOTSTRAP_KEYS.each_with_object({}) do |key, h| h[key] = bs[key] if bs[key] end agent_secrets.empty? ? nil : agent_secrets end |
#build_agent_cloud_init(name, tags, cluster_ref, node_index: 'dynamic', use_join_placeholder: false) ⇒ Object
Build cloud-init for a worker/agent node. Workers receive only the k3s_server_token from bootstrap_secrets (they need it to authenticate to the control plane for cluster join).
node_index defaults to ‘dynamic’ — the generated shell script queries EC2 instance metadata at boot time to derive a unique index from the instance ID. This prevents duplicate hostnames when multiple ASG instances share the same launch template. Backends that create individual resources (e.g., Hetzner) can override with a static index.
When use_join_placeholder is true, the join_server value is replaced with JOIN_SERVER_PLACEHOLDER. This allows backends that use Terraform functions (e.g., base64encode) to inject the actual Terraform expression via replace() at apply time, avoiding premature encoding of $… references by Ruby.
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/pangea/kubernetes/backends/nixos_base.rb', line 139 def build_agent_cloud_init(name, , cluster_ref, node_index: 'dynamic', use_join_placeholder: false) track = if cluster_ref.respond_to?(:distribution_track) && cluster_ref.distribution_track cluster_ref.distribution_track else [:DistributionTrack] || '1.34' end agent_secrets = if cluster_ref.respond_to?(:agent_bootstrap_secrets) cluster_ref.agent_bootstrap_secrets end join_server = use_join_placeholder ? JOIN_SERVER_PLACEHOLDER : cluster_ref.ipv4_address BareMetal::CloudInit.generate( cluster_name: name.to_s, distribution: [:Distribution]&.to_sym || :k3s, profile: [:Profile] || 'cloud-server', distribution_track: track, role: 'agent', node_index: node_index, cluster_init: false, join_server: join_server, bootstrap_secrets: agent_secrets ) end |
#build_bootstrap_secrets(config) ⇒ Object
Extract bootstrap secrets from config for cloud-init delivery. These are written to disk at first boot before sops-nix activates. Returns nil when no bootstrap secrets are configured.
191 192 193 194 195 196 197 |
# File 'lib/pangea/kubernetes/backends/nixos_base.rb', line 191 def build_bootstrap_secrets(config) bs = config.bootstrap_secrets return nil unless bs.is_a?(Hash) && bs.any? return nil if bs.values.all? { |v| v.nil? || (v.is_a?(String) && v.empty?) } bs end |
#build_secrets_hash(config) ⇒ Object
Extract secrets path references from config. Returns nil when no secrets are configured.
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/pangea/kubernetes/backends/nixos_base.rb', line 167 def build_secrets_hash(config) paths = {} if config.fluxcd paths[:flux_ssh_key_path] = config.fluxcd.source_ssh_key_file if config.fluxcd.source_ssh_key_file paths[:flux_token_path] = config.fluxcd.source_token_file if config.fluxcd.source_token_file paths[:sops_age_key_path] = config.fluxcd.sops_age_key_file if config.fluxcd.sops_age_key_file end if config.nixos&.secrets secrets = config.nixos.secrets paths[:flux_ssh_key_path] ||= secrets.flux_ssh_key_path if secrets.flux_ssh_key_path paths[:flux_token_path] ||= secrets.flux_token_path if secrets.flux_token_path paths[:sops_age_key_path] ||= secrets.sops_age_key_path if secrets.sops_age_key_path paths[:join_token_path] = secrets.join_token_path if secrets.join_token_path paths.merge!(secrets.extra_paths) if secrets.extra_paths.any? end paths.empty? ? nil : paths end |
#build_server_cloud_init(name, config, index, result) ⇒ Object
Build cloud-init for a control plane server with full option passthrough.
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/pangea/kubernetes/backends/nixos_base.rb', line 98 def build_server_cloud_init(name, config, index, result) gitops_config = case config.gitops_operator when :fluxcd then config.fluxcd&.to_h when :argocd then config.argocd&.to_h end BareMetal::CloudInit.generate( cluster_name: name.to_s, distribution: config.distribution, profile: config.profile, distribution_track: config.distribution_track || config.kubernetes_version, role: 'server', node_index: index, cluster_init: index.zero?, network_id: result.network&.dig(:network)&.id, fluxcd: config.gitops_operator == :fluxcd ? gitops_config : nil, argocd: config.gitops_operator == :argocd ? gitops_config : nil, k3s: config.distribution == :k3s ? config.nixos&.k3s&.to_h : nil, kubernetes: config.distribution == :kubernetes ? config.nixos&.kubernetes&.to_h : nil, secrets: build_secrets_hash(config), vpn: config.vpn&.to_h, bootstrap_secrets: build_bootstrap_secrets(config), persistent_state: config.persistent_state&.to_h ) end |
#create_compute_instance(_ctx, _name, _config, _result, _cloud_init, _index, _tags) ⇒ Object
Create a single compute instance. Returns a resource reference.
219 220 221 |
# File 'lib/pangea/kubernetes/backends/nixos_base.rb', line 219 def create_compute_instance(_ctx, _name, _config, _result, _cloud_init, _index, ) raise NotImplementedError, "#{self} must implement create_compute_instance" end |
#create_worker_pool(_ctx, _name, _cluster_ref, _pool_config, _cloud_init, _tags) ⇒ Object
Create a worker pool (ASG, MIG, VMSS, or server loop). Returns a resource reference.
224 225 226 |
# File 'lib/pangea/kubernetes/backends/nixos_base.rb', line 224 def create_worker_pool(_ctx, _name, _cluster_ref, _pool_config, _cloud_init, ) raise NotImplementedError, "#{self} must implement create_worker_pool" end |
#nixos_create_cluster(ctx, name, config, result, tags) ⇒ Object
Create control plane server(s) via template hooks. Subclasses override create_compute_instance and create_firewall_resources.
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/pangea/kubernetes/backends/nixos_base.rb', line 67 def nixos_create_cluster(ctx, name, config, result, ) system_pool = config.system_node_pool cp_count = [system_pool.min_size, 1].max servers = [] cp_count.times do |idx| cloud_init = build_server_cloud_init(name, config, idx, result) server = create_compute_instance(ctx, name, config, result, cloud_init, idx, ) post_create_instance(ctx, name, server, result, idx, ) servers << server end servers.first end |
#nixos_create_node_pool(ctx, name, cluster_ref, pool_config, tags) ⇒ Object
Create worker node pool via template hooks. Subclasses override create_worker_pool.
92 93 94 95 |
# File 'lib/pangea/kubernetes/backends/nixos_base.rb', line 92 def nixos_create_node_pool(ctx, name, cluster_ref, pool_config, ) cloud_init = build_agent_cloud_init(name, , cluster_ref) create_worker_pool(ctx, name, cluster_ref, pool_config, cloud_init, ) end |
#post_create_instance(_ctx, _name, _server, _result, _index, _tags) ⇒ Object
Post-instance creation hook (e.g., Hetzner network attachment). No-op by default.
229 230 231 |
# File 'lib/pangea/kubernetes/backends/nixos_base.rb', line 229 def post_create_instance(_ctx, _name, _server, _result, _index, ) # no-op — subclasses override when needed end |