Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Helm's ports merging logic differs from Kubernetes #12974

Open
jhandguy opened this issue Apr 22, 2024 · 2 comments
Open

Helm's ports merging logic differs from Kubernetes #12974

jhandguy opened this issue Apr 22, 2024 · 2 comments

Comments

@jhandguy
Copy link

Problem statement

In Helm, ports are merged via their port name while in Kubernetes ports are merged via their port number.
This difference in logic can lead to state inconsistencies in Helm releases (see Example section) and the only way to "repair" Helm's broken state is to use the --force flag of helm upgrade.

Related to closed issue #7957.

Example

Given 2 ports with names first-port and second-port and respective values 8080 and 9090:

spec:
  template:
    spec:
      containers:
        - ports:
            - name: first-port
              containerPort: 8080
              protocol: TCP
            - name: second-port
              containerPort: 9090
              protocol: TCP

If the value of second-port is changed from 9090 to 8080, Helm will retain both ports with the same port number (8080), while Kubernetes will have in fact merged both ports into first-port with port number 8080.

The port second-port is therefore completely removed from the Kubernetes resources:

spec:
  template:
    spec:
      containers:
        - ports:
            - name: first-port
              containerPort: 8080
              protocol: TCP

Yet Helm retains it in its release manifest with the new port number 8080:

spec:
  template:
    spec:
      containers:
        - ports:
            - name: first-port
              containerPort: 8080
              protocol: TCP
            - name: second-port
              containerPort: 8080
              protocol: TCP

Steps to reproduce

See helm-ports-issue for an example of Helm chart reproducing the issue.

gh repo clone jhandguy/helm-ports-issue
cd helm-ports-issue
./script.sh

Note: script.sh makes use of the following tools: kind, helm, kubectl and jq

Output

Helm release manifest after installing:
---
# Source: agnhost/templates/service.yaml
kind: Service
apiVersion: v1
metadata:
  name: agnhost
  labels: &labels
    app: agnhost
spec:
  selector: *labels
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
      name: first-port
    - protocol: TCP
      port: 9090
      targetPort: 9090
      name: second-port
---
# Source: agnhost/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: agnhost
spec:
  replicas: 1
  selector:
    matchLabels: &labels
      app: agnhost
  template:
    metadata:
      labels: *labels
    spec:
      containers:
        - name: agnhost
          image: registry.k8s.io/e2e-test-images/agnhost:2.39
          ports:
            - name: first-port
              containerPort: 8080
              protocol: TCP
            - name: second-port
              containerPort: 9090
              protocol: TCP

Deployment ports after installing:
[
  {
    "containerPort": 8080,
    "name": "first-port",
    "protocol": "TCP"
  },
  {
    "containerPort": 9090,
    "name": "second-port",
    "protocol": "TCP"
  }
]

Service ports after installing:
[
  {
    "name": "first-port",
    "port": 8080,
    "protocol": "TCP",
    "targetPort": 8080
  },
  {
    "name": "second-port",
    "port": 9090,
    "protocol": "TCP",
    "targetPort": 9090
  }
]

Helm release manifest after updating second port from 9090 to 8080:
---
# Source: agnhost/templates/service.yaml
kind: Service
apiVersion: v1
metadata:
  name: agnhost
  labels: &labels
    app: agnhost
spec:
  selector: *labels
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
      name: first-port
    - protocol: TCP
      port: 8080
      targetPort: 8080
      name: second-port
---
# Source: agnhost/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: agnhost
spec:
  replicas: 1
  selector:
    matchLabels: &labels
      app: agnhost
  template:
    metadata:
      labels: *labels
    spec:
      containers:
        - name: agnhost
          image: registry.k8s.io/e2e-test-images/agnhost:2.39
          ports:
            - name: first-port
              containerPort: 8080
              protocol: TCP
            - name: second-port
              containerPort: 8080
              protocol: TCP

Deployment ports after updating second port from 9090 to 8080:
[
  {
    "containerPort": 8080,
    "name": "first-port",
    "protocol": "TCP"
  }
]

Service ports after updating second port from 9090 to 8080:
[
  {
    "name": "first-port",
    "port": 8080,
    "protocol": "TCP",
    "targetPort": 8080
  }
]

Helm release manifest after updating first port from 8080 to 9090:
---
# Source: agnhost/templates/service.yaml
kind: Service
apiVersion: v1
metadata:
  name: agnhost
  labels: &labels
    app: agnhost
spec:
  selector: *labels
  ports:
    - protocol: TCP
      port: 9090
      targetPort: 9090
      name: first-port
    - protocol: TCP
      port: 9090
      targetPort: 9090
      name: second-port
---
# Source: agnhost/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: agnhost
spec:
  replicas: 1
  selector:
    matchLabels: &labels
      app: agnhost
  template:
    metadata:
      labels: *labels
    spec:
      containers:
        - name: agnhost
          image: registry.k8s.io/e2e-test-images/agnhost:2.39
          ports:
            - name: first-port
              containerPort: 9090
              protocol: TCP
            - name: second-port
              containerPort: 9090
              protocol: TCP

Deployment ports after updating first port from 8080 to 9090:
[
  {
    "containerPort": 9090,
    "name": "first-port",
    "protocol": "TCP"
  }
]

Service ports after updating first port from 8080 to 9090:
[
  {
    "name": "first-port",
    "port": 9090,
    "protocol": "TCP",
    "targetPort": 9090
  }
]

Versions

Output of helm version:

version.BuildInfo{Version:"v3.14.4", GitCommit:"81c902a123462fd4052bc5e9aa9c513c4c8fc142", GitTreeState:"clean", GoVersion:"go1.22.2"}

Output of kubectl version:

Client Version: v1.30.0
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Server Version: v1.29.2

Cloud Provider/Platform:

kind v0.22.0 go1.21.7 darwin/arm64
@jhandguy
Copy link
Author

Also related to #12771

@gjenkins8
Copy link
Contributor

Unfortnately, Helm does not yet support Server Side Apply, which is necessary to merge by e.g. name

see: helm/community#312

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants