Kubernetes workloads are most commonly defined in YAML format files.
One of the problems with YAML is that it is difficult to describe the constraints or relationships between manifest files.
What if you want to check whether all the images deployed to the cluster have been extracted from the trusted registry?
How to prevent workloads without Pod security policies from being submitted to the cluster?
Integrated static checking can catch errors and violations of policy closer to the development life cycle.
And because the effectiveness and security of resource definitions are improved, you can trust that production workloads follow best practices.
The static inspection ecosystem of Kubernetes YAML files can be divided into the following categories:
- API Verifier: This type of tool verifies the given YAML list against the Kubernetes API server.
- Built-in checker: This type of tool is bundled with conscious checks for security, best practices, etc.
- Custom validators: These tools allow custom checks to be written in multiple languages (such as python and Javascript).
In this article, you will learn six different tools:
Kubeval
Kube-score
Config-lint
Copper
Conftest
Polaris
Let's Go ~~~
Benchmark service
First deploy a benchmark service for later testing and comparison
apiVersion: apps/v1
kind: Deployment
metadata:
name: http-echo
spec:
replicas: 2
selector:
matchLabels:
app: http-echo
template:
metadata:
labels:
app: http-echo
spec:
containers:
- name: http-echo
image: hashicorp/http-echo
args: ["-text", "hello-world"]
ports:
- containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
name: http-echo
spec:
ports:
- port: 5678
protocol: TCP
targetPort: 5678
selector:
app: http-echo
The deployment is completed and verified as follows:
[root@k8s-node001 Test]# kubectl get po
NAME READY STATUS RESTARTS AGE
http-echo-57dd74545-rtxzm 1/1 Running 0 65s
http-echo-57dd74545-trst7 1/1 Running 0 65s
[root@k8s-node001 Test]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
http-echo ClusterIP 10.102.221.64 <none> 5678/TCP 70s
[root@k8s-node001 Test]# curl 10.102.221.64:5678
hello-world
The above YAML file can be deployed successfully, but does it follow best practices?
Let's start.
kubeval
The premise of kubeval is that any interaction with Kubernetes is done through its REST API.
Therefore, the API pattern can be used to verify whether a given YAML input conforms to the pattern.
Install kubeval
wget https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-linux-amd64.tar.gz
tar xf kubeval-linux-amd64.tar.gz
cp kubeval /usr/local/bin
Now let's change base.yaml and delete
selector:
matchLabels:
app: http-echo
Then use kubeval to check base.yaml
[root@k8s-node001 Test]# kubeval base.yaml
WARN - base.yaml contains an invalid Deployment (http-echo) - selector: selector is required
PASS - base.yaml contains a valid Service (http-echo)
The output sees a WARN indicating that the selector is a required field,
then restore the selector, and check again
[root@k8s-node001 Test]# kubeval base.yaml
PASS - base.yaml contains a valid Deployment (http-echo)
PASS - base.yaml contains a valid Service (http-echo)
Check PASS
The advantage of tools like kubeval is that we can detect such errors early in the deployment cycle.
In addition, you don't need to visit the cluster to run the checks-they can be run offline.
By default, kubeval will validate resources based on the latest unpublished Kubernetes API pattern.
For more usage details, please refer to the official website
be a score
kube-score will analyze the YAML list you provide and score it against the cluster's built-in checks.
kube-score provides online and offline versions. If you are
lazy, use the online version.
First open https://kube-score.com/, and then paste the written YAML list in the input box. Here, the above text base.yaml will analyze
The analysis results are as follows
From the above, you can see the suggestions given for this file, such as resource restrictions, mirroring TAG, Pod network strategy, etc. Not bad, very useful tool. . .
Of course, kube-score is not scalable, and you cannot add or adjust strategies.
If you want to write custom checks to comply with organizational policies, you can use one of four tools: config-lint, copper, conftest or Polaris.
Config-lint
Config-lint is a tool for validating configuration files written in YAML, JSON, Terraform, CSV and Kubernetes manifests.
Install Config-lint
wget https://github.com/stelligent/config-lint/releases/download/v1.6.0/config-lint_Linux_x86_64.tar.gz
tar -zxf config-lint_Linux_x86_64.tar.gz
mv config-lint /usr/local/bin/
Config-lint does not perform built-in checks on the Kubernetes manifest. You must write your own rules to perform any verification.
The rules are written as YAML files, called rule sets, and have the following structure:
version: 1
description: Rules for Kubernetes spec files
type: Kubernetes
files:
- "*.yaml"
rules:
# list of rules
Suppose we want to check whether the image in the deployment is always extracted from a trusted repository (for example, kubeops.net/app:1.0).
The config-lint rules for implementing such checks are as follows:
- id: MY_DEPLOYMENT_IMAGE_TAG
severity: FAILURE
message: Deployment must use a valid image tag
resource: Deployment
assertions:
- every:
key: spec.template.spec.containers
expressions:
- key: image
op: starts-with
value: "kubeops.net/"
A complete rule set is as follows:
version: 1
description: Rules for Kubernetes spec files
type: Kubernetes
files:
- "*.yaml"
rules:
- id: DEPLOYMENT_IMAGE_REPOSITORY
severity: FAILURE
message: Deployment must use a valid image repository
resource: Deployment
assertions:
- every:
key: spec.template.spec.containers
expressions:
- key: image
op: starts-with
value: "kubeops.net/"
If you want to test the inspection, you can save the rule set as check_image_repo.yaml.
Then use config-lint to perform the check
[root@k8s-node001 Test]# config-lint -rules check_image_repo.yaml base.yaml
[
{
"AssertionMessage": "Every expression fails: And expression fails: image does not start with kubeops.net/",
"Category": "",
"CreatedAt": "2020-11-02T08:28:43Z",
"Filename": "base.yaml",
"LineNumber": 0,
"ResourceID": "http-echo",
"ResourceType": "Deployment",
"RuleID": "DEPLOYMENT_IMAGE_REPOSITORY",
"RuleMessage": "Deployment must use a valid image repository",
"Status": "FAILURE"
}
]
You can see Every expression fails, the test failed.
Now let's change the images address to image: kubeops.net/http-echo
, and check again
[root@k8s-node001 Test]# config-lint -rules check_image_repo.yaml base.yaml
[]
If the output does not report an error, it means success.
Config-lint is a promising framework that allows you to use YAML DSL to write custom checks for Kubernetes YAML lists.
But what if you want to express more complex logic and inspections?
Does YAML have restrictions on this?
What if you can express these checks in a real programming language? Next look at Copper
Copper
Copper V2 is a framework that uses custom checks to verify the list, just like config-lint.
However, Copper does not use YAML definition checks.
Instead, the tests are written in JavaScript, and Copper provides a library containing some basic helpers to help read Kubernetes objects and report errors.
Install Copper
https://github.com/cloud66-oss/copper/releases/download/2.0.1/linux_amd64_2.0.1
mv linux_amd64_2.0.1 copper
chmod + x copper
mv copper /usr/local/bin/
Similar to config-lint, Copper does not provide built-in checks.
Let's customize a check to ensure that the deployment image tag must be non-latest.
check_image_repo.js
$$.forEach(function($){
if ($.kind === 'Deployment') {
$.spec.template.spec.containers.forEach(function(container) {
var image = new DockerImage(container.image);
if (image.tag === 'latest') {
errors.add_error('no_latest',"latest is used in " + $.metadata.name, 1)
}
});
}
});
Perform inspection
[root@k8s-node001 Test]# copper validate --in=base.yaml --validator=check_image_tag.js
Check no_latest failed with severity 1 due to latest is used in http-echo
Validation failed
Now amended toimage: kubeops.net/http-echo:v1.0.0
[root@k8s-node001 Test]# copper validate --in=base.yaml --validator=check_image_tag.js
Validation successful
For more usage, see
Conftest
Conftest is a testing framework for configuration data that can be used to check and verify the Kubernetes inventory.
The test is written using the dedicated query language Rego.
Install Conftest
wget https://github.com/open-policy-agent/conftest/releases/download/v0.21.0/conftest_0.21.0_Linux_x86_64.tar.gz
tar -xzf conftest_0.21.0_Linux_x86_64.tar.gz
mv conftest /usr/local/bin
Similar to config-lint and copper, conftest does not have any built-in checks.
First create a new directory conftest-checks and a file named check_image_registry.rego with the following content:
package main
deny[msg] {
input.kind == "Deployment"
image := input.spec.template.spec.containers[_].image
not startswith(image, "kubeops.net/")
msg := sprintf("image '%v' doesn't come from kubeops.net repository", [image])
}
First modify base.yaml and image: docker.io/http-echo
use conftest to perform detection
[root@k8s-node001 Test]# conftest test --policy ./conftest-checks base.yaml
FAIL - base.yaml - image 'docker.io/http-echo:v1.0.0' doesn't come from kubeops.net repository
2 tests, 1 passed, 0 warnings, 1 failure, 0 exceptions
Modify it to base.yaml again,image: kubeops.net/http-echo
[root@k8s-node001 Test]# conftest test --policy ./conftest-checks base.yaml
2 tests, 2 passed, 0 warnings, 0 failures, 0 exceptions
For more usage, see
Polaris
The last tool is Polaris. Polaris can be installed inside the cluster or used as a command line tool to statically analyze the Kubernetes list.
When run as a command line tool, it contains multiple built-in checks covering aspects such as security and best practices, similar to kube-score.
In addition, you can use it to write custom checks similar to config-lint, copper and conftest.
In other words, Polaris combines the advantages of two categories: built-in and custom checkers.
Install Polaris, here only install the command line mode
wget https://github.com/FairwindsOps/polaris/releases/download/1.2.1/polaris_1.2.1_linux_amd64.tar.gz
tar -zxf polaris_1.2.1_linux_amd64.tar.gz
mv polaris /usr/local/bin/
After the installation is complete, you can use Polaris to check base.yaml
[root@k8s-node001 Test]# polaris audit --audit-path base.yaml The
result is as follows, there is a lot of information, here only part of the information is intercepted, you can read it carefully Look at the results of the analysis.
"PolarisOutputVersion": "1.0",
"AuditTime": "0001-01-01T00:00:00Z",
"SourceType": "Path",
"SourceName": "base.yaml",
"DisplayName": "base.yaml",
"ClusterInfo": {
"Version": "unknown",
"Nodes": 0,
"Pods": 1,
"Namespaces": 0,
"Controllers": 1
},
"Results": [
{
"Name": "http-echo",
"Namespace": "",
"Kind": "Deployment",
"Results": {},
"PodResult": {
"Name": "",
"Results": {
"hostIPCSet": {
"ID": "hostIPCSet",
"Message": "Host IPC is not configured",
"Success": true,
"Severity": "danger",
"Category": "Security"
..............
"tagNotSpecified": {
"ID": "tagNotSpecified",
"Message": "Image tag is specified",
"Success": true,
"Severity": "danger",
"Category": "Images"
}
}
}
]
},
"CreatedTime": "0001-01-01T00:00:00Z"
}
]
}
In addition, you can output only the score
[root@k8s-node001 Test]# polaris audit --audit-path base.yaml --format score
66
The following YAML code snippet defines a new check called checkImageRepo:
config_with_custom_check.yaml
checks:
checkImageRepo: danger
customChecks:
checkImageRepo:
successMessage: Image registry is valid
failureMessage: Image registry is not valid
category: Images
target: Container
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
image:
type: string
pattern: ^kubeops.net/.+$
Now the image of base.yaml is: image: docker.io/http-echo:v1.0.0
Let’s use custom rules to perform checks
[root@k8s-node001 Test]# polaris audit --config config_with_custom_check.yaml --audit-path base.yaml
{
"PolarisOutputVersion": "1.0",
"AuditTime": "0001-01-01T00:00:00Z",
"SourceType": "Path",
"SourceName": "base.yaml",
"DisplayName": "base.yaml",
"ClusterInfo": {
"Version": "unknown",
"Nodes": 0,
"Pods": 1,
"Namespaces": 0,
"Controllers": 1
},
"Results": [
{
"Name": "http-echo",
"Namespace": "",
"Kind": "Deployment",
"Results": {},
"PodResult": {
"Name": "",
"Results": {},
"ContainerResults": [
{
"Name": "http-echo",
"Results": {
"checkImageRepo": {
"ID": "checkImageRepo",
"Message": "Image registry is not valid",
"Success": false,
"Severity": "danger",
"Category": "Images"
}
}
}
]
},
"CreatedTime": "0001-01-01T00:00:00Z"
}
]
}
The result shows "Message": "Image registry is not valid", "Success": false,
then modify the image of base.yaml to: image: kubeops.net/http-echo:v1.0.0
perform the check again
[root@k8s-node001 Test]# polaris audit --config config_with_custom_check.yaml --audit-path base.yaml
{
"PolarisOutputVersion": "1.0",
"AuditTime": "0001-01-01T00:00:00Z",
"SourceType": "Path",
"SourceName": "base.yaml",
"DisplayName": "base.yaml",
"ClusterInfo": {
"Version": "unknown",
"Nodes": 0,
"Pods": 1,
"Namespaces": 0,
"Controllers": 1
},
"Results": [
{
"Name": "http-echo",
"Namespace": "",
"Kind": "Deployment",
"Results": {},
"PodResult": {
"Name": "",
"Results": {},
"ContainerResults": [
{
"Name": "http-echo",
"Results": {
"checkImageRepo": {
"ID": "checkImageRepo",
"Message": "Image registry is valid",
"Success": true,
"Severity": "danger",
"Category": "Images"
}
}
}
]
},
"CreatedTime": "0001-01-01T00:00:00Z"
}
]
}
From the output, you see "Message": "Image registry is valid", "Success": true, and the check passed. . .
For more usage, see
to sum up
Although there are many tools for verifying, scoring, and organizing Kubernetes YAML files, it is important to have a healthy model to design and perform checks.
For example, if you want to consider a Kubernetes manifest through a pipeline, kubeval may be the first step in the pipeline because it can verify that the object definition conforms to the Kubernetes API pattern. Once this check is successful, you can proceed with more detailed tests, such as standard best practices and custom strategies.
Kube-score and Polaris are better choices.
If you have complex requirements and want to customize the details of the inspection, you should consider using copper, config-lint and conftest.
Although both conftest and config-lint use more YAML to define custom validation rules, Copper allows access to a true programming language, which makes it quite attractive.
But should you use one of them and write all the checks from scratch? Or should I use Polaris and just write other custom checks?
It all depends on you, the best is the one that suits you. . .