class Seira::NodePools

Constants

SUMMARY
VALID_ACTIONS

Attributes

action[R]
args[R]
context[R]
settings[R]

Public Class Methods

new(action:, args:, context:, settings:) click to toggle source
# File lib/seira/node_pools.rb, line 15
def initialize(action:, args:, context:, settings:)
  @action = action
  @args = args
  @context = context
  @settings = settings
end

Public Instance Methods

run() click to toggle source
# File lib/seira/node_pools.rb, line 22
def run
  case action
  when 'help'
    run_help
  when 'list'
    run_list
  when 'list-nodes'
    run_list_nodes
  when 'add'
    run_add
  when 'cordon'
    run_cordon
  when 'drain'
    run_drain
  when 'delete'
    run_delete
  else
    fail "Unknown command encountered"
  end
end

Private Instance Methods

fail_if_lone_node_pool() click to toggle source
# File lib/seira/node_pools.rb, line 177
def fail_if_lone_node_pool
  return if node_pools.count > 1

  puts "Operation is unsafe to run with only one node pool. Please add a new node pool first to ensure services in cluster can continue running."
  exit(1)
end
node_pools() click to toggle source

TODO: Represent by a ruby object?

# File lib/seira/node_pools.rb, line 169
def node_pools
  JSON.parse(gcloud("container node-pools list --cluster #{context[:cluster]} --region=#{context[:region]}", context: context, format: :json))
end
nodes_for_pool(pool_name) click to toggle source
# File lib/seira/node_pools.rb, line 173
def nodes_for_pool(pool_name)
  kubectl("get nodes -l cloud.google.com/gke-nodepool=#{pool_name} -o name", context: :none, return_output: true).split("\n")
end
run_add() click to toggle source
# File lib/seira/node_pools.rb, line 68
def run_add
  new_pool_name = args.shift
  disk_size = nil
  image_type = nil
  machine_type = nil
  service_account = nil
  num_nodes = nil

  args.each do |arg|
    if arg.start_with? '--copy='
      node_pool_name_to_copy = arg.split('=')[1]
      node_pool_to_copy = node_pools.find { |p| p['name'] == node_pool_name_to_copy }

      fail "Could not find node pool with name #{node_pool_name_to_copy} to copy from." if node_pool_to_copy.nil?

      disk_size = node_pool_to_copy['config']['diskSizeGb']
      image_type = node_pool_to_copy['config']['imageType']
      machine_type = node_pool_to_copy['config']['machineType']
      service_account = node_pool_to_copy['serviceAccount']
      num_nodes = nodes_for_pool(node_pool_name_to_copy).count
    else
      puts "Warning: Unrecognized argument '#{arg}'"
    end
  end

  command =
    "container node-pools create #{new_pool_name} \
    --cluster=#{context[:cluster]} \
    --disk-size=#{disk_size} \
    --image-type=#{image_type} \
    --machine-type=#{machine_type} \
    --num-nodes=#{num_nodes} \
    --service-account=#{service_account}"

  if gcloud(command, conext: context, format: :boolean)
    puts 'New pool created successfully'
  else
    puts 'Failed to create new pool'
    exit(1)
  end
end
run_cordon() click to toggle source
# File lib/seira/node_pools.rb, line 110
def run_cordon
  fail_if_lone_node_pool

  node_pool_name = args.first
  nodes = nodes_for_pool(node_pool_name)

  nodes.each do |node|
    unless kubectl("cordon #{node}", context: :none)
      puts "Failed to cordon node #{node}"
      exit(1)
    end
  end

  puts "Successfully cordoned node pool #{node_pool_name}. No new workloads will be placed on #{node_pool_name} nodes."
end
run_delete() click to toggle source
# File lib/seira/node_pools.rb, line 149
def run_delete
  fail_if_lone_node_pool

  node_pool_name = args.first

  puts "Running cordon and drain as a safety measure first. If you haven't run these yet, please do so separately before deleting this node pool."
  run_cordon
  run_drain

  exit(1) unless HighLine.agree "Node pool has successfully been cordoned and drained, and should be safe to delete. Continue deleting node pool #{node_pool_name}?"

  if gcloud("container node-pools delete #{node_pool_name} --cluster #{context[:cluster]} --region=#{context[:region]}", context: context, format: :boolean)
    puts 'Node pool deleted successfully'
  else
    puts 'Failed to delete old pool'
    exit(1)
  end
end
run_drain() click to toggle source
# File lib/seira/node_pools.rb, line 126
def run_drain
  fail_if_lone_node_pool

  node_pool_name = args.first
  nodes = nodes_for_pool(node_pool_name)

  nodes.each do |node|
    # --force deletes pods that aren't managed by a ReplicationController, Job, or DaemonSet,
    #   which shouldn't be any besides manually created temp pods
    # --ignore-daemonsets prevents failing due to presence of DaemonSets, which cannot be moved
    #   because they're tied to a specific node
    # --delete-local-data prevents failing due to presence of local data, which cannot be moved
    #   but is bad practice to use for anything that can't be lost
    puts "Draining #{node}"
    unless kubectl("drain --force --ignore-daemonsets --delete-local-data #{node}", context: :none)
      puts "Failed to drain node #{node}"
      exit(1)
    end
  end

  puts "Successfully drained all nodes in node pool #{node_pool_name}. No pods are running on #{node_pool_name} nodes."
end
run_help() click to toggle source
# File lib/seira/node_pools.rb, line 45
def run_help
  puts SUMMARY
  puts "\n\n"
  puts "Possible actions:\n\n"
  puts "list: List the node pools for this cluster: `node-pools list`"
  puts "list-nodes: List the nodes in specified node pool: `node-pools list-nodes <node-pool-name>`"
  puts "add: Create a node pool. First arg is the name to use, and use --copy to specify the existing node pool to copy."
  puts "     `node-pools add <node-pool-name> --copy=<existing-node-pool-name>`"
  puts "cordon: Cordon nodes in specified node pool: `node-pools cordon <node-pool-name>`"
  puts "drain: Drain all pods from specified node pool:  `node-pools drain <node-pool-name>`"
  puts "delete: Delete a node pool. Will force-run cordon and drain, first:  `node-pools delete <node-pool-name>`"
end
run_list() click to toggle source

TODO: Info about what is running on it? TODO: What information do we get in the json format we could include here?

# File lib/seira/node_pools.rb, line 60
def run_list
  gcloud("container node-pools list --cluster #{context[:cluster]} --region=#{context[:region]}", context: context, format: :boolean)
end
run_list_nodes() click to toggle source
# File lib/seira/node_pools.rb, line 64
def run_list_nodes
  puts nodes_for_pool(args.first)
end