Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(inputs.kubernetes): Extend kube_inventory plugin to include resourcequota,secrets measurement and extend node and pod measurement #13040

Merged
merged 20 commits into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion plugins/inputs/kube_inventory/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Kubernetes resources:
- pods (containers)
- services
- statefulsets
- resourcequotas

Kubernetes is a fast moving project, with a new minor release every 3 months.
As such, we will aim to maintain support only for versions that are supported
Expand Down Expand Up @@ -240,6 +241,9 @@ tls_key = "/run/telegraf-kubernetes-key"
- kubernetes_node
- tags:
- node_name
- status
- condition
- cluster_namespace
- fields:
- capacity_cpu_cores
- capacity_millicpu_cores
Expand All @@ -249,6 +253,9 @@ tls_key = "/run/telegraf-kubernetes-key"
- allocatable_millicpu_cores
- allocatable_memory_bytes
- allocatable_pods
- status_condition
- spec_unschedulable
- node_count

- kubernetes_persistentvolume
- tags:
Expand Down Expand Up @@ -278,6 +285,7 @@ tls_key = "/run/telegraf-kubernetes-key"
- phase
- state
- readiness
- condition
- fields:
- restarts_total
- state_code
Expand All @@ -288,6 +296,7 @@ tls_key = "/run/telegraf-kubernetes-key"
- resource_requests_memory_bytes
- resource_limits_millicpu_units
- resource_limits_memory_bytes
- status_condition

- kubernetes_service
- tags:
Expand Down Expand Up @@ -319,6 +328,49 @@ tls_key = "/run/telegraf-kubernetes-key"
- spec_replicas
- observed_generation

- kubernetes_resourcequota
- tags:
- resource
- namespace
- fields:
- hard_cpu_limits
- hard_cpu_requests
- hard_memory_limit
- hard_memory_requests
- hard_pods
- used_cpu_limits
- used_cpu_requests
- used_memory_limits
- used_memory_requests
- used_pods

- kubernetes_certificate
- tags:
- common_name
- signature_algorithm
- public_key_algorithm
- issuer_common_name
- san
- verification
- name
- namespace
- fields:
- age
- expiry
- startdate
- enddate
- verification_code

### kuberntes node status `status`

The node status ready can mean 3 different values.

| Tag value | Corresponding field value | Meaning |
| --------- | ------------------------- | -------
| ready | 0 | NotReady|
| ready | 1 | Ready |
| ready | 2 | Unknown |

### pv `phase_type`

The persistentvolume "phase" is saved in the `phase` tag with a correlated
Expand Down Expand Up @@ -351,11 +403,16 @@ numeric field called `phase_type` corresponding with that tag value.
kubernetes_configmap,configmap_name=envoy-config,namespace=default,resource_version=56593031 created=1544103867000000000i 1547597616000000000
kubernetes_daemonset,daemonset_name=telegraf,selector_select1=s1,namespace=logging number_unavailable=0i,desired_number_scheduled=11i,number_available=11i,number_misscheduled=8i,number_ready=11i,updated_number_scheduled=11i,created=1527758699000000000i,generation=16i,current_number_scheduled=11i 1547597616000000000
kubernetes_deployment,deployment_name=deployd,selector_select1=s1,namespace=default replicas_unavailable=0i,created=1544103082000000000i,replicas_available=1i 1547597616000000000
kubernetes_node,node_name=ip-172-17-0-2.internal allocatable_pods=110i,capacity_memory_bytes=128837533696,capacity_pods=110i,capacity_cpu_cores=16i,allocatable_cpu_cores=16i,allocatable_memory_bytes=128732676096 1547597616000000000
kubernetes_node,host=vjain node_count=8i 1628918652000000000
kubernetes_node,condition=Ready,host=vjain,node_name=ip-172-17-0-2.internal,status=True status_condition=1i 1629177980000000000
kubernetes_node,cluster_namespace=tools,condition=Ready,host=vjain,node_name=ip-172-17-0-2.internal,status=True allocatable_cpu_cores=4i,allocatable_memory_bytes=7186567168i,allocatable_millicpu_cores=4000i,allocatable_pods=110i,capacity_cpu_cores=4i,capacity_memory_bytes=7291424768i,capacity_millicpu_cores=4000i,capacity_pods=110i,spec_unschedulable=0i,status_condition=1i 1628918652000000000
kubernetes_resourcequota,host=vjain,namespace=default,resource=pods-high hard_cpu=1000i,hard_memory=214748364800i,hard_pods=10i,used_cpu=0i,used_memory=0i,used_pods=0i 1629110393000000000
kubernetes_resourcequota,host=vjain,namespace=default,resource=pods-low hard_cpu=5i,hard_memory=10737418240i,hard_pods=10i,used_cpu=0i,used_memory=0i,used_pods=0i 1629110393000000000
kubernetes_persistentvolume,phase=Released,pv_name=pvc-aaaaaaaa-bbbb-cccc-1111-222222222222,storageclass=ebs-1-retain phase_type=3i 1547597616000000000
kubernetes_persistentvolumeclaim,namespace=default,phase=Bound,pvc_name=data-etcd-0,selector_select1=s1,storageclass=ebs-1-retain phase_type=0i 1547597615000000000
kubernetes_pod,namespace=default,node_name=ip-172-17-0-2.internal,pod_name=tick1 last_transition_time=1547578322000000000i,ready="false" 1547597616000000000
kubernetes_service,cluster_ip=172.29.61.80,namespace=redis-cache-0001,port_name=redis,port_protocol=TCP,selector_app=myapp,selector_io.kompose.service=redis,selector_role=slave,service_name=redis-slave created=1588690034000000000i,generation=0i,port=6379i,target_port=0i 1547597616000000000
kubernetes_pod_container,condition=Ready,host=vjain,pod_name=uefi-5997f76f69-xzljt,status=True status_condition=1i 1629177981000000000
kubernetes_pod_container,container_name=telegraf,namespace=default,node_name=ip-172-17-0-2.internal,node_selector_node-role.kubernetes.io/compute=true,pod_name=tick1,phase=Running,state=running,readiness=ready resource_requests_cpu_units=0.1,resource_limits_memory_bytes=524288000,resource_limits_cpu_units=0.5,restarts_total=0i,state_code=0i,state_reason="",phase_reason="",resource_requests_memory_bytes=524288000 1547597616000000000
kubernetes_statefulset,namespace=default,selector_select1=s1,statefulset_name=etcd replicas_updated=3i,spec_replicas=3i,observed_generation=1i,created=1544101669000000000i,generation=1i,replicas=3i,replicas_current=3i,replicas_ready=3i 1547597616000000000
```
Expand Down
94 changes: 94 additions & 0 deletions plugins/inputs/kube_inventory/certificate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package kube_inventory

import (
"context"
"crypto/x509"
"encoding/pem"
"strings"
"time"

corev1 "k8s.io/api/core/v1"

"github.com/influxdata/telegraf"
)

func collectSecrets(ctx context.Context, acc telegraf.Accumulator, ki *KubernetesInventory) {
list, err := ki.client.getTLSSecrets(ctx)
if err != nil {
acc.AddError(err)
return
}
for _, i := range list.Items {
ki.gatherCertificates(i, acc)
}
}

func getFields(cert *x509.Certificate, now time.Time) map[string]interface{} {
age := int(now.Sub(cert.NotBefore).Seconds())
expiry := int(cert.NotAfter.Sub(now).Seconds())
startdate := cert.NotBefore.Unix()
enddate := cert.NotAfter.Unix()

fields := map[string]interface{}{
"age": age,
"expiry": expiry,
"startdate": startdate,
"enddate": enddate,
}
powersj marked this conversation as resolved.
Show resolved Hide resolved

return fields
}

func getTags(cert *x509.Certificate) map[string]string {
tags := map[string]string{
"common_name": cert.Subject.CommonName,
"signature_algorithm": cert.SignatureAlgorithm.String(),
"public_key_algorithm": cert.PublicKeyAlgorithm.String(),
}
tags["issuer_common_name"] = cert.Issuer.CommonName

san := append(cert.DNSNames, cert.EmailAddresses...)
for _, ip := range cert.IPAddresses {
san = append(san, ip.String())
}
for _, uri := range cert.URIs {
san = append(san, uri.String())
}
tags["san"] = strings.Join(san, ",")

return tags
}

func (ki *KubernetesInventory) gatherCertificates(r corev1.Secret, acc telegraf.Accumulator) {
now := time.Now()

for resourceName, val := range r.Data {
if resourceName != "tls.crt" {
continue
}
block, _ := pem.Decode(val)
if block == nil {
return
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return
}
fields := getFields(cert, now)
tags := getTags(cert)
tags["name"] = r.Name
tags["namespace"] = r.Namespace
opts := x509.VerifyOptions{
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
}
_, err = cert.Verify(opts)
if err == nil {
tags["verification"] = "valid"
fields["verification_code"] = 0
} else {
tags["verification"] = "invalid"
fields["verification_code"] = 1
}
acc.AddFields(certificateMeasurement, fields, tags)
}
}
16 changes: 16 additions & 0 deletions plugins/inputs/kube_inventory/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
corev1 "k8s.io/api/core/v1"
netv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"

Expand Down Expand Up @@ -120,3 +121,18 @@ func (c *client) getStatefulSets(ctx context.Context) (*appsv1.StatefulSetList,
defer cancel()
return c.AppsV1().StatefulSets(c.namespace).List(ctx, metav1.ListOptions{})
}

func (c *client) getResourceQuotas(ctx context.Context) (*corev1.ResourceQuotaList, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
return c.CoreV1().ResourceQuotas(c.namespace).List(ctx, metav1.ListOptions{})
}

func (c *client) getTLSSecrets(ctx context.Context) (*corev1.SecretList, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
labelSelector := metav1.LabelSelector{MatchLabels: map[string]string{"type": "kubernetes.io/tls"}}
return c.CoreV1().Secrets(c.namespace).List(ctx, metav1.ListOptions{
FieldSelector: labels.Set(labelSelector.MatchLabels).String(),
})
}
4 changes: 4 additions & 0 deletions plugins/inputs/kube_inventory/kube_inventory.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ var availableCollectors = map[string]func(ctx context.Context, acc telegraf.Accu
"statefulsets": collectStatefulSets,
"persistentvolumes": collectPersistentVolumes,
"persistentvolumeclaims": collectPersistentVolumeClaims,
"resourcequotas": collectResourceQuotas,
"secrets": collectSecrets,
}

func atoi(s string) int64 {
Expand Down Expand Up @@ -159,6 +161,8 @@ var (
podContainerMeasurement = "kubernetes_pod_container"
serviceMeasurement = "kubernetes_service"
statefulSetMeasurement = "kubernetes_statefulset"
resourcequotaMeasurement = "kubernetes_resourcequota"
certificateMeasurement = "kubernetes_certificate"
)

func init() {
Expand Down
47 changes: 46 additions & 1 deletion plugins/inputs/kube_inventory/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,27 @@ func collectNodes(ctx context.Context, acc telegraf.Accumulator, ki *KubernetesI
acc.AddError(err)
return
}

ki.gatherNodeCount(len(list.Items), acc)

for _, n := range list.Items {
ki.gatherNode(n, acc)
}
}

func (ki *KubernetesInventory) gatherNodeCount(count int, acc telegraf.Accumulator) {
fields := map[string]interface{}{"node_count": count}
tags := map[string]string{}

acc.AddFields(nodeMeasurement, fields, tags)
}

func (ki *KubernetesInventory) gatherNode(n corev1.Node, acc telegraf.Accumulator) {
fields := map[string]interface{}{}
tags := map[string]string{
"node_name": n.Name,
"node_name": n.Name,
"cluster_namespace": n.Annotations["cluster.x-k8s.io/cluster-namespace"],
"version": n.Status.NodeInfo.KubeletVersion,
}

for resourceName, val := range n.Status.Capacity {
Expand All @@ -49,5 +61,38 @@ func (ki *KubernetesInventory) gatherNode(n corev1.Node, acc telegraf.Accumulato
}
}

for _, val := range n.Status.Conditions {
conditionfields := map[string]interface{}{}
conditiontags := map[string]string{
"status": string(val.Status),
"condition": string(val.Type),
}
for k, v := range tags {
conditiontags[k] = v
}
running := 0
nodeready := 0
if val.Status == "True" {
if val.Type == "Ready" {
nodeready = 1
}
running = 1
} else if val.Status == "Unknown" {
if val.Type == "Ready" {
nodeready = 0
}
running = 2
powersj marked this conversation as resolved.
Show resolved Hide resolved
}
conditionfields["status_condition"] = running
conditionfields["ready"] = nodeready
acc.AddFields(nodeMeasurement, conditionfields, conditiontags)
}

unschedulable := 0
if n.Spec.Unschedulable {
unschedulable = 1
}
fields["spec_unschedulable"] = unschedulable

acc.AddFields(nodeMeasurement, fields, tags)
}
Loading