Unused config map: check if there are unused configmaps in the cluster.

varsha/versions
Varsha Varadarajan 2019-06-26 11:16:02 -04:00
parent 5bbf305db0
commit 2d097ba31a
3 changed files with 255 additions and 0 deletions

View File

@ -277,5 +277,20 @@ Description: This check reports all the persistent volume claims in the cluster
How to fix: How to fix:
```bash ```bash
<<<<<<< HEAD
kubectl delete pvc <unused pvc> kubectl delete pvc <unused pvc>
``` ```
###### Unused Config Maps
Name: `unused-config-map`
Group: `basic`
Description: This check reports all the config maps in the cluster that are not referenced by pods in the respective namespaces. The cluster can be cleaned up based on this information and there will be fewer objects to manage.
How to fix:
```bash
kubectl delete configmap <unused config map>
```

View File

@ -0,0 +1,109 @@
package basic
import (
"sync"
"github.com/digitalocean/clusterlint/checks"
"github.com/digitalocean/clusterlint/kube"
"golang.org/x/sync/errgroup"
corev1 "k8s.io/api/core/v1"
)
func init() {
checks.Register(&unusedCMCheck{})
}
type unusedCMCheck struct{}
type identifier struct {
Name string
Namespace string
}
// Name returns a unique name for this check.
func (c *unusedCMCheck) Name() string {
return "unused-config-map"
}
// Groups returns a list of group names this check should be part of.
func (c *unusedCMCheck) Groups() []string {
return []string{"basic"}
}
// Description returns a detailed human-readable description of what this check
// does.
func (c *unusedCMCheck) Description() string {
return "Checks if there are unused config maps in the cluster"
}
// Run runs this check on a set of Kubernetes objects. It can return warnings
// (low-priority problems) and errors (high-priority problems) as well as an
// error value indicating that the check failed to run.
func (c *unusedCMCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, error) {
var diagnostics []checks.Diagnostic
used, err := checkReferences(objects)
if err != nil {
return nil, err
}
for _, cm := range objects.ConfigMaps.Items {
if _, ok := used[identifier{Name: cm.GetName(), Namespace: cm.GetNamespace()}]; !ok {
cm := cm
d := checks.Diagnostic{
Severity: checks.Warning,
Message: "Unused config map",
Kind: checks.ConfigMap,
Object: &cm.ObjectMeta,
Owners: cm.ObjectMeta.GetOwnerReferences(),
}
diagnostics = append(diagnostics, d)
}
}
return diagnostics, nil
}
//checkReferences checks each pod for config map references in volumes and environment variables
func checkReferences(objects *kube.Objects) (map[identifier]bool, error) {
used := make(map[identifier]bool)
var mu sync.Mutex
var g errgroup.Group
for _, pod := range objects.Pods.Items {
pod := pod
namespace := pod.GetNamespace()
g.Go(func() error {
for _, volume := range pod.Spec.Volumes {
cm := volume.VolumeSource.ConfigMap
if cm != nil {
mu.Lock()
used[identifier{Name: cm.LocalObjectReference.Name, Namespace: namespace}] = true
mu.Unlock()
}
}
identifiers := checkEnvVars(pod.Spec.Containers, namespace)
identifiers = append(identifiers, checkEnvVars(pod.Spec.InitContainers, namespace)...)
mu.Lock()
for _, i := range identifiers {
used[i] = true
}
mu.Unlock()
return nil
})
}
return used, g.Wait()
}
// checkEnvVars checks for config map references in container environment variables
func checkEnvVars(containers []corev1.Container, namespace string) []identifier {
var refs []identifier
for _, container := range containers {
for _, env := range container.EnvFrom {
if env.ConfigMapRef != nil {
refs = append(refs, identifier{Name: env.ConfigMapRef.LocalObjectReference.Name, Namespace: namespace})
}
}
}
return refs
}

View File

@ -0,0 +1,131 @@
package basic
import (
"testing"
"github.com/digitalocean/clusterlint/checks"
"github.com/digitalocean/clusterlint/kube"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const cmNamespace = "k8s"
func TestUnusedConfigMapCheckMeta(t *testing.T) {
unusedCMCheck := unusedCMCheck{}
assert.Equal(t, "unused-config-map", unusedCMCheck.Name())
assert.Equal(t, []string{"basic"}, unusedCMCheck.Groups())
assert.NotEmpty(t, unusedCMCheck.Description())
}
func TestUnusedConfigMapCheckRegistration(t *testing.T) {
unusedCMCheck := &unusedCMCheck{}
check, err := checks.Get("unused-config-map")
assert.NoError(t, err)
assert.Equal(t, check, unusedCMCheck)
}
func TestUnusedConfigMapWarning(t *testing.T) {
tests := []struct {
name string
objs *kube.Objects
expected []checks.Diagnostic
}{
{
name: "no config maps",
objs: &kube.Objects{Pods: &corev1.PodList{}, ConfigMaps: &corev1.ConfigMapList{}},
expected: nil,
},
{
name: "volume mounted config map",
objs: configMapVolume(),
expected: nil,
},
{
name: "environment variable references to config map",
objs: configMapEnvSource(),
expected: nil,
},
{
name: "unused config map",
objs: initConfigMap(),
expected: []checks.Diagnostic{
{
Severity: checks.Warning,
Message: "Unused config map",
Kind: checks.ConfigMap,
Object: &metav1.ObjectMeta{Name: "cm_foo", Namespace: cmNamespace},
Owners: GetOwners(),
},
},
},
}
unusedCMCheck := unusedCMCheck{}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
d, err := unusedCMCheck.Run(test.objs)
assert.NoError(t, err)
assert.ElementsMatch(t, test.expected, d)
})
}
}
func initConfigMap() *kube.Objects {
objs := &kube.Objects{
Pods: &corev1.PodList{
Items: []corev1.Pod{
{
TypeMeta: metav1.TypeMeta{Kind: "Pod", APIVersion: "v1"},
ObjectMeta: metav1.ObjectMeta{Name: "pod_foo", Namespace: cmNamespace},
},
},
},
ConfigMaps: &corev1.ConfigMapList{
Items: []corev1.ConfigMap{
{
TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
ObjectMeta: metav1.ObjectMeta{Name: "cm_foo", Namespace: cmNamespace},
},
},
},
}
return objs
}
func configMapVolume() *kube.Objects {
objs := initConfigMap()
objs.Pods.Items[0].Spec = corev1.PodSpec{
Volumes: []corev1.Volume{
{
Name: "bar",
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{Name: "cm_foo"},
},
},
}},
}
return objs
}
func configMapEnvSource() *kube.Objects {
objs := initConfigMap()
objs.Pods.Items[0].Spec = corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "test-container",
Image: "docker.io/nginx",
EnvFrom: []corev1.EnvFromSource{
{
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{Name: "cm_foo"},
},
},
},
}},
}
return objs
}