k8s.io/kubernetes@v1.29.3/test/e2e/instrumentation/monitoring/custom_metrics_deployments.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package monitoring
    18  
    19  import (
    20  	"fmt"
    21  	"os/exec"
    22  	"strings"
    23  
    24  	appsv1 "k8s.io/api/apps/v1"
    25  	v1 "k8s.io/api/core/v1"
    26  	rbacv1 "k8s.io/api/rbac/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/kubernetes/test/e2e/framework"
    29  	e2edeployment "k8s.io/kubernetes/test/e2e/framework/deployment"
    30  	e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"
    31  	imageutils "k8s.io/kubernetes/test/utils/image"
    32  
    33  	gcm "google.golang.org/api/monitoring/v3"
    34  )
    35  
    36  var (
    37  	// CustomMetricName is the metrics name used in test cases.
    38  	CustomMetricName = "foo"
    39  	// UnusedMetricName is the unused metrics name used in test cases.
    40  	UnusedMetricName = "unused"
    41  	// CustomMetricValue is the value for CustomMetricName.
    42  	CustomMetricValue = int64(448)
    43  	// UnusedMetricValue is the value for UnusedMetricName.
    44  	UnusedMetricValue = int64(446)
    45  	// StackdriverExporter is exporter name.
    46  	StackdriverExporter = "stackdriver-exporter"
    47  	// HPAPermissions is a ClusterRoleBinding that grants unauthenticated user permissions granted for
    48  	// HPA for testing purposes, i.e. it should grant permission to read custom metrics.
    49  	HPAPermissions = &rbacv1.ClusterRoleBinding{
    50  		ObjectMeta: metav1.ObjectMeta{
    51  			Name: "custom-metrics-reader",
    52  		},
    53  		RoleRef: rbacv1.RoleRef{
    54  			APIGroup: "rbac.authorization.k8s.io",
    55  			Kind:     "ClusterRole",
    56  			Name:     "system:controller:horizontal-pod-autoscaler",
    57  		},
    58  		Subjects: []rbacv1.Subject{
    59  			{
    60  				APIGroup: "rbac.authorization.k8s.io",
    61  				Kind:     "Group",
    62  				Name:     "system:unauthenticated",
    63  			},
    64  		},
    65  	}
    66  	// StagingDeploymentsLocation is the location where the adapter deployment files are stored.
    67  	StagingDeploymentsLocation = "https://raw.githubusercontent.com/GoogleCloudPlatform/k8s-stackdriver/master/custom-metrics-stackdriver-adapter/deploy/staging/"
    68  	// AdapterForOldResourceModel is file name for the old resource model.
    69  	AdapterForOldResourceModel = "adapter_old_resource_model.yaml"
    70  	// AdapterForNewResourceModel is file name for the new resource model.
    71  	AdapterForNewResourceModel = "adapter_new_resource_model.yaml"
    72  	// AdapterDefault is the default model.
    73  	AdapterDefault = AdapterForOldResourceModel
    74  	// ClusterAdminBinding is the cluster rolebinding name for test cases.
    75  	ClusterAdminBinding = "e2e-test-cluster-admin-binding"
    76  )
    77  
    78  // CustomMetricContainerSpec allows to specify a config for StackdriverExporterDeployment
    79  // with multiple containers exporting different metrics.
    80  type CustomMetricContainerSpec struct {
    81  	Name        string
    82  	MetricName  string
    83  	MetricValue int64
    84  }
    85  
    86  // SimpleStackdriverExporterDeployment is a Deployment of simple application that exports a metric of
    87  // fixed value to Stackdriver in a loop.
    88  func SimpleStackdriverExporterDeployment(name, namespace string, replicas int32, metricValue int64) *appsv1.Deployment {
    89  	return StackdriverExporterDeployment(name, namespace, replicas,
    90  		[]CustomMetricContainerSpec{
    91  			{
    92  				Name:        StackdriverExporter,
    93  				MetricName:  CustomMetricName,
    94  				MetricValue: metricValue,
    95  			},
    96  		})
    97  }
    98  
    99  // StackdriverExporterDeployment is a Deployment of an application that can expose
   100  // an arbitrary amount of metrics of fixed value to Stackdriver in a loop. Each metric
   101  // is exposed by a different container in one pod.
   102  // The metric names and values are configured via the containers parameter.
   103  func StackdriverExporterDeployment(name, namespace string, replicas int32, containers []CustomMetricContainerSpec) *appsv1.Deployment {
   104  	podSpec := v1.PodSpec{Containers: []v1.Container{}}
   105  	for _, containerSpec := range containers {
   106  		podSpec.Containers = append(podSpec.Containers, stackdriverExporterContainerSpec(containerSpec.Name, namespace, containerSpec.MetricName, containerSpec.MetricValue))
   107  	}
   108  
   109  	d := e2edeployment.NewDeployment(name, replicas, map[string]string{"name": name}, "", "", appsv1.RollingUpdateDeploymentStrategyType)
   110  	d.ObjectMeta.Namespace = namespace
   111  	d.Spec.Template.Spec = podSpec
   112  	return d
   113  }
   114  
   115  // StackdriverExporterPod is a Pod of simple application that exports a metric of fixed value to
   116  // Stackdriver in a loop.
   117  func StackdriverExporterPod(podName, namespace, podLabel, metricName string, metricValue int64) *v1.Pod {
   118  	return &v1.Pod{
   119  		ObjectMeta: metav1.ObjectMeta{
   120  			Name:      podName,
   121  			Namespace: namespace,
   122  			Labels: map[string]string{
   123  				"name": podLabel,
   124  			},
   125  		},
   126  		Spec: v1.PodSpec{
   127  			Containers: []v1.Container{stackdriverExporterContainerSpec(StackdriverExporter, namespace, metricName, metricValue)},
   128  		},
   129  	}
   130  }
   131  
   132  func stackdriverExporterContainerSpec(name string, namespace string, metricName string, metricValue int64) v1.Container {
   133  	return v1.Container{
   134  		Name:            name,
   135  		Image:           imageutils.GetE2EImage(imageutils.SdDummyExporter),
   136  		ImagePullPolicy: v1.PullPolicy("Always"),
   137  		Command: []string{
   138  			"/bin/sh",
   139  			"-c",
   140  			strings.Join([]string{
   141  				"./sd_dummy_exporter",
   142  				"--pod-id=$(POD_ID)",
   143  				"--pod-name=$(POD_NAME)",
   144  				"--namespace=" + namespace,
   145  				"--metric-name=" + metricName,
   146  				fmt.Sprintf("--metric-value=%v", metricValue),
   147  				"--use-old-resource-model",
   148  				"--use-new-resource-model",
   149  			}, " "),
   150  		},
   151  		Env: []v1.EnvVar{
   152  			{
   153  				Name: "POD_ID",
   154  				ValueFrom: &v1.EnvVarSource{
   155  					FieldRef: &v1.ObjectFieldSelector{
   156  						FieldPath: "metadata.uid",
   157  					},
   158  				},
   159  			},
   160  			{
   161  				Name: "POD_NAME",
   162  				ValueFrom: &v1.EnvVarSource{
   163  					FieldRef: &v1.ObjectFieldSelector{
   164  						FieldPath: "metadata.name",
   165  					},
   166  				},
   167  			},
   168  		},
   169  		Ports: []v1.ContainerPort{{ContainerPort: 80}},
   170  	}
   171  }
   172  
   173  // PrometheusExporterDeployment is a Deployment of simple application with two containers
   174  // one exposing a metric in prometheus format and second a prometheus-to-sd container
   175  // that scrapes the metric and pushes it to stackdriver.
   176  func PrometheusExporterDeployment(name, namespace string, replicas int32, metricValue int64) *appsv1.Deployment {
   177  	d := e2edeployment.NewDeployment(name, replicas, map[string]string{"name": name}, "", "", appsv1.RollingUpdateDeploymentStrategyType)
   178  	d.ObjectMeta.Namespace = namespace
   179  	d.Spec.Template.Spec = prometheusExporterPodSpec(CustomMetricName, metricValue, 8080)
   180  	return d
   181  }
   182  
   183  func prometheusExporterPodSpec(metricName string, metricValue int64, port int32) v1.PodSpec {
   184  	return v1.PodSpec{
   185  		Containers: []v1.Container{
   186  			{
   187  				Name:            "prometheus-exporter",
   188  				Image:           imageutils.GetE2EImage(imageutils.PrometheusDummyExporter),
   189  				ImagePullPolicy: v1.PullPolicy("Always"),
   190  				Command: []string{"/prometheus_dummy_exporter", "--metric-name=" + metricName,
   191  					fmt.Sprintf("--metric-value=%v", metricValue), fmt.Sprintf("=--port=%d", port)},
   192  				Ports: []v1.ContainerPort{{ContainerPort: port}},
   193  			},
   194  			{
   195  				Name:            "prometheus-to-sd",
   196  				Image:           imageutils.GetE2EImage(imageutils.PrometheusToSd),
   197  				ImagePullPolicy: v1.PullPolicy("Always"),
   198  				Command: []string{"/monitor", fmt.Sprintf("--source=:http://localhost:%d", port),
   199  					"--stackdriver-prefix=custom.googleapis.com", "--pod-id=$(POD_ID)", "--namespace-id=$(POD_NAMESPACE)"},
   200  				Env: []v1.EnvVar{
   201  					{
   202  						Name: "POD_ID",
   203  						ValueFrom: &v1.EnvVarSource{
   204  							FieldRef: &v1.ObjectFieldSelector{
   205  								FieldPath: "metadata.uid",
   206  							},
   207  						},
   208  					},
   209  					{
   210  						Name: "POD_NAMESPACE",
   211  						ValueFrom: &v1.EnvVarSource{
   212  							FieldRef: &v1.ObjectFieldSelector{
   213  								FieldPath: "metadata.namespace",
   214  							},
   215  						},
   216  					},
   217  				},
   218  			},
   219  		},
   220  	}
   221  }
   222  
   223  // CreateAdapter creates Custom Metrics - Stackdriver adapter
   224  // adapterDeploymentFile should be a filename for adapter deployment located in StagingDeploymentLocation
   225  func CreateAdapter(adapterDeploymentFile string) error {
   226  	// A workaround to make the work on GKE. GKE doesn't normally allow to create cluster roles,
   227  	// which the adapter deployment does. The solution is to create cluster role binding for
   228  	// cluster-admin role and currently used service account.
   229  	err := createClusterAdminBinding()
   230  	if err != nil {
   231  		return err
   232  	}
   233  	adapterURL := StagingDeploymentsLocation + adapterDeploymentFile
   234  	err = exec.Command("wget", adapterURL).Run()
   235  	if err != nil {
   236  		return err
   237  	}
   238  	stat, err := e2ekubectl.RunKubectl("", "apply", "-f", adapterURL)
   239  	framework.Logf(stat)
   240  	return err
   241  }
   242  
   243  func createClusterAdminBinding() error {
   244  	stdout, stderr, err := framework.RunCmd("gcloud", "config", "get-value", "core/account")
   245  	if err != nil {
   246  		framework.Logf(stderr)
   247  		return err
   248  	}
   249  	serviceAccount := strings.TrimSpace(stdout)
   250  	framework.Logf("current service account: %q", serviceAccount)
   251  	stat, err := e2ekubectl.RunKubectl("", "create", "clusterrolebinding", ClusterAdminBinding, "--clusterrole=cluster-admin", "--user="+serviceAccount)
   252  	framework.Logf(stat)
   253  	return err
   254  }
   255  
   256  // CreateDescriptors creates descriptors for metrics: CustomMetricName and UnusedMetricName.
   257  func CreateDescriptors(service *gcm.Service, projectID string) error {
   258  	_, err := service.Projects.MetricDescriptors.Create(fmt.Sprintf("projects/%s", projectID), &gcm.MetricDescriptor{
   259  		Name:       CustomMetricName,
   260  		ValueType:  "INT64",
   261  		Type:       "custom.googleapis.com/" + CustomMetricName,
   262  		MetricKind: "GAUGE",
   263  	}).Do()
   264  	if err != nil {
   265  		return err
   266  	}
   267  	_, err = service.Projects.MetricDescriptors.Create(fmt.Sprintf("projects/%s", projectID), &gcm.MetricDescriptor{
   268  		Name:       UnusedMetricName,
   269  		ValueType:  "INT64",
   270  		Type:       "custom.googleapis.com/" + UnusedMetricName,
   271  		MetricKind: "GAUGE",
   272  	}).Do()
   273  	return err
   274  }
   275  
   276  // CleanupDescriptors deletes descriptors for metrics: CustomMetricName and UnusedMetricName.
   277  // TODO: Cleanup time series as well
   278  func CleanupDescriptors(service *gcm.Service, projectID string) {
   279  	_, err := service.Projects.MetricDescriptors.Delete(fmt.Sprintf("projects/%s/metricDescriptors/custom.googleapis.com/%s", projectID, CustomMetricName)).Do()
   280  	if err != nil {
   281  		framework.Logf("Failed to delete descriptor for metric '%s': %v", CustomMetricName, err)
   282  	}
   283  	_, err = service.Projects.MetricDescriptors.Delete(fmt.Sprintf("projects/%s/metricDescriptors/custom.googleapis.com/%s", projectID, UnusedMetricName)).Do()
   284  	if err != nil {
   285  		framework.Logf("Failed to delete descriptor for metric '%s': %v", CustomMetricName, err)
   286  	}
   287  }
   288  
   289  // CleanupAdapter deletes Custom Metrics - Stackdriver adapter deployments.
   290  func CleanupAdapter(adapterDeploymentFile string) {
   291  	stat, err := e2ekubectl.RunKubectl("", "delete", "-f", adapterDeploymentFile)
   292  	framework.Logf(stat)
   293  	if err != nil {
   294  		framework.Logf("Failed to delete adapter deployments: %s", err)
   295  	}
   296  	err = exec.Command("rm", adapterDeploymentFile).Run()
   297  	if err != nil {
   298  		framework.Logf("Failed to delete adapter deployment file: %s", err)
   299  	}
   300  	cleanupClusterAdminBinding()
   301  }
   302  
   303  func cleanupClusterAdminBinding() {
   304  	stat, err := e2ekubectl.RunKubectl("", "delete", "clusterrolebinding", ClusterAdminBinding)
   305  	framework.Logf(stat)
   306  	if err != nil {
   307  		framework.Logf("Failed to delete cluster admin binding: %s", err)
   308  	}
   309  }