Skip to content

Commit 21e899c

Browse files
authored
pkg/metrics: Add ownerReference to metrics Service object (#1037)
* pkg/metrics: Add ownerRef to Service This allows the Service cleanup to happen when owner is deleted. Owner is detected by tracing up until last owner is found. If no owner is found, the pod is set as owner. * Gopkg.lock: Rerun dep ensure * pkg/metrics: Update Service if it exists When Service already exists but the OwnerRef might change, we must make sure to the propagate those changes to the Service object by updating the object.
1 parent c307af7 commit 21e899c

File tree

5 files changed

+135
-46
lines changed

5 files changed

+135
-46
lines changed

commands/operator-sdk/cmd/test/cluster.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
k8sInternal "github.com/operator-framework/operator-sdk/internal/util/k8sutil"
2525
"github.com/operator-framework/operator-sdk/internal/util/projutil"
2626
"github.com/operator-framework/operator-sdk/pkg/k8sutil"
27-
"github.com/operator-framework/operator-sdk/pkg/leader"
2827
"github.com/operator-framework/operator-sdk/pkg/scaffold"
2928
"github.com/operator-framework/operator-sdk/pkg/scaffold/ansible"
3029
"github.com/operator-framework/operator-sdk/pkg/test"
@@ -113,7 +112,7 @@ func testClusterFunc(cmd *cobra.Command, args []string) error {
113112
Name: k8sutil.OperatorNameEnvVar,
114113
Value: "test-operator",
115114
}, {
116-
Name: leader.PodNameEnv,
115+
Name: k8sutil.PodNameEnvVar,
117116
ValueFrom: &v1.EnvVarSource{FieldRef: &v1.ObjectFieldSelector{FieldPath: "metadata.name"}},
118117
}},
119118
}},

pkg/k8sutil/constants.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ const (
2525
WatchNamespaceEnvVar = "WATCH_NAMESPACE"
2626

2727
// OperatorNameEnvVar is the constant for env variable OPERATOR_NAME
28-
// wich is the name of the current operator
28+
// which is the name of the current operator
2929
OperatorNameEnvVar = "OPERATOR_NAME"
30+
31+
// PodNameEnvVar is the contast for env variable POD_NAME
32+
// which is the name of the current pod.
33+
PodNameEnvVar = "POD_NAME"
3034
)

pkg/k8sutil/k8sutil.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@
1515
package k8sutil
1616

1717
import (
18+
"context"
1819
"fmt"
1920
"io/ioutil"
2021
"os"
2122
"strings"
2223

24+
corev1 "k8s.io/api/core/v1"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2326
discovery "k8s.io/client-go/discovery"
27+
crclient "sigs.k8s.io/controller-runtime/pkg/client"
2428
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
2529
)
2630

@@ -83,3 +87,32 @@ func ResourceExists(dc discovery.DiscoveryInterface, apiGroupVersion, kind strin
8387
}
8488
return false, nil
8589
}
90+
91+
// GetPod returns a Pod object that corresponds to the pod in which the code
92+
// is currently running.
93+
// It expects the environment variable POD_NAME to be set by the downwards API.
94+
func GetPod(ctx context.Context, client crclient.Client, ns string) (*corev1.Pod, error) {
95+
podName := os.Getenv(PodNameEnvVar)
96+
if podName == "" {
97+
return nil, fmt.Errorf("required env %s not set, please configure downward API", PodNameEnvVar)
98+
}
99+
100+
log.V(1).Info("Found podname", "Pod.Name", podName)
101+
102+
pod := &corev1.Pod{
103+
TypeMeta: metav1.TypeMeta{
104+
APIVersion: "v1",
105+
Kind: "Pod",
106+
},
107+
}
108+
key := crclient.ObjectKey{Namespace: ns, Name: podName}
109+
err := client.Get(ctx, key, pod)
110+
if err != nil {
111+
log.Error(err, "Failed to get Pod", "Pod.Namespace", ns, "Pod.Name", podName)
112+
return nil, err
113+
}
114+
115+
log.V(1).Info("Found Pod", "Pod.Namespace", ns, "Pod.Name", pod.Name)
116+
117+
return pod, nil
118+
}

pkg/leader/leader.go

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ package leader
1616

1717
import (
1818
"context"
19-
"fmt"
20-
"os"
2119
"time"
2220

2321
"github.com/operator-framework/operator-sdk/pkg/k8sutil"
@@ -37,8 +35,6 @@ var log = logf.Log.WithName("leader")
3735
// attempts to become the leader.
3836
const maxBackoffInterval = time.Second * 16
3937

40-
const PodNameEnv = "POD_NAME"
41-
4238
// Become ensures that the current pod is the leader within its namespace. If
4339
// run outside a cluster, it will skip leader election and return nil. It
4440
// continuously tries to create a ConfigMap with the provided name and the
@@ -143,24 +139,8 @@ func Become(ctx context.Context, lockName string) error {
143139
// this code is currently running.
144140
// It expects the environment variable POD_NAME to be set by the downwards API
145141
func myOwnerRef(ctx context.Context, client crclient.Client, ns string) (*metav1.OwnerReference, error) {
146-
podName := os.Getenv(PodNameEnv)
147-
if podName == "" {
148-
return nil, fmt.Errorf("required env %s not set, please configure downward API", PodNameEnv)
149-
}
150-
151-
log.V(1).Info("Found podname", "Pod.Name", podName)
152-
153-
myPod := &corev1.Pod{
154-
TypeMeta: metav1.TypeMeta{
155-
APIVersion: "v1",
156-
Kind: "Pod",
157-
},
158-
}
159-
160-
key := crclient.ObjectKey{Namespace: ns, Name: podName}
161-
err := client.Get(ctx, key, myPod)
142+
myPod, err := k8sutil.GetPod(ctx, client, ns)
162143
if err != nil {
163-
log.Error(err, "Failed to get pod", "Pod.Namespace", ns, "Pod.Name", podName)
164144
return nil, err
165145
}
166146

pkg/metrics/metrics.go

Lines changed: 95 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,72 +23,78 @@ import (
2323
v1 "k8s.io/api/core/v1"
2424
apierrors "k8s.io/apimachinery/pkg/api/errors"
2525
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2627
"k8s.io/apimachinery/pkg/types"
2728
"k8s.io/apimachinery/pkg/util/intstr"
28-
"k8s.io/client-go/rest"
2929
crclient "sigs.k8s.io/controller-runtime/pkg/client"
30+
"sigs.k8s.io/controller-runtime/pkg/client/config"
3031
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
3132
)
3233

3334
var log = logf.Log.WithName("metrics")
3435

35-
// PrometheusPortName defines the port name used in kubernetes deployment and service resources
36-
const PrometheusPortName = "metrics"
36+
var trueVar = true
37+
38+
const (
39+
// PrometheusPortName defines the port name used in the metrics Service.
40+
PrometheusPortName = "metrics"
41+
)
3742

3843
// ExposeMetricsPort creates a Kubernetes Service to expose the passed metrics port.
3944
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+
}
4049
// We do not need to check the validity of the port, as controller-runtime
4150
// would error out and we would never get to this stage.
42-
s, err := initOperatorService(port, PrometheusPortName)
51+
s, err := initOperatorService(ctx, client, port, PrometheusPortName)
4352
if err != nil {
4453
if err == k8sutil.ErrNoNamespace {
4554
log.Info("Skipping metrics Service creation; not running in a cluster.")
4655
return nil, nil
4756
}
4857
return nil, fmt.Errorf("failed to initialize service object for metrics: %v", err)
4958
}
50-
service, err := createService(ctx, s)
59+
service, err := createOrUpdateService(ctx, client, s)
5160
if err != nil {
5261
return nil, fmt.Errorf("failed to create or get service for metrics: %v", err)
5362
}
5463

5564
return service, nil
5665
}
5766

58-
func createService(ctx context.Context, s *v1.Service) (*v1.Service, error) {
59-
config, err := rest.InClusterConfig()
60-
if err != nil {
61-
return nil, err
62-
}
63-
64-
client, err := crclient.New(config, crclient.Options{})
65-
if err != nil {
66-
return nil, err
67-
}
68-
67+
func createOrUpdateService(ctx context.Context, client crclient.Client, s *v1.Service) (*v1.Service, error) {
6968
if err := client.Create(ctx, s); err != nil {
7069
if !apierrors.IsAlreadyExists(err) {
7170
return nil, err
7271
}
73-
// Get existing Service and return it
72+
// Service already exists, we want to update it
73+
// as we do not know if any fields might have changed.
7474
existingService := &v1.Service{}
7575
err := client.Get(ctx, types.NamespacedName{
7676
Name: s.Name,
7777
Namespace: s.Namespace,
7878
}, 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)
7985
if err != nil {
8086
return nil, err
8187
}
82-
log.Info("Metrics Service object already exists", "name", existingService.Name)
88+
log.V(1).Info("Metrics Service object updated", "Service.Name", s.Name, "Service.Namespace", s.Namespace)
8389
return existingService, nil
8490
}
8591

86-
log.Info("Metrics Service object created", "name", s.Name)
92+
log.Info("Metrics Service object created", "Service.Name", s.Name, "Service.Namespace", s.Namespace)
8793
return s, nil
8894
}
8995

9096
// initOperatorService returns the static service which exposes specifed port.
91-
func initOperatorService(port int32, portName string) (*v1.Service, error) {
97+
func initOperatorService(ctx context.Context, client crclient.Client, port int32, portName string) (*v1.Service, error) {
9298
operatorName, err := k8sutil.GetOperatorName()
9399
if err != nil {
94100
return nil, err
@@ -97,11 +103,14 @@ func initOperatorService(port int32, portName string) (*v1.Service, error) {
97103
if err != nil {
98104
return nil, err
99105
}
106+
107+
label := map[string]string{"name": operatorName}
108+
100109
service := &v1.Service{
101110
ObjectMeta: metav1.ObjectMeta{
102111
Name: operatorName,
103112
Namespace: namespace,
104-
Labels: map[string]string{"name": operatorName},
113+
Labels: label,
105114
},
106115
TypeMeta: metav1.TypeMeta{
107116
Kind: "Service",
@@ -119,8 +128,72 @@ func initOperatorService(port int32, portName string) (*v1.Service, error) {
119128
Name: portName,
120129
},
121130
},
122-
Selector: map[string]string{"name": operatorName},
131+
Selector: label,
123132
},
124133
}
134+
135+
ownRef, err := getPodOwnerRef(ctx, client, namespace)
136+
if err != nil {
137+
return nil, err
138+
}
139+
service.SetOwnerReferences([]metav1.OwnerReference{*ownRef})
140+
125141
return service, nil
126142
}
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+
}

0 commit comments

Comments
 (0)