Skip to content
Snippets Groups Projects
Commit 1b748440 authored by Robert Speicher's avatar Robert Speicher
Browse files

Merge branch 'pawel/connect_to_prometheus_through_proxy-30480' into 'master'

Deploy prometheus through kubernetes and autoconnect to cluster

Closes #30480 and #28916

See merge request gitlab-org/gitlab-ce!16182
parents bb6895ec 0e90284c
No related branches found
No related tags found
No related merge requests found
Showing
with 263 additions and 52 deletions
Loading
Loading
@@ -32,6 +32,7 @@ export default class Clusters {
installIngressPath,
installRunnerPath,
installPrometheusPath,
managePrometheusPath,
clusterStatus,
clusterStatusReason,
helpPath,
Loading
Loading
@@ -40,6 +41,7 @@ export default class Clusters {
 
this.store = new ClustersStore();
this.store.setHelpPaths(helpPath, ingressHelpPath);
this.store.setManagePrometheusPath(managePrometheusPath);
this.store.updateStatus(clusterStatus);
this.store.updateStatusReason(clusterStatusReason);
this.service = new ClustersService({
Loading
Loading
@@ -95,6 +97,7 @@ export default class Clusters {
applications: this.state.applications,
helpPath: this.state.helpPath,
ingressHelpPath: this.state.ingressHelpPath,
managePrometheusPath: this.state.managePrometheusPath,
},
});
},
Loading
Loading
Loading
Loading
@@ -32,6 +32,10 @@
type: String,
required: false,
},
manageLink: {
type: String,
required: false,
},
description: {
type: String,
required: true,
Loading
Loading
@@ -89,6 +93,12 @@
 
return label;
},
showManageButton() {
return this.manageLink && this.status === APPLICATION_INSTALLED;
},
manageButtonLabel() {
return s__('ClusterIntegration|Manage');
},
hasError() {
return this.status === APPLICATION_ERROR ||
this.requestStatus === REQUEST_FAILURE;
Loading
Loading
@@ -141,9 +151,21 @@
<div v-html="description"></div>
</div>
<div
class="table-section table-button-footer section-15 section-align-top"
class="table-section table-button-footer section-align-top"
:class="{ 'section-20': showManageButton, 'section-15': !showManageButton }"
role="gridcell"
>
<div
v-if="showManageButton"
class="btn-group table-action-buttons"
>
<a
class="btn"
:href="manageLink"
>
{{ manageButtonLabel }}
</a>
</div>
<div class="btn-group table-action-buttons">
<loading-button
class="js-cluster-application-install-button"
Loading
Loading
Loading
Loading
@@ -23,13 +23,19 @@
required: false,
default: '',
},
managePrometheusPath: {
type: String,
required: false,
default: '',
},
},
computed: {
generalApplicationDescription() {
return sprintf(
_.escape(s__(`ClusterIntegration|Install applications on your Kubernetes cluster.
Read more about %{helpLink}`)),
{
_.escape(s__(
`ClusterIntegration|Install applications on your Kubernetes cluster.
Read more about %{helpLink}`,
)), {
helpLink: `<a href="${this.helpPath}">
${_.escape(s__('ClusterIntegration|installing applications'))}
</a>`,
Loading
Loading
@@ -96,11 +102,12 @@
},
prometheusDescription() {
return sprintf(
_.escape(s__(`ClusterIntegration|Prometheus is an open-source monitoring system
with %{gitlabIntegrationLink} to monitor deployed applications.`)),
{
_.escape(s__(
`ClusterIntegration|Prometheus is an open-source monitoring system
with %{gitlabIntegrationLink} to monitor deployed applications.`,
)), {
gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html"
target="_blank" rel="noopener noreferrer">
target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|GitLab Integration'))}</a>`,
},
false,
Loading
Loading
@@ -149,6 +156,7 @@ target="_blank" rel="noopener noreferrer">
id="prometheus"
:title="applications.prometheus.title"
title-link="https://prometheus.io/docs/introduction/overview/"
:manage-link="managePrometheusPath"
:description="prometheusDescription"
:status="applications.prometheus.status"
:status-reason="applications.prometheus.statusReason"
Loading
Loading
Loading
Loading
@@ -45,6 +45,10 @@ export default class ClusterStore {
this.state.ingressHelpPath = ingressHelpPath;
}
 
setManagePrometheusPath(managePrometheusPath) {
this.state.managePrometheusPath = managePrometheusPath;
}
updateStatus(status) {
this.state.status = status;
}
Loading
Loading
Loading
Loading
@@ -27,6 +27,7 @@
hasMetrics: convertPermissionToBoolean(metricsData.hasMetrics),
documentationPath: metricsData.documentationPath,
settingsPath: metricsData.settingsPath,
clustersPath: metricsData.clustersPath,
tagsPath: metricsData.tagsPath,
projectPath: metricsData.projectPath,
metricsEndpoint: metricsData.additionalMetrics,
Loading
Loading
@@ -132,6 +133,7 @@
:selected-state="state"
:documentation-path="documentationPath"
:settings-path="settingsPath"
:clusters-path="clustersPath"
:empty-getting-started-svg-path="emptyGettingStartedSvgPath"
:empty-loading-svg-path="emptyLoadingSvgPath"
:empty-unable-to-connect-svg-path="emptyUnableToConnectSvgPath"
Loading
Loading
Loading
Loading
@@ -10,6 +10,11 @@
required: false,
default: '',
},
clustersPath: {
type: String,
required: false,
default: '',
},
selectedState: {
type: String,
required: true,
Loading
Loading
@@ -35,7 +40,10 @@
title: 'Get started with performance monitoring',
description: `Stay updated about the performance and health
of your environment by configuring Prometheus to monitor your deployments.`,
buttonText: 'Configure Prometheus',
buttonText: 'Install Prometheus on clusters',
buttonPath: this.clustersPath,
secondaryButtonText: 'Configure existing Prometheus',
secondaryButtonPath: this.settingsPath,
},
loading: {
svgUrl: this.emptyLoadingSvgPath,
Loading
Loading
@@ -43,6 +51,7 @@
description: `Creating graphs uses the data from the Prometheus server.
If this takes a long time, ensure that data is available.`,
buttonText: 'View documentation',
buttonPath: this.documentationPath,
},
noData: {
svgUrl: this.emptyUnableToConnectSvgPath,
Loading
Loading
@@ -50,12 +59,14 @@
description: `You are connected to the Prometheus server, but there is currently
no data to display.`,
buttonText: 'Configure Prometheus',
buttonPath: this.settingsPath,
},
unableToConnect: {
svgUrl: this.emptyUnableToConnectSvgPath,
title: 'Unable to connect to Prometheus server',
description: 'Ensure connectivity is available from the GitLab server to the ',
buttonText: 'View documentation',
buttonPath: this.documentationPath,
},
},
};
Loading
Loading
@@ -65,13 +76,6 @@
return this.states[this.selectedState];
},
 
buttonPath() {
if (this.selectedState === 'gettingStarted') {
return this.settingsPath;
}
return this.documentationPath;
},
showButtonDescription() {
if (this.selectedState === 'unableToConnect') return true;
return false;
Loading
Loading
@@ -99,11 +103,21 @@
</p>
<div class="state-button">
<a
v-if="currentState.buttonPath"
class="btn btn-success"
:href="buttonPath"
:href="currentState.buttonPath"
>
{{ currentState.buttonText }}
</a>
</div>
<div class="state-button">
<a
v-if="currentState.secondaryButtonPath"
class="btn"
:href="currentState.secondaryButtonPath"
>
{{ currentState.secondaryButtonText }}
</a>
</div>
</div>
</template>
Loading
Loading
@@ -205,7 +205,7 @@
}
 
.prometheus-state {
max-width: 430px;
max-width: 460px;
margin: 10px auto;
text-align: center;
 
Loading
Loading
@@ -213,6 +213,10 @@
max-width: 80vw;
margin: 0 auto;
}
.state-button {
padding: $gl-padding / 2;
}
}
 
.environments-actions {
Loading
Loading
Loading
Loading
@@ -135,6 +135,17 @@
padding-top: 0;
}
 
.integration-settings-form {
.well {
padding: $gl-padding / 2;
box-shadow: none;
}
.svg-container {
max-width: 150px;
}
}
.token-token-container {
#impersonation-token-token {
width: 80%;
Loading
Loading
Loading
Loading
@@ -32,6 +32,7 @@ module ServiceParams
:issues_events,
:issues_url,
:jira_issue_transition_id,
:manual_configuration,
:merge_requests_events,
:mock_service_url,
:namespace,
Loading
Loading
Loading
Loading
@@ -10,10 +10,26 @@ module Clusters
 
default_value_for :version, VERSION
 
state_machine :status do
after_transition any => [:installed] do |application|
application.cluster.projects.each do |project|
project.find_or_initialize_service('prometheus').update(active: true)
end
end
end
def chart
'stable/prometheus'
end
 
def service_name
'prometheus-prometheus-server'
end
def service_port
80
end
def chart_values_file
"#{Rails.root}/vendor/#{name}/values.yaml"
end
Loading
Loading
@@ -21,6 +37,22 @@ module Clusters
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart, chart_values_file: chart_values_file)
end
def proxy_client
return unless kube_client
proxy_url = kube_client.proxy_url('service', service_name, service_port, Gitlab::Kubernetes::Helm::NAMESPACE)
# ensures headers containing auth data are appended to original k8s client options
options = kube_client.rest_client.options.merge(headers: kube_client.headers)
RestClient::Resource.new(proxy_url, options)
end
private
def kube_client
cluster&.kubeclient
end
end
end
end
Loading
Loading
@@ -49,6 +49,9 @@ module Clusters
scope :enabled, -> { where(enabled: true) }
scope :disabled, -> { where(enabled: false) }
 
scope :for_environment, -> (env) { where(environment_scope: ['*', '', env.slug]) }
scope :for_all_environments, -> { where(environment_scope: ['*', '']) }
def status_name
if provider
provider.status_name
Loading
Loading
Loading
Loading
@@ -7,11 +7,14 @@ class PrometheusService < MonitoringService
 
# Access to prometheus is directly through the API
prop_accessor :api_url
boolean_accessor :manual_configuration
 
with_options presence: true, if: :activated? do
with_options presence: true, if: :manual_configuration? do
validates :api_url, url: true
end
 
before_save :synchronize_service_state!
after_save :clear_reactive_cache!
 
def initialize_properties
Loading
Loading
@@ -20,12 +23,20 @@ class PrometheusService < MonitoringService
end
end
 
def show_active_box?
false
end
def editable?
manual_configuration? || !prometheus_installed?
end
def title
'Prometheus'
end
 
def description
s_('PrometheusService|Prometheus monitoring')
s_('PrometheusService|Time-series monitoring service')
end
 
def self.to_param
Loading
Loading
@@ -33,7 +44,15 @@ class PrometheusService < MonitoringService
end
 
def fields
return [] unless editable?
[
{
type: 'checkbox',
name: 'manual_configuration',
title: s_('PrometheusService|Active'),
required: true
},
{
type: 'text',
name: 'api_url',
Loading
Loading
@@ -59,7 +78,7 @@ class PrometheusService < MonitoringService
end
 
def deployment_metrics(deployment)
metrics = with_reactive_cache(Gitlab::Prometheus::Queries::DeploymentQuery.name, deployment.id, &method(:rename_data_to_metrics))
metrics = with_reactive_cache(Gitlab::Prometheus::Queries::DeploymentQuery.name, deployment.environment.id, deployment.id, &method(:rename_data_to_metrics))
metrics&.merge(deployment_time: deployment.created_at.to_i) || {}
end
 
Loading
Loading
@@ -68,7 +87,7 @@ class PrometheusService < MonitoringService
end
 
def additional_deployment_metrics(deployment)
with_reactive_cache(Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery.name, deployment.id, &:itself)
with_reactive_cache(Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery.name, deployment.environment.id, deployment.id, &:itself)
end
 
def matched_metrics
Loading
Loading
@@ -79,6 +98,9 @@ class PrometheusService < MonitoringService
def calculate_reactive_cache(query_class_name, *args)
return unless active? && project && !project.pending_delete?
 
environment_id = args.first
client = client(environment_id)
data = Kernel.const_get(query_class_name).new(client).query(*args)
{
success: true,
Loading
Loading
@@ -89,14 +111,55 @@ class PrometheusService < MonitoringService
{ success: false, result: err.message }
end
 
def client
@prometheus ||= Gitlab::PrometheusClient.new(api_url: api_url)
def client(environment_id = nil)
if manual_configuration?
Gitlab::PrometheusClient.new(RestClient::Resource.new(api_url))
else
cluster = cluster_with_prometheus(environment_id)
raise Gitlab::PrometheusError, "couldn't find cluster with Prometheus installed" unless cluster
rest_client = client_from_cluster(cluster)
raise Gitlab::PrometheusError, "couldn't create proxy Prometheus client" unless rest_client
Gitlab::PrometheusClient.new(rest_client)
end
end
def prometheus_installed?
return false if template?
return false unless project
project.clusters.enabled.any? { |cluster| cluster.application_prometheus&.installed? }
end
 
private
 
def cluster_with_prometheus(environment_id = nil)
clusters = if environment_id
::Environment.find_by(id: environment_id).try do |env|
# sort results by descending order based on environment_scope being longer
# thus more closely matching environment slug
project.clusters.enabled.for_environment(env).sort_by { |c| c.environment_scope&.length }.reverse!
end
else
project.clusters.enabled.for_all_environments
end
clusters&.detect { |cluster| cluster.application_prometheus&.installed? }
end
def client_from_cluster(cluster)
cluster.application_prometheus.proxy_client
end
def rename_data_to_metrics(metrics)
metrics[:metrics] = metrics.delete :data
metrics
end
def synchronize_service_state!
self.active = prometheus_installed? || manual_configuration?
true
end
end
Loading
Loading
@@ -14,7 +14,8 @@
cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason,
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-ip-address') } }
ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-ip-address'),
manage_prometheus_path: edit_project_service_path(@cluster.project, 'prometheus') } }
 
.js-cluster-application-notice
.flash-container
Loading
Loading
Loading
Loading
@@ -13,6 +13,7 @@
= link_to @environment.name, environment_path(@environment)
 
#prometheus-graphs{ data: { "settings-path": edit_project_service_path(@project, 'prometheus'),
"clusters-path": project_clusters_path(@project),
"documentation-path": help_page_path('administration/monitoring/prometheus/index.md'),
"empty-getting-started-svg-path": image_path('illustrations/monitoring/getting_started.svg'),
"empty-loading-svg-path": image_path('illustrations/monitoring/loading.svg'),
Loading
Loading
Loading
Loading
@@ -9,7 +9,7 @@
 
%p= @service.description
.col-lg-9
= form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form|
= form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal integration-settings-form js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form|
= render 'shared/service_settings', form: form, subject: @service
- if @service.editable?
.footer-block.row-content-block
Loading
Loading
%h4
= s_('PrometheusService|Auto configuration')
- if @service.manual_configuration?
.well
= s_('PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below')
- else
.container-fluid
.row
- if @service.prometheus_installed?
.col-sm-2
.svg-container
= image_tag 'illustrations/monitoring/getting_started.svg'
.col-sm-10
%p.text-success.prepend-top-default
= s_('PrometheusService|Prometheus is being automatically managed on your clusters')
= link_to s_('PrometheusService|Manage clusters'), project_clusters_path(@project), class: 'btn'
- else
.col-sm-2
= image_tag 'illustrations/monitoring/loading.svg'
.col-sm-10
%p.prepend-top-default
= s_('PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments')
= link_to s_('PrometheusService|Install Prometheus on clusters'), project_clusters_path(@project), class: 'btn btn-success'
%hr
%h4.append-bottom-default
= s_('PrometheusService|Manual configuration')
- unless @service.editable?
.well
= s_('PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters')
---
title: Implement multi server support and use kube proxy to connect to Prometheus
servers inside K8S cluster
merge_request: 16182
author:
type: added
Loading
Loading
@@ -4,7 +4,7 @@ module Gitlab
class AdditionalMetricsDeploymentQuery < BaseQuery
include QueryAdditionalMetrics
 
def query(deployment_id)
def query(environment_id, deployment_id)
Deployment.find_by(id: deployment_id).try do |deployment|
query_metrics(
common_query_context(
Loading
Loading
Loading
Loading
@@ -2,7 +2,7 @@ module Gitlab
module Prometheus
module Queries
class DeploymentQuery < BaseQuery
def query(deployment_id)
def query(environment_id, deployment_id)
Deployment.find_by(id: deployment_id).try do |deployment|
environment_slug = deployment.environment.slug
 
Loading
Loading
Loading
Loading
@@ -3,10 +3,10 @@ module Gitlab
 
# Helper methods to interact with Prometheus network services & resources
class PrometheusClient
attr_reader :api_url
attr_reader :rest_client, :headers
 
def initialize(api_url:)
@api_url = api_url
def initialize(rest_client)
@rest_client = rest_client
end
 
def ping
Loading
Loading
@@ -40,37 +40,40 @@ module Gitlab
private
 
def json_api_get(type, args = {})
get(join_api_url(type, args))
path = ['api', 'v1', type].join('/')
get(path, args)
rescue JSON::ParserError
raise PrometheusError, 'Parsing response failed'
rescue Errno::ECONNREFUSED
raise PrometheusError, 'Connection refused'
end
 
def join_api_url(type, args = {})
url = URI.parse(api_url)
rescue URI::Error
raise PrometheusError, "Invalid API URL: #{api_url}"
else
url.path = [url.path.sub(%r{/+\z}, ''), 'api', 'v1', type].join('/')
url.query = args.to_query
url.to_s
end
def get(url)
handle_response(HTTParty.get(url))
def get(path, args)
response = rest_client[path].get(params: args)
handle_response(response)
rescue SocketError
raise PrometheusError, "Can't connect to #{url}"
raise PrometheusError, "Can't connect to #{rest_client.url}"
rescue OpenSSL::SSL::SSLError
raise PrometheusError, "#{url} contains invalid SSL data"
rescue HTTParty::Error
raise PrometheusError, "#{rest_client.url} contains invalid SSL data"
rescue RestClient::ExceptionWithResponse => ex
handle_exception_response(ex.response)
rescue RestClient::Exception
raise PrometheusError, "Network connection error"
end
 
def handle_response(response)
if response.code == 200 && response['status'] == 'success'
response['data'] || {}
elsif response.code == 400
raise PrometheusError, response['error'] || 'Bad data received'
json_data = JSON.parse(response.body)
if response.code == 200 && json_data['status'] == 'success'
json_data['data'] || {}
else
raise PrometheusError, "#{response.code} - #{response.body}"
end
end
def handle_exception_response(response)
if response.code == 400
json_data = JSON.parse(response.body)
raise PrometheusError, json_data['error'] || 'Bad data received'
else
raise PrometheusError, "#{response.code} - #{response.body}"
end
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment