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

Автор: | 04/26/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

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

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

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

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

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

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

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

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'

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

Решения

kubectl apply --force

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

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

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

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

kubectl edit service

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

kubectl edit svc test-svc

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

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

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

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

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

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

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

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

helm create test-svc
Creating test-svc

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

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

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

vim -p values.yaml templates/svc.yaml

В 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

Деплоим:

helm upgrade --install test-svc .

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

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

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

#serviceType: NodePort
serviceType: ClusterIP

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

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'

А теперь добавим костыль – проверяем передаваемый тип, и если он 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

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

helm upgrade --install test-svc .

Проверяем:

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

Готово.