Kubernetes v1.36: Застарівання та видалення Service ExternalIPs
Поле .spec.externalIPs для Service було ранньою спробою забезпечити функціональність, подібну до балансувальника навантаження в хмарі, для кластерів без хмари. На жаль, API передбачає, що кожному користувачу в кластері повністю довіряють, і в будь-якій ситуації, коли це не так, це відкриває можливості для різних зловживань у системі безпеки, як описано в CVE-2020-8554.
Починаючи з Kubernetes 1.21, команда проєкту Kubernetes рекомендувала усім користувачам вимкнути .spec.externalIPs. Щоб спростити цей процес, у Kubernetes також було додано контролер допуску (DenyServiceExternalIPs), який можна увімкнути для цього. На той час SIG Network вважала, що блокування цієї функції є занадто значною зміною, що може стати порушенням сумісності, щоб її розглядати.
Однак проблеми безпеки все ще залишаються, і як проєкт ми все більше незадоволені станом "небезпечний зазвичай" для цієї функції. Крім того, тепер існує кілька кращих альтернатив для кластерів без хмари, які бажають мати функціональність, подібну до балансувальника навантаження.
В результаті, поле .spec.externalIPs для Service тепер офіційно визнано застарілим в Kubernetes 1.36. Ми очікуємо, що в майбутньому мінорному випуску Kubernetes воно буде видалено з реалізації цієї поведінки в kube-proxy, а критерії відповідності Kubernetes будуть оновлені, щоб вимагати, щоб відповідні реалізації не надавали таку підтримку.
Примітка щодо термінології та того, що не застаріло
Фраза external IP має дещо перевантажене значення в Kubernetes:
API Service має поле
.spec.externalIPs, яке можна використовувати для додавання додаткових IP-адрес, на які Service буде відповідати.API Node має поле
.status.addresses, яке може містити адреси кількох різних типів, один з яких називаєтьсяExternalIP.Інструмент
kubectl, при виводі інформації про Service типу LoadBalancer у стандартному форматі виводу, покаже IP-адресу балансувальника навантаження в стовпці з заголовкомEXTERNAL-IP.
Ця інформація стосується саме першого з них. Якщо ви не задаєте поле externalIPs у жодному зі своїх Сервісів, то це вас не стосується.
Проте, як запобіжний захід, ви все одно можете увімкнути контролер допуску DenyServiceExternalIPs, щоб заблокувати будь-яке майбутнє використання поля externalIPs.
Альтернативи для externalIPs
Якщо ви використовуєте .spec.externalIPs, то існує кілька альтернатив.
Розглянемо Service, як показано нижче:
apiVersion: v1
kind: Service
metadata:
name: my-example-service
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: my-example-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
externalIPs:
- "192.0.2.4"
Використання вручну керованих LoadBalancer Services замість externalIPs
Найпростіший (але також найгірший) варіант — просто перейти від використання externalIPs до використання сервісу type: LoadBalancer і призначення IP-адреси балансувальника навантаження вручну. Це, по суті, точно те ж саме, що і externalIPs, з однією важливою відмінністю: IP-адреса балансувальника навантаження є частиною .status сервісу, а не його .spec, і в кластері з увімкненим RBAC її зазвичай не можна редагувати звичайним користувачам. Таким чином, ця заміна для externalIPs буде доступна лише користувачам, яким адміністратори надали дозволи (хоча ці користувачі тоді будуть повністю уповноважені повторити CVE-2020-8554; все одно не буде жодних додаткових перевірок, щоб переконатися, що один користувач не краде IP-адреси іншого користувача тощо).
Через те, як працює .status у Kubernetes, спочатку потрібно створити Service без IP-адреси балансувальника навантаження, а потім додати IP як другий крок:
$ cat loadbalancer-service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-example-service
spec:
# prevent any real load balancer controllers from managing this service
# by using a non-existent loadBalancerClass
loadBalancerClass: non-existent-class
type: LoadBalancer
selector:
app.kubernetes.io/name: my-example-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
$ kubectl apply -f loadbalancer-service.yaml
service/my-example-service created
$ kubectl patch service my-example-service --subresource=status --type=merge -p '{"status":{"loadBalancer":{"ingress":[{"ip":"192.0.2.4"}]}}}'
Використання контролера балансування навантаження, що працює не у хмарі
Хоча сервіси LoadBalancer спочатку були розроблені для підтримки хмарних балансувальників навантаження, Kubernetes також може підтримувати їх на не хмарних платформах за допомогою стороннього контролера балансування навантаження, такого як MetalLB. Це вирішує проблеми безпеки, повʼязані з externalIPs, оскільки адміністратор може налаштувати, які діапазони IP-адрес контролер призначатиме сервісам, і контролер забезпечить, щоб два сервіси не могли одночасно використовувати одну й ту ж IP-адресу.
Отже, наприклад, після встановлення та налаштування MetalLB, адміністратор кластера може налаштувати пул IP-адрес для використання в кластері:
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: production
namespace: metallb-system
spec:
addresses:
- 192.0.2.0/24
autoAssign: true
avoidBuggyIPs: false
Після цього користувач може створити сервіс type: LoadBalancer, і MetalLB обробить призначення IP-адреси. MetalLB навіть підтримує застаріле поле loadBalancerIP у Service, тому кінцевий користувач може запитати конкретну IP-адресу (за умови, що вона доступна) для зворотної сумісності з підходом externalIPs, замість того, щоб отримати її випадковим чином:
apiVersion: v1
kind: Service
metadata:
name: my-example-service
spec:
type: LoadBalancer
selector:
app.kubernetes.io/name: my-example-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
loadBalancerIP: "192.0.2.4"
Подібні підходи працюватимуть і з іншими контролерами балансування навантаження. Цей підхід дозволяє адміністраторам кластера контролювати, які IP-адреси призначаються, замість того щоб це робили користувачі.
Використання Gateway API
Ще одним потенційним рішенням є використання реалізації Gateway API.
Gateway API дозволяє адміністраторам кластера визначати ресурс Gateway, до якого можна прикріпити IP-адресу через поле .spec.addresses. Оскільки ресурси Gateway призначені для керування адміністраторами кластера, можна встановити правила RBAC, щоб лише привілейовані користувачі могли ними керувати.
Приклад того, як це може виглядати:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: example-gateway
spec:
gatewayClassName: example-gateway-class
addresses:
- type: IPAddress
value: "192.0.2.4"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: example-route
spec:
parentRefs:
- name: example-gateway
rules:
- backendRefs:
- name: example-svc
port: 80
---
apiVersion: v1
kind: Service
metadata:
name: example-svc
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: example-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
Проєкт Gateway API — це нове покоління API-інтерфейсів Kubernetes для Ingress, балансування навантаження та Service Mesh. Gateway API було створено з метою усунення недоліків ресурсів Service та Ingress, що робить його надзвичайно надійним і стабільним рішенням, яке наразі активно розвивається.
План дій щодо припинення підтримки externalIPs
Орієнтовний план дій щодо припинення підтримки виглядає наступним чином:
- З випуском Kubernetes 1.36 поле було визнане застарілим; Kubernetes тепер видає попередження, коли користувач використовує це поле
- Приблизно через рік (v1.40 найраніше) підтримка
.spec.externalIPsбуде вимкнена в kube-proxy, але користувачі матимуть можливість повторно ввімкнути її, якщо їм знадобиться більше часу для міграції - Приблизно через ще один рік (v1.43 найраніше) підтримка буде повністю вимкнена; користувачі не матимуть можливості повторно ввімкнути її