require 'picsolve_docker_builder/helpers/kubeclient' require 'picsolve_docker_builder/helpers/kubernetes/rc' require 'picsolve_docker_builder/helpers/kubernetes/service' require 'picsolve_docker_builder/helpers/kubernetes/pod' require 'picsolve_docker_builder/helpers/ssh_connection' require 'picsolve_docker_builder/base' module PicsolveDockerBuilder module Helpers # Ruby representation of a kuberentes cluster class KubernetesManager include PicsolveDockerBuilder::Base attr_reader :port, :host, :type def initialize(composer, host, type = nil, port = nil) # default type is http type ||= :http if type == :ssh_forward @type = type @port = port || 22 end if type == :http @type = type @port = port || 8080 end @host = host @composer = composer end def user 'core' end def cleanup stages_allowed = %w(ci test) unless stages_allowed.include? stage log.error "Refuse to cleanup in stage #{stage}." \ "I only clean up in stages #{stages_allowed.join ', '}" exit 1 end log.info "Cleanup databases for '#{app_name}' in '#{stage}'" rcs = [] images.each do |i| i.requirements(self).each do |requirements| continue unless requirements.is_a?( PicsolveDockerBuilder::Composer::Requirements::Postgres ) requirements.cleanup_postgres_database end rcs += i.rc(self).existing_rcs end log.info "Cleanup replication controllers for '#{app_name}' " \ "in '#{stage}'" rcs.each do |rc| rc.remove rc.remove_pods_orphan end end def create_client Kubeclient::Client.new kubernetes_url, 'v1' end def kubernetes_url if type == :ssh_forward kubernetes_url_ssh_forward elsif type == :http kubernetes_url_http end end def kubernetes_url_http "http://#{host}:#{port}" end def kubernetes_url_ssh_forward_stop @ssh.stop unless @ssh_forward.nil? @ssh = nil end def stop kubernetes_url_ssh_forward_stop if type == :ssh_forward end def kubernetes_url_ssh_forward @ssh = SshConnection.new( ssh_host: host, ssh_port: port ) forward = @ssh.forward('127.0.0.1', 8080, 10_000) @ssh.start at_exit do kubernetes_url_ssh_forward_stop end "http://127.0.0.1:#{forward.local_port}" end def client @client ||= create_client end def images @composer.images end def app_name @composer.app_name end def stage @composer.stage end def service(image) Kubernetes::Service.new(image, self) end def rc(image) Kubernetes::Rc.new(image, self) end def wait_for_deployed_rcs wait_timeout = 300 wait_sleep = 2 wait_beginning = 15 wait_count = wait_beginning unready_images = images.dup log.info \ 'Wait for the new pods to be up and running for at least 20 seconds' sleep wait_beginning loop do unready_images.each do |i| unready_images.delete(i) if i.rc(self).ready? end log.debug "Still waiting for: #{unready_images.map(&:name)}" $stdout.flush $stderr.flush wait_count += wait_sleep break if wait_count > wait_timeout break if unready_images.length == 0 sleep wait_sleep end successful_images = images.dup unready_images.each do |u| successful_images.delete(u) end [successful_images, unready_images] end def remove_old_rcs(successful_images) # build list of successful deplyoed images successful_images.each do |i| log.debug "Remove old pods for #{i.name}" i.rc(self).remove_old_rcs log.debug "Remove orphaned pods for #{i.name}" i.rc(self).remove_pods_orphan end end def deploy log.info "start deploying app_name=#{app_name} to stage #{stage}" # reset hash @hash = nil deploy_services deploy_rcs successful_images, unready_images = wait_for_deployed_rcs remove_old_rcs(successful_images) fail "Failed to deploy this service: #{unready_images.map(&:name)}" \ if unready_images.length > 0 end def deploy_services images.each do |i| i.service(self).deploy end end def deploy_rcs images.each do |i| i.rc(self).deploy end end def template_labels_pods(i) c = template_labels(i) c['deployment'] = i.composer.hash c end def template_labels(i) { 'name' => i.name, 'app_name' => i.composer.app_name, 'stage' => i.composer.stage } end end end end