ubicloud/prog/kubernetes/kubernetes_nodepool_nexus.rb
Eren Başak 0d61f0359b Update billing after kubernetes nodepool is scaled
KubernetesClusterNexus#create_billing_records is replaced with
update_billing_records. This method compares the desired list and
existing list of billing records and performs add/remove to make
the desired list the reality with minimal changes.

The removal of billing records are done on the later-created ones,
in order to create less confusion around the final bill. If a user
creates a 3-node cluster, then scales it to 5 nodes to reduce it back
to 3 nodes, at the end of the month, the user will see 3 long and 2 short
nodes in the billing items.

The billing update is done right at the beginning of node removal and
at the end of node addition process, skewing the bill in favor of the user.
The update semaphore is raised right after the node removal begins, and
the updating logic eliminates the nodes flagged for retirement
from the equation.
2025-09-19 09:13:43 +03:00

102 lines
3 KiB
Ruby

# frozen_string_literal: true
class Prog::Kubernetes::KubernetesNodepoolNexus < Prog::Base
subject_is :kubernetes_nodepool
def self.assemble(name:, node_count:, kubernetes_cluster_id:, target_node_size: "standard-2", target_node_storage_size_gib: nil)
DB.transaction do
unless KubernetesCluster[kubernetes_cluster_id]
fail "No existing cluster"
end
Validation.validate_kubernetes_worker_node_count(node_count)
kn = KubernetesNodepool.create(name:, node_count:, kubernetes_cluster_id:, target_node_size:, target_node_storage_size_gib:)
Strand.create_with_id(kn.id, prog: "Kubernetes::KubernetesNodepoolNexus", label: "start")
end
end
def before_run
when_destroy_set? do
if strand.label != "destroy"
hop_destroy
end
end
end
label def start
register_deadline("wait", 120 * 60)
when_start_bootstrapping_set? do
hop_bootstrap_worker_nodes
end
nap 10
end
label def bootstrap_worker_nodes
current_node_count = kubernetes_nodepool.functional_nodes.count
desired_node_count = kubernetes_nodepool.node_count
if current_node_count < desired_node_count
(desired_node_count - current_node_count).times do
bud Prog::Kubernetes::ProvisionKubernetesNode, {"nodepool_id" => kubernetes_nodepool.id, "subject_id" => kubernetes_nodepool.kubernetes_cluster_id}
end
elsif current_node_count > desired_node_count
excess_nodes = kubernetes_nodepool.functional_nodes.first(current_node_count - desired_node_count)
excess_nodes.each(&:incr_retire)
end
hop_wait_worker_node
end
label def wait_worker_node
reap do
kubernetes_nodepool.cluster.incr_update_billing_records
hop_wait
end
end
label def wait
when_upgrade_set? do
hop_upgrade
end
when_scale_worker_count_set? do
decr_scale_worker_count
hop_bootstrap_worker_nodes
end
nap 6 * 60 * 60
end
label def upgrade
decr_upgrade
node_to_upgrade = kubernetes_nodepool.nodes.find do |node|
node_version = kubernetes_nodepool.cluster.client(session: node.sshable.connect).version
node_minor_version = node_version.match(/^v\d+\.(\d+)$/)&.captures&.first&.to_i
cluster_minor_version = kubernetes_nodepool.cluster.version.match(/^v\d+\.(\d+)$/)&.captures&.first&.to_i
next false unless node_minor_version && cluster_minor_version
node_minor_version == cluster_minor_version - 1
end
hop_wait unless node_to_upgrade
bud Prog::Kubernetes::UpgradeKubernetesNode, {"old_node_id" => node_to_upgrade.id, "nodepool_id" => kubernetes_nodepool.id, "subject_id" => kubernetes_nodepool.cluster.id}
hop_wait_upgrade
end
label def wait_upgrade
reap(:upgrade)
end
label def destroy
reap do
decr_destroy
kubernetes_nodepool.nodes.each(&:incr_destroy)
kubernetes_nodepool.vms.each(&:incr_destroy)
nap 5 unless kubernetes_nodepool.nodes.empty?
kubernetes_nodepool.destroy
pop "kubernetes nodepool is deleted"
end
end
end