kor

GitHub go.mod Go version (subdirectory of monorepo) GitHub release (with filter) Docker Pulls codecov Discord

Kor - Kubernetes Orphaned Resources Finder

Kor Logo

Kor is a tool to discover unused Kubernetes resources. Currently, Kor can identify and list unused:

Kor Screenshot

Installation

Download the binary for your operating system from the releases page and add it to your system’s PATH.

Homebrew

For macOS users, you can install Kor using Homebrew:

brew install kor

Build from source

Install the binary to your $GOBIN or $GOPATH/bin:

go install github.com/yonahd/kor@latest

Docker

Run a container with your kubeconfig mounted:

docker run --rm -i yonahdissen/kor

docker run --rm -i -v "/path/to/.kube/config:/root/.kube/config" yonahdissen/kor all

Kubectl plugin (krew)

kubectl krew install kor

Helm

Run as a cronjob in your Cluster (with an option for sending slack updates)

helm upgrade -i kor \
    --namespace kor \
    --create-namespace \
    --set cronJob.enabled=true
    ./charts/kor

Run as a deployment in your Cluster exposing prometheus metrics

helm upgrade -i kor \
    --namespace kor \
    --create-namespace \
    ./charts/kor

For more information see in cluster usage

Usage

Kor provides various subcommands to identify and list unused resources. The available commands are:

Supported Flags

      --delete                       Delete unused resources
  -l, --exclude-labels strings       Selector to filter out, Example: --exclude-labels key1=value1,key2=value2. If --include-labels is set, --exclude-labels will be ignored.
  -e, --exclude-namespaces strings   Namespaces to be excluded, split by commas. Example: --exclude-namespaces ns1,ns2,ns3. If --include-namespaces is set, --exclude-namespaces will be ignored.
      --group-by string              Group output by (namespace, resource) (default "namespace")
  -h, --help                         help for kor
      --include-labels string        Selector to filter in, Example: --include-labels key1=value1.(currently supports one label)
  -n, --include-namespaces strings   Namespaces to run on, split by commas. Example: --include-namespaces ns1,ns2,ns3. If set, non-namespaced resources will be ignored.
  -k, --kubeconfig string            Path to kubeconfig file (optional)
      --newer-than string            The maximum age of the resources to be considered unused. This flag cannot be used together with older-than flag. Example: --newer-than=1h2m
      --no-interactive               Do not prompt for confirmation when deleting resources. Be careful using this flag!
      --older-than string            The minimum age of the resources to be considered unused. This flag cannot be used together with newer-than flag. Example: --older-than=1h2m
  -o, --output string                Output format (table, json or yaml) (default "table")
  -r, --show-reason                  Print reason resource is considered unused
      --slack-auth-token string      Slack auth token to send notifications to. --slack-auth-token requires --slack-channel to be set.
      --slack-channel string         Slack channel to send notifications to. --slack-channel requires --slack-auth-token to be set.
      --slack-webhook-url string     Slack webhook URL to send notifications to
  -v, --verbose                      Verbose output (print empty namespaces)

To use a specific subcommand, run kor [subcommand] [flags].

kor all --include-namespaces my-namespace

For more information about each subcommand and its available flags, you can use the --help flag.

kor [subcommand] --help

Supported resources and limitations

Resource What it looks for Known False Positives ⚠️
ConfigMaps ConfigMaps not used in the following places:
- Pods
- Containers
- ConfigMaps used through Volumes
- ConfigMaps used through environment variables
ConfigMaps used by resources which don’t explicitly state them in the config.
e.g Grafana dashboards loaded dynamically OPA policies fluentd configs CRD configs
Secrets Secrets not used in the following places:
- Pods
- Containers
- Secrets used through volumes
- Secrets used through environment variables
- Secrets used by Ingress TLS
- Secrets used by ServiceAccounts
Secrets used by resources which don’t explicitly state them in the config e.g. secrets used by CRDs
Services Services with no endpoints  
Deployments Deployments with no Replicas  
ServiceAccounts ServiceAccounts unused by Pods
ServiceAccounts unused by roleBinding or clusterRoleBinding
 
StatefulSets Statefulsets with no Replicas  
Roles Roles not used in roleBinding  
ClusterRoles ClusterRoles not used in roleBinding or clusterRoleBinding  
PVCs PVCs not used in Pods  
Ingresses Ingresses not pointing at any Service  
Hpas HPAs not used in Deployments
HPAs not used in StatefulSets
 
CRDs CRDs not used the cluster  
Pvs PVs not bound to a PVC  
Pdbs PDBs not used in Deployments
PDBs not used in StatefulSets
 
Jobs Jobs status is completed
Jobs failed with no retries left
 
ReplicaSets replicaSets that specify replicas to 0 and has already completed it’s work  
DaemonSets DaemonSets not scheduled on any nodes  
StorageClasses StorageClasses not used by any PVs/PVCs  

Deleting Unused resources

If you want to delete resources in an interactive way using Kor you can run:

kor configmap --include-namespaces my-namespace --delete

You will be prompted with:

Do you want to delete ConfigMap test-configmap in namespace my-namespace? (Y/N):

To delete with no prompt ( ⚠️ use with caution):

kor configmap --include-namespaces my-namespace --delete --no-interactive

Ignore Resources

The resources labeled with:

kor/used=true

Will be ignored by kor even if they are unused. You can add this label to resources you want to ignore.

Force clean Resources

The resources labeled with:

kor/used=false

Will be cleaned always. This is a good way to mark resources for later cleanup.

Output Formats

Kor supports three output formats: table, json, and yaml. The default output format is table. Additionally, you can use the --group-by flag to group the output by namespace or resource.

Group by resource

kor all --group-by=resource --output=table
Unused ConfigMaps:
+---+-----------+---------------+
| # | NAMESPACE | RESOURCE NAME |
+---+-----------+---------------+
| 1 | ns1       | cm1           |
| 2 | ns1       | cm2           |
| 3 | ns2       | cm3           |
+---+-----------+---------------+
Unused Deployments:
+---+-----------+---------------+
| # | NAMESPACE | RESOURCE NAME |
+---+-----------+---------------+
| 1 | ns1       | deploy1       |
| 2 | ns2       | deploy2       |
+---+-----------+---------------+
Unused ReplicaSets:
+---+-----------+--------------------+
| # | NAMESPACE |   RESOURCE NAME    |
+---+-----------+--------------------+
| 1 | ns1       | deploy1-654d48b75f |
| 2 | ns2       | deploy2-79f48888c6 |
+---+-----------+--------------------+

Group by namespace

kor all --group-by=namespace --output=table
Unused resources in namespace: "ns1"
+---+---------------+--------------------+
| # | RESOURCE TYPE |   RESOURCE NAME    |
+---+---------------+--------------------+
| 1 | ConfigMap     | cm1                |
| 2 | ConfigMap     | cm2                |
| 3 | ReplicaSet    | deploy1-654d48b75f |
| 4 | Deployment    | deploy1            |
+---+---------------+--------------------+
Unused resources in namespace: "ns2"
+---+---------------+--------------------+
| # | RESOURCE TYPE |   RESOURCE NAME    |
+---+---------------+--------------------+
| 1 | ReplicaSet    | deploy2-79f48888c6 |
| 2 | ConfigMap     | cm3                |
| 3 | Deployment    | deploy2            |
+---+---------------+--------------------+

In Cluster Usage

To use this tool inside the cluster running as a CronJob and sending the results to a Slack Webhook as raw text(has characters limits of 4000) or to a Slack channel by uploading a file(recommended), you can use the following commands:

# Send to a Slack webhook as raw text
helm upgrade -i kor \
    --namespace kor \
    --create-namespace \
    --set cronJob.slackWebhookUrl=<slack-webhook-url> \
    ./charts/kor
# Send to a Slack channel by uploading a file
helm upgrade -i kor \
    --namespace kor \
    --create-namespace \
    --set cronJob.slackChannel=<slack-channel> \
    --set cronJob.slackToken=<slack-token> \
    ./charts/kor

Note: To send it to Slack as a file it’s required to set the slackToken and slackChannel values.

It’s set to run every Monday at 1 a.m. by default. You can change the schedule by setting the cronJob.schedule value.

helm upgrade -i kor \
    --namespace kor \
    --create-namespace \
    --set cronJob.slackChannel=<slack-channel> \
    --set cronJob.slackToken=<slack-token> \
    --set cronJob.schedule="0 1 * * 1" \
    ./charts/kor

Grafana Dashboard

Dashboard can be found here. Grafana Dashboard

Contributing

Contributions are welcome! If you encounter any bugs or have suggestions for improvements, please open an issue in the issue tracker.

Follow CONTRIBUTING.md for more.

License

This open-source project is available under the MIT License. Feel free to use, modify, and distribute it as per the terms of the license.