-
Notifications
You must be signed in to change notification settings - Fork 982
Description
What happened:
The command kubectl diff --server-side fails to detect fields that are removed from the manifest even though kubectl apply reconciles the removal correctly. Using kubectl diff without the --server-side switch works fine since it leverages the last-applied-configuration annotation.
What you expected to happen:
The kubectl diff --server-side command should align with kubectl apply --server-side which AFAIK uses the structured-merge-diff library to do its work. Otherwise the diff is generating output that is IMHO errorneous and goes against user expectations that diff should output what the effects of an apply will be.
Ideally diff --server-side should use the structured-merge-diff library rather then relying on an alternate way to generate the diff.
How to reproduce it (as minimally and precisely as possible):
Save this deployment as a file:
apiVersion: apps/v1
kind: Deployment
metadata:
name: basic-app
labels:
test: value
annotations:
test: value
test2: value
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: basic-app
template:
metadata:
labels:
app: basic-app
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.kubernetes.io/worker
operator: In
values:
- ''
- key: kubernetes.io/os
operator: In
values:
- 'linux'
containers:
- name: basic-app
image: busybox:latest # A simple image for demonstration
command: ["sh", "-c", "echo $APP_MESSAGE && echo 'Application running on port:' $APP_PORT && sleep 3600"]
env:
- name: TEST_VAR
value: value
- name: TEST_VAR2
value: value2
And then apply it to the cluster:
kubectl apply -f deployment.yaml
Remove the affinity section from the manifest (but note it is the same issue regardless of what you remove) and then diff it with server side:
$ kubectl diff -f deployment.yaml --server-side
diff -u -N /tmp/LIVE-486847088/apps.v1.Deployment.diff-bug.basic-app /tmp/MERGED-2682306472/apps.v1.Deployment.diff-bug.basic-app
--- /tmp/LIVE-486847088/apps.v1.Deployment.diff-bug.basic-app 2025-11-05 13:18:43.473757610 -0800
+++ /tmp/MERGED-2682306472/apps.v1.Deployment.diff-bug.basic-app 2025-11-05 13:18:43.474757618 -0800
@@ -4,11 +4,11 @@
annotations:
deployment.kubernetes.io/revision: "1"
kubectl.kubernetes.io/last-applied-configuration: |
- {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"test":"value","test2":"value"},"labels":{"test":"value"},"name":"basic-app","namespace":"diff-bug"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"basic-app"}},"template":{"metadata":{"labels":{"app":"basic-app"}},"spec":{"affinity":{"nodeAffinity":{"requiredDuringSchedulingIgnoredDuringExecution":{"nodeSelectorTerms":[{"matchExpressions":[{"key":"node-role.kubernetes.io/worker","operator":"In","values":[""]},{"key":"kubernetes.io/os","operator":"In","values":["linux"]}]}]}}},"containers":[{"command":["sh","-c","echo $APP_MESSAGE \u0026\u0026 echo 'Application running on port:' $APP_PORT \u0026\u0026 sleep 3600"],"env":[{"name":"TEST_VAR","value":"value"},{"name":"TEST_VAR2","value":"value2"}],"image":"busybox:latest","name":"basic-app"}]}}}}
+ {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"test":"value","test2":"value"},"labels":{"test":"value"},"name":"basic-app","namespace":"diff-bug"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"basic-app"}},"template":{"metadata":{"labels":{"app":"basic-app"}},"spec":{"containers":[{"command":["sh","-c","echo $APP_MESSAGE \u0026\u0026 echo 'Application running on port:' $APP_PORT \u0026\u0026 sleep 3600"],"env":[{"name":"TEST_VAR","value":"value"},{"name":"TEST_VAR2","value":"value2"}],"image":"busybox:latest","name":"basic-app"}]}}}}
test: value
test2: value
creationTimestamp: "2025-11-05T21:18:15Z"
- generation: 1
+ generation: 2
labels:
test: value
name: basic-app
The diff fails to show the removed affinity. Diff'ing this using client-side diff works fine since it leverages the last-applied-configuration. Also running kubectl apply -f deployment.yaml updates the manifest as expected and removes the affinity section using either client-side or server-side apply.
Anything else we need to know?:
Tools like Argo CD rely on the way Kubernetes handles the diff and this dissonance between diff and apply causes these tools to fail to report differences as expected to their users.
Environment:
- Kubernetes client and server versions (use
kubectl version):
Client Version: v1.34.1
Kustomize Version: v5.7.1
Server Version: v1.33.5
- Cloud provider or hardware configuration:
on-prem
- OS (e.g:
cat /etc/os-release):
NAME="Arch Linux"
PRETTY_NAME="Arch Linux"
ID=arch
BUILD_ID=rolling
ANSI_COLOR="38;2;23;147;209"
HOME_URL="https://archlinux.org/"
DOCUMENTATION_URL="https://wiki.archlinux.org/"
SUPPORT_URL="https://bbs.archlinux.org/"
BUG_REPORT_URL="https://gitlab.archlinux.org/groups/archlinux/-/issues"
PRIVACY_POLICY_URL="https://terms.archlinux.org/docs/privacy-policy/"
LOGO=archlinux-logo