Kubernetes: spec.ports[0].nodePort: Forbidden: may not be used when `type` is ‘ClusterIP’

Автор: | 26/04/2021
 

Во время деплоя приложения из Helm-чарта, описанного в посте Istio: общий Ingress/AWS ALB, Helm-чарт с условиями, Istio и ExternalDNS, возникает ошибка “spec.ports[0].nodePort: Forbidden: may not be used when `type` is ‘ClusterIP’“.

Воспроизведём ошибку вручную, и рассмотрим способы обхода проблемы с помощью kubectl и при использовании Helm.

Ошибка spec.ports[0].nodePort: Forbidden: may not be used when `type` is ‘ClusterIP’

Создаём Service с типом NodePort:

---
apiVersion: v1
kind: Service 
metadata:
  name: test-svc
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
  selector:
    app: test

Деплоим, проверяем:

[simterm]

$ kubectl get svc test-svc
NAME       TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
test-svc   NodePort   172.20.129.242   <none>        80:31853/TCP   22s

[/simterm]

И порты этого Сервиса:

[simterm]

$ kubectl get svc test-svc -o json | jq '.spec.ports[]'
{
  "nodePort": 31231,
  "port": 80,
  "protocol": "TCP",
  "targetPort": 80
}

[/simterm]

Обновляем манифест, меняем тип на ClusterIP:

---
apiVersion: v1
kind: Service 
metadata:
  name: test-svc
spec:
  type: ClusterIP
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
  selector:
    app: test

Ещё раз деплоим, и:

[simterm]

$ kubectl apply -f test-svc.yaml 
The Service "test-svc" is invalid: spec.ports[0].nodePort: Forbidden: may not be used when `type` is 'ClusterIP'

[/simterm]

Собственно, из-за nodePort и возникает проблема, так как в спецификации ClusterIP его нет.

Решения

kubectl apply --force

Первый вариант – выполнить apply --force – тогда сначала Service будет удалён, а потом создан новый:

[simterm]

$ kubectl apply -f test-svc.yaml --force
service/test-svc configured

[/simterm]

Проверяем порты:

[simterm]

$ kubectl get svc test-svc -o json | jq '.spec.ports[]'
{
  "port": 80,
  "protocol": "TCP",
  "targetPort": 80
}

[/simterm]

kubectl edit service

Ещё вариант – выполнить kubectl edit service вручную, а потом накатить манифест:

[simterm]

$ kubectl edit svc test-svc

[/simterm]

Удаляем  – nodePort: 32729 и type: NodePort:

Применяем с apply, но уже без --force:

[simterm]

$ kubectl apply -f test-svc.yaml 
service/test-svc configured

[/simterm]

Снова проверяем порты:

[simterm]

$ kubectl get svc test-svc -o json | jq '.spec.ports[]'
{
  "port": 80,
  "protocol": "TCP",
  "targetPort": 80
}

[/simterm]

Решение для Helm-чарта

Но основная задача – делать это автоматически во время Helm-деплоя.

Сделаем тестовый чарт:

[simterm]

$ helm create test-svc
Creating test-svc

[/simterm]

Удаляем шаблоны:

[simterm]

$ cd test-svc
$ rm -rf templates/* values.yaml

[/simterm]

Пишем свой values.yaml и манифест для Service:

[simterm]

$ vim -p values.yaml templates/svc.yaml

[/simterm]

В values зададим тип NodePort:

serviceType: NodePort

И опишем Service с этим типом:

---
apiVersion: v1
kind: Service 
metadata:
  name: test-svc
spec:
  type: {{ .Values.serviceType }}
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
  selector:
    app: test

Деплоим:

[simterm]

$ helm upgrade --install test-svc .

[/simterm]

Проверим тип:

[simterm]

$ kk get svc test-svc
NAME       TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
test-svc   NodePort   172.20.107.220   <none>        80:30506/TCP   3m7s

[/simterm]

Ещё раз воспроизводим ошибку – меняем в values тип на ClusterIP:

#serviceType: NodePort
serviceType: ClusterIP

Запускаем инстал, и:

[simterm]

$ helm upgrade --install test-svc .
Error: UPGRADE FAILED: cannot patch "test-svc" with kind Service: Service "test-svc" is invalid: spec.ports[0].nodePort: Forbidden: may not be used when `type` is 'ClusterIP'

[/simterm]

А теперь добавим костыль – проверяем передаваемый тип, и если он ClusterIP, то задаём nodePort == null:

---
apiVersion: v1
kind: Service 
metadata:
  name: test-svc
spec:
  type: {{ .Values.serviceType }}
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
    {{- if (eq .Values.serviceType "ClusterIP") }}
    nodePort: null
    {{- end }}
  selector:
    app: test

Повторяем деплой:

[simterm]

$ helm upgrade --install test-svc .

[/simterm]

Проверяем:

[simterm]

$ kk get svc test-svc
NAME       TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
test-svc   ClusterIP  172.20.107.220   <none>        80:30506/TCP   3m7s

[/simterm]

Готово.