1
0
Fork 0
mirror of https://github.com/prometheus-operator/prometheus-operator.git synced 2025-04-21 11:48:53 +00:00

ServiceMonitor: Enable Prometheus to select ServMon outside own ns

So far a Prometheus object could only select ServiceMonitors inside its
own namespace. This patch enables a Prometheus object to select
ServiceMonitors outside its own namespace via the
`ServiceMonitorNamespaceSelector` field in the Prometheus spec.

Use case: There is one Prometheus inside the `monitoring` namespace,
which is supposed to monitor applications across namespaces for an
entire Kubernetes cluster.  Each app team is supposed to manage its own
ServiceMonitors. Instead of granting each app team access to the
`monitoring` namespace to manage its ServiceMonitor objects,
ServiceMonitors can be shipped along with the application itself in each
application namespace.
This commit is contained in:
Max Leonard Inden 2018-04-17 16:08:46 +02:00
parent 2a4edbe53c
commit 1d00e0ab4b
No known key found for this signature in database
GPG key ID: 5403C5464810BC26
13 changed files with 173 additions and 25 deletions

View file

@ -198,6 +198,7 @@ Specification of the desired behavior of the Prometheus cluster. More info: http
| ----- | ----------- | ------ | -------- |
| podMetadata | Standard objects metadata. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#metadata Metadata Labels and Annotations gets propagated to the prometheus pods. | *[metav1.ObjectMeta](https://v1-6.docs.kubernetes.io/docs/api-reference/v1.6/#objectmeta-v1-meta) | false |
| serviceMonitorSelector | ServiceMonitors to be selected for target discovery. | *[metav1.LabelSelector](https://v1-6.docs.kubernetes.io/docs/api-reference/v1.6/#labelselector-v1-meta) | false |
| serviceMonitorNamespaceSelector | Namespaces to be selected for ServiceMonitor discovery. If empty, only check own namespace. | *[metav1.LabelSelector](https://v1-6.docs.kubernetes.io/docs/api-reference/v1.6/#labelselector-v1-meta) | false |
| version | Version of Prometheus to be deployed. | string | false |
| paused | When a Prometheus deployment is paused, no actions except for deletion will be performed on the underlying objects. | bool | false |
| baseImage | Base image to use for a Prometheus deployment. | string | false |

View file

@ -37,7 +37,7 @@ The `endpoints` section of the `ServiceMonitorSpec`, is used to configure which
> Note: `endpoints` (lowercase) is the field in the `ServiceMonitor` CRD, while `Endpoints` (capitalized) is the Kubernetes object kind.
While `ServiceMonitor`s must live in the same namespace as the `Prometheus` resource, discovered targets may come from any namespace. This is important to allow cross-namespace monitoring use cases, e.g. for meta-monitoring. Using the `namespaceSelector` of the `ServiceMonitorSpec`, one can restrict the namespaces the `Endpoints` objects are allowed to be discovered from.
Both `ServiceMonitors` as well as discovered targets may come from any namespace. This is important to allow cross-namespace monitoring use cases, e.g. for meta-monitoring. Using the `ServiceMonitorNamespaceSelector` of the `PrometheusSpec`, one can restrict the namespaces `ServiceMonitor`s are selected from by the respective Prometheus server. Using the `namespaceSelector` of the `ServiceMonitorSpec`, one can restrict the namespaces the `Endpoints` objects are allowed to be discovered from.
To discover targets in all namespaces the `namespaceSelector` has to be empty:
```yaml
spec:

View file

@ -64,11 +64,8 @@ rules:
- apiGroups: [""]
resources:
- nodes
verbs: ["list", "watch"]
- apiGroups: [""]
resources:
- namespaces
verbs: ["list"]
verbs: ["list", "watch"]
```
> Note: A cluster admin is required to create this `ClusterRole` and create a `ClusterRoleBinding` or `RoleBinding` to the `ServiceAccount` used by the Prometheus Operator `Pod`. The `ServiceAccount` used by the Prometheus Operator `Pod` can be specified in the `Deployment` object used to deploy it.

View file

@ -78,11 +78,8 @@ rules:
- apiGroups: [""]
resources:
- nodes
verbs: ["list", "watch"]
- apiGroups: [""]
resources:
- namespaces
verbs: ["list"]
verbs: ["list", "watch"]
---
apiVersion: v1
kind: ServiceAccount

View file

@ -60,11 +60,8 @@ rules:
- apiGroups: [""]
resources:
- nodes
verbs: ["list", "watch"]
- apiGroups: [""]
resources:
- namespaces
verbs: ["list"]
verbs: ["list", "watch"]
---
apiVersion: v1
kind: ServiceAccount

View file

@ -1983,6 +1983,48 @@ spec:
description: ServiceAccountName is the name of the ServiceAccount to
use to run the Prometheus Pods.
type: string
serviceMonitorNamespaceSelector:
description: A label selector is a label query over a set of resources.
The result of matchLabels and matchExpressions are ANDed. An empty
label selector matches all objects. A null label selector matches
no objects.
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements.
The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains
values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies
to.
type: string
operator:
description: operator represents a key's relationship to a
set of values. Valid operators are In, NotIn, Exists and
DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator
is In or NotIn, the values array must be non-empty. If the
operator is Exists or DoesNotExist, the values array must
be empty. This array is replaced during a strategic merge
patch.
items:
type: string
type: array
required:
- key
- operator
type: array
matchLabels:
description: matchLabels is a map of {key,value} pairs. A single
{key,value} in the matchLabels map is equivalent to an element
of matchExpressions, whose key field is "key", the operator is
"In", and the values array contains only "value". The requirements
are ANDed.
type: object
serviceMonitorSelector:
description: A label selector is a label query over a set of resources.
The result of matchLabels and matchExpressions are ANDed. An empty

View file

@ -47,8 +47,5 @@ rules:
- apiGroups: [""]
resources:
- nodes
verbs: ["list", "watch"]
- apiGroups: [""]
resources:
- namespaces
verbs: ["list"]
verbs: ["list", "watch"]

View file

@ -690,6 +690,12 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"),
},
},
"serviceMonitorNamespaceSelector": {
SchemaProps: spec.SchemaProps{
Description: "Namespaces to be selected for ServiceMonitor discovery. If empty, only check own namespace.",
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"),
},
},
"version": {
SchemaProps: spec.SchemaProps{
Description: "Version of Prometheus to be deployed.",

View file

@ -60,6 +60,9 @@ type PrometheusSpec struct {
PodMetadata *metav1.ObjectMeta `json:"podMetadata,omitempty"`
// ServiceMonitors to be selected for target discovery.
ServiceMonitorSelector *metav1.LabelSelector `json:"serviceMonitorSelector,omitempty"`
// Namespaces to be selected for ServiceMonitor discovery. If empty, only
// check own namespace.
ServiceMonitorNamespaceSelector *metav1.LabelSelector `json:"serviceMonitorNamespaceSelector,omitempty"`
// Version of Prometheus to be deployed.
Version string `json:"version,omitempty"`
// When a Prometheus deployment is paused, no actions except for deletion

View file

@ -451,6 +451,15 @@ func (in *PrometheusSpec) DeepCopyInto(out *PrometheusSpec) {
(*in).DeepCopyInto(*out)
}
}
if in.ServiceMonitorNamespaceSelector != nil {
in, out := &in.ServiceMonitorNamespaceSelector, &out.ServiceMonitorNamespaceSelector
if *in == nil {
*out = nil
} else {
*out = new(meta_v1.LabelSelector)
(*in).DeepCopyInto(*out)
}
}
if in.ImagePullSecrets != nil {
in, out := &in.ImagePullSecrets, &out.ImagePullSecrets
*out = make([]core_v1.LocalObjectReference, len(*in))

View file

@ -62,6 +62,7 @@ type Operator struct {
cmapInf cache.SharedIndexInformer
secrInf cache.SharedIndexInformer
ssetInf cache.SharedIndexInformer
nsInf cache.SharedIndexInformer
queue workqueue.RateLimitingInterface
@ -239,6 +240,11 @@ func New(conf Config, logger log.Logger) (*Operator, error) {
UpdateFunc: c.handleUpdateStatefulSet,
})
c.nsInf = cache.NewSharedIndexInformer(
cache.NewListWatchFromClient(c.kclient.Core().RESTClient(), "namespaces", metav1.NamespaceAll, fields.Everything()),
&v1.Namespace{}, resyncPeriod, cache.Indexers{},
)
return c, nil
}
@ -291,6 +297,7 @@ func (c *Operator) Run(stopc <-chan struct{}) error {
go c.cmapInf.Run(stopc)
go c.secrInf.Run(stopc)
go c.ssetInf.Run(stopc)
go c.nsInf.Run(stopc)
if c.kubeletSyncEnabled {
go c.reconcileNodeEndpoints(stopc)
@ -1037,22 +1044,37 @@ func (c *Operator) createConfig(p *monitoringv1.Prometheus, ruleFileConfigMaps [
}
func (c *Operator) selectServiceMonitors(p *monitoringv1.Prometheus) (map[string]*monitoringv1.ServiceMonitor, error) {
namespaces := []string{}
// Selectors might overlap. Deduplicate them along the keyFunc.
res := make(map[string]*monitoringv1.ServiceMonitor)
selector, err := metav1.LabelSelectorAsSelector(p.Spec.ServiceMonitorSelector)
servMonSelector, err := metav1.LabelSelectorAsSelector(p.Spec.ServiceMonitorSelector)
if err != nil {
return nil, err
}
// Only service monitors within the same namespace as the Prometheus
// object can belong to it.
cache.ListAllByNamespace(c.smonInf.GetIndexer(), p.Namespace, selector, func(obj interface{}) {
k, ok := c.keyFunc(obj)
if ok {
res[k] = obj.(*monitoringv1.ServiceMonitor)
// If 'ServiceMonitorNamespaceSelector' is empty, only check own namespace.
if p.Spec.ServiceMonitorNamespaceSelector == nil || p.Spec.ServiceMonitorNamespaceSelector.Size() == 0 {
namespaces = append(namespaces, p.Namespace)
} else {
servMonNSSelector, err := metav1.LabelSelectorAsSelector(p.Spec.ServiceMonitorNamespaceSelector)
if err != nil {
return nil, err
}
})
cache.ListAll(c.nsInf.GetStore(), servMonNSSelector, func(obj interface{}) {
namespaces = append(namespaces, obj.(*v1.Namespace).Name)
})
}
for _, ns := range namespaces {
cache.ListAllByNamespace(c.smonInf.GetIndexer(), ns, servMonSelector, func(obj interface{}) {
k, ok := c.keyFunc(obj)
if ok {
res[k] = obj.(*monitoringv1.ServiceMonitor)
}
})
}
return res, nil
}

View file

@ -21,6 +21,7 @@ import (
"log"
"reflect"
"sort"
"strings"
"testing"
"time"
@ -496,6 +497,60 @@ func TestPrometheusDiscoverTargetPort(t *testing.T) {
}
}
func TestPromOpMatchPromAndServMonInDiffNSs(t *testing.T) {
ctx := framework.NewTestCtx(t)
defer ctx.Cleanup(t)
prometheusNSName := ctx.CreateNamespace(t, framework.KubeClient)
serviceMonitorNSName := ctx.CreateNamespace(t, framework.KubeClient)
ctx.SetupPrometheusRBAC(t, prometheusNSName, framework.KubeClient)
if err := testFramework.AddLabelsToNamespace(
framework.KubeClient,
serviceMonitorNSName,
map[string]string{"team": "frontend"},
); err != nil {
t.Fatal(err)
}
group := "sample-app"
prometheusJobName := serviceMonitorNSName + "/" + group
prometheusName := "test"
svc := framework.MakePrometheusService(prometheusName, group, v1.ServiceTypeClusterIP)
s := framework.MakeBasicServiceMonitor(group)
if _, err := framework.MonClient.ServiceMonitors(serviceMonitorNSName).Create(s); err != nil {
t.Fatal("Creating ServiceMonitor failed: ", err)
}
p := framework.MakeBasicPrometheus(prometheusNSName, prometheusName, group, 1)
p.Spec.ServiceMonitorNamespaceSelector = &metav1.LabelSelector{
MatchLabels: map[string]string{
"team": "frontend",
},
}
if err := framework.CreatePrometheusAndWaitUntilReady(prometheusNSName, p); err != nil {
t.Fatal(err)
}
if finalizerFn, err := testFramework.CreateServiceAndWaitUntilReady(framework.KubeClient, prometheusNSName, svc); err != nil {
t.Fatal(errors.Wrap(err, "creating prometheus service failed"))
} else {
ctx.AddFinalizerFn(finalizerFn)
}
resp, err := framework.QueryPrometheusSVC(prometheusNSName, svc.Name, "/api/v1/status/config", map[string]string{})
if err != nil {
t.Fatal(err)
}
if strings.Count(string(resp), prometheusJobName) != 1 {
t.Fatalf("expected Prometheus operator to configure Prometheus in ns '%v' to scrape the service monitor in ns '%v'", prometheusNSName, serviceMonitorNSName)
}
}
func isDiscoveryWorking(ns, svcName, prometheusName string) func() (bool, error) {
return func() (bool, error) {
pods, err := framework.KubeClient.CoreV1().Pods(ns).List(prometheus.ListOptions(prometheusName))

View file

@ -57,3 +57,25 @@ func (ctx *TestCtx) CreateNamespace(t *testing.T, kubeClient kubernetes.Interfac
func DeleteNamespace(kubeClient kubernetes.Interface, name string) error {
return kubeClient.Core().Namespaces().Delete(name, nil)
}
func AddLabelsToNamespace(kubeClient kubernetes.Interface, name string, additionalLabels map[string]string) error {
ns, err := kubeClient.CoreV1().Namespaces().Get(name, metav1.GetOptions{})
if err != nil {
return err
}
if ns.Labels == nil {
ns.Labels = map[string]string{}
}
for k, v := range additionalLabels {
ns.Labels[k] = v
}
_, err = kubeClient.CoreV1().Namespaces().Update(ns)
if err != nil {
return err
}
return nil
}