{{- /* Three kinds of Service, and the split is forced rather than stylistic. 1. ONE NodePort Service PER PUBLIC PORT, not per node group. A NodePort is unique across the whole cluster, so "full" and "mx" can only accept SMTP behind the SAME Service object - two Services asking for 30025 is a conflict. Hence the selector matches a per-port LABEL on the pods instead of a node group, which lets one Service cover every group that lists the port and still exclude the ones that do not. Putting all the ports into one Service does not work either: a Service has exactly one selector, so 30993 would then be routed to "mx" pods as well, where nothing is listening. 2. ONE ClusterIP http Service PER NODE GROUP. The Ingress must reach exactly one group (ingress.nodeGroup): the management surface is not safe to spread across a group that may not run the management tasks, and a port-label selector would send each request to a random group. So http gets a per-group Service and the Ingress names one of them. 3. ONE headless Service PER NODE GROUP, for the StatefulSet's stable pod DNS. */ -}} {{- $root := . -}} {{- $ports := fromYaml (include "stalwart.ports" .) -}} {{- $nodes := fromYaml (include "stalwart.nodes" .) -}} {{- $release := include "stalwart.fullname" . -}} {{- /* 1. Per-port NodePort Services. */ -}} {{- range $portName, $port := $ports }} {{- if $port.public }} {{- $groups := list }} {{- range $group, $spec := $nodes }} {{- if has $portName $spec.exposedPorts }}{{- $groups = append $groups $group }}{{- end }} {{- end }} {{- if $groups }} --- apiVersion: v1 kind: Service metadata: name: {{ $release }}-{{ $portName }}-service labels: app: {{ $release }} {{- with $root.Values.service.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} spec: type: NodePort # Cluster, never Local. No Stalwart pod is ever scheduled on a control-plane # node, and the HA proxy delivers to the control-plane nodes - under Local # every packet would be dropped, not some. The cost is that kube-proxy # masquerades the source address; the PROXY v2 header the HA proxy prepends # is what makes that harmless. values.yaml has the full reasoning and the # list of what a lost source address breaks on a mail server. externalTrafficPolicy: {{ $root.Values.service.externalTrafficPolicy }} selector: # Scoped to this release, then to the port. Node groups serving this port: # {{ $groups | sortAlpha | join ", " }} stalwart.jk-dev.de/release: {{ $release }} {{ include "stalwart.portLabel" $portName }}: "true" ports: - name: {{ $portName }} protocol: {{ $port.protocol | default "TCP" }} # port and nodePort are the published number; targetPort is the number # the application binds inside the pod. They differ here on purpose. port: {{ int $port.nodePort }} nodePort: {{ int $port.nodePort }} targetPort: {{ $portName }} {{- end }} {{- end }} {{- end }} {{- /* 2. + 3. Per-group http ClusterIP and headless Services. */ -}} {{- range $group, $spec := $nodes }} {{- $name := printf "%s-%s" $release $group }} {{- if has "http" $spec.exposedPorts }} {{- $http := index $ports "http" }} --- apiVersion: v1 kind: Service metadata: name: {{ $name }}-http-service labels: app: {{ $name }} {{- with $root.Values.service.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} spec: type: ClusterIP selector: app: {{ $name }}-sfs ports: - name: http protocol: {{ $http.protocol | default "TCP" }} port: {{ int $http.containerPort }} targetPort: http {{- end }} --- apiVersion: v1 kind: Service metadata: name: {{ $name }}-headless labels: app: {{ $name }} spec: # Headless: this exists for the StatefulSet's stable per-pod DNS names # (.-stalwart--headless), not for load balancing. clusterIP: None # So the DNS names resolve before a pod is Ready - a cluster member has to be # findable by its peers while it is still starting up. publishNotReadyAddresses: true selector: app: {{ $name }}-sfs ports: {{- range $portName := $spec.exposedPorts }} {{- $port := index $ports $portName }} - name: {{ $portName }} protocol: {{ $port.protocol | default "TCP" }} port: {{ int $port.containerPort }} targetPort: {{ $portName }} {{- end }} {{- end }}