<p>When doing <code>helm upgrade ... --force</code> I'm getting this below error </p>

<pre><code>Error: UPGRADE FAILED: failed to replace object: Service "api" is invalid: spec.clusterIP: Invalid value: "": field is immutable
</code></pre>

<p>And This is how my service file looks like: (Not passing clusterIP anywhere )</p>

<pre><code>apiVersion: v1
kind: Service
metadata:
  name: {{ .Chart.Name }}
  namespace: {{ .Release.Namespace }}
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "https"
    service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
  labels:
    app: {{ .Chart.Name }}-service
    kubernetes.io/name: {{ .Chart.Name | quote }}
    dns: route53
    chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
    release: "{{ .Release.Name }}"
spec:
  selector:
    app: {{ .Chart.Name }}
  type: LoadBalancer
  ports:
  - port: 443
    name: https
    targetPort: http-port
    protocol: TCP
</code></pre>

<p><strong>Helm</strong> Version: <strong>3.0.1</strong></p>

<p><strong>Kubectl</strong> Version: <strong>1.13.1</strong> [Tried with the <strong>1.17.1</strong> as well]</p>

<p><strong>Server</strong>: <strong>1.14</strong></p>

<p><strong>Note</strong>: Previously I was using some old version (of server, kubectl, helm) at that time I did not face this kind of issue. 
I can see lots of similar issues in GitHub regarding this, but unable to find any working solution for me.</p>

<p>few of the similar issues:</p>

<p><a href="https://github.com/kubernetes/kubernetes/issues/25241" rel="noreferrer">https://github.com/kubernetes/kubernetes/issues/25241</a></p>

<p><a href="https://github.com/helm/charts/pull/13646" rel="noreferrer">https://github.com/helm/charts/pull/13646</a> [For Nginx chart]</p>

Saikat Chakrabortty
<p>I've made some tests with Helm and got the same issue when trying to change the Service type from <code>NodePort/ClusterIP</code> to <code>LoadBalancer</code>.</p>

<p>This is how I've reproduced your issue:</p>

<p><strong>Kubernetes</strong> 1.15.3 (GKE)
<strong>Helm</strong> 3.1.1</p>

<p>Helm chart used for test: <a href="https://github.com/helm/charts/tree/master/stable/nginx-ingress" rel="noreferrer">stable/nginx-ingress</a></p>

<h3>How I reproduced:</h3>

<ol>
<li>Get and decompress the file:</li>
</ol>

<pre><code>helm fetch stable/nginx-ingress  
tar xzvf nginx-ingress-1.33.0.tgz  
</code></pre>

<ol start="2">
<li>Modify service type from <code>type: LoadBalancer</code> to <code>type: NodePort</code> in the <code>values.yaml</code> file (line 271):</li>
</ol>

<pre><code>sed -i '271s/LoadBalancer/NodePort/' values.yaml
</code></pre>

<ol start="3">
<li>Install the chart:</li>
</ol>

<pre><code>helm install nginx-ingress ./
</code></pre>

<ol start="4">
<li>Check service type, must be <code>NodePort</code>:</li>
</ol>

<pre><code>kubectl get svc -l app=nginx-ingress,component=controller

NAME                       TYPE       CLUSTER-IP   EXTERNAL-IP   PORT(S)                      AGE
nginx-ingress-controller   NodePort   10.0.3.137   &lt;none&gt;        80:30117/TCP,443:30003/TCP   1m
</code></pre>

<ol start="5">
<li>Now modify the Service type again to <code>LoadBalancer</code> in the <code>values.yaml</code>:</li>
</ol>

<pre><code>sed -i '271s/NodePort/LoadBalancer/' values.yaml
</code></pre>

<ol start="6">
<li>Finally, try to upgrade the chart using <code>--force</code> flag:</li>
</ol>

<pre><code>helm upgrade nginx-ingress ./ --force
</code></pre>

<p>And then:</p>

<pre><code>Error: UPGRADE FAILED: failed to replace object: Service "nginx-ingress-controller" is invalid: spec.clusterIP: Invalid value: "": field is immutable
</code></pre>

<h3>Explanation</h3>

<p>Digging around I found this in HELM <a href="https://github.com/helm/helm/blob/6bed5949cbff923851aac168373c5279f659c46a/pkg/kube/client.go#L428" rel="noreferrer">source code:</a></p>

<pre class="lang-golang prettyprint-override"><code>// if --force is applied, attempt to replace the existing resource with the new object.
    if force {
        obj, err = helper.Replace(target.Namespace, target.Name, true, target.Object)
        if err != nil {
            return errors.Wrap(err, "failed to replace object")
        }
        c.Log("Replaced %q with kind %s for kind %s\n", target.Name, currentObj.GetObjectKind().GroupVersionKind().Kind, kind)
    } else {
        // send patch to server
        obj, err = helper.Patch(target.Namespace, target.Name, patchType, patch, nil)
        if err != nil {
            return errors.Wrapf(err, "cannot patch %q with kind %s", target.Name, kind)
        }
    }
</code></pre>

<p>Analyzing the code above Helm will use similar to <code>kubectl replace</code> api request (instead of <code>kubectl replace --force</code> as we could expect)... when the helm <code>--force</code> flag is set.</p>

<p>If not, then Helm will use <code>kubectl patch</code> api request to make the upgrade.</p>

<p>Let's check if it make sense:</p>

<h3>PoC using kubectl</h3>

<ol>
<li>Create a simple service as <code>NodePort</code>:</li>
</ol>

<pre><code>kubectl apply -f - &lt;&lt;EOF
apiVersion: v1
kind: Service
metadata:
 labels:
   app: test-svc
 name: test-svc
spec:
 selector:
   app: test-app
 ports:
 - port: 80
   protocol: TCP
   targetPort: 80
 type: NodePort
EOF
</code></pre>

<p>Make the service was created:</p>

<pre><code>kubectl get svc -l app=test-svc

NAME       TYPE       CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
test-svc   NodePort   10.0.7.37    &lt;none&gt;        80:31523/TCP   25
</code></pre>

<p>Now lets try to use <code>kubectl replace</code> to upgrade the service to <code>LoadBalancer</code>, like <code>helm upgrade --force</code>:</p>

<pre><code>kubectl replace -f - &lt;&lt;EOF
apiVersion: v1
kind: Service
metadata:
 labels:
   app: test-svc
 name: test-svc
spec:
 selector:
   app: test-app
 ports:
 - port: 80
   protocol: TCP
   targetPort: 80
 type: LoadBalancer
EOF
</code></pre>

<p>This shows the error:</p>

<pre><code>The Service "test-svc" is invalid: spec.clusterIP: Invalid value: "": field is immutable
</code></pre>

<p>Now, lets use <code>kubectl patch</code> to change the NodePort to LoadBalancer, simulating the helm upgrade command <em>without</em> <code>--force</code> flag:</p>

<p><em><a href="https://jamesdefabia.github.io/docs/user-guide/kubectl/kubectl_patch/" rel="noreferrer">Here</a> is the kubectl patch documentation, if want to see how to use.</em></p>

<pre><code>kubectl patch svc test-svc -p '{"spec":{"type":"LoadBalancer"}}'
</code></pre>

<p>Then you see:
<code>service/test-svc patched</code></p>

<h3>Workaround</h3>

<p>You should to use <code>helm upgrade</code> without <code>--force</code>, it will work.</p>

<p>If you really need to use <code>--force</code> to recreate some resources, like pods to get the latest <code>configMap</code> update, for example, then I suggest you first manually change the service specs before Helm upgrade.</p>

<p>If you are trying to change the service type you could do it exporting the service <code>yaml</code>, changing the type and apply it again (because I experienced this behavior only when I tried to apply the same template from the first time):</p>

<pre><code>kubectl get svc test-svc -o yaml | sed 's/NodePort/LoadBalancer/g' | kubectl replace --force -f -
</code></pre>

<p>The Output:</p>

<pre><code>service "test-svc" deleted
service/test-svc replaced
</code></pre>

<p>Now, if you try to use <code>helm upgrade --force</code> and doesn't have any change to do in the service, it will work and will recreate your pods and others resources.</p>

<p>I hope that helps you!</p>
