diff --git a/helm/slurm-operator/templates/networkpolicy/operator-netpol.yaml b/helm/slurm-operator/templates/networkpolicy/operator-netpol.yaml new file mode 100644 index 00000000..8af7164b --- /dev/null +++ b/helm/slurm-operator/templates/networkpolicy/operator-netpol.yaml @@ -0,0 +1,56 @@ +{{- /* +SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +SPDX-License-Identifier: Apache-2.0 +*/}} + +{{- if and .Values.networkPolicy.enabled .Values.operator.enabled .Values.operator.networkPolicy.enabled }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "slurm-operator.name" . }} + namespace: {{ include "slurm-operator.namespace" . }} + labels: + {{- include "slurm-operator.operator.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "slurm-operator.operator.selectorLabels" . | nindent 6 }} + policyTypes: + - Ingress + - Egress + ingress: + {{- if ne (int .Values.operator.metricsPort) 0 }} + - ports: + - protocol: TCP + port: {{ .Values.operator.metricsPort | default 8080 }} + {{- end }} + {{- with .Values.networkPolicy.extraIngress }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.operator.networkPolicy.extraIngress }} + {{- toYaml . | nindent 4 }} + {{- end }} + egress: + - ports: + - protocol: TCP + port: 443 + - ports: + - protocol: TCP + port: 6820 + to: + - namespaceSelector: {} + podSelector: + matchLabels: + app.kubernetes.io/name: slurmrestd + - ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + {{- with .Values.networkPolicy.extraEgress }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.operator.networkPolicy.extraEgress }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm/slurm-operator/templates/networkpolicy/webhook-netpol.yaml b/helm/slurm-operator/templates/networkpolicy/webhook-netpol.yaml new file mode 100644 index 00000000..669c5e01 --- /dev/null +++ b/helm/slurm-operator/templates/networkpolicy/webhook-netpol.yaml @@ -0,0 +1,51 @@ +{{- /* +SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +SPDX-License-Identifier: Apache-2.0 +*/}} + +{{- if and .Values.networkPolicy.enabled .Values.webhook.enabled .Values.webhook.networkPolicy.enabled }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "slurm-operator.webhook.name" . }} + namespace: {{ include "slurm-operator.namespace" . }} + labels: + {{- include "slurm-operator.webhook.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "slurm-operator.webhook.selectorLabels" . | nindent 6 }} + policyTypes: + - Ingress + - Egress + ingress: + - ports: + - protocol: TCP + port: {{ .Values.webhook.serverPort | default 9443 }} + {{- if ne (int .Values.webhook.metricsPort) 0 }} + - ports: + - protocol: TCP + port: {{ .Values.webhook.metricsPort }} + {{- end }} + {{- with .Values.networkPolicy.extraIngress }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.webhook.networkPolicy.extraIngress }} + {{- toYaml . | nindent 4 }} + {{- end }} + egress: + - ports: + - protocol: TCP + port: 443 + - ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + {{- with .Values.networkPolicy.extraEgress }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.webhook.networkPolicy.extraEgress }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm/slurm-operator/tests/__snapshot__/networkpolicy_test.yaml.snap b/helm/slurm-operator/tests/__snapshot__/networkpolicy_test.yaml.snap new file mode 100644 index 00000000..589b1584 --- /dev/null +++ b/helm/slurm-operator/tests/__snapshot__/networkpolicy_test.yaml.snap @@ -0,0 +1,78 @@ +operator manifest should match snapshot: + 1: | + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + labels: + app.kubernetes.io/instance: test-release + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: slurm-operator + app.kubernetes.io/part-of: slurm-operator + app.kubernetes.io/version: 1.2.3 + helm.sh/chart: slurm-operator-1.2.3 + name: slurm-operator + namespace: test-namespace + spec: + egress: + - ports: + - port: 443 + protocol: TCP + - ports: + - port: 6820 + protocol: TCP + to: + - namespaceSelector: {} + podSelector: + matchLabels: + app.kubernetes.io/name: slurmrestd + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + ingress: + - ports: + - port: 8080 + protocol: TCP + podSelector: + matchLabels: + app.kubernetes.io/instance: test-release + app.kubernetes.io/name: slurm-operator + policyTypes: + - Ingress + - Egress +webhook manifest should match snapshot: + 1: | + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + labels: + app.kubernetes.io/instance: test-release + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: slurm-operator-webhook + app.kubernetes.io/part-of: slurm-operator + app.kubernetes.io/version: 1.2.3 + helm.sh/chart: slurm-operator-1.2.3 + name: slurm-operator-webhook + namespace: test-namespace + spec: + egress: + - ports: + - port: 443 + protocol: TCP + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + ingress: + - ports: + - port: 9443 + protocol: TCP + podSelector: + matchLabels: + app.kubernetes.io/instance: test-release + app.kubernetes.io/name: slurm-operator-webhook + policyTypes: + - Ingress + - Egress diff --git a/helm/slurm-operator/tests/networkpolicy_test.yaml b/helm/slurm-operator/tests/networkpolicy_test.yaml new file mode 100644 index 00000000..376d73f1 --- /dev/null +++ b/helm/slurm-operator/tests/networkpolicy_test.yaml @@ -0,0 +1,234 @@ +--- +suite: test networkpolicy +templates: + - networkpolicy/operator-netpol.yaml + - networkpolicy/webhook-netpol.yaml +chart: + version: 1.2.3 + appVersion: 1.2.3 +release: + name: test-release + namespace: test-namespace +tests: + - it: should not create any networkpolicy when disabled + set: + networkPolicy: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: operator manifest should match snapshot + template: networkpolicy/operator-netpol.yaml + set: + networkPolicy: + enabled: true + asserts: + - matchSnapshot: {} + + - it: should not create operator networkpolicy when operator.networkPolicy.enabled is false + template: networkpolicy/operator-netpol.yaml + set: + networkPolicy: + enabled: true + operator: + networkPolicy: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: should not create operator networkpolicy when operator is disabled + template: networkpolicy/operator-netpol.yaml + set: + networkPolicy: + enabled: true + operator: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: operator should include metrics ingress when metricsPort is non-zero + template: networkpolicy/operator-netpol.yaml + set: + networkPolicy: + enabled: true + operator: + metricsPort: 8080 + asserts: + - contains: + path: spec.ingress + content: + ports: + - protocol: TCP + port: 8080 + + - it: operator should include slurmrestd egress + template: networkpolicy/operator-netpol.yaml + set: + networkPolicy: + enabled: true + asserts: + - contains: + path: spec.egress + content: + ports: + - protocol: TCP + port: 6820 + to: + - namespaceSelector: {} + podSelector: + matchLabels: + app.kubernetes.io/name: slurmrestd + + - it: operator should not include metrics ingress when metricsPort is zero + template: networkpolicy/operator-netpol.yaml + set: + networkPolicy: + enabled: true + operator: + metricsPort: 0 + asserts: + - isEmpty: + path: spec.ingress + + - it: webhook manifest should match snapshot + template: networkpolicy/webhook-netpol.yaml + set: + networkPolicy: + enabled: true + asserts: + - matchSnapshot: {} + + - it: should not create webhook networkpolicy when webhook.networkPolicy.enabled is false + template: networkpolicy/webhook-netpol.yaml + set: + networkPolicy: + enabled: true + webhook: + networkPolicy: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: should not create webhook networkpolicy when webhook is disabled + template: networkpolicy/webhook-netpol.yaml + set: + networkPolicy: + enabled: true + webhook: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: webhook should use custom serverPort + template: networkpolicy/webhook-netpol.yaml + set: + networkPolicy: + enabled: true + webhook: + serverPort: 8443 + asserts: + - contains: + path: spec.ingress + content: + ports: + - protocol: TCP + port: 8443 + + - it: should append global extraIngress to all networkpolicies + set: + networkPolicy: + enabled: true + extraIngress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: monitoring + ports: + - protocol: TCP + port: 9090 + asserts: + - contains: + path: spec.ingress + content: + from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: monitoring + ports: + - protocol: TCP + port: 9090 + + - it: should append global extraEgress to all networkpolicies + set: + networkPolicy: + enabled: true + extraEgress: + - to: + - ipBlock: + cidr: 10.0.0.0/8 + ports: + - protocol: TCP + port: 443 + asserts: + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 10.0.0.0/8 + ports: + - protocol: TCP + port: 443 + + - it: should append operator-specific extraEgress + template: networkpolicy/operator-netpol.yaml + set: + networkPolicy: + enabled: true + operator: + networkPolicy: + extraEgress: + - to: + - ipBlock: + cidr: 192.168.0.0/16 + ports: + - protocol: TCP + port: 6443 + asserts: + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 192.168.0.0/16 + ports: + - protocol: TCP + port: 6443 + + - it: should append webhook-specific extraIngress + template: networkpolicy/webhook-netpol.yaml + set: + networkPolicy: + enabled: true + webhook: + networkPolicy: + extraIngress: + - from: + - namespaceSelector: {} + ports: + - protocol: TCP + port: 9443 + asserts: + - contains: + path: spec.ingress + content: + from: + - namespaceSelector: {} + ports: + - protocol: TCP + port: 9443 diff --git a/helm/slurm-operator/values.yaml b/helm/slurm-operator/values.yaml index 774a7e8d..f66d85ae 100644 --- a/helm/slurm-operator/values.yaml +++ b/helm/slurm-operator/values.yaml @@ -30,6 +30,14 @@ crds: operator: # -- Enables the operator. enabled: true + # NetworkPolicy settings for this component. + networkPolicy: + # -- Enable NetworkPolicy for the operator. + enabled: true + # -- Extra ingress rules appended to the operator NetworkPolicy. + extraIngress: [] + # -- Extra egress rules appended to the operator NetworkPolicy. + extraEgress: [] # -- Set the number of replicas to deploy. replicas: 1 # -- Set the image pull policy. @@ -99,6 +107,14 @@ operator: webhook: # -- Enable the webhook. enabled: true + # NetworkPolicy settings for this component. + networkPolicy: + # -- Enable NetworkPolicy for the webhook. + enabled: true + # -- Extra ingress rules appended to the webhook NetworkPolicy. + extraIngress: [] + # -- Extra egress rules appended to the webhook NetworkPolicy. + extraEgress: [] # -- Set the number of replicas to deploy. replicas: 1 # -- Set the image pull policy. @@ -166,6 +182,16 @@ certManager: # -- Certificate renewal time. Should be before the expiration. renewBefore: 8760h0m0s # 1 year +# Configure Kubernetes NetworkPolicies for slurm-operator components. +# Ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ +networkPolicy: + # -- Enable NetworkPolicy resources for all slurm-operator components. + enabled: false + # -- Extra ingress rules appended to every NetworkPolicy. + extraIngress: [] + # -- Extra egress rules appended to every NetworkPolicy. + extraEgress: [] + # -- List of Kubernetes Node Conditions, by type, to propagate to the Slurm node drain reason. # Ref: https://kubernetes.io/docs/reference/node/node-status/#condition propagatedNodeConditions: [] diff --git a/helm/slurm/templates/networkpolicy/accounting-netpol.yaml b/helm/slurm/templates/networkpolicy/accounting-netpol.yaml new file mode 100644 index 00000000..7f6ec582 --- /dev/null +++ b/helm/slurm/templates/networkpolicy/accounting-netpol.yaml @@ -0,0 +1,68 @@ +{{- /* +SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +SPDX-License-Identifier: Apache-2.0 +*/}} + +{{- if and .Values.networkPolicy.enabled .Values.accounting.networkPolicy.enabled .Values.accounting.enabled }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "slurm.fullname" . }}-accounting + namespace: {{ include "slurm.namespace" . }} + labels: + {{- include "slurm.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: slurmdbd + app.kubernetes.io/instance: {{ include "slurm.fullname" . }} + policyTypes: + - Ingress + - Egress + ingress: + - ports: + - protocol: TCP + port: 6819 + from: + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmctld + app.kubernetes.io/instance: {{ include "slurm.fullname" . }} + {{- range $key, $loginset := .Values.loginsets }} + {{- if $loginset.enabled }} + - podSelector: + matchLabels: + app.kubernetes.io/name: login + app.kubernetes.io/instance: {{ printf "%s-%s" (include "slurm.login.name" $) $key }} + {{- end }} + {{- end }} + {{- with .Values.networkPolicy.extraIngress }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.accounting.networkPolicy.extraIngress }} + {{- toYaml . | nindent 4 }} + {{- end }} + egress: + - ports: + - protocol: TCP + port: {{ include "slurm.controller.port" . }} + to: + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmctld + app.kubernetes.io/instance: {{ include "slurm.fullname" . }} + - ports: + - protocol: TCP + port: 3306 + - ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + {{- with .Values.networkPolicy.extraEgress }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.accounting.networkPolicy.extraEgress }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm/slurm/templates/networkpolicy/controller-netpol.yaml b/helm/slurm/templates/networkpolicy/controller-netpol.yaml new file mode 100644 index 00000000..60c13c54 --- /dev/null +++ b/helm/slurm/templates/networkpolicy/controller-netpol.yaml @@ -0,0 +1,91 @@ +{{- /* +SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +SPDX-License-Identifier: Apache-2.0 +*/}} + +{{- if and .Values.networkPolicy.enabled .Values.controller.networkPolicy.enabled }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "slurm.fullname" . }}-controller + namespace: {{ include "slurm.namespace" . }} + labels: + {{- include "slurm.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: slurmctld + app.kubernetes.io/instance: {{ include "slurm.fullname" . }} + policyTypes: + - Ingress + - Egress + ingress: + - ports: + - protocol: TCP + port: {{ include "slurm.controller.port" . }} + from: + {{- range $key, $nodeset := .Values.nodesets }} + {{- if $nodeset.enabled }} + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmd + app.kubernetes.io/instance: {{ printf "%s-%s" (include "slurm.worker.name" $) $key }} + {{- end }} + {{- end }} + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmdbd + app.kubernetes.io/instance: {{ include "slurm.fullname" . }} + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmrestd + app.kubernetes.io/instance: {{ include "slurm.fullname" . }} + {{- range $key, $loginset := .Values.loginsets }} + {{- if $loginset.enabled }} + - podSelector: + matchLabels: + app.kubernetes.io/name: login + app.kubernetes.io/instance: {{ printf "%s-%s" (include "slurm.login.name" $) $key }} + {{- end }} + {{- end }} + {{- with .Values.networkPolicy.extraIngress }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.controller.networkPolicy.extraIngress }} + {{- toYaml . | nindent 4 }} + {{- end }} + egress: + - ports: + - protocol: TCP + port: {{ include "slurm.worker.port" . }} + to: + {{- range $key, $nodeset := .Values.nodesets }} + {{- if $nodeset.enabled }} + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmd + app.kubernetes.io/instance: {{ printf "%s-%s" (include "slurm.worker.name" $) $key }} + {{- end }} + {{- end }} + {{- if .Values.accounting.enabled }} + - ports: + - protocol: TCP + port: 6819 + to: + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmdbd + app.kubernetes.io/instance: {{ include "slurm.fullname" . }} + {{- end }} + - ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + {{- with .Values.networkPolicy.extraEgress }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.controller.networkPolicy.extraEgress }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm/slurm/templates/networkpolicy/loginset-netpol.yaml b/helm/slurm/templates/networkpolicy/loginset-netpol.yaml new file mode 100644 index 00000000..7126209b --- /dev/null +++ b/helm/slurm/templates/networkpolicy/loginset-netpol.yaml @@ -0,0 +1,75 @@ +{{- /* +SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +SPDX-License-Identifier: Apache-2.0 +*/}} + +{{- range $key, $loginset := $.Values.loginsets }} +{{- if and $.Values.networkPolicy.enabled $loginset.enabled (dig "networkPolicy" "enabled" true $loginset) }} +{{- $name := printf "%s-%s" (include "slurm.login.name" $) $key }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ $name }} + namespace: {{ include "slurm.namespace" $ }} + labels: + {{- include "slurm.labels" $ | nindent 4 }} +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: login + app.kubernetes.io/instance: {{ $name }} + policyTypes: + - Ingress + - Egress + ingress: + - ports: + - protocol: TCP + port: 22 + {{- with $.Values.networkPolicy.extraIngress }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with (dig "networkPolicy" "extraIngress" nil $loginset) }} + {{- toYaml . | nindent 4 }} + {{- end }} + egress: + - ports: + - protocol: TCP + port: {{ include "slurm.controller.port" $ }} + to: + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmctld + app.kubernetes.io/instance: {{ include "slurm.fullname" $ }} + {{- if $.Values.accounting.enabled }} + - ports: + - protocol: TCP + port: 6819 + to: + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmdbd + app.kubernetes.io/instance: {{ include "slurm.fullname" $ }} + {{- end }} + - to: + {{- range $nk, $ns := $.Values.nodesets }} + {{- if $ns.enabled }} + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmd + app.kubernetes.io/instance: {{ printf "%s-%s" (include "slurm.worker.name" $) $nk }} + {{- end }} + {{- end }} + - ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + {{- with $.Values.networkPolicy.extraEgress }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with (dig "networkPolicy" "extraEgress" nil $loginset) }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/helm/slurm/templates/networkpolicy/nodeset-netpol.yaml b/helm/slurm/templates/networkpolicy/nodeset-netpol.yaml new file mode 100644 index 00000000..d192d6dd --- /dev/null +++ b/helm/slurm/templates/networkpolicy/nodeset-netpol.yaml @@ -0,0 +1,88 @@ +{{- /* +SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +SPDX-License-Identifier: Apache-2.0 +*/}} + +{{- range $key, $nodeset := $.Values.nodesets }} +{{- if and $.Values.networkPolicy.enabled $nodeset.enabled (dig "networkPolicy" "enabled" true $nodeset) }} +{{- $name := printf "%s-%s" (include "slurm.worker.name" $) $key }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ $name }} + namespace: {{ include "slurm.namespace" $ }} + labels: + {{- include "slurm.labels" $ | nindent 4 }} +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: slurmd + app.kubernetes.io/instance: {{ $name }} + policyTypes: + - Ingress + - Egress + ingress: + - ports: + - protocol: TCP + port: {{ include "slurm.worker.port" $ }} + from: + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmctld + app.kubernetes.io/instance: {{ include "slurm.fullname" $ }} + - from: + {{- range $nk, $ns := $.Values.nodesets }} + {{- if $ns.enabled }} + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmd + app.kubernetes.io/instance: {{ printf "%s-%s" (include "slurm.worker.name" $) $nk }} + {{- end }} + {{- end }} + - from: + {{- range $lk, $ls := $.Values.loginsets }} + {{- if $ls.enabled }} + - podSelector: + matchLabels: + app.kubernetes.io/name: login + app.kubernetes.io/instance: {{ printf "%s-%s" (include "slurm.login.name" $) $lk }} + {{- end }} + {{- end }} + {{- with $.Values.networkPolicy.extraIngress }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with (dig "networkPolicy" "extraIngress" nil $nodeset) }} + {{- toYaml . | nindent 4 }} + {{- end }} + egress: + - ports: + - protocol: TCP + port: {{ include "slurm.controller.port" $ }} + to: + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmctld + app.kubernetes.io/instance: {{ include "slurm.fullname" $ }} + - to: + {{- range $nk, $ns := $.Values.nodesets }} + {{- if $ns.enabled }} + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmd + app.kubernetes.io/instance: {{ printf "%s-%s" (include "slurm.worker.name" $) $nk }} + {{- end }} + {{- end }} + - ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + {{- with $.Values.networkPolicy.extraEgress }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with (dig "networkPolicy" "extraEgress" nil $nodeset) }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/helm/slurm/templates/networkpolicy/restapi-netpol.yaml b/helm/slurm/templates/networkpolicy/restapi-netpol.yaml new file mode 100644 index 00000000..2da51495 --- /dev/null +++ b/helm/slurm/templates/networkpolicy/restapi-netpol.yaml @@ -0,0 +1,52 @@ +{{- /* +SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +SPDX-License-Identifier: Apache-2.0 +*/}} + +{{- if and .Values.networkPolicy.enabled .Values.restapi.networkPolicy.enabled }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "slurm.fullname" . }}-restapi + namespace: {{ include "slurm.namespace" . }} + labels: + {{- include "slurm.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: slurmrestd + app.kubernetes.io/instance: {{ include "slurm.fullname" . }} + policyTypes: + - Ingress + - Egress + ingress: + - ports: + - protocol: TCP + port: {{ include "slurm.restapi.port" . }} + {{- with .Values.networkPolicy.extraIngress }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.restapi.networkPolicy.extraIngress }} + {{- toYaml . | nindent 4 }} + {{- end }} + egress: + - ports: + - protocol: TCP + port: {{ include "slurm.controller.port" . }} + to: + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmctld + app.kubernetes.io/instance: {{ include "slurm.fullname" . }} + - ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + {{- with .Values.networkPolicy.extraEgress }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.restapi.networkPolicy.extraEgress }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm/slurm/tests/__snapshot__/networkpolicy_test.yaml.snap b/helm/slurm/tests/__snapshot__/networkpolicy_test.yaml.snap new file mode 100644 index 00000000..9e6fd8fb --- /dev/null +++ b/helm/slurm/tests/__snapshot__/networkpolicy_test.yaml.snap @@ -0,0 +1,232 @@ +accounting manifest should match snapshot: + 1: | + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + labels: + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/part-of: slurm + app.kubernetes.io/version: 1.2.3 + helm.sh/chart: slurm-1.2.3 + name: test-release-slurm-accounting + namespace: test-namespace + spec: + egress: + - ports: + - port: 6817 + protocol: TCP + to: + - podSelector: + matchLabels: + app.kubernetes.io/instance: test-release-slurm + app.kubernetes.io/name: slurmctld + - ports: + - port: 3306 + protocol: TCP + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/instance: test-release-slurm + app.kubernetes.io/name: slurmctld + ports: + - port: 6819 + protocol: TCP + podSelector: + matchLabels: + app.kubernetes.io/instance: test-release-slurm + app.kubernetes.io/name: slurmdbd + policyTypes: + - Ingress + - Egress +controller manifest should match snapshot: + 1: | + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + labels: + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/part-of: slurm + app.kubernetes.io/version: 1.2.3 + helm.sh/chart: slurm-1.2.3 + name: test-release-slurm-controller + namespace: test-namespace + spec: + egress: + - ports: + - port: 6818 + protocol: TCP + to: + - podSelector: + matchLabels: + app.kubernetes.io/instance: test-release-slurm-worker-slinky + app.kubernetes.io/name: slurmd + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/instance: test-release-slurm-worker-slinky + app.kubernetes.io/name: slurmd + - podSelector: + matchLabels: + app.kubernetes.io/instance: test-release-slurm + app.kubernetes.io/name: slurmdbd + - podSelector: + matchLabels: + app.kubernetes.io/instance: test-release-slurm + app.kubernetes.io/name: slurmrestd + ports: + - port: 6817 + protocol: TCP + podSelector: + matchLabels: + app.kubernetes.io/instance: test-release-slurm + app.kubernetes.io/name: slurmctld + policyTypes: + - Ingress + - Egress +loginset manifest should match snapshot: + 1: | + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + labels: + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/part-of: slurm + app.kubernetes.io/version: 1.2.3 + helm.sh/chart: slurm-1.2.3 + name: test-release-slurm-login-slinky + namespace: test-namespace + spec: + egress: + - ports: + - port: 6817 + protocol: TCP + to: + - podSelector: + matchLabels: + app.kubernetes.io/instance: test-release-slurm + app.kubernetes.io/name: slurmctld + - to: + - podSelector: + matchLabels: + app.kubernetes.io/instance: test-release-slurm-worker-slinky + app.kubernetes.io/name: slurmd + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + ingress: + - ports: + - port: 22 + protocol: TCP + podSelector: + matchLabels: + app.kubernetes.io/instance: test-release-slurm-login-slinky + app.kubernetes.io/name: login + policyTypes: + - Ingress + - Egress +nodeset manifest should match snapshot: + 1: | + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + labels: + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/part-of: slurm + app.kubernetes.io/version: 1.2.3 + helm.sh/chart: slurm-1.2.3 + name: test-release-slurm-worker-slinky + namespace: test-namespace + spec: + egress: + - ports: + - port: 6817 + protocol: TCP + to: + - podSelector: + matchLabels: + app.kubernetes.io/instance: test-release-slurm + app.kubernetes.io/name: slurmctld + - to: + - podSelector: + matchLabels: + app.kubernetes.io/instance: test-release-slurm-worker-slinky + app.kubernetes.io/name: slurmd + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/instance: test-release-slurm + app.kubernetes.io/name: slurmctld + ports: + - port: 6818 + protocol: TCP + - from: + - podSelector: + matchLabels: + app.kubernetes.io/instance: test-release-slurm-worker-slinky + app.kubernetes.io/name: slurmd + - from: null + podSelector: + matchLabels: + app.kubernetes.io/instance: test-release-slurm-worker-slinky + app.kubernetes.io/name: slurmd + policyTypes: + - Ingress + - Egress +restapi manifest should match snapshot: + 1: | + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + labels: + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/part-of: slurm + app.kubernetes.io/version: 1.2.3 + helm.sh/chart: slurm-1.2.3 + name: test-release-slurm-restapi + namespace: test-namespace + spec: + egress: + - ports: + - port: 6817 + protocol: TCP + to: + - podSelector: + matchLabels: + app.kubernetes.io/instance: test-release-slurm + app.kubernetes.io/name: slurmctld + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + ingress: + - ports: + - port: 6820 + protocol: TCP + podSelector: + matchLabels: + app.kubernetes.io/instance: test-release-slurm + app.kubernetes.io/name: slurmrestd + policyTypes: + - Ingress + - Egress diff --git a/helm/slurm/tests/networkpolicy_test.yaml b/helm/slurm/tests/networkpolicy_test.yaml new file mode 100644 index 00000000..8a8c3a91 --- /dev/null +++ b/helm/slurm/tests/networkpolicy_test.yaml @@ -0,0 +1,627 @@ +--- +suite: test networkpolicy +templates: + - networkpolicy/controller-netpol.yaml + - networkpolicy/nodeset-netpol.yaml + - networkpolicy/accounting-netpol.yaml + - networkpolicy/restapi-netpol.yaml + - networkpolicy/loginset-netpol.yaml +release: + name: test-release + namespace: test-namespace +chart: + version: 1.2.3 + appVersion: 1.2.3 +tests: + - it: should not create any networkpolicy when disabled + set: + networkPolicy: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: controller manifest should match snapshot + template: networkpolicy/controller-netpol.yaml + set: + networkPolicy: + enabled: true + asserts: + - matchSnapshot: {} + + - it: should not create controller networkpolicy when controller.networkPolicy.enabled is false + template: networkpolicy/controller-netpol.yaml + set: + networkPolicy: + enabled: true + controller: + networkPolicy: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: nodeset manifest should match snapshot + template: networkpolicy/nodeset-netpol.yaml + set: + networkPolicy: + enabled: true + asserts: + - matchSnapshot: {} + + - it: should not create nodeset networkpolicy when per-instance networkPolicy.enabled is false + template: networkpolicy/nodeset-netpol.yaml + set: + networkPolicy: + enabled: true + nodesets: + slinky: + enabled: true + networkPolicy: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: nodeset should allow all TCP ingress from login pods + template: networkpolicy/nodeset-netpol.yaml + set: + networkPolicy: + enabled: true + loginsets: + slinky: + enabled: true + asserts: + - contains: + path: spec.ingress + content: + from: + - podSelector: + matchLabels: + app.kubernetes.io/name: login + app.kubernetes.io/instance: test-release-slurm-login-slinky + + - it: nodeset should allow all TCP ingress from slurmd pods + template: networkpolicy/nodeset-netpol.yaml + set: + networkPolicy: + enabled: true + asserts: + - contains: + path: spec.ingress + content: + from: + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmd + app.kubernetes.io/instance: test-release-slurm-worker-slinky + + - it: nodeset should allow all TCP egress to slurmd pods + template: networkpolicy/nodeset-netpol.yaml + set: + networkPolicy: + enabled: true + asserts: + - contains: + path: spec.egress + content: + to: + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmd + app.kubernetes.io/instance: test-release-slurm-worker-slinky + + - it: nodeset podSelector should use CR name as instance + template: networkpolicy/nodeset-netpol.yaml + set: + networkPolicy: + enabled: true + asserts: + - equal: + path: spec.podSelector.matchLabels + value: + app.kubernetes.io/name: slurmd + app.kubernetes.io/instance: test-release-slurm-worker-slinky + + - it: nodeset should create one networkpolicy per enabled instance + template: networkpolicy/nodeset-netpol.yaml + set: + networkPolicy: + enabled: true + nodesets: + gpu: + enabled: true + cpu: + enabled: true + disabled: + enabled: false + asserts: + - hasDocuments: + count: 3 + + - it: loginset manifest should match snapshot + template: networkpolicy/loginset-netpol.yaml + set: + networkPolicy: + enabled: true + loginsets: + slinky: + enabled: true + asserts: + - matchSnapshot: {} + + - it: should not create loginset networkpolicy when per-instance networkPolicy.enabled is false + template: networkpolicy/loginset-netpol.yaml + set: + networkPolicy: + enabled: true + loginsets: + slinky: + enabled: true + networkPolicy: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: loginset podSelector should use CR name as instance + template: networkpolicy/loginset-netpol.yaml + set: + networkPolicy: + enabled: true + loginsets: + slinky: + enabled: true + asserts: + - equal: + path: spec.podSelector.matchLabels + value: + app.kubernetes.io/name: login + app.kubernetes.io/instance: test-release-slurm-login-slinky + + - it: loginset should create one networkpolicy per enabled instance + template: networkpolicy/loginset-netpol.yaml + set: + networkPolicy: + enabled: true + loginsets: + alpha: + enabled: true + beta: + enabled: true + gamma: + enabled: false + asserts: + - hasDocuments: + count: 2 + + - it: loginset should allow all TCP egress to nodeset + template: networkpolicy/loginset-netpol.yaml + set: + networkPolicy: + enabled: true + loginsets: + slinky: + enabled: true + asserts: + - contains: + path: spec.egress + content: + to: + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmd + app.kubernetes.io/instance: test-release-slurm-worker-slinky + + - it: loginset should include accounting egress when accounting is enabled + template: networkpolicy/loginset-netpol.yaml + set: + networkPolicy: + enabled: true + accounting: + enabled: true + loginsets: + slinky: + enabled: true + asserts: + - contains: + path: spec.egress + content: + ports: + - protocol: TCP + port: 6819 + to: + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmdbd + app.kubernetes.io/instance: test-release-slurm + + - it: loginset should not include accounting egress when accounting is disabled + template: networkpolicy/loginset-netpol.yaml + set: + networkPolicy: + enabled: true + accounting: + enabled: false + loginsets: + slinky: + enabled: true + asserts: + - notContains: + path: spec.egress + content: + ports: + - protocol: TCP + port: 6819 + to: + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmdbd + app.kubernetes.io/instance: test-release-slurm + + - it: accounting manifest should match snapshot + template: networkpolicy/accounting-netpol.yaml + set: + networkPolicy: + enabled: true + accounting: + enabled: true + asserts: + - matchSnapshot: {} + + - it: should not create accounting networkpolicy when accounting is disabled + template: networkpolicy/accounting-netpol.yaml + set: + networkPolicy: + enabled: true + accounting: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: should not create accounting networkpolicy when accounting.networkPolicy.enabled is false + template: networkpolicy/accounting-netpol.yaml + set: + networkPolicy: + enabled: true + accounting: + enabled: true + networkPolicy: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: restapi manifest should match snapshot + template: networkpolicy/restapi-netpol.yaml + set: + networkPolicy: + enabled: true + asserts: + - matchSnapshot: {} + + - it: should not create restapi networkpolicy when restapi.networkPolicy.enabled is false + template: networkpolicy/restapi-netpol.yaml + set: + networkPolicy: + enabled: true + restapi: + networkPolicy: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: controller should include accounting egress when accounting is enabled + template: networkpolicy/controller-netpol.yaml + set: + networkPolicy: + enabled: true + accounting: + enabled: true + asserts: + - contains: + path: spec.egress + content: + ports: + - protocol: TCP + port: 6819 + to: + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmdbd + app.kubernetes.io/instance: test-release-slurm + + - it: controller should not include accounting egress when accounting is disabled + template: networkpolicy/controller-netpol.yaml + set: + networkPolicy: + enabled: true + accounting: + enabled: false + asserts: + - notContains: + path: spec.egress + content: + ports: + - protocol: TCP + port: 6819 + to: + - podSelector: + matchLabels: + app.kubernetes.io/name: slurmdbd + app.kubernetes.io/instance: test-release-slurm + + - it: should append global extraIngress to all networkpolicies + set: + networkPolicy: + enabled: true + extraIngress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: monitoring + ports: + - protocol: TCP + port: 9090 + accounting: + enabled: true + loginsets: + slinky: + enabled: true + asserts: + - contains: + path: spec.ingress + content: + from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: monitoring + ports: + - protocol: TCP + port: 9090 + + - it: should append global extraEgress to all networkpolicies + set: + networkPolicy: + enabled: true + extraEgress: + - to: + - ipBlock: + cidr: 10.0.0.0/8 + ports: + - protocol: TCP + port: 443 + accounting: + enabled: true + loginsets: + slinky: + enabled: true + asserts: + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 10.0.0.0/8 + ports: + - protocol: TCP + port: 443 + + - it: should append controller-specific extraIngress + template: networkpolicy/controller-netpol.yaml + set: + networkPolicy: + enabled: true + controller: + networkPolicy: + extraIngress: + - from: + - podSelector: + matchLabels: + app: custom-client + ports: + - protocol: TCP + port: 8080 + asserts: + - contains: + path: spec.ingress + content: + from: + - podSelector: + matchLabels: + app: custom-client + ports: + - protocol: TCP + port: 8080 + + - it: should append controller-specific extraEgress + template: networkpolicy/controller-netpol.yaml + set: + networkPolicy: + enabled: true + controller: + networkPolicy: + extraEgress: + - to: + - ipBlock: + cidr: 192.168.0.0/16 + ports: + - protocol: TCP + port: 8443 + asserts: + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 192.168.0.0/16 + ports: + - protocol: TCP + port: 8443 + + - it: should append restapi-specific extraIngress + template: networkpolicy/restapi-netpol.yaml + set: + networkPolicy: + enabled: true + restapi: + networkPolicy: + extraIngress: + - from: + - namespaceSelector: + matchLabels: + name: ingress + ports: + - protocol: TCP + port: 6820 + asserts: + - contains: + path: spec.ingress + content: + from: + - namespaceSelector: + matchLabels: + name: ingress + ports: + - protocol: TCP + port: 6820 + + - it: should append accounting-specific extraEgress + template: networkpolicy/accounting-netpol.yaml + set: + networkPolicy: + enabled: true + accounting: + enabled: true + networkPolicy: + extraEgress: + - to: + - ipBlock: + cidr: 10.1.0.0/16 + ports: + - protocol: TCP + port: 5432 + asserts: + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 10.1.0.0/16 + ports: + - protocol: TCP + port: 5432 + + - it: should append nodeset-specific extraIngress + template: networkpolicy/nodeset-netpol.yaml + set: + networkPolicy: + enabled: true + nodesets: + slinky: + enabled: true + networkPolicy: + extraIngress: + - from: + - podSelector: + matchLabels: + app: gpu-monitor + ports: + - protocol: TCP + port: 9100 + asserts: + - contains: + path: spec.ingress + content: + from: + - podSelector: + matchLabels: + app: gpu-monitor + ports: + - protocol: TCP + port: 9100 + + - it: should append nodeset-specific extraEgress + template: networkpolicy/nodeset-netpol.yaml + set: + networkPolicy: + enabled: true + nodesets: + slinky: + enabled: true + networkPolicy: + extraEgress: + - to: + - ipBlock: + cidr: 10.2.0.0/16 + ports: + - protocol: TCP + port: 2049 + asserts: + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 10.2.0.0/16 + ports: + - protocol: TCP + port: 2049 + + - it: should append loginset-specific extraIngress + template: networkpolicy/loginset-netpol.yaml + set: + networkPolicy: + enabled: true + loginsets: + slinky: + enabled: true + networkPolicy: + extraIngress: + - from: + - namespaceSelector: + matchLabels: + name: bastion + ports: + - protocol: TCP + port: 22 + asserts: + - contains: + path: spec.ingress + content: + from: + - namespaceSelector: + matchLabels: + name: bastion + ports: + - protocol: TCP + port: 22 + + - it: should append loginset-specific extraEgress + template: networkpolicy/loginset-netpol.yaml + set: + networkPolicy: + enabled: true + loginsets: + slinky: + enabled: true + networkPolicy: + extraEgress: + - to: + - ipBlock: + cidr: 10.3.0.0/16 + ports: + - protocol: TCP + port: 636 + asserts: + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 10.3.0.0/16 + ports: + - protocol: TCP + port: 636 diff --git a/helm/slurm/values.yaml b/helm/slurm/values.yaml index bd3dcfaf..f75f9bf2 100644 --- a/helm/slurm/values.yaml +++ b/helm/slurm/values.yaml @@ -160,6 +160,14 @@ epilogScripts: {} # Slurm controller (slurmctld) configuration. controller: + # NetworkPolicy settings for this component. + networkPolicy: + # -- Enable NetworkPolicy for the Controller. + enabled: true + # -- Extra ingress rules appended to the Controller NetworkPolicy. + extraIngress: [] + # -- Extra egress rules appended to the Controller NetworkPolicy. + extraEgress: [] # -- Configures this component as external (not in Kubernetes). external: false # Details required to communicate with an external slurmdbd. @@ -322,6 +330,14 @@ controller: # Slurm REST API (slurmrestd) configuration. restapi: + # NetworkPolicy settings for this component. + networkPolicy: + # -- Enable NetworkPolicy for the REST API. + enabled: true + # -- Extra ingress rules appended to the REST API NetworkPolicy. + extraIngress: [] + # -- Extra egress rules appended to the REST API NetworkPolicy. + extraEgress: [] # -- Number of replicas to deploy. replicas: 1 # slurmrestd container configurations. @@ -394,6 +410,14 @@ restapi: # Slurm accounting (slurmdbd) configuration. accounting: + # NetworkPolicy settings for this component. + networkPolicy: + # -- Enable NetworkPolicy for Accounting. + enabled: true + # -- Extra ingress rules appended to the Accounting NetworkPolicy. + extraIngress: [] + # -- Extra egress rules appended to the Accounting NetworkPolicy. + extraEgress: [] # -- Enables Slurm accounting subsystem, stores job/step historical records. # Ref: https://slurm.schedmd.com/accounting.html#Overview enabled: false @@ -535,6 +559,14 @@ loginsets: slinky: # -- Enable use of this LoginSet. enabled: false + # NetworkPolicy settings for this LoginSet. + networkPolicy: + # -- Enable NetworkPolicy for this LoginSet. + enabled: true + # -- Extra ingress rules appended to this LoginSet NetworkPolicy. + extraIngress: [] + # -- Extra egress rules appended to this LoginSet NetworkPolicy. + extraEgress: [] # -- Number of replicas to deploy. replicas: 1 # -- Deployment strategy configuration. @@ -649,6 +681,14 @@ nodesets: slinky: # -- Enable use of this NodeSet. enabled: true + # NetworkPolicy settings for this NodeSet. + networkPolicy: + # -- Enable NetworkPolicy for this NodeSet. + enabled: true + # -- Extra ingress rules appended to this NodeSet NetworkPolicy. + extraIngress: [] + # -- Extra egress rules appended to this NodeSet NetworkPolicy. + extraEgress: [] # -- Scaling mode: "StatefulSet" (fixed replica count) or "DaemonSet" (one pod per matching node). scalingMode: StatefulSet # -- Number of replicas to deploy. Ignored when scalingMode is daemonset. @@ -816,6 +856,18 @@ vendor: # -- Script execution priority (lower numbers run first) scriptPriority: "90" +# Configure Kubernetes NetworkPolicies for Slurm components. +# Ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ +networkPolicy: + # -- Enable NetworkPolicy resources for all Slurm components. + enabled: false + # -- Extra ingress rules appended to every NetworkPolicy. + # Ref: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#networkpolicyingressrule-v1-networking-k8s-io + extraIngress: [] + # -- Extra egress rules appended to every NetworkPolicy. + # Ref: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#networkpolicyegressrule-v1-networking-k8s-io + extraEgress: [] + # -- Extra Kubernetes objects to deploy alongside the chart. # Each entry is rendered as a standalone Kubernetes object. # Supports Helm templating (e.g. {{ .Release.Namespace }}).