In the Kubernetes world, we all play with YAML files, deploy them in order to create various Kubernetes objects, but the challenge is whether we are following best practices when writing them? Are we using the right set of standard configurations? Can we have YAML checked prior to the deployment of applications or even Helm charts? The answer to all these is yes, we can. On 28th October 2020, StackRox introduced a new open-source tool named KubeLinter aimed at identifying any misconfigurations in YAML files.
By definition, KubeLinter is a static analysis tool that checks Kubernetes YAML files and Helm charts to ensure the applications represented in them adhere to best practices. Once a YAML file is supplied to the tool, it will run through the built-in checks and then give a detailed report any errors, and the remediations for solving them. The best part about this tool is that it is configurable and extensible: The built-in checks can be enabled or disabled, and you can define and use your own custom checks.
Usage
I will be installing KubeLinter on a Mac, but the same instructions can be used for Linux as well by just downloading the appropriate release for your operating system:
Download the KubeLinter CLI from the releases page.
$ curl -LO https://github.com/stackrox/kube-linter/releases/download/0.1.1/kube-linter-darwin.zip
$ unzip kube-linter-darwin.zip
$ mv kube-linter /usr/local/bin
#check if it's working
$ kube-linter version
0.1.1
Now, in order to simply check a single YAML file, just supply the YAML filename. Say that you have the below deploy.yaml
file that you want to check for best security and configuration practices saved in your current directory:
apiVersion: apps/v1
kind: Deployment
metadata:
name: portainer
namespace: portainer
labels:
io.portainer.kubernetes.application.stack: portainer
app.kubernetes.io/name: portainer
app.kubernetes.io/instance: portainer
app.kubernetes.io/version: "2.0.0"
spec:
replicas: 1
strategy:
type: "Recreate"
selector:
matchLabels:
app.kubernetes.io/name: portainer
app.kubernetes.io/instance: portainer
template:
metadata:
labels:
app.kubernetes.io/name: portainer
app.kubernetes.io/instance: portainer
spec:
serviceAccountName: portainer-sa-clusteradmin
volumes:
- name: "data"
persistentVolumeClaim:
claimName: portainer
containers:
- name: portainer
image: "portainer/portainer-ce:latest"
imagePullPolicy: IfNotPresent
args: [ '--tunnel-port','30776' ]
volumeMounts:
- name: data
mountPath: /data
ports:
- name: http
containerPort: 9000
protocol: TCP
- name: tcp-edge
containerPort: 8000
protocol: TCP
livenessProbe:
httpGet:
path: /
port: 9000
readinessProbe:
httpGet:
path: /
port: 9000
resources:
{}
Let's run the test using kube-linter lint deploy.yaml
. We should get output like the following:
deploy.yaml: (object: portainer/portainer apps/v1, Kind=Deployment) container "portainer" does not have a read-only root file system (check: no-read-only-root-fs, remediation: Set readOnlyRootFilesystem to true in your container's securityContext.)
deploy.yaml: (object: portainer/portainer apps/v1, Kind=Deployment) serviceAccount "portainer-sa-clusteradmin" not found (check: non-existent-service-account, remediation: Make sure to create the service account, or to refer to an existing service account.)
deploy.yaml: (object: portainer/portainer apps/v1, Kind=Deployment) container "portainer" is not set to runAsNonRoot (check: run-as-non-root, remediation: Set runAsUser to a non-zero number, and runAsNonRoot to true, in your pod or container securityContext. See https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ for more details.)
deploy.yaml: (object: portainer/portainer apps/v1, Kind=Deployment) container "portainer" has cpu request 0 (check: unset-cpu-requirements, remediation: Set your container's CPU requests and limits depending on its requirements. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits for more details.)
deploy.yaml: (object: portainer/portainer apps/v1, Kind=Deployment) container "portainer" has cpu limit 0 (check: unset-cpu-requirements, remediation: Set your container's CPU requests and limits depending on its requirements. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits for more details.)
deploy.yaml: (object: portainer/portainer apps/v1, Kind=Deployment) container "portainer" has memory request 0 (check: unset-memory-requirements, remediation: Set your container's memory requests and limits depending on its requirements. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits for more details.)
deploy.yaml: (object: portainer/portainer apps/v1, Kind=Deployment) container "portainer" has memory limit 0 (check: unset-memory-requirements, remediation: Set your container's memory requests and limits depending on its requirements. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits for more details.)
Error: found 7 lint errors
Types of checks
As you can see there are errors spotted in the YAML file with clear remediation steps for each.
If you want to have a look at the built-in checks, all of them are listed here – or you can have KubeLinter tell you the list:
$ kube-linter checks list
Name: dangling-service
Description: Alert on services that don't have any matching deployments
Remediation: Make sure your service's selector correctly matches the labels on one of your deployments.
Template: dangling-service
Parameters: map[]
Enabled by default: true
------------------------------
Name: default-service-account
Description: Alert on pods that use the default service account
Remediation: Create a dedicated service account for your pod. See https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ for more details.
Template: service-account
Parameters: map[serviceAccount:^(|default)$]
Enabled by default: false
|
|
|
and many more
Ignoring a check
You can ignore a specific check for a YAML file, or groups of checks as per your need by using the following annotations:
ignore-check.kube-linter.io/<check-name>
for example:
ignore-check.kube-linter.io/unset-cpu-requirements
You can add a ignore-check rule to the above deployment file example like this:
metadata:
name: portainer
namespace: portainer
labels:
io.portainer.kubernetes.application.stack: portainer
app.kubernetes.io/name: portainer
app.kubernetes.io/instance: portainer
app.kubernetes.io/version: "2.0.0"
annotations:
ignore-check.kube-linter.io/unset-cpu-requirements : "cpu requirements not required"
Now, when you run kube-linter lint deploy.yaml
again you should see no lint errors related to CPU requirements.
Run with a configuration
KubeLinter can also run with a config where you can provide all the information on the checks to be included/excluded. Below is the example configuration from the repository:
# customChecks defines custom checks.
customChecks:
- name: "required-label-app"
template: "required-label"
params:
key: "app"
checks:
# if doNotAutoAddDefaults is true, default checks are not automatically added.
doNotAutoAddDefaults: false
# addAllBuiltIn, if set, adds all built-in checks. This allows users to
# explicitly opt-out of checks that are not relevant using Exclude.
# Takes precedence over doNotAutoAddDefaults, if both are set.
addAllBuiltIn: false
# include explicitly adds checks, by name. You can reference any of the built-in checks.
# Note that customChecks defined above are included automatically.
include:
- "required-label-owner"
# exclude explicitly excludes checks, by name. exclude has the highest priority: if a check is
# in exclude, then it is not considered, even if it is in include as well.
exclude:
- "privileged"
If you run kubelinter --config config lint deploy.yaml
where config
is a file with the above-mentioned configuration, you should be able to see extra check added for "required-label":
deploy.yaml: (object: portainer/portainer apps/v1, Kind=Deployment) no label matching "owner=<any>" found (check: required-label-owner, remediation: Add an email annotation to your object with information about the object's owner.)
KubeLinter can prove to be a very useful tool when built into a CI pipeline. Code pushed into the repository can be checked and validated for best practices and security considerations, generating alerts if any are detected.
This way there can be healthy production-ready applications deployed to the cluster. This project is at an alpha stage but looks promising as a CI integration tool to validate any deployments before actually deploying to production.