summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorAlessio Caiazza <acaiazza@gitlab.com>2017-11-02 11:14:10 +0100
committerAlessio Caiazza <acaiazza@gitlab.com>2017-11-02 11:14:39 +0100
commit6950f38f830079199c382c974b51ad73048a6939 (patch)
tree80f4275ba2bfa0a4a8124362003be85d79debe1f /app
parent84f5aaa729d6286252602800a1f9e1bf1e5b47d3 (diff)
downloadgitlab-ce-6950f38f830079199c382c974b51ad73048a6939.tar.gz
Install k8s application with helm running inside the cluster
Diffstat (limited to 'app')
-rw-r--r--app/models/clusters/concerns.rb4
-rw-r--r--app/models/clusters/concerns/app_status.rb33
-rw-r--r--app/models/clusters/kubernetes.rb16
-rw-r--r--app/models/clusters/kubernetes/helm_app.rb18
-rw-r--r--app/models/project_services/kubernetes_service.rb6
-rw-r--r--app/services/clusters/base_helm_service.rb17
-rw-r--r--app/services/clusters/fetch_app_installation_status_service.rb13
-rw-r--r--app/services/clusters/finalize_app_installation_service.rb15
-rw-r--r--app/services/clusters/install_app_service.rb23
-rw-r--r--app/services/clusters/install_tiller_service.rb24
-rw-r--r--app/workers/cluster_install_app_worker.rb11
-rw-r--r--app/workers/cluster_wait_for_app_installation_worker.rb33
-rw-r--r--app/workers/concerns/cluster_app.rb10
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