Member post originally published on the Nirmata Blog by Jim Bugwadia
The Don’t Repeat Yourself (DRY) principle of software development advocates avoiding repetition of code that is likely to change. Replacing similar code with reusable abstractions makes software easier to maintain, and avoids bugs.
In this post, I will show you a couple of ways to apply the DRY principle in Kyverno policies, which are written in YAML.
DRY Using Variables
Kyverno policies can declare and reuse variables. Consider this policy that mutates various container types in a Pod, to add a memory request if one is not specified. The policy rule iterates over a list of containers, initContainers, and ephemeralContainers all of which have the same structure.
Since the policy rule uses a RFC 6902 JSON Patch, the path for the patch is dependent on the type of container. An initial implementation of the policy rule duplicates the patch for each container type:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-resources
spec:
background: false
rules:
- name: default-memory
match:
any:
- resources:
kinds:
- Pod
operations:
- CREATE
mutate:
foreach:
- list: request.object.spec.containers[]
preconditions:
all:
- key: "{{ element.resources.requests.memory || `0` }}"
operator: Equals
value: 0
patchesJson6902: |-
- path: /spec/containers/{{elementIndex}}/resources/requests/memory
op: add
value: 50Mi
- list: request.object.spec.initContainers[]
preconditions:
all:
- key: "{{ element.resources.requests.memory || `0` }}"
operator: Equals
value: 0
patchesJson6902: |-
- path: /spec/initContainers/{{elementIndex}}/resources/requests/memory
op: add
value: 50Mi
- list: request.object.spec.ephemeralContainers[]
preconditions:
all:
- key: "{{ element.resources.requests.memory || `0` }}"
operator: Equals
value: 0
patchesJson6902: |-
- path: /spec/ephemeralContainers/{{elementIndex}}/resources/requests/memory
op: add
value: 50Mi
While this policy works, the patch is duplicated three times, once for each container type. This duplication can be avoided by extracting the patch into a policy variable and then reusing the variable for each container type. Using JMESPath, the index and the container type, can be overridden for each type:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-resources
spec:
background: false
rules:
- name: default-memory
match:
any:
- resources:
kinds:
- Pod
operations:
- CREATE
context:
- name: patch
variable:
value: |-
- path: /spec/containers/0/resources/requests/memory
op: add
value: 50Mi
mutate:
foreach:
- list: request.object.spec.containers[]
preconditions:
all:
- key: "{{ element.resources.requests.memory || `0` }}"
operator: Equals
value: 0
patchesJson6902: "{{ patch | replace_all(@, '0', '{{elementIndex}}') }}"
- list: request.object.spec.ephemeralContainers[]
preconditions:
all:
- key: "{{ element.resources.requests.memory || `0` }}"
operator: Equals
value: 0
patchesJson6902: "{{ patch | replace_all(@,'0','{{elementIndex}}') | replace_all(@,'containers','ephemeralContainers')}}"
- list: request.object.spec.initContainers[]
preconditions:
all:
- key: "{{ element.resources.requests.memory || `0` }}"
operator: Equals
value: 0
patchesJson6902: "{{ patch | replace_all(@,'0','{{elementIndex}}') | replace_all(@,'containers','initContainers')}}"
DRY Using YAML Anchors and Aliases
The other duplication in the policy rule, is the precondition check. YAML allows reuse using anchors and aliases. We can leverage this YAML feature to remove the duplicated preconditions in the rule logic for each container type:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-resources
spec:
background: false
rules:
- name: default-memory
match:
any:
- resources:
kinds:
- Pod
operations:
- CREATE
context:
- name: patch
variable:
value: |-
- path: /spec/containers/0/resources/requests/memory
op: add
value: 50Mi
mutate:
foreach:
- list: request.object.spec.containers[]
preconditions: &pre
all:
- key: "{{ element.resources.requests.memory || `0` }}"
operator: Equals
value: 0
patchesJson6902: "{{ patch | replace_all(@, '0', '{{elementIndex}}') }}"
- list: request.object.spec.ephemeralContainers[]
preconditions: *pre
patchesJson6902: "{{ patch | replace_all(@,'0','{{elementIndex}}') | replace_all(@,'containers','ephemeralContainers')}}"
- list: request.object.spec.initContainers[]
preconditions: *pre
patchesJson6902: "{{ patch | replace_all(@,'0','{{elementIndex}}') | replace_all(@,'containers','initContainers')}}"
The &pre
declares an anchor named pre
that can be subsequently referenced using *pre
. YAML anchors also allow overrides, when additional flexibility is required.
Keep reading
- Kyverno 1.12 is released
- Write YAML for Kubernetes
- How to generate K8s ValidatingAdmissionPolicies from Kyverno policies
- Register for KubeCon + CloudNativeCon North American 2024 today
Conclusion
The DRY Principle of coding applies to Policy as Code. In this post we used two powerful techniques to reduce duplication of logic that may need to change. Kyverno variables can be used to extract, or “pull-up” common elements and declare them once. And, YAML anchors and aliases allow reusing declarations.
Other techniques to apply DRY can be to use Helm templates, Kustomize, or other IaC tools.
Nirmata is the creator and a maintainer of Kyverno. If you are using Kyverno, and need any assistance, we would love to hear from you!