A dynamic Kubernetes admission controller that validates or modifies resources based on conditions you define using Helm templates.
It can retrieve data from other resources and inject it into templates, allowing for customizable conditions and messages.
Kubernetes has not an implementation of an admission controller to validate/mutate resources. Instead, it leverages its creation to the clusters administrators.
This project was initiated to fill that gap by providing a potent and flexible admission controller. It allows for dynamic validation and mutation of resources, using data from other resources and enabling conditions and messages defined through Helm templates.
Our goal is to equip administrators with a tool that offers greater control and adaptability in managing Kubernetes resources due to:
There are Kubernetes clusters where resources are introduced from a multitude of sources, where maintaining harmony and preventing conflicts in production environments is a significant challenge.
It's essential to have a comprehensive set of policies that can enforce rules and prevent resource collisions effectively.
Existing solutions often fall short—they may not offer the level of power and dynamism necessary to address complex deployment scenarios.
We have designed the deployment of this project to allow remote deployment using Kustomize or Helm. This way it is possible to use it with a GitOps approach, using tools such as ArgoCD or FluxCD.
If you prefer Kustomize, just make a Kustomization manifest referencing the tag of the version you want to deploy as follows:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- https://github.com/freepik-company/admitik/releases/download/v0.1.0/install.yaml
🧚🏼 Hey, listen! If you prefer to deploy using Helm, go to the Helm registry
Some configuration parameters can be defined by flags that can be passed to the controller. They are described in the following table:
Name | Description | Default |
---|---|---|
--metrics-bind-address |
The address the metric endpoint binds to. 0 disables the server | 0 |
--health-probe-bind-address |
he address the probe endpoint binds to | :8081 |
--leader-elect |
Enable leader election for controller manager | false |
--metrics-secure |
If set the metrics endpoint is served securely | false |
--enable-http2 |
If set, HTTP/2 will be enabled for the metrirs | false |
--sources-time-to-resync-informers |
Interval to resynchronize all resources in the informers | 60s |
--sources-time-to-reconcile-watchers |
Time between each reconciliation loop of the watchers | 10s |
--sources-time-to-ack-watcher |
Wait time before marking a watcher as acknowledged (ACK) after it starts | 2s |
--webhook-client-hostname |
The hostname used by Kubernetes when calling the webhooks server | webhooks.admitik.svc |
--webhook-client-port |
The port used by Kubernetes when calling the webhooks server | 10250 |
--webhook-client-timeout |
The seconds until timout waited by Kubernetes when calling the webhooks server | 10 |
--webhook-server-port |
The port where the webhooks server listens | 10250 |
--webhook-server-path |
The path where the webhooks server listens | /validate |
--webhook-server-ca |
The CA bundle to use for the webhooks server | - |
--webhook-server-certificate |
The Certificate used by webhooks server | - |
--webhook-server-private-key |
The Private Key used by webhooks server | - |
After deploying this operator, you will have new resources available. Let's talk about them.
To create a dynamic admission policy in your cluster, you will need to create a ClusterAdmissionPolicy
resource.
You may prefer to learn directly from an example, so let's explain it creating a ClusterAdmissionPolicy:
[!TIP] You can find the spec samples for all the versions of the resource in the examples directory
[!IMPORTANT] When you have multiple ClusterAdmissionPolicy resources with the same watchedResources parameters, a resource can be rejected due to conditions specified in any of these policies.
However, because conditions are evaluated one after the other, the rejection message displayed will be the one defined in the specific ClusterAdmissionPolicy where the rejecting condition is located.
apiVersion: admitik.freepik.com/v1alpha1
kind: ClusterAdmissionPolicy
metadata:
labels:
app.kubernetes.io/name: admitik
app.kubernetes.io/managed-by: kustomize
name: avoid-colisioning-routes
spec:
# (Optional) Action to perform with the conditions are not met
# Posible values: Enforce, Permissive
# Enforce: (default) Reject the object.
# Permissive: Accept the object
# Both results create an event in Kubernetes
failureAction: Enforce
# Resources to be validated against the webhooks server.
# Those matching any combination of following params will be sent.
# As a hint: don't set operations you don't need for a resource type
watchedResources:
group: gateway.networking.k8s.io
version: v1
resource: httproutes
operations:
- CREATE
- UPDATE
# Other resources to be retrieved for later templates.
# They will be included under .sources scope for conditions and message
sources:
- group: gateway.networking.k8s.io
version: v1
resource: httproutes
# (Optional) It's possible to retrieve specific resources
# name: secondary-route
# namespace: default
# ALL the conditions must be valid to allow the resource entrance.
conditions:
- name: confirm-non-existing-routes
# The 'key' field admits vitamin Golang templating (well known from Helm)
# The result of this field will be compared with 'value' for equality
key: |
{{- $operation := .operation -}}
{{- $object := .object -}}
{{- $oldObject := .oldObject -}}
{{- $sources := .sources -}}
{{- $routeFound := false -}}
{{- $routes := (index .sources 0) -}}
{{- range $routeObjIndex, $routeObj := $routes -}}
{{/* Here some logic to confirm you found the route already existing */}}
{{- $routeFound := true -}}
{{- end -}}
{{- if $routeFound -}}
{{- printf "route-already-created" -}}
{{- else -}}
{{- printf "route-not-found" -}}
{{- end -}}
value: "route-not-found"
message:
template: |
{{- $object := .object -}}
{{- printf "Resource '%s' was rejected as some of declared routes already exists" $object.metadata.name -}}
As you probably noticed in the previous example, conditions are made using vitamin Golang template (better known as Helm template), so all the functions available in Helm are available here too. This way you start creating wonderful policies from first minute.
Sometimes you need to store information during conditions' evaluation that will be useful in later messages shown to the team. This will help your mates having meaningful messages that save time during debug.
Because of that, there is a special function available in templates called setVar
. It can be used as follows:
apiVersion: admitik.freepik.com/v1alpha1
kind: ClusterAdmissionPolicy
spec:
...
conditions:
- name: store-vars-for-later-usage
key: |
{{- $someDataForLater := dict "name" "example-name" "namespace" "example-namespace" -}}
{{/* Store your data under your desired key. You can use as many keys as needed */}}
{{- setVar "some_key" $someDataForLater -}}
{{- printf "force-condition-not-being-met" -}}
value: "condition-key-result"
message:
template: |
{{- $myVars := .vars -}}
{{- $someKeyInside := $myVars.some_key-}}
{{- printf "let's show some stored data: %s/%s" $someKeyInside.name $someKeyInside.namespace -}}
[!TIP] Another useful function that can be used in templates is
logPrintf
. It accepts the same params as printf but throw the result in controller's logs instead of returning it
We recommend you to use a development tool like Kind or Minikube to launch a lightweight Kubernetes on your local machine for development purposes
For learning purposes, we will suppose you are going to use Kind. So the first step is to create a Kubernetes cluster on your local machine executing the following command:
kind create cluster
Once you have launched a safe play place, execute the following command. It will install the custom resource definitions (CRDs) in the cluster configured in your ~/.kube/config file and run Kuberbac locally against the cluster:
make install run
[!IMPORTANT] When executing this, a temporary public reverse tunnel will be created. It goes from Kube Apiserver to your local webhooks server. It's done this way to be able to test the webhooks server using local development tools such as Kind
If you would like to test the operator against some resources, our examples can be applied to see the result in your Kind cluster
kubectl apply -k config/samples/
Remember that your
kubectl
is pointing to your Kind cluster. However, you should always review the context your kubectl CLI is pointing to
Each release of this operator is done following several steps carefully in order not to break the things for anyone. Reliability is important to us, so we automated all the process of launching a release. For a better understanding of the process, the steps are described in the following recipe:
Test the changes on the code:
make test
A release is not done if this stage fails
Define the package information
export VERSION="0.0.1"
export IMG="ghcr.io/freepik-company/admitik:v$VERSION"
Generate and push the Docker image (published on Docker Hub).
make docker-build docker-push
Generate the manifests for deployments using Kustomize
make build-installer
This project is done on top of Kubebuilder, so read about that project before collaborating. Of course, we are open to external collaborations for this project. For doing it you must fork the repository, make your changes to the code and open a PR. The code will be reviewed and tested (always)
We are developers and hate bad code. For that reason we ask you the highest quality on each line of code to improve this project on each iteration.
Copyright 2022.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.