Skip to content

Commit d7c0cf5

Browse files
Merge pull request #480 from miguellavalle/collector-ovndbcluster
Add Prometheus metrics support to OVNDBCluster
2 parents 06c80f9 + b31a644 commit d7c0cf5

17 files changed

+394
-37
lines changed

api/bases/ovn.openstack.org_ovndbclusters.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ spec:
6767
to use on db creation (in milliseconds)
6868
format: int32
6969
type: integer
70+
exporterImage:
71+
description: ExporterImage - Container Image URL for the openstack-network-exporter
72+
metrics sidecar (will be set to environmental default if empty)
73+
type: string
7074
inactivityProbe:
7175
default: 60000
7276
description: Probe interval for the OVSDB session (in milliseconds)
@@ -76,6 +80,11 @@ spec:
7680
default: info
7781
description: LogLevel - Set log level info, dbg, emer etc
7882
type: string
83+
metricsEnabled:
84+
default: true
85+
description: MetricsEnabled enables the metrics sidecar container
86+
for collecting OVN DB metrics
87+
type: boolean
7988
networkAttachment:
8089
description: |-
8190
NetworkAttachment is a NetworkAttachment resource name to expose the service to the given network.

api/v1beta1/common.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ func SetupDefaults() {
2424
ovnDbClusterDefaults := OVNDBClusterDefaults{
2525
NBContainerImageURL: util.GetEnvVar("RELATED_IMAGE_OVN_NB_DBCLUSTER_IMAGE_URL_DEFAULT", OVNNBContainerImage),
2626
SBContainerImageURL: util.GetEnvVar("RELATED_IMAGE_OVN_SB_DBCLUSTER_IMAGE_URL_DEFAULT", OVNSBContainerImage),
27+
ExporterImageURL: util.GetEnvVar("RELATED_IMAGE_OPENSTACK_NETWORK_EXPORTER_IMAGE_URL_DEFAULT", OpenstackNetworkExporterImage),
2728
}
2829

2930
SetupOVNDBClusterDefaults(ovnDbClusterDefaults)

api/v1beta1/ovndbcluster_types.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ package v1beta1
1919
import (
2020
"fmt"
2121

22+
topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1"
2223
"github.com/openstack-k8s-operators/lib-common/modules/common/condition"
2324
"github.com/openstack-k8s-operators/lib-common/modules/common/service"
2425
"github.com/openstack-k8s-operators/lib-common/modules/common/tls"
25-
topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1"
2626
"k8s.io/apimachinery/pkg/util/validation/field"
2727

2828
corev1 "k8s.io/api/core/v1"
@@ -56,6 +56,10 @@ type OVNDBClusterSpec struct {
5656
// ContainerImage - Container Image URL (will be set to environmental default if empty)
5757
ContainerImage string `json:"containerImage"`
5858

59+
// +kubebuilder:validation:Optional
60+
// ExporterImage - Container Image URL for the openstack-network-exporter metrics sidecar (will be set to environmental default if empty)
61+
ExporterImage string `json:"exporterImage,omitempty"`
62+
5963
OVNDBClusterSpecCore `json:",inline"`
6064
}
6165

@@ -130,6 +134,11 @@ type OVNDBClusterSpecCore struct {
130134
// TopologyRef to apply the Topology defined by the associated CR referenced
131135
// by name
132136
TopologyRef *topologyv1.TopoRef `json:"topologyRef,omitempty"`
137+
138+
// +kubebuilder:validation:Optional
139+
// +kubebuilder:default=true
140+
// MetricsEnabled enables the metrics sidecar container for collecting OVN DB metrics
141+
MetricsEnabled *bool `json:"metricsEnabled,omitempty"`
133142
}
134143

135144
// OVNDBClusterOverrideSpec to override the generated manifest of several child resources.

api/v1beta1/ovndbcluster_webhook.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,21 @@ limitations under the License.
2323
package v1beta1
2424

2525
import (
26+
apierrors "k8s.io/apimachinery/pkg/api/errors"
2627
"k8s.io/apimachinery/pkg/runtime"
28+
"k8s.io/apimachinery/pkg/runtime/schema"
29+
"k8s.io/apimachinery/pkg/util/validation/field"
2730
ctrl "sigs.k8s.io/controller-runtime"
2831
logf "sigs.k8s.io/controller-runtime/pkg/log"
2932
"sigs.k8s.io/controller-runtime/pkg/webhook"
3033
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
31-
"k8s.io/apimachinery/pkg/util/validation/field"
32-
apierrors "k8s.io/apimachinery/pkg/api/errors"
33-
"k8s.io/apimachinery/pkg/runtime/schema"
3434
)
3535

3636
// OVNDBClusterDefaults -
3737
type OVNDBClusterDefaults struct {
3838
NBContainerImageURL string
3939
SBContainerImageURL string
40+
ExporterImageURL string
4041
}
4142

4243
var ovnDbClusterDefaults OVNDBClusterDefaults
@@ -77,6 +78,9 @@ func (spec *OVNDBClusterSpec) Default() {
7778
spec.ContainerImage = ovnDbClusterDefaults.SBContainerImageURL
7879
}
7980
}
81+
if spec.ExporterImage == "" {
82+
spec.ExporterImage = ovnDbClusterDefaults.ExporterImageURL
83+
}
8084
spec.OVNDBClusterSpecCore.Default()
8185
}
8286

api/v1beta1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/ovn.openstack.org_ovndbclusters.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ spec:
6767
to use on db creation (in milliseconds)
6868
format: int32
6969
type: integer
70+
exporterImage:
71+
description: ExporterImage - Container Image URL for the openstack-network-exporter
72+
metrics sidecar (will be set to environmental default if empty)
73+
type: string
7074
inactivityProbe:
7175
default: 60000
7276
description: Probe interval for the OVSDB session (in milliseconds)
@@ -76,6 +80,11 @@ spec:
7680
default: info
7781
description: LogLevel - Set log level info, dbg, emer etc
7882
type: string
83+
metricsEnabled:
84+
default: true
85+
description: MetricsEnabled enables the metrics sidecar container
86+
for collecting OVN DB metrics
87+
type: boolean
7988
networkAttachment:
8089
description: |-
8190
NetworkAttachment is a NetworkAttachment resource name to expose the service to the given network.

controllers/ovndbcluster_controller.go

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,7 @@ func (r *OVNDBClusterReconciler) reconcileNormal(ctx context.Context, instance *
706706
}
707707

708708
}
709+
709710
Log.Info("Reconciled Service successfully")
710711
return ctrl.Result{}, nil
711712
}
@@ -828,7 +829,14 @@ func (r *OVNDBClusterReconciler) reconcileServices(
828829
common.AppSelector: serviceName,
829830
"statefulset.kubernetes.io/pod-name": ovnPod.Name,
830831
}
831-
ovndbServiceLabels := util.MergeMaps(ovndbSelectorLabels, map[string]string{"type": ovnv1.ServiceClusterType})
832+
// Build service labels - always use cluster type, add metrics label if enabled
833+
serviceTypeLabels := map[string]string{"type": ovnv1.ServiceClusterType}
834+
// Add metrics label if metrics are enabled and exporter image is specified
835+
if instance.Spec.ExporterImage != "" && (instance.Spec.MetricsEnabled == nil || *instance.Spec.MetricsEnabled) {
836+
serviceTypeLabels["metrics"] = "enabled"
837+
}
838+
ovndbServiceLabels := util.MergeMaps(ovndbSelectorLabels, serviceTypeLabels)
839+
832840
svc, err := service.NewService(
833841
ovndbcluster.Service(ovnPod.Name, instance, ovndbServiceLabels, ovndbSelectorLabels),
834842
time.Duration(5)*time.Second,
@@ -843,6 +851,23 @@ func (r *OVNDBClusterReconciler) reconcileServices(
843851
} else if (ctrlResult != ctrl.Result{}) {
844852
return ctrl.Result{}, nil
845853
}
854+
855+
// Check if we need to remove metrics label from existing service
856+
needsMetricsRemoval := instance.Spec.ExporterImage == "" || (instance.Spec.MetricsEnabled != nil && !*instance.Spec.MetricsEnabled)
857+
if needsMetricsRemoval {
858+
// Get the service that was just created/updated
859+
existingSvc, err := service.GetServiceWithName(ctx, helper, ovnPod.Name, ovnPod.Namespace)
860+
if err == nil && existingSvc != nil {
861+
if _, exists := existingSvc.Labels["metrics"]; exists {
862+
// Create a patch to remove the metrics label
863+
delete(existingSvc.Labels, "metrics")
864+
err = r.GetClient().Update(ctx, existingSvc)
865+
if err != nil {
866+
return ctrl.Result{}, fmt.Errorf("error removing metrics label from service %s: %w", ovnPod.Name, err)
867+
}
868+
}
869+
}
870+
}
846871
// create service - end
847872
}
848873

@@ -985,9 +1010,12 @@ func (r *OVNDBClusterReconciler) generateExternalConfigMaps(
9851010
cms := []util.Template{
9861011
// EDP ConfigMap
9871012
{
988-
Name: "ovncontroller-config",
989-
Namespace: instance.Namespace,
990-
Type: util.TemplateTypeConfig,
1013+
Name: "ovncontroller-config",
1014+
Namespace: instance.Namespace,
1015+
Type: util.TemplateTypeNone,
1016+
AdditionalTemplate: map[string]string{
1017+
"ovsdb-config": "/ovndbcluster/config/ovsdb-config",
1018+
},
9911019
InstanceType: instance.Kind,
9921020
Labels: cmLabels,
9931021
ConfigOptions: externalTemplateParameters,
@@ -1045,6 +1073,8 @@ func (r *OVNDBClusterReconciler) generateServiceConfigMaps(
10451073
templateParameters["OVNDB_CERT_PATH"] = ovn_common.OVNDbCertPath
10461074
templateParameters["OVNDB_KEY_PATH"] = ovn_common.OVNDbKeyPath
10471075
templateParameters["OVNDB_CACERT_PATH"] = ovn_common.OVNDbCaCertPath
1076+
templateParameters["OVN_METRICS_CERT_PATH"] = ovn_common.OVNMetricsCertPath
1077+
templateParameters["OVN_METRICS_KEY_PATH"] = ovn_common.OVNMetricsKeyPath
10481078

10491079
cms := []util.Template{
10501080
// ScriptsConfigMap
@@ -1057,6 +1087,20 @@ func (r *OVNDBClusterReconciler) generateServiceConfigMaps(
10571087
ConfigOptions: templateParameters,
10581088
},
10591089
}
1090+
// Add ConfigConfigMap for network exporter only if metrics are enabled and exporter image is specified
1091+
if instance.Spec.ExporterImage != "" && (instance.Spec.MetricsEnabled == nil || *instance.Spec.MetricsEnabled) {
1092+
cms = append(cms, util.Template{
1093+
Name: fmt.Sprintf("%s-config", instance.Name),
1094+
Namespace: instance.Namespace,
1095+
Type: util.TemplateTypeNone,
1096+
AdditionalTemplate: map[string]string{
1097+
"openstack-network-exporter.yaml": "/ovndbcluster/config/openstack-network-exporter.yaml",
1098+
},
1099+
InstanceType: instance.Kind,
1100+
Labels: cmLabels,
1101+
ConfigOptions: templateParameters,
1102+
})
1103+
}
10601104
return configmap.EnsureConfigMaps(ctx, h, instance, cms, envVars)
10611105
}
10621106

pkg/ovndbcluster/service.go

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,29 @@ func Service(
2323
dbPort = DbPortSB
2424
raftPort = RaftPortSB
2525
}
26+
27+
ports := []corev1.ServicePort{
28+
{
29+
Name: dbPortName,
30+
Port: dbPort,
31+
Protocol: corev1.ProtocolTCP,
32+
},
33+
{
34+
Name: raftPortName,
35+
Port: raftPort,
36+
Protocol: corev1.ProtocolTCP,
37+
},
38+
}
39+
40+
// Add metrics port if metrics are enabled and exporter image is specified
41+
if instance.Spec.ExporterImage != "" && (instance.Spec.MetricsEnabled == nil || *instance.Spec.MetricsEnabled) {
42+
ports = append(ports, corev1.ServicePort{
43+
Name: "metrics",
44+
Port: 1981,
45+
Protocol: corev1.ProtocolTCP,
46+
})
47+
}
48+
2649
return &corev1.Service{
2750
ObjectMeta: metav1.ObjectMeta{
2851
Name: serviceName,
@@ -31,18 +54,7 @@ func Service(
3154
},
3255
Spec: corev1.ServiceSpec{
3356
Selector: selectorLabels,
34-
Ports: []corev1.ServicePort{
35-
{
36-
Name: dbPortName,
37-
Port: dbPort,
38-
Protocol: corev1.ProtocolTCP,
39-
},
40-
{
41-
Name: raftPortName,
42-
Port: raftPort,
43-
Protocol: corev1.ProtocolTCP,
44-
},
45-
},
57+
Ports: ports,
4658
},
4759
}
4860
}

pkg/ovndbcluster/statefulset.go

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,67 @@ func StatefulSet(
143143
// nevertheless, and manual cluster recovery will be needed.
144144
terminationGracePeriodSeconds := int64(300)
145145

146+
// Create container list starting with the main ovndbcluster container
147+
containers := []corev1.Container{
148+
{
149+
Name: serviceName,
150+
Command: cmd,
151+
Args: args,
152+
Image: instance.Spec.ContainerImage,
153+
Env: env.MergeEnvs([]corev1.EnvVar{}, envVars),
154+
VolumeMounts: volumeMounts,
155+
Resources: instance.Spec.Resources,
156+
ReadinessProbe: readinessProbe,
157+
LivenessProbe: livenessProbe,
158+
StartupProbe: startupProbe,
159+
Lifecycle: lifecycle,
160+
TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
161+
},
162+
}
163+
164+
// Add metrics sidecar container if MetricsEnabled is true (default) and exporter image is specified
165+
if instance.Spec.ExporterImage != "" && (instance.Spec.MetricsEnabled == nil || *instance.Spec.MetricsEnabled) {
166+
metricsVolumeMounts := []corev1.VolumeMount{
167+
{
168+
Name: "ovsdb-rundir",
169+
MountPath: "/tmp",
170+
},
171+
{
172+
Name: "config",
173+
MountPath: "/etc/config",
174+
ReadOnly: true,
175+
},
176+
}
177+
178+
// add TLS volume mounts if TLS is enabled - use dedicated metrics certificate
179+
if instance.Spec.TLS.Enabled() {
180+
metricsSvc := tls.Service{
181+
SecretName: "cert-ovn-metrics",
182+
CertMount: ptr.To(ovn_common.OVNMetricsCertPath),
183+
KeyMount: ptr.To(ovn_common.OVNMetricsKeyPath),
184+
CaMount: ptr.To(ovn_common.OVNDbCaCertPath), // Use the same CA for now
185+
}
186+
// Add the metrics certificate volume to the main volumes list
187+
// Use "metrics-certs" as volume name to stay within 63 char limit
188+
volumes = append(volumes, metricsSvc.CreateVolume("metrics-certs"))
189+
metricsVolumeMounts = append(metricsVolumeMounts, metricsSvc.CreateVolumeMounts("metrics-certs")...)
190+
}
191+
192+
metricsContainer := corev1.Container{
193+
Name: "openstack-network-exporter",
194+
Image: instance.Spec.ExporterImage,
195+
Command: []string{"/app/openstack-network-exporter"},
196+
Env: []corev1.EnvVar{
197+
{
198+
Name: "OPENSTACK_NETWORK_EXPORTER_YAML",
199+
Value: "/etc/config/openstack-network-exporter.yaml",
200+
},
201+
},
202+
VolumeMounts: metricsVolumeMounts,
203+
}
204+
containers = append(containers, metricsContainer)
205+
}
206+
146207
statefulset := &appsv1.StatefulSet{
147208
ObjectMeta: metav1.ObjectMeta{
148209
Name: serviceName,
@@ -163,22 +224,7 @@ func StatefulSet(
163224
Spec: corev1.PodSpec{
164225
TerminationGracePeriodSeconds: &terminationGracePeriodSeconds,
165226
ServiceAccountName: instance.RbacResourceName(),
166-
Containers: []corev1.Container{
167-
{
168-
Name: serviceName,
169-
Command: cmd,
170-
Args: args,
171-
Image: instance.Spec.ContainerImage,
172-
Env: env.MergeEnvs([]corev1.EnvVar{}, envVars),
173-
VolumeMounts: volumeMounts,
174-
Resources: instance.Spec.Resources,
175-
ReadinessProbe: readinessProbe,
176-
LivenessProbe: livenessProbe,
177-
StartupProbe: startupProbe,
178-
Lifecycle: lifecycle,
179-
TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
180-
},
181-
},
227+
Containers: containers,
182228
},
183229
},
184230
},

0 commit comments

Comments
 (0)