Deployment Label Selector update with apps/v1 not throwing 'field is immutable' error and changes are not getting reflected

6/17/2020

kubectl version --short Client Version: v1.15.0 Server Version: v1.14.10-gke.36

I am trying to update a deployment's Label Selector with apps/v1 API, which as per Kubernetes documentation (documentation) should error out. But in practice kubectl apply -f <filename> is not throwing any error and changes are not visible in deployment.

Existing deployment kubectl get deployment my-deployment -o yaml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  annotations:
    canonical-name: my-deployment
    deployment.kubernetes.io/revision: "219"
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"canonical-name":"my-deployment"},"name":"my-deployment","namespace":"default"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"my-deployment","deployment":"my-deployment"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"0%"},"type":"RollingUpdate"},"template":{"metadata":{"labels":{"app":"my-deployment","deployment":"my-deployment","enableRolling":"true","testComponent":"true","testName":"my-deployment","type":"REST"}},"spec":{"affinity":{"nodeAffinity":{"requiredDuringSchedulingIgnoredDuringExecution":{"nodeSelectorTerms":[{"matchExpressions":[{"key":"node-pool-type","operator":"In","values":["rest","components"]}]}]}},"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app","operator":"In","values":["my-deployment"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]}},"containers":[{"env":[{"name":"HOST_IP","valueFrom":{"fieldRef":{"fieldPath":"status.hostIP"}}},{"name":"POD_IP","valueFrom":{"fieldRef":{"fieldPath":"status.podIP"}}}],"envFrom":[{"configMapRef":{"name":"cp-env"}},{"configMapRef":{"name":"gcp-project-env"}},{"secretRef":{"name":"cp-secret-env"}},{"configMapRef":{"name":"my-deployment-db-env"}},{"configMapRef":{"name":"my-deployment-env"}}],"image":"quay.io/testrd/my-deployment:latest","imagePullPolicy":"IfNotPresent","livenessProbe":{"failureThreshold":3,"httpGet":{"path":"/heathz","port":8080},"initialDelaySeconds":3700,"periodSeconds":18,"successThreshold":1},"name":"my-deployment","ports":[{"containerPort":8080}],"readinessProbe":{"failureThreshold":3,"httpGet":{"path":"/heathz","port":8080},"initialDelaySeconds":60,"periodSeconds":30,"successThreshold":1},"resources":{"limits":{"memory":"3840Mi"},"requests":{"cpu":"247m","memory":"1536Mi"}},"volumeMounts":[{"mountPath":"/var/run/secrets/kubernetes.io/accesscontrol","name":"gcp-service-admin-account"},{"mountPath":"/component","name":"host-volume"},{"mountPath":"/pinpoint-agent","name":"pinpoint-volume"}]}],"imagePullSecrets":[{"name":"mysecret"},{"name":"gcr-mysecret"}],"priorityClassName":"low-priority","volumes":[{"name":"gcp-service-admin-account","secret":{"secretName":"gcp-service-admin-key"}},{"hostPath":{"path":"/mnt/stateful_partition/component","type":"DirectoryOrCreate"},"name":"host-volume"},{"hostPath":{"path":"/mnt/stateful_partition/agent","type":"DirectoryOrCreate"},"name":"agent-volume"}]}}}}
  creationTimestamp: "2019-04-26T16:43:43Z"
  generation: 508
  labels:
    app: my-deployment
    deployment: my-deployment
    enableRolling: "true"
    testComponent: "true"
    testComponentName: my-deployment
    type: REST
  name: my-deployment
  namespace: default
  resourceVersion: "237385987"
  selfLink: /apis/extensions/v1beta1/namespaces/default/deployments/my-deployment
  uid: 73e059d0-6842-11e9-b98b-42010a960025
spec:
  progressDeadlineSeconds: 2147483647
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: my-deployment
      deployment: my-deployment
      enableRolling: "true"
      type: REST
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 0%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: my-deployment
        deployment: my-deployment
        enableRolling: "true"
        testComponent: "true"
        testComponentName: my-deployment
        type: REST
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: node-pool-type
                operator: In
                values:
                - rest
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - my-deployment
              topologyKey: kubernetes.io/hostname
            weight: 100
      containers:
      - env:
        - name: LAST_RESTART
          value: "1592331191"
        - name: HOST_IP
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: status.hostIP
        - name: POD_IP
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: status.podIP
        - name: VIRTUAL_HOST
          value: my-deployment.default.svc.cluster.local
        envFrom:
        - configMapRef:
            name: my-deployment-env
        image: quay.io/testrd/my-deployment:latest
        imagePullPolicy: IfNotPresent
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /get/healthz
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 2700
          periodSeconds: 15
          successThreshold: 1
          timeoutSeconds: 1
        name: my-deployment
        ports:
        - containerPort: 8080
          protocol: TCP
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /get/healthz
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 60
          periodSeconds: 30
          successThreshold: 1
          timeoutSeconds: 1
        resources:
          limits:
            memory: 384Mi
          requests:
            cpu: 247m
            memory: 1536Mi
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /var/run/secrets/kubernetes.io/accesscontrol
          name: gcp-service-acc
      dnsPolicy: ClusterFirst
      imagePullSecrets:
      - name: mysecret
      - name: gcr-mysecret
      priorityClassName: low-priority
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
      volumes:
      - name: gcp-service-acc
        secret:
          defaultMode: 420
          secretName: gcp-service-admin-key
status:
  availableReplicas: 1
  conditions:
  - lastTransitionTime: "2020-06-15T18:59:25Z"
    lastUpdateTime: "2020-06-15T18:59:25Z"
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  observedGeneration: 508
  readyReplicas: 1
  replicas: 1
  updatedReplicas: 1

New my-deployment.yml

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-deployment
  namespace: default
  annotations:
  canonical-name: my-deployment
  labels:
    type: "REST"
    testComponent: "true"
    testComponentName: "my-deployment"
spec:
  replicas: 1
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 15%
      maxUnavailable: 0%
  selector:
    matchLabels:
      deployment: my-deployment
      app: my-deployment
  template:
    metadata:
      labels:
        app: my-deployment
        type: REST
        enableRolling: "true"
        deployment: my-deployment
        testComponent: "true"
        testComponentName: my-deployment
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                - key: node-pool-type
                  operator: In
                  values:
                    - rest
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - my-deployment
              topologyKey: "kubernetes.io/hostname"
      # scheduling and preemption priority of this pod
      priorityClassName: low-priority
      containers:
      - name: my-deployment
        image: quay.io/testrd/my-deployment:latest
        imagePullPolicy: IfNotPresent
        resources:
          requests:
            cpu: "247m"
            memory: "1536Mi"
          limits:
            memory: "384Mi"
        
        readinessProbe:
          httpGet:
            path: /get/healthz
            port: 8080
          # start after 60s of container launch
          initialDelaySeconds: 60
          # probe every 30s
          periodSeconds: 30
          # declare success (ready) if 1 (default) attempt results in a success
          successThreshold: 1
          failureThreshold: 3
        livenessProbe:
          httpGet:
            path: /get/healthz
            port: 8080
          # start after 45 minutes of container launch
          initialDelaySeconds: 2700
          # probe every 15s
          periodSeconds: 15
          # declare success (live) if 1 (default) attempt results in a success
          successThreshold: 1
          # declare failure (not live) if 3 consecutive probes result in a failure
          failureThreshold: 3
        ports:
        - containerPort: 8080
        envFrom:
        - configMapRef:
            name: my-deployment-env
        # component specific environment variables
        env:
        - name: HOST_IP
          valueFrom:
            fieldRef:
              fieldPath: status.hostIP
        - name: POD_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        # VIRTUAL_HOST is set as the service name
        - name: VIRTUAL_HOST
          value: "my-deployment.default.svc.cluster.local"
      volumes:
      # volume carrying the Google Cloud Platform authorization key data
      - name: "gcp-service-acc
        secret:
          secretName: "gcp-service-key"
      imagePullSecrets:
      - name: mysecret
      - name: gcr-mysecret

kubectl apply -f my-deployment.yml

deployment.apps/my-deployment configured

Ideally it should fail with the error

The Deployment "my-deployment" is invalid: spec.selector: Invalid value: v1.LabelSelector{MatchLabels:map[string]string{"deployment":"my-deployment"}, MatchExpressions:[]v1.LabelSelectorRequirement(nil)}: field is immutable

which is not happening in this case and new labels are not reflecting in deployment. Though they are visible in kubectl.kubernetes.io/last-applied-configuration.

Update Wed Jun 17 16:46:15 UTC 2020 Created new test deployment

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-deployment
  namespace: default
spec:
  # scale com-manh-cp-remote to 1 instances
  replicas: 1
  selector:
    matchLabels:
      app: test-deployment
      deployment: test-deployment
      stereotype: REST
  template:
    metadata:
      labels:
        app: test-deployment
        deployment: test-deployment
        stereotype: REST
    spec:
      containers:
      - name: test-deployment
        image: quay.io/manhrd/kube-cp-stax:latest
        imagePullPolicy: IfNotPresent
      imagePullSecrets:
      - name: regsecret
      - name: gcr-regsecret

Applied update by changing label selector

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-deployment
  namespace: default
spec:
  # scale com-manh-cp-remote to 1 instances
  replicas: 1
  selector:
    matchLabels:
      deployment: test-deployment
  template:
    metadata:
      labels:
        app: test-deployment
        deployment: test-deployment
        stereotype: REST
    spec:
      containers:
      - name: test-deployment
        image: quay.io/manhrd/kube-cp-stax:latest
        imagePullPolicy: IfNotPresent
      imagePullSecrets:
      - name: mysecret
      - name: gcr-mysecret

Got below error.

kubectl apply -f dep.yaml
The Deployment "test-deployment" is invalid: spec.selector: Invalid value: v1.LabelSelector{MatchLabels:map[string]string{"deployment":"test-deployment"}, MatchExpressions:[]v1.LabelSelectorRequirement(nil)}: field is immutable

It is working expected for new deployments.

-- Sumant Pangotra
google-kubernetes-engine
kubernetes

1 Answer

6/17/2020

This is because

  1. The existing deployment that you are trying to update is on extensions/v1beta1 API group where label selector is immutable.
  2. Kubernetes version is older than 1.16
-- Arghya Sadhu
Source: StackOverflow