Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Boxiang Sun
gitlab-ce
Commits
6950f38f
Commit
6950f38f
authored
Nov 02, 2017
by
Alessio Caiazza
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Install k8s application with helm running inside the cluster
parent
84f5aaa7
Changes
18
Show whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
366 additions
and
1 deletion
+366
-1
app/models/clusters/concerns.rb
app/models/clusters/concerns.rb
+4
-0
app/models/clusters/concerns/app_status.rb
app/models/clusters/concerns/app_status.rb
+33
-0
app/models/clusters/kubernetes.rb
app/models/clusters/kubernetes.rb
+16
-0
app/models/clusters/kubernetes/helm_app.rb
app/models/clusters/kubernetes/helm_app.rb
+18
-0
app/models/project_services/kubernetes_service.rb
app/models/project_services/kubernetes_service.rb
+6
-0
app/services/clusters/base_helm_service.rb
app/services/clusters/base_helm_service.rb
+17
-0
app/services/clusters/fetch_app_installation_status_service.rb
...ervices/clusters/fetch_app_installation_status_service.rb
+13
-0
app/services/clusters/finalize_app_installation_service.rb
app/services/clusters/finalize_app_installation_service.rb
+15
-0
app/services/clusters/install_app_service.rb
app/services/clusters/install_app_service.rb
+23
-0
app/services/clusters/install_tiller_service.rb
app/services/clusters/install_tiller_service.rb
+24
-0
app/workers/cluster_install_app_worker.rb
app/workers/cluster_install_app_worker.rb
+11
-0
app/workers/cluster_wait_for_app_installation_worker.rb
app/workers/cluster_wait_for_app_installation_worker.rb
+33
-0
app/workers/concerns/cluster_app.rb
app/workers/concerns/cluster_app.rb
+10
-0
db/migrate/20171031100710_create_clusters_kubernetes_helm_apps.rb
...te/20171031100710_create_clusters_kubernetes_helm_apps.rb
+14
-0
lib/gitlab/clusters/helm.rb
lib/gitlab/clusters/helm.rb
+104
-0
spec/models/clusters/kubernetes/helm_app_spec.rb
spec/models/clusters/kubernetes/helm_app_spec.rb
+14
-0
spec/models/clusters/kubernetes_spec.rb
spec/models/clusters/kubernetes_spec.rb
+9
-0
spec/models/project_services/kubernetes_service_spec.rb
spec/models/project_services/kubernetes_service_spec.rb
+2
-1
No files found.
app/models/clusters/concerns.rb
0 → 100644
View file @
6950f38f
module
Clusters
module
Concerns
end
end
app/models/clusters/concerns/app_status.rb
0 → 100644
View file @
6950f38f
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
app/models/clusters/kubernetes.rb
0 → 100644
View file @
6950f38f
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
app/models/clusters/kubernetes/helm_app.rb
0 → 100644
View file @
6950f38f
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
app/models/project_services/kubernetes_service.rb
View file @
6950f38f
...
...
@@ -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
...
...
app/services/clusters/base_helm_service.rb
0 → 100644
View file @
6950f38f
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
app/services/clusters/fetch_app_installation_status_service.rb
0 → 100644
View file @
6950f38f
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
app/services/clusters/finalize_app_installation_service.rb
0 → 100644
View file @
6950f38f
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
app/services/clusters/install_app_service.rb
0 → 100644
View file @
6950f38f
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
app/services/clusters/install_tiller_service.rb
0 → 100644
View file @
6950f38f
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
app/workers/cluster_install_app_worker.rb
0 → 100644
View file @
6950f38f
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
app/workers/cluster_wait_for_app_installation_worker.rb
0 → 100644
View file @
6950f38f
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
app/workers/concerns/cluster_app.rb
0 → 100644
View file @
6950f38f
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
db/migrate/20171031100710_create_clusters_kubernetes_helm_apps.rb
0 → 100644
View file @
6950f38f
class
CreateClustersKubernetesHelmApps
<
ActiveRecord
::
Migration
def
change
create_table
:clusters_kubernetes_helm_apps
do
|
t
|
t
.
integer
:status
,
null:
false
t
.
datetime_with_timezone
:created_at
,
null:
false
t
.
datetime_with_timezone
:updated_at
,
null:
false
t
.
references
:service
,
index:
true
,
null:
false
,
foreign_key:
{
on_delete: :cascade
}
t
.
string
:version
,
null:
false
t
.
text
:status_reason
end
end
end
lib/gitlab/clusters/helm.rb
0 → 100644
View file @
6950f38f
module
Gitlab
module
Clusters
class
Helm
Error
=
Class
.
new
(
StandardError
)
HELM_VERSION
=
'2.7.0'
.
freeze
NAMESPACE
=
'gitlab-managed-apps'
.
freeze
COMMAND_SCRIPT
=
<<-
EOS
.
freeze
set -eo pipefail
apk add -U ca-certificates openssl >/dev/null
wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v${HELM_VERSION}-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
mv /tmp/linux-amd64/helm /usr/bin/
helm init ${HELM_INIT_OPTS} >/dev/null
[[ -z "${HELM_COMMAND+x}" ]] || helm ${HELM_COMMAND} >/dev/null
EOS
def
initialize
(
kubeclient
)
@kubeclient
=
kubeclient
end
def
init!
ensure_namespace!
@kubeclient
.
create_pod
(
pod_resource
(
OpenStruct
.
new
(
name:
'helm'
)))
end
def
install
(
app
)
ensure_namespace!
@kubeclient
.
create_pod
(
pod_resource
(
app
))
end
##
# Returns Pod phase
#
# https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase
#
# values: "Pending", "Running", "Succeeded", "Failed", "Unknown"
#
def
installation_status
(
app
)
@kubeclient
.
get_pod
(
pod_name
(
app
),
NAMESPACE
).
status
.
phase
end
def
installation_log
(
app
)
@kubeclient
.
get_pod_log
(
pod_name
(
app
),
NAMESPACE
).
body
end
def
delete_installation_pod!
(
app
)
@kubeclient
.
delete_pod
(
pod_name
(
app
),
NAMESPACE
)
end
private
def
pod_name
(
app
)
"install-
#{
app
.
name
}
"
end
def
pod_resource
(
app
)
labels
=
{
'gitlab.org/action'
:
'install'
,
'gitlab.org/application'
:
app
.
name
}
metadata
=
{
name:
pod_name
(
app
),
namespace:
NAMESPACE
,
labels:
labels
}
container
=
{
name:
'helm'
,
image:
'alpine:3.6'
,
env:
generate_pod_env
(
app
),
command:
%w(/bin/sh)
,
args:
%w(-c $(COMMAND_SCRIPT))
}
spec
=
{
containers:
[
container
],
restartPolicy:
'Never'
}
::
Kubeclient
::
Resource
.
new
(
metadata:
metadata
,
spec:
spec
)
end
def
generate_pod_env
(
app
)
env
=
{
HELM_VERSION
:
HELM_VERSION
,
TILLER_NAMESPACE
:
NAMESPACE
,
COMMAND_SCRIPT
:
COMMAND_SCRIPT
}
if
app
.
name
!=
'helm'
env
[
:HELM_INIT_OPTS
]
=
'--client-only'
env
[
:HELM_COMMAND
]
=
helm_install_comand
(
app
)
end
env
.
map
{
|
key
,
value
|
{
name:
key
,
value:
value
}
}
end
def
helm_install_comand
(
app
)
"install
#{
app
.
chart
}
--name
#{
app
.
name
}
--namespace
#{
NAMESPACE
}
"
end
def
ensure_namespace!
begin
@kubeclient
.
get_namespace
(
NAMESPACE
)
rescue
KubeException
=>
ke
raise
ke
unless
ke
.
error_code
==
404
namespace_resource
=
::
Kubeclient
::
Resource
.
new
namespace_resource
.
metadata
=
{}
namespace_resource
.
metadata
.
name
=
NAMESPACE
@kubeclient
.
create_namespace
(
namespace_resource
)
end
end
end
end
end
spec/models/clusters/kubernetes/helm_app_spec.rb
0 → 100644
View file @
6950f38f
require
'rails_helper'
require_relative
'../kubernetes_spec'
RSpec
.
describe
Clusters
::
Kubernetes
::
HelmApp
,
type: :model
do
it_behaves_like
'a registered kubernetes app'
it
{
is_expected
.
to
belong_to
(
:kubernetes_service
)
}
describe
'#cluster'
do
it
'is an alias to #kubernetes_service'
do
expect
(
subject
.
method
(
:cluster
).
original_name
).
to
eq
(
:kubernetes_service
)
end
end
end
spec/models/clusters/kubernetes_spec.rb
0 → 100644
View file @
6950f38f
require
'rails_helper'
RSpec
.
shared_examples
'a registered kubernetes app'
do
let
(
:name
)
{
described_class
::
NAME
}
it
'can be retrieved with Clusters::Kubernetes.app'
do
expect
(
Clusters
::
Kubernetes
.
app
(
name
)).
to
eq
(
described_class
)
end
end
spec/models/project_services/kubernetes_service_spec.rb
View file @
6950f38f
...
...
@@ -7,8 +7,9 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
let
(
:project
)
{
build_stubbed
(
:kubernetes_project
)
}
let
(
:service
)
{
project
.
kubernetes_service
}
describe
"Associations"
do
describe
'Associations'
do
it
{
is_expected
.
to
belong_to
:project
}
it
{
is_expected
.
to
have_one
(
:helm_app
)
}
end
describe
'Validations'
do
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment