diff options
author | Alessio Caiazza <acaiazza@gitlab.com> | 2017-11-02 11:14:10 +0100 |
---|---|---|
committer | Alessio Caiazza <acaiazza@gitlab.com> | 2017-11-02 11:14:39 +0100 |
commit | 6950f38f830079199c382c974b51ad73048a6939 (patch) | |
tree | 80f4275ba2bfa0a4a8124362003be85d79debe1f /app | |
parent | 84f5aaa729d6286252602800a1f9e1bf1e5b47d3 (diff) | |
download | gitlab-ce-6950f38f830079199c382c974b51ad73048a6939.tar.gz |
Install k8s application with helm running inside the cluster
Diffstat (limited to 'app')
-rw-r--r-- | app/models/clusters/concerns.rb | 4 | ||||
-rw-r--r-- | app/models/clusters/concerns/app_status.rb | 33 | ||||
-rw-r--r-- | app/models/clusters/kubernetes.rb | 16 | ||||
-rw-r--r-- | app/models/clusters/kubernetes/helm_app.rb | 18 | ||||
-rw-r--r-- | app/models/project_services/kubernetes_service.rb | 6 | ||||
-rw-r--r-- | app/services/clusters/base_helm_service.rb | 17 | ||||
-rw-r--r-- | app/services/clusters/fetch_app_installation_status_service.rb | 13 | ||||
-rw-r--r-- | app/services/clusters/finalize_app_installation_service.rb | 15 | ||||
-rw-r--r-- | app/services/clusters/install_app_service.rb | 23 | ||||
-rw-r--r-- | app/services/clusters/install_tiller_service.rb | 24 | ||||
-rw-r--r-- | app/workers/cluster_install_app_worker.rb | 11 | ||||
-rw-r--r-- | app/workers/cluster_wait_for_app_installation_worker.rb | 33 | ||||
-rw-r--r-- | app/workers/concerns/cluster_app.rb | 10 |
13 files changed, 223 insertions, 0 deletions
diff --git a/app/models/clusters/concerns.rb b/app/models/clusters/concerns.rb new file mode 100644 index 00000000000..cd09863bcfc --- /dev/null +++ b/app/models/clusters/concerns.rb @@ -0,0 +1,4 @@ +module Clusters + module Concerns + end +end diff --git a/app/models/clusters/concerns/app_status.rb b/app/models/clusters/concerns/app_status.rb new file mode 100644 index 00000000000..f6b817e9ce7 --- /dev/null +++ b/app/models/clusters/concerns/app_status.rb @@ -0,0 +1,33 @@ +module Clusters + module Concerns + module AppStatus + extend ActiveSupport::Concern + + included do + state_machine :status, initial: :scheduled do + state :errored, value: -1 + state :scheduled, value: 0 + state :installing, value: 1 + state :installed, value: 2 + + event :make_installing do + transition any - [:installing] => :installing + end + + event :make_installed do + transition any - [:installed] => :installed + end + + event :make_errored do + transition any - [:errored] => :errored + end + + before_transition any => [:errored] do |app_status, transition| + status_reason = transition.args.first + app_status.status_reason = status_reason if status_reason + end + end + end + end + end +end diff --git a/app/models/clusters/kubernetes.rb b/app/models/clusters/kubernetes.rb new file mode 100644 index 00000000000..b68e2ae401e --- /dev/null +++ b/app/models/clusters/kubernetes.rb @@ -0,0 +1,16 @@ +module Clusters + module Kubernetes + def self.table_name_prefix + 'clusters_kubernetes_' + end + + def self.app(app_name) + case app_name + when HelmApp::NAME + HelmApp + else + raise ArgumentError, "Unknown app #{app_name}" + end + end + end +end diff --git a/app/models/clusters/kubernetes/helm_app.rb b/app/models/clusters/kubernetes/helm_app.rb new file mode 100644 index 00000000000..32c9e13a469 --- /dev/null +++ b/app/models/clusters/kubernetes/helm_app.rb @@ -0,0 +1,18 @@ +module Clusters + module Kubernetes + class HelmApp < ActiveRecord::Base + NAME = 'helm'.freeze + + include ::Clusters::Concerns::AppStatus + belongs_to :kubernetes_service, class_name: 'KubernetesService', foreign_key: :service_id + + default_value_for :version, Gitlab::Clusters::Helm::HELM_VERSION + + alias_method :cluster, :kubernetes_service + + def name + NAME + end + end + end +end diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb index 8ba07173c74..4b754b4d3ce 100644 --- a/app/models/project_services/kubernetes_service.rb +++ b/app/models/project_services/kubernetes_service.rb @@ -3,6 +3,8 @@ class KubernetesService < DeploymentService include Gitlab::Kubernetes include ReactiveCaching + has_one :helm_app, class_name: 'Clusters::Kubernetes::HelmApp', foreign_key: :service_id + self.reactive_cache_key = ->(service) { [service.class.model_name.singular, service.project_id] } # Namespace defaults to the project path, but can be overridden in case that @@ -136,6 +138,10 @@ class KubernetesService < DeploymentService { pods: read_pods } end + def helm + Gitlab::Clusters::Helm.new(build_kubeclient!) + end + TEMPLATE_PLACEHOLDER = 'Kubernetes namespace'.freeze private diff --git a/app/services/clusters/base_helm_service.rb b/app/services/clusters/base_helm_service.rb new file mode 100644 index 00000000000..b8ed52bf376 --- /dev/null +++ b/app/services/clusters/base_helm_service.rb @@ -0,0 +1,17 @@ +module Clusters + class BaseHelmService + attr_accessor :app + + def initialize(app) + @app = app + end + + protected + + def helm + return @helm if defined?(@helm) + + @helm = @app.cluster.helm + end + end +end diff --git a/app/services/clusters/fetch_app_installation_status_service.rb b/app/services/clusters/fetch_app_installation_status_service.rb new file mode 100644 index 00000000000..e21aa49bb43 --- /dev/null +++ b/app/services/clusters/fetch_app_installation_status_service.rb @@ -0,0 +1,13 @@ +module Clusters + class FetchAppInstallationStatusService < BaseHelmService + def execute + return unless app.installing? + + phase = helm.installation_status(app) + log = helm.installation_log(app) if phase == 'Failed' + yield(phase, log) if block_given? + rescue KubeException => ke + app.make_errored!("Kubernetes error: #{ke.message}") unless app.errored? + end + end +end diff --git a/app/services/clusters/finalize_app_installation_service.rb b/app/services/clusters/finalize_app_installation_service.rb new file mode 100644 index 00000000000..c921747febc --- /dev/null +++ b/app/services/clusters/finalize_app_installation_service.rb @@ -0,0 +1,15 @@ +module Clusters + class FinalizeAppInstallationService < BaseHelmService + def execute + helm.delete_installation_pod!(app) + + app.make_errored!('Installation aborted') if aborted? + end + + private + + def aborted? + app.installing? || app.scheduled? + end + end +end diff --git a/app/services/clusters/install_app_service.rb b/app/services/clusters/install_app_service.rb new file mode 100644 index 00000000000..dd8556108d4 --- /dev/null +++ b/app/services/clusters/install_app_service.rb @@ -0,0 +1,23 @@ +module Clusters + class InstallAppService < BaseHelmService + def execute + return unless app.scheduled? + + begin + helm.install(app) + if app.make_installing + ClusterWaitForAppInstallationWorker.perform_in( + ClusterWaitForAppInstallationWorker::INITIAL_INTERVAL, app.name, app.id) + else + app.make_errored!("Failed to update app record; #{app.errors}") + end + + rescue KubeException => ke + app.make_errored!("Kubernetes error: #{ke.message}") + rescue StandardError => e + Rails.logger.warn(e.message) + app.make_errored!("Can't start installation process") + end + end + end +end diff --git a/app/services/clusters/install_tiller_service.rb b/app/services/clusters/install_tiller_service.rb new file mode 100644 index 00000000000..ac77a7ea3c2 --- /dev/null +++ b/app/services/clusters/install_tiller_service.rb @@ -0,0 +1,24 @@ +module Clusters + class InstallTillerService < BaseService + def execute + ensure_namespace + install + end + + private + + def kubernetes_service + return @kubernetes_service if defined?(@kubernetes_service) + + @kubernetes_service = project&.kubernetes_service + end + + def ensure_namespace + kubernetes_service&.ensure_namespace! + end + + def install + kubernetes_service&.helm_client&.init! + end + end +end diff --git a/app/workers/cluster_install_app_worker.rb b/app/workers/cluster_install_app_worker.rb new file mode 100644 index 00000000000..4993b2b7349 --- /dev/null +++ b/app/workers/cluster_install_app_worker.rb @@ -0,0 +1,11 @@ +class ClusterInstallAppWorker + include Sidekiq::Worker + include ClusterQueue + include ClusterApp + + def perform(app_name, app_id) + find_app(app_name, app_id) do |app| + Clusters::InstallAppService.new(app).execute + end + end +end diff --git a/app/workers/cluster_wait_for_app_installation_worker.rb b/app/workers/cluster_wait_for_app_installation_worker.rb new file mode 100644 index 00000000000..21149cf2d19 --- /dev/null +++ b/app/workers/cluster_wait_for_app_installation_worker.rb @@ -0,0 +1,33 @@ +class ClusterWaitForAppInstallationWorker + include Sidekiq::Worker + include ClusterQueue + include ClusterApp + + INITIAL_INTERVAL = 30.seconds + EAGER_INTERVAL = 10.seconds + TIMEOUT = 20.minutes + + def perform(app_name, app_id) + find_app(app_name, app_id) do |app| + Clusters::FetchAppInstallationStatusService.new(app).execute do |phase, log| + case phase + when 'Succeeded' + if app.make_installed + Clusters::FinalizeAppInstallationService.new(app).execute + else + app.make_errored!("Failed to update app record; #{app.errors}") + end + when 'Failed' + app.make_errored!(log || 'Installation silently failed') + Clusters::FinalizeAppInstallationService.new(app).execute + else + if Time.now.utc - app.updated_at.to_time.utc > TIMEOUT + app.make_errored!('App installation timeouted') + else + ClusterWaitForAppInstallationWorker.perform_in(EAGER_INTERVAL, app.name, app.id) + end + end + end + end + end +end diff --git a/app/workers/concerns/cluster_app.rb b/app/workers/concerns/cluster_app.rb new file mode 100644 index 00000000000..2170f8be6f6 --- /dev/null +++ b/app/workers/concerns/cluster_app.rb @@ -0,0 +1,10 @@ +module ClusterApp + extend ActiveSupport::Concern + + included do + def find_app(app_name, id) + app = Clusters::Kubernetes.app(app_name).find(id) + yield(app) if block_given? + end + end +end |