feat(helm): add support for nodePort specification in LoadBalancer services helm chart (#16032)

## Short description: 
This pull request introduces support for optionally specify `nodePort`
values when using `LoadBalancer` service type in the Coder Helm chart.
This enhancement addresses a limitation where `httpNodePort` and
`httpsNodePort` values were previously ignored for `LoadBalancer`
services. This PR should expand the service customization options
without disrupting existing configurations.

## Why this is Useful
In some enterprise environments, applications may be required to use
specific ports for compliance with organizational policies or cloud
infrastructure requirements. For instance:

- Reserved port blocks are allocated for specific applications for
security and clarity.
- Ensuring predictable port assignments helps in debugging and
management scenarios.

Since LoadBalancer in Kubernetes operates on top of nodePort, this
feature is useful for enabling enterprises to adhere to such policies if
they whish.

## What Was Changed
- Updated helm/coder/templates/service.yaml:
- Allowed nodePort specification for both NodePort and LoadBalancer
service types.
- Updated helm/coder/templates/values.yaml:
- Updated inline comments to reflect the changes for nodeport values use
cases.
  
### Regarding backward compatibility:
If nodePort is not specified, Kubernetes dynamically assigns a port,
maintaining the current behavior.
### Testing Performed
- Validated through Helm dry-run: nodePort values are rendered correctly
in the resulting Kubernetes YAML.
- Deployed the updated chart in an enterprise Kubernetes cluster.
- Tested coder environment with LoadBalancer service and specified
nodePort values for both HTTP and HTTPS.

## Additional Notes
- This PR expands the nodeport functionality introduced in PR #8993 to
the Loadbalancer service.
- If merged, an update to the documentation to include examples of
LoadBalancer with nodePort values may be useful.
- I've read the contributing guidelines and code of conduct. This is my
first PR for the Coder project, and I hope it meets the community
standards. Any advice, feedback, or help is greatly appreciated!
This commit is contained in:
MRColor
2025-01-20 14:14:14 +01:00
committed by GitHub
parent 2913fe8677
commit d8fbbcbd36
25 changed files with 427 additions and 26 deletions

View File

@ -16,17 +16,17 @@ spec:
port: 80
targetPort: "http"
protocol: TCP
{{ if eq .Values.coder.service.type "NodePort" }}
{{- if or (eq .Values.coder.service.type "NodePort") (eq .Values.coder.service.type "LoadBalancer") }}
nodePort: {{ .Values.coder.service.httpNodePort }}
{{ end }}
{{- end }}
{{- if eq (include "coder.tlsEnabled" .) "true" }}
- name: "https"
port: 443
targetPort: "https"
protocol: TCP
{{ if eq .Values.coder.service.type "NodePort" }}
{{- if or (eq .Values.coder.service.type "NodePort") (eq .Values.coder.service.type "LoadBalancer") }}
nodePort: {{ .Values.coder.service.httpsNodePort }}
{{ end }}
{{- end }}
{{- end }}
{{- if eq "LoadBalancer" .Values.coder.service.type }}
{{- with .Values.coder.service.loadBalancerIP }}

View File

@ -100,6 +100,14 @@ var testCases = []testCase{
name: "svc_loadbalancer_class",
expectedError: "",
},
{
name: "svc_nodeport",
expectedError: "",
},
{
name: "svc_loadbalancer",
expectedError: "",
},
}
type testCase struct {

View File

@ -90,7 +90,7 @@ spec:
port: 80
targetPort: "http"
protocol: TCP
nodePort:
externalTrafficPolicy: "Cluster"
selector:
app.kubernetes.io/name: coder

View File

@ -90,7 +90,7 @@ spec:
port: 80
targetPort: "http"
protocol: TCP
nodePort:
externalTrafficPolicy: "Cluster"
selector:
app.kubernetes.io/name: coder

View File

@ -90,7 +90,7 @@ spec:
port: 80
targetPort: "http"
protocol: TCP
nodePort:
externalTrafficPolicy: "Cluster"
selector:
app.kubernetes.io/name: coder

View File

@ -90,7 +90,7 @@ spec:
port: 80
targetPort: "http"
protocol: TCP
nodePort:
externalTrafficPolicy: "Cluster"
selector:
app.kubernetes.io/name: coder

View File

@ -90,7 +90,7 @@ spec:
port: 80
targetPort: "http"
protocol: TCP
nodePort:
externalTrafficPolicy: "Cluster"
selector:
app.kubernetes.io/name: coder

View File

@ -90,7 +90,7 @@ spec:
port: 80
targetPort: "http"
protocol: TCP
nodePort:
externalTrafficPolicy: "Cluster"
selector:
app.kubernetes.io/name: coder

View File

@ -90,7 +90,7 @@ spec:
port: 80
targetPort: "http"
protocol: TCP
nodePort:
externalTrafficPolicy: "Cluster"
selector:
app.kubernetes.io/name: coder

View File

@ -99,7 +99,7 @@ spec:
port: 80
targetPort: "http"
protocol: TCP
nodePort:
externalTrafficPolicy: "Cluster"
selector:
app.kubernetes.io/name: coder

View File

@ -90,7 +90,7 @@ spec:
port: 80
targetPort: "http"
protocol: TCP
nodePort:
externalTrafficPolicy: "Cluster"
selector:
app.kubernetes.io/name: coder

View File

@ -90,9 +90,7 @@ spec:
port: 80
targetPort: "http"
protocol: TCP
nodePort:
selector:
app.kubernetes.io/name: coder
app.kubernetes.io/instance: release-name

View File

@ -90,7 +90,7 @@ spec:
port: 80
targetPort: "http"
protocol: TCP
nodePort:
externalTrafficPolicy: "Cluster"
selector:
app.kubernetes.io/name: coder

View File

@ -91,7 +91,7 @@ spec:
port: 80
targetPort: "http"
protocol: TCP
nodePort:
externalTrafficPolicy: "Cluster"
selector:
app.kubernetes.io/name: coder

View File

@ -76,7 +76,7 @@ spec:
port: 80
targetPort: "http"
protocol: TCP
nodePort:
externalTrafficPolicy: "Cluster"
selector:
app.kubernetes.io/name: coder

View File

@ -104,7 +104,7 @@ spec:
port: 80
targetPort: "http"
protocol: TCP
nodePort:
externalTrafficPolicy: "Cluster"
selector:
app.kubernetes.io/name: coder

View File

@ -0,0 +1,190 @@
---
# Source: coder/templates/coder.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
annotations: {}
labels:
app.kubernetes.io/instance: release-name
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: coder
app.kubernetes.io/part-of: coder
app.kubernetes.io/version: 0.1.0
helm.sh/chart: coder-0.1.0
name: coder
---
# Source: coder/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: coder-workspace-perms
rules:
- apiGroups: [""]
resources: ["pods"]
verbs:
- create
- delete
- deletecollection
- get
- list
- patch
- update
- watch
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs:
- create
- delete
- deletecollection
- get
- list
- patch
- update
- watch
- apiGroups:
- apps
resources:
- deployments
verbs:
- create
- delete
- deletecollection
- get
- list
- patch
- update
- watch
---
# Source: coder/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: "coder"
subjects:
- kind: ServiceAccount
name: "coder"
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: coder-workspace-perms
---
# Source: coder/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: coder
labels:
helm.sh/chart: coder-0.1.0
app.kubernetes.io/name: coder
app.kubernetes.io/instance: release-name
app.kubernetes.io/part-of: coder
app.kubernetes.io/version: "0.1.0"
app.kubernetes.io/managed-by: Helm
annotations:
{}
spec:
type: LoadBalancer
sessionAffinity: None
ports:
- name: "http"
port: 80
targetPort: "http"
protocol: TCP
nodePort: 30080
externalTrafficPolicy: "Cluster"
selector:
app.kubernetes.io/name: coder
app.kubernetes.io/instance: release-name
---
# Source: coder/templates/coder.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
annotations: {}
labels:
app.kubernetes.io/instance: release-name
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: coder
app.kubernetes.io/part-of: coder
app.kubernetes.io/version: 0.1.0
helm.sh/chart: coder-0.1.0
name: coder
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/instance: release-name
app.kubernetes.io/name: coder
template:
metadata:
annotations: {}
labels:
app.kubernetes.io/instance: release-name
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: coder
app.kubernetes.io/part-of: coder
app.kubernetes.io/version: 0.1.0
helm.sh/chart: coder-0.1.0
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/instance
operator: In
values:
- coder
topologyKey: kubernetes.io/hostname
weight: 1
containers:
- args:
- server
command:
- /opt/coder
env:
- name: CODER_HTTP_ADDRESS
value: 0.0.0.0:8080
- name: CODER_PROMETHEUS_ADDRESS
value: 0.0.0.0:2112
- name: CODER_ACCESS_URL
value: http://coder.default.svc.cluster.local
- name: KUBE_POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: CODER_DERP_SERVER_RELAY_URL
value: http://$(KUBE_POD_IP):8080
image: ghcr.io/coder/coder:latest
imagePullPolicy: IfNotPresent
lifecycle: {}
livenessProbe:
httpGet:
path: /healthz
port: http
scheme: HTTP
name: coder
ports:
- containerPort: 8080
name: http
protocol: TCP
readinessProbe:
httpGet:
path: /healthz
port: http
scheme: HTTP
resources: {}
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: null
runAsGroup: 1000
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
volumeMounts: []
restartPolicy: Always
serviceAccountName: coder
terminationGracePeriodSeconds: 60
volumes: []

View File

@ -0,0 +1,8 @@
coder:
image:
tag: latest
service:
type: LoadBalancer
httpNodePort: 30080
httpsNodePort: 30043

View File

@ -90,7 +90,7 @@ spec:
port: 80
targetPort: "http"
protocol: TCP
nodePort:
externalTrafficPolicy: "Cluster"
loadBalancerClass: "test"
selector:

View File

@ -0,0 +1,189 @@
---
# Source: coder/templates/coder.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
annotations: {}
labels:
app.kubernetes.io/instance: release-name
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: coder
app.kubernetes.io/part-of: coder
app.kubernetes.io/version: 0.1.0
helm.sh/chart: coder-0.1.0
name: coder
---
# Source: coder/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: coder-workspace-perms
rules:
- apiGroups: [""]
resources: ["pods"]
verbs:
- create
- delete
- deletecollection
- get
- list
- patch
- update
- watch
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs:
- create
- delete
- deletecollection
- get
- list
- patch
- update
- watch
- apiGroups:
- apps
resources:
- deployments
verbs:
- create
- delete
- deletecollection
- get
- list
- patch
- update
- watch
---
# Source: coder/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: "coder"
subjects:
- kind: ServiceAccount
name: "coder"
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: coder-workspace-perms
---
# Source: coder/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: coder
labels:
helm.sh/chart: coder-0.1.0
app.kubernetes.io/name: coder
app.kubernetes.io/instance: release-name
app.kubernetes.io/part-of: coder
app.kubernetes.io/version: "0.1.0"
app.kubernetes.io/managed-by: Helm
annotations:
{}
spec:
type: NodePort
sessionAffinity: None
ports:
- name: "http"
port: 80
targetPort: "http"
protocol: TCP
nodePort: 30080
selector:
app.kubernetes.io/name: coder
app.kubernetes.io/instance: release-name
---
# Source: coder/templates/coder.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
annotations: {}
labels:
app.kubernetes.io/instance: release-name
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: coder
app.kubernetes.io/part-of: coder
app.kubernetes.io/version: 0.1.0
helm.sh/chart: coder-0.1.0
name: coder
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/instance: release-name
app.kubernetes.io/name: coder
template:
metadata:
annotations: {}
labels:
app.kubernetes.io/instance: release-name
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: coder
app.kubernetes.io/part-of: coder
app.kubernetes.io/version: 0.1.0
helm.sh/chart: coder-0.1.0
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/instance
operator: In
values:
- coder
topologyKey: kubernetes.io/hostname
weight: 1
containers:
- args:
- server
command:
- /opt/coder
env:
- name: CODER_HTTP_ADDRESS
value: 0.0.0.0:8080
- name: CODER_PROMETHEUS_ADDRESS
value: 0.0.0.0:2112
- name: CODER_ACCESS_URL
value: http://coder.default.svc.cluster.local
- name: KUBE_POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: CODER_DERP_SERVER_RELAY_URL
value: http://$(KUBE_POD_IP):8080
image: ghcr.io/coder/coder:latest
imagePullPolicy: IfNotPresent
lifecycle: {}
livenessProbe:
httpGet:
path: /healthz
port: http
scheme: HTTP
name: coder
ports:
- containerPort: 8080
name: http
protocol: TCP
readinessProbe:
httpGet:
path: /healthz
port: http
scheme: HTTP
resources: {}
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: null
runAsGroup: 1000
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
volumeMounts: []
restartPolicy: Always
serviceAccountName: coder
terminationGracePeriodSeconds: 60
volumes: []

View File

@ -0,0 +1,8 @@
coder:
image:
tag: latest
service:
type: NodePort
httpNodePort: 30080
httpsNodePort: 30043

View File

@ -90,12 +90,12 @@ spec:
port: 80
targetPort: "http"
protocol: TCP
nodePort:
- name: "https"
port: 443
targetPort: "https"
protocol: TCP
nodePort:
externalTrafficPolicy: "Cluster"
selector:
app.kubernetes.io/name: coder

View File

@ -90,7 +90,7 @@ spec:
port: 80
targetPort: "http"
protocol: TCP
nodePort:
externalTrafficPolicy: "Cluster"
selector:
app.kubernetes.io/name: coder

View File

@ -90,7 +90,7 @@ spec:
port: 80
targetPort: "http"
protocol: TCP
nodePort:
externalTrafficPolicy: "Cluster"
selector:
app.kubernetes.io/name: coder

View File

@ -288,11 +288,11 @@ coder:
# https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer
annotations: {}
# coder.service.httpNodePort -- Enabled if coder.service.type is set to
# NodePort. If not set, Kubernetes will allocate a port from the default
# NodePort or LoadBalancer. If not set, Kubernetes will allocate a port from the default
# range, 30000-32767.
httpNodePort: ""
# coder.service.httpsNodePort -- Enabled if coder.service.type is set to
# NodePort. If not set, Kubernetes will allocate a port from the default
# NodePort or LoadBalancer. If not set, Kubernetes will allocate a port from the default
# range, 30000-32767.
httpsNodePort: ""