Unused PVC: Check for unused claims in the cluster.
parent
f1a6a18208
commit
05502e3b32
14
checks.md
14
checks.md
|
@ -265,3 +265,17 @@ How to fix:
|
|||
```bash
|
||||
kubectl delete pv <unused pv>
|
||||
```
|
||||
|
||||
###### Unused Persistent Volume Claims
|
||||
|
||||
Name: `unused-pvc`
|
||||
|
||||
Group: `basic`
|
||||
|
||||
Description: This check reports all the persistent volume claims 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 pvc <unused pvc>
|
||||
```
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
package basic
|
||||
|
||||
import (
|
||||
"github.com/digitalocean/clusterlint/checks"
|
||||
"github.com/digitalocean/clusterlint/kube"
|
||||
)
|
||||
|
||||
func init() {
|
||||
checks.Register(&unusedClaimCheck{})
|
||||
}
|
||||
|
||||
type unusedClaimCheck struct{}
|
||||
|
||||
// Name returns a unique name for this check.
|
||||
func (c *unusedClaimCheck) Name() string {
|
||||
return "unused-pvc"
|
||||
}
|
||||
|
||||
// Groups returns a list of group names this check should be part of.
|
||||
func (c *unusedClaimCheck) Groups() []string {
|
||||
return []string{"basic"}
|
||||
}
|
||||
|
||||
// Description returns a detailed human-readable description of what this check
|
||||
// does.
|
||||
func (c *unusedClaimCheck) Description() string {
|
||||
return "Check if there are unused persistent volume claims in the cluster"
|
||||
}
|
||||
|
||||
type identifier struct {
|
||||
Name string
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// 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 *unusedClaimCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, error) {
|
||||
var diagnostics []checks.Diagnostic
|
||||
used := make(map[identifier]bool)
|
||||
for _, pod := range objects.Pods.Items {
|
||||
for _, volume := range pod.Spec.Volumes {
|
||||
claim := volume.VolumeSource.PersistentVolumeClaim
|
||||
if claim != nil {
|
||||
used[identifier{Name: claim.ClaimName, Namespace: pod.GetNamespace()}] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, claim := range objects.PersistentVolumeClaims.Items {
|
||||
if _, ok := used[identifier{Name: claim.GetName(), Namespace: claim.GetNamespace()}]; !ok {
|
||||
d := checks.Diagnostic{
|
||||
Severity: checks.Warning,
|
||||
Message: "Unused persistent volume claim",
|
||||
Kind: checks.PersistentVolumeClaim,
|
||||
Object: &claim.ObjectMeta,
|
||||
Owners: claim.ObjectMeta.GetOwnerReferences(),
|
||||
}
|
||||
diagnostics = append(diagnostics, d)
|
||||
}
|
||||
}
|
||||
|
||||
return diagnostics, nil
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
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"
|
||||
)
|
||||
|
||||
func TestUnusedPVCCheckMeta(t *testing.T) {
|
||||
unusedClaimCheck := unusedClaimCheck{}
|
||||
assert.Equal(t, "unused-pvc", unusedClaimCheck.Name())
|
||||
assert.Equal(t, []string{"basic"}, unusedClaimCheck.Groups())
|
||||
assert.NotEmpty(t, unusedClaimCheck.Description())
|
||||
}
|
||||
|
||||
func TestUnusedPVCCheckRegistration(t *testing.T) {
|
||||
unusedClaimCheck := &unusedClaimCheck{}
|
||||
check, err := checks.Get("unused-pvc")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, check, unusedClaimCheck)
|
||||
}
|
||||
|
||||
func TestUnusedPVCWarning(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
objs *kube.Objects
|
||||
expected []checks.Diagnostic
|
||||
}{
|
||||
{
|
||||
name: "no pvcs",
|
||||
objs: &kube.Objects{Pods: &corev1.PodList{}, PersistentVolumeClaims: &corev1.PersistentVolumeClaimList{}},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "pod with pvc",
|
||||
objs: boundPVC(corev1.VolumeSource{
|
||||
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: "pvc_foo",
|
||||
},
|
||||
}),
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "unused pvc",
|
||||
objs: initPVC(),
|
||||
expected: []checks.Diagnostic{
|
||||
{
|
||||
Severity: checks.Warning,
|
||||
Message: "Unused persistent volume claim",
|
||||
Kind: checks.PersistentVolumeClaim,
|
||||
Object: &metav1.ObjectMeta{Name: "pvc_foo", Namespace: "k8s"},
|
||||
Owners: GetOwners(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
unusedClaimCheck := unusedClaimCheck{}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
d, err := unusedClaimCheck.Run(test.objs)
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, test.expected, d)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func initPVC() *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: "k8s"},
|
||||
},
|
||||
},
|
||||
},
|
||||
PersistentVolumeClaims: &corev1.PersistentVolumeClaimList{
|
||||
Items: []corev1.PersistentVolumeClaim{
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{Kind: "PersistentVolumeClaim", APIVersion: "v1"},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pvc_foo", Namespace: "k8s"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return objs
|
||||
}
|
||||
|
||||
func boundPVC(volumeSrc corev1.VolumeSource) *kube.Objects {
|
||||
objs := initPVC()
|
||||
objs.Pods.Items[0].Spec = corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "bar",
|
||||
VolumeSource: volumeSrc,
|
||||
}},
|
||||
}
|
||||
return objs
|
||||
}
|
Loading…
Reference in New Issue