Commit eb82ce60 authored by Thong Kuah's avatar Thong Kuah 🌴

Merge branch '27502-enable-cloud-run-on-gke-cluster-creation' into 'master'

Resolve "Enable Cloud Run on GKE cluster creation"

Closes #27502

See merge request gitlab-org/gitlab!16566
parents 2a81e0a8 e01c7534
......@@ -41,6 +41,8 @@ export default class Clusters {
managePrometheusPath,
clusterEnvironmentsPath,
hasRbac,
providerType,
preInstalledKnative,
clusterType,
clusterStatus,
clusterStatusReason,
......@@ -50,6 +52,7 @@ export default class Clusters {
environmentsHelpPath,
clustersHelpPath,
deployBoardsHelpPath,
cloudRunHelpPath,
clusterId,
} = document.querySelector('.js-edit-cluster-form').dataset;
......@@ -65,10 +68,13 @@ export default class Clusters {
environmentsHelpPath,
clustersHelpPath,
deployBoardsHelpPath,
cloudRunHelpPath,
);
this.store.setManagePrometheusPath(managePrometheusPath);
this.store.updateStatus(clusterStatus);
this.store.updateStatusReason(clusterStatusReason);
this.store.updateProviderType(providerType);
this.store.updatePreInstalledKnative(preInstalledKnative);
this.store.updateRbac(hasRbac);
this.service = new ClustersService({
endpoint: statusPath,
......@@ -153,6 +159,9 @@ export default class Clusters {
ingressHelpPath: this.state.ingressHelpPath,
managePrometheusPath: this.state.managePrometheusPath,
ingressDnsHelpPath: this.state.ingressDnsHelpPath,
cloudRunHelpPath: this.state.cloudRunHelpPath,
providerType: this.state.providerType,
preInstalledKnative: this.state.preInstalledKnative,
rbac: this.state.rbac,
},
});
......
......@@ -78,6 +78,10 @@ export default {
required: false,
default: false,
},
installedVia: {
type: String,
required: false,
},
version: {
type: String,
required: false,
......@@ -311,6 +315,11 @@ export default {
>
<span v-else class="js-cluster-application-title">{{ title }}</span>
</strong>
<span
v-if="installedVia"
class="js-cluster-application-installed-via"
v-html="installedVia"
></span>
<slot name="description"></slot>
<div v-if="hasError" class="cluster-application-error text-danger prepend-top-10">
<p class="js-cluster-application-general-error-message append-bottom-0">
......
......@@ -16,7 +16,7 @@ import { s__, sprintf } from '../../locale';
import applicationRow from './application_row.vue';
import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
import KnativeDomainEditor from './knative_domain_editor.vue';
import { CLUSTER_TYPE, APPLICATION_STATUS, INGRESS } from '../constants';
import { CLUSTER_TYPE, PROVIDER_TYPE, APPLICATION_STATUS, INGRESS } from '../constants';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import eventHub from '~/clusters/event_hub';
......@@ -54,11 +54,26 @@ export default {
required: false,
default: '',
},
cloudRunHelpPath: {
type: String,
required: false,
default: '',
},
managePrometheusPath: {
type: String,
required: false,
default: '',
},
providerType: {
type: String,
required: false,
default: '',
},
preInstalledKnative: {
type: Boolean,
required: false,
default: false,
},
rbac: {
type: Boolean,
required: false,
......@@ -156,6 +171,25 @@ export default {
knative() {
return this.applications.knative;
},
cloudRun() {
return this.providerType === PROVIDER_TYPE.GCP && this.preInstalledKnative;
},
installedVia() {
if (this.cloudRun) {
return sprintf(
_.escape(s__(`ClusterIntegration|installed via %{installed_via}`)),
{
installed_via: `<a href="${
this.cloudRunHelpPath
}" target="_blank" rel="noopener noreferrer">${_.escape(
s__('ClusterIntegration|Cloud Run'),
)}</a>`,
},
false,
);
}
return null;
},
},
created() {
this.helmInstallIllustration = helmInstallIllustration;
......@@ -468,6 +502,7 @@ export default {
:installed="applications.knative.installed"
:install-failed="applications.knative.installFailed"
:install-application-request-params="{ hostname: applications.knative.hostname }"
:installed-via="installedVia"
:uninstallable="applications.knative.uninstallable"
:uninstall-successful="applications.knative.uninstallSuccessful"
:uninstall-failed="applications.knative.uninstallFailed"
......@@ -499,7 +534,7 @@ export default {
</p>
<knative-domain-editor
v-if="knative.installed || (helmInstalled && rbac)"
v-if="(knative.installed || (helmInstalled && rbac)) && !preInstalledKnative"
:knative="knative"
:ingress-dns-help-path="ingressDnsHelpPath"
@save="saveKnativeDomain"
......
......@@ -5,6 +5,11 @@ export const CLUSTER_TYPE = {
PROJECT: 'project_type',
};
// These need to match the available providers in app/models/clusters/providers/
export const PROVIDER_TYPE = {
GCP: 'gcp',
};
// These need to match what is returned from the server
export const APPLICATION_STATUS = {
NO_STATUS: null,
......@@ -19,6 +24,7 @@ export const APPLICATION_STATUS = {
UNINSTALLING: 'uninstalling',
UNINSTALL_ERRORED: 'uninstall_errored',
ERROR: 'errored',
PRE_INSTALLED: 'pre_installed',
};
/*
......@@ -29,6 +35,7 @@ export const APPLICATION_INSTALLED_STATUSES = [
APPLICATION_STATUS.INSTALLED,
APPLICATION_STATUS.UPDATING,
APPLICATION_STATUS.UNINSTALLING,
APPLICATION_STATUS.PRE_INSTALLED,
];
// These are only used client-side
......
......@@ -13,6 +13,7 @@ const {
UPDATE_ERRORED,
UNINSTALLING,
UNINSTALL_ERRORED,
PRE_INSTALLED,
} = APPLICATION_STATUS;
const applicationStateMachine = {
......@@ -63,6 +64,9 @@ const applicationStateMachine = {
uninstallFailed: true,
},
},
[PRE_INSTALLED]: {
target: PRE_INSTALLED,
},
},
},
[NOT_INSTALLABLE]: {
......@@ -123,6 +127,27 @@ const applicationStateMachine = {
},
},
},
[PRE_INSTALLED]: {
on: {
[UPDATE_EVENT]: {
target: UPDATING,
effects: {
updateFailed: false,
updateSuccessful: false,
},
},
[NOT_INSTALLABLE]: {
target: NOT_INSTALLABLE,
},
[UNINSTALL_EVENT]: {
target: UNINSTALLING,
effects: {
uninstallFailed: false,
uninstallSuccessful: false,
},
},
},
},
[UPDATING]: {
on: {
[UPDATED]: {
......
......@@ -35,7 +35,10 @@ export default class ClusterStore {
environmentsHelpPath: null,
clustersHelpPath: null,
deployBoardsHelpPath: null,
cloudRunHelpPath: null,
status: null,
providerType: null,
preInstalledKnative: false,
rbac: false,
statusReason: null,
applications: {
......@@ -95,6 +98,7 @@ export default class ClusterStore {
environmentsHelpPath,
clustersHelpPath,
deployBoardsHelpPath,
cloudRunHelpPath,
) {
this.state.helpPath = helpPath;
this.state.ingressHelpPath = ingressHelpPath;
......@@ -102,6 +106,7 @@ export default class ClusterStore {
this.state.environmentsHelpPath = environmentsHelpPath;
this.state.clustersHelpPath = clustersHelpPath;
this.state.deployBoardsHelpPath = deployBoardsHelpPath;
this.state.cloudRunHelpPath = cloudRunHelpPath;
}
setManagePrometheusPath(managePrometheusPath) {
......@@ -112,6 +117,14 @@ export default class ClusterStore {
this.state.status = status;
}
updateProviderType(providerType) {
this.state.providerType = providerType;
}
updatePreInstalledKnative(preInstalledKnative) {
this.state.preInstalledKnative = parseBoolean(preInstalledKnative);
}
updateRbac(rbac) {
this.state.rbac = parseBoolean(rbac);
}
......
......@@ -170,6 +170,7 @@ class Clusters::ClustersController < Clusters::BaseController
:zone,
:num_nodes,
:machine_type,
:cloud_run,
:legacy_abac
]).merge(
provider_type: :gcp,
......
......@@ -27,7 +27,7 @@ module Clusters
def set_initial_status
return unless not_installable?
self.status = 'installable' if cluster&.platform_kubernetes_active?
self.status = status_states[:installable] if cluster&.platform_kubernetes_active?
end
# It can only be uninstalled if there are no other applications installed
......
......@@ -23,7 +23,7 @@ module Clusters
return unless cluster&.application_ingress_available?
ingress = cluster.application_ingress
self.status = 'installable' if ingress.external_ip_or_hostname?
self.status = status_states[:installable] if ingress.external_ip_or_hostname?
end
def chart
......
......@@ -21,7 +21,7 @@ module Clusters
return unless not_installable?
return unless verify_cluster?
self.status = 'installable'
self.status = status_states[:installable]
end
state_machine :status do
......@@ -47,6 +47,10 @@ module Clusters
{ "domain" => hostname }.to_yaml
end
def allowed_to_uninstall?
!pre_installed?
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
......
......@@ -194,6 +194,10 @@ module Clusters
end
end
def knative_pre_installed?
provider&.knative_pre_installed?
end
private
def instance_domain
......
......@@ -15,7 +15,7 @@ module Clusters
def set_initial_status
return unless not_installable?
self.status = 'installable' if cluster&.application_helm_available?
self.status = status_states[:installable] if cluster&.application_helm_available?
end
def can_uninstall?
......
......@@ -28,6 +28,13 @@ module Clusters
state :uninstalling, value: 7
state :uninstall_errored, value: 8
# Used for applications that are pre-installed by the cluster,
# e.g. Knative in GCP Cloud Run enabled clusters
# Because we cannot upgrade or uninstall Knative in these clusters,
# we define only one simple state transition to enter the `pre_installed` state,
# and no exit transitions.
state :pre_installed, value: 9
event :make_scheduled do
transition [:installable, :errored, :installed, :updated, :update_errored, :uninstall_errored] => :scheduled
end
......@@ -41,6 +48,10 @@ module Clusters
transition [:updating] => :updated
end
event :make_pre_installed do
transition any => :pre_installed
end
event :make_errored do
transition any - [:updating, :uninstalling] => :errored
transition [:updating] => :update_errored
......@@ -90,12 +101,18 @@ module Clusters
end
end
def status_states
self.class.state_machines[:status].states.each_with_object({}) do |state, states|
states[state.name] = state.value
end
end
def updateable?
installed? || updated? || update_errored?
end
def available?
installed? || updated?
pre_installed? || installed? || updated?
end
def update_in_progress?
......
......@@ -10,6 +10,9 @@ module Clusters
default_value_for :zone, 'us-central1-a'
default_value_for :num_nodes, 3
default_value_for :machine_type, 'n1-standard-2'
default_value_for :cloud_run, false
scope :cloud_run, -> { where(cloud_run: true) }
attr_encrypted :access_token,
mode: :per_attribute_iv,
......@@ -77,6 +80,10 @@ module Clusters
@api_client ||= GoogleApi::CloudPlatform::Client.new(access_token, nil)
end
def knative_pre_installed?
cloud_run?
end
end
end
end
......@@ -11,6 +11,7 @@ module Clusters
configure_provider
create_gitlab_service_account!
configure_kubernetes
configure_pre_installed_knative if provider.knative_pre_installed?
cluster.save!
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
log_service_error(e.class.name, provider.id, e.message)
......@@ -48,6 +49,13 @@ module Clusters
token: request_kubernetes_token)
end
def configure_pre_installed_knative
knative = cluster.build_application_knative(
hostname: 'example.com'
)
knative.make_pre_installed!
end
def request_kubernetes_token
Clusters::Kubernetes::FetchKubernetesTokenService.new(
kube_client,
......
......@@ -3,6 +3,8 @@
module Clusters
module Gcp
class ProvisionService
CLOUD_RUN_ADDONS = %i[http_load_balancing istio_config cloud_run_config].freeze
attr_reader :provider
def execute(provider)
......@@ -22,13 +24,16 @@ module Clusters
private
def get_operation_id
enable_addons = provider.cloud_run? ? CLOUD_RUN_ADDONS : []
operation = provider.api_client.projects_zones_clusters_create(
provider.gcp_project_id,
provider.zone,
provider.cluster.name,
provider.num_nodes,
machine_type: provider.machine_type,
legacy_abac: provider.legacy_abac
legacy_abac: provider.legacy_abac,
enable_addons: enable_addons
)
unless operation.status == 'PENDING' || operation.status == 'RUNNING'
......
......@@ -65,6 +65,13 @@
%p.form-text.text-muted
= s_('ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}.').html_safe % { help_link_start_machine_type: help_link_start % { url: machine_type_link_url }, help_link_start_pricing: help_link_start % { url: pricing_link_url }, help_link_end: help_link_end }
.form-group
= provider_gcp_field.check_box :cloud_run, { label: s_('ClusterIntegration|Enable Cloud Run on GKE (beta)'),
label_class: 'label-bold' }
.form-text.text-muted
= s_('ClusterIntegration|Uses the Cloud Run, Istio, and HTTP Load Balancing addons for this cluster.')
= link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'cloud-run-on-gke'), target: '_blank'
.form-group
= field.check_box :managed, { label: s_('ClusterIntegration|GitLab-managed cluster'),
label_class: 'label-bold' }
......
......@@ -23,12 +23,15 @@
cluster_type: @cluster.cluster_type,
cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason,
provider_type: @cluster.provider_type,
pre_installed_knative: @cluster.knative_pre_installed? ? 'true': 'false',
help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'),
ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-endpoint'),
ingress_dns_help_path: help_page_path('user/project/clusters/index.md', anchor: 'manually-determining-the-external-endpoint'),
environments_help_path: help_page_path('ci/environments', anchor: 'defining-environments'),
clusters_help_path: help_page_path('user/project/clusters/index.md', anchor: 'deploying-to-a-kubernetes-cluster'),
deploy_boards_help_path: help_page_path('user/project/deploy_boards.html', anchor: 'enabling-deploy-boards'),
cloud_run_help_path: help_page_path('user/project/clusters/index.md', anchor: 'cloud-run-on-gke'),
manage_prometheus_path: manage_prometheus_path,
cluster_id: @cluster.id } }
......
---
title: Enable Cloud Run on GKE cluster creation
merge_request: 16566
author:
type: added
# frozen_string_literal: true
#
# google-api-client >= 0.26.0 supports enabling CloudRun and Istio during
# cluster creation, but fog-google currently hard deps on '~> 0.23.0', which
# prevents us from upgrading. We are injecting these options as hashes below
# as a workaround until this is resolved.
#
# This can be removed once fog-google and google-api-client can be upgraded.
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/66630 for more details.
#
require 'google/apis/container_v1beta1'
Google::Apis::ContainerV1beta1::AddonsConfig::Representation.tap do |representation|
representation.hash :cloud_run_config, as: 'cloudRunConfig'
representation.hash :istio_config, as: 'istioConfig'
end
# frozen_string_literal: true
class AddCloudRunToClustersProvidersGcp < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default(:cluster_providers_gcp, :cloud_run, :boolean, default: false)
end
def down
remove_column(:cluster_providers_gcp, :cloud_run)
end
end
# frozen_string_literal: true
class AddIndexToClustersProvidersGcpOnCloudRun < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index(:cluster_providers_gcp, :cloud_run)
end
def down
remove_concurrent_index(:cluster_providers_gcp, :cloud_run)
end
end
......@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_09_18_104222) do
ActiveRecord::Schema.define(version: 2019_09_19_162036) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
......@@ -965,6 +965,8 @@ ActiveRecord::Schema.define(version: 2019_09_18_104222) do
t.text "encrypted_access_token"
t.string "encrypted_access_token_iv"
t.boolean "legacy_abac", default: false, null: false
t.boolean "cloud_run", default: false, null: false
t.index ["cloud_run"], name: "index_cluster_providers_gcp_on_cloud_run"
t.index ["cluster_id"], name: "index_cluster_providers_gcp_on_cluster_id", unique: true
end
......
......@@ -154,6 +154,7 @@ new Kubernetes cluster to your project:
- **Number of nodes** - Enter the number of nodes you wish the cluster to have.
- **Machine type** - The [machine type](https://cloud.google.com/compute/docs/machine-types)
of the Virtual Machine instance that the cluster will be based on.
- **Enable Cloud Run on GKE (beta)** - Check this if you want to use Cloud Run on GKE for this cluster. See the [Cloud Run on GKE section](#cloud-run-on-gke) for more information.
- **GitLab-managed cluster** - Leave this checked if you want GitLab to manage namespaces and service accounts for this cluster. See the [Managed clusters section](#gitlab-managed-clusters) for more information.
1. Finally, click the **Create Kubernetes cluster** button.
......@@ -339,6 +340,15 @@ functionalities needed to successfully build and deploy a containerized
application. Bear in mind that the same credentials are used for all the
applications running on the cluster.
### Cloud Run on GKE
> [Introduced](https://gitlab.com/gitlab-org/gitlab/merge_requests/16566) in GitLab 12.4.
You can choose to use Cloud Run on GKE in place of installing Knative and Istio
separately after the cluster has been created. This means that Cloud Run
(Knative), Istio, and HTTP Load Balancing will be enabled on the cluster at
create time and cannot be [installed or uninstalled](../../clusters/applications.md) separately.
### GitLab-managed clusters
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/22011) in GitLab 11.5.
......
......@@ -2,6 +2,7 @@
require 'google/apis/compute_v1'
require 'google/apis/container_v1'
require 'google/apis/container_v1beta1'
require 'google/apis/cloudbilling_v1'
require 'google/apis/cloudresourcemanager_v1'
......@@ -53,30 +54,13 @@ module GoogleApi
service.get_zone_cluster(project_id, zone, cluster_id, options: user_agent_header)
end
def projects_zones_clusters_create(project_id, zone, cluster_name, cluster_size, machine_type:, legacy_abac:)
service = Google::Apis::ContainerV1::ContainerService.new
def projects_zones_clusters_create(project_id, zone, cluster_name, cluster_size, machine_type:, legacy_abac:, enable_addons: [])
service = Google::Apis::ContainerV1beta1::ContainerService.new
service.authorization = access_token
request_body = Google::Apis::ContainerV1::CreateClusterRequest.new(
{
"cluster": {
"name": cluster_name,
"initial_node_count": cluster_size,
"node_config": {
"machine_type": machine_type
},
"master_auth": {
"username": CLUSTER_MASTER_AUTH_USERNAME,
"client_certificate_config": {
issue_client_certificate: true
}
},
"legacy_abac": {
"enabled": legacy_abac
}
}
}
)
cluster_options = make_cluster_options(cluster_name, cluster_size, machine_type, legacy_abac, enable_addons)
request_body = Google::Apis::ContainerV1beta1::CreateClusterRequest.new(cluster_options)
service.create_cluster(project_id, zone, request_body, options: user_agent_header)
end
......@@ -95,6 +79,30 @@ module GoogleApi
private
def make_cluster_options(cluster_name, cluster_size, machine_type, legacy_abac, enable_addons)
{
cluster: {
name: cluster_name,
initial_node_count: cluster_size,
node_config: {
machine_type: machine_type
},
master_auth: {
username: CLUSTER_MASTER_AUTH_USERNAME,
client_certificate_config: {
issue_client_certificate: true
}
},
legacy_abac: {
enabled: legacy_abac
},
addons_config: enable_addons.each_with_object({}) do |addon, hash|
hash[addon] = { disabled: false }
end
}
}
end
def token_life_time(expires_at)
DateTime.strptime(expires_at, '%s').to_time.utc - Time.now.utc
end
......
......@@ -3325,6 +3325,9 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""