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 }