github.xiaoq7.com/operator-framework/operator-sdk@v0.8.2/pkg/metrics/metrics.go (about)

     1  // Copyright 2018 The Operator-SDK Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package metrics
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  
    21  	"github.com/operator-framework/operator-sdk/pkg/k8sutil"
    22  
    23  	v1 "k8s.io/api/core/v1"
    24  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    27  	"k8s.io/apimachinery/pkg/types"
    28  	"k8s.io/apimachinery/pkg/util/intstr"
    29  	crclient "sigs.k8s.io/controller-runtime/pkg/client"
    30  	"sigs.k8s.io/controller-runtime/pkg/client/config"
    31  	logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
    32  )
    33  
    34  var log = logf.Log.WithName("metrics")
    35  
    36  var trueVar = true
    37  
    38  const (
    39  	// PrometheusPortName defines the port name used in the metrics Service.
    40  	PrometheusPortName = "metrics"
    41  )
    42  
    43  // ExposeMetricsPort creates a Kubernetes Service to expose the passed metrics port.
    44  func ExposeMetricsPort(ctx context.Context, port int32) (*v1.Service, error) {
    45  	client, err := createClient()
    46  	if err != nil {
    47  		return nil, fmt.Errorf("failed to create new client: %v", err)
    48  	}
    49  	// We do not need to check the validity of the port, as controller-runtime
    50  	// would error out and we would never get to this stage.
    51  	s, err := initOperatorService(ctx, client, port, PrometheusPortName)
    52  	if err != nil {
    53  		if err == k8sutil.ErrNoNamespace {
    54  			log.Info("Skipping metrics Service creation; not running in a cluster.")
    55  			return nil, nil
    56  		}
    57  		return nil, fmt.Errorf("failed to initialize service object for metrics: %v", err)
    58  	}
    59  	service, err := createOrUpdateService(ctx, client, s)
    60  	if err != nil {
    61  		return nil, fmt.Errorf("failed to create or get service for metrics: %v", err)
    62  	}
    63  
    64  	return service, nil
    65  }
    66  
    67  func createOrUpdateService(ctx context.Context, client crclient.Client, s *v1.Service) (*v1.Service, error) {
    68  	if err := client.Create(ctx, s); err != nil {
    69  		if !apierrors.IsAlreadyExists(err) {
    70  			return nil, err
    71  		}
    72  		// Service already exists, we want to update it
    73  		// as we do not know if any fields might have changed.
    74  		existingService := &v1.Service{}
    75  		err := client.Get(ctx, types.NamespacedName{
    76  			Name:      s.Name,
    77  			Namespace: s.Namespace,
    78  		}, existingService)
    79  
    80  		s.ResourceVersion = existingService.ResourceVersion
    81  		if existingService.Spec.Type == v1.ServiceTypeClusterIP {
    82  			s.Spec.ClusterIP = existingService.Spec.ClusterIP
    83  		}
    84  		err = client.Update(ctx, s)
    85  		if err != nil {
    86  			return nil, err
    87  		}
    88  		log.V(1).Info("Metrics Service object updated", "Service.Name", s.Name, "Service.Namespace", s.Namespace)
    89  		return existingService, nil
    90  	}
    91  
    92  	log.Info("Metrics Service object created", "Service.Name", s.Name, "Service.Namespace", s.Namespace)
    93  	return s, nil
    94  }
    95  
    96  // initOperatorService returns the static service which exposes specified port.
    97  func initOperatorService(ctx context.Context, client crclient.Client, port int32, portName string) (*v1.Service, error) {
    98  	operatorName, err := k8sutil.GetOperatorName()
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	namespace, err := k8sutil.GetOperatorNamespace()
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	label := map[string]string{"name": operatorName}
   108  
   109  	service := &v1.Service{
   110  		ObjectMeta: metav1.ObjectMeta{
   111  			Name:      operatorName,
   112  			Namespace: namespace,
   113  			Labels:    label,
   114  		},
   115  		TypeMeta: metav1.TypeMeta{
   116  			Kind:       "Service",
   117  			APIVersion: "v1",
   118  		},
   119  		Spec: v1.ServiceSpec{
   120  			Ports: []v1.ServicePort{
   121  				{
   122  					Port:     port,
   123  					Protocol: v1.ProtocolTCP,
   124  					TargetPort: intstr.IntOrString{
   125  						Type:   intstr.Int,
   126  						IntVal: port,
   127  					},
   128  					Name: portName,
   129  				},
   130  			},
   131  			Selector: label,
   132  		},
   133  	}
   134  
   135  	ownRef, err := getPodOwnerRef(ctx, client, namespace)
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  	service.SetOwnerReferences([]metav1.OwnerReference{*ownRef})
   140  
   141  	return service, nil
   142  }
   143  
   144  func getPodOwnerRef(ctx context.Context, client crclient.Client, ns string) (*metav1.OwnerReference, error) {
   145  	// Get current Pod the operator is running in
   146  	pod, err := k8sutil.GetPod(ctx, client, ns)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  	podOwnerRefs := metav1.NewControllerRef(pod, pod.GroupVersionKind())
   151  	// Get Owner that the Pod belongs to
   152  	ownerRef := metav1.GetControllerOf(pod)
   153  	finalOwnerRef, err := findFinalOwnerRef(ctx, client, ns, ownerRef)
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  	if finalOwnerRef != nil {
   158  		return finalOwnerRef, nil
   159  	}
   160  
   161  	// Default to returning Pod as the Owner
   162  	return podOwnerRefs, nil
   163  }
   164  
   165  // findFinalOwnerRef tries to locate the final controller/owner based on the owner reference provided.
   166  func findFinalOwnerRef(ctx context.Context, client crclient.Client, ns string, ownerRef *metav1.OwnerReference) (*metav1.OwnerReference, error) {
   167  	if ownerRef == nil {
   168  		return nil, nil
   169  	}
   170  
   171  	obj := &unstructured.Unstructured{}
   172  	obj.SetAPIVersion(ownerRef.APIVersion)
   173  	obj.SetKind(ownerRef.Kind)
   174  	err := client.Get(ctx, types.NamespacedName{Namespace: ns, Name: ownerRef.Name}, obj)
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  	newOwnerRef := metav1.GetControllerOf(obj)
   179  	if newOwnerRef != nil {
   180  		return findFinalOwnerRef(ctx, client, ns, newOwnerRef)
   181  	}
   182  
   183  	log.V(1).Info("Pods owner found", "Kind", ownerRef.Kind, "Name", ownerRef.Name, "Namespace", ns)
   184  	return ownerRef, nil
   185  }
   186  
   187  func createClient() (crclient.Client, error) {
   188  	config, err := config.GetConfig()
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  
   193  	client, err := crclient.New(config, crclient.Options{})
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  
   198  	return client, nil
   199  }