Kubernetes — это колоссальный зверь. Вам необходимо понять множество различных концепций, прежде чем он начнет приносить пользу. Когда все будет готово, вы, вероятно, захотите открыть доступ к некоторым стручкам за пределами кластера. Kubernetes предоставляет различные способы сделать это: Я опишу их в этом посте.
Настройка
Для демонстрации я буду использовать Kind:
kind — это инструмент для запуска локальных кластеров Kubernetes с помощью «узлов» контейнеров Docker. kind был разработан в первую очередь для тестирования самого Kubernetes, но может быть использован для локальной разработки или CI.
Я буду использовать кластер из двух узлов:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30800 # 1
hostPort: 30800 # 1
- role: worker # 2
- role: worker # 2
- Перенаправление портов, чтобы справиться с уровнем Docker VM на Mac (см. ниже).
- Два узла
kind create cluster -- config kind.yml
Далее нам нужен контейнер. Он не должен просто запускаться и останавливаться: Используем последний образ Nginx, доступный на момент написания этой статьи.
В Kind мы должны предварительно загрузить изображения, чтобы они были доступны.
docker pull nginx:1.23
kind load docker-image nginx:1.23
Наконец, я псевдоним kubetcl
на k
:
alias k=kubectl
Отсутствие внешнего доступа по умолчанию
По умолчанию доступ к внешней части кластера не предоставляется.
k create deployment nginx --image=nginx:1.23 # 1
- Создание развертывания одной капсулы
Давайте проверим, все ли в порядке:
k get pods
NAME READY STATUS RESTARTS AGE
nginx-6c7985744b-c7cpl 1/1 Running 0 67s
У стручка есть IP, но мы не можем связаться с ним за пределами кластера.
k get pod nginx-6c7985744b-c7cpl --template '{{.status.podIP}}'
10.244.1.2
Давайте подтвердим IP, запустив оболочку внутри самого стручка:
k exec -it nginx-6c7985744b-c7cpl -- /bin/bash
hostname -I
10.244.1.2
Мы не можем успешно пропинговать этот IP за пределами кластера; это внутренний IP.
Внутренние IP-адреса не являются стабильными
Мы создали развертывание. Следовательно, если мы удалим одиночную капсулу, Kubernetes обнаружит это и создаст новую, благодаря своим возможностям самовосстановления.
k delete pod nginx-6c7985744b-c7cpl
k get pods
NAME READY STATUS RESTARTS AGE
nginx-6c7985744b-c6f92 1/1 Running 0 71s
Давайте проверим его новый IP:
k exec -it nginx-6c7985744b-c6f92 -- /bin/bash
hostname -I
10.244.2.2
Kubernetes создал новый pod, но его IP отличается от IP удаленного pod. Мы не можем полагаться на этот IP для связи между pod и pod. Действительно, мы никогда не должны напрямую использовать IP-адрес стручка.
Для решения этой проблемы Kubernetes предоставляет объект Service
. Сервисы представляют собой стабильный интерфейс перед стручками. Kubernetes управляет связками между сервисом и его pod(s). Он связывает новые стручки и отвязывает удаленные.
Давайте откроем существующее развертывание с помощью сервиса:
k expose deployment nginx --type=ClusterIP --port=8080
ClusterIP
: ВыставляетService
на внутреннем IP-адресе кластера. Выбор этого значения делаетService
доступным только изнутри кластера. Это типServiceType
по умолчанию.— Службы публикации (ClusterIP)
k get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 9m47s
nginx ClusterIP 10.96.93.97 <none> 8080/TCP 4s
С этого момента можно получить доступ к капсуле через ClusterIP
службы.
Все настроено для доступа внутри кластера. Снаружи это пока невозможно. Так почему же мы будем использовать ClusteIP
? Это очень полезно для сервисов, которые вы не хотите открывать внешнему миру: базы данных, узлы ElasticSearch, узлы Redis и т.д.
Доступ к стручкам
Доступ к стручкам извне кластера — вот когда все становится интересным.
Сначала нам нужно удалить существующее развертывание и службу.
k delete deployment nginx
k delete svc nginx
Самый простой способ разрешить внешний доступ — изменить тип службы на NodePort
.
NodePort
: Открывает службу на IP каждого узла через статический порт (NodePort
). СлужбаClusterIP
, к которой направляется СлужбаNodePort
, создается автоматически. Вы сможете связаться со службойNodePort
извне кластера, запросив<NodeIP>:<NodePort>
.— Службы публикации (NodePort)
Я хочу, чтобы pod возвращал свой IP и имя хоста для демонстрации. Мы должны перейти от командной строки к специальному файлу манифеста Kubernetes, потому что нам нужно настроить Nginx. В результате мы получим то же состояние, что и при использовании командной строки, с добавленной конфигурацией Nginx:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.23
volumeMounts: # 1
- name: conf
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
readOnly: true
volumes: # 1
- name: conf
configMap:
name: nginx-conf
items:
- key: nginx.conf
path: nginx.conf
---
apiVersion: v1 # 1
kind: ConfigMap
metadata:
name: nginx-conf
data:
nginx.conf: |
events {
worker_connections 1024;
}
http {
server {
location / {
default_type text/plain;
return 200 "host: $hostnamenIP: $server_addrn";
}
}
}
---
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
type: NodePort # 2
ports:
- port: 80
nodePort: 30800
- Переопределить конфигурацию по умолчанию для возврата имени хоста и IP-адреса
Давайте применим конфигурацию:
k apply -f deployment.yml
Обратите внимание, что я работаю на Mac; следовательно, контейнер VM находится вокруг Docker, как в Windows. По этой причине Kind должен перенаправить VM на хост. Пожалуйста, ознакомьтесь с документацией о том, как этого добиться.
После того как Kubernetes запланировал pod, мы можем получить к нему доступ через настроенный порт:
curl localhost:30800
host: nginx-b69d8877c-p2s79
IP: 10.244.2.2
Путь запроса выглядит следующим образом (несмотря на слой VM на Mac/Windows):
- Запрос
curl
идет к любому узлу.
Обратите внимание, что при настройке облачного провайдера вы можете адресовать запрос любому узлу Kubernetes, на котором находится часть развертывания pod. При локальной настройке мы выбираем localhost
и позволяем уровню VM нацеливаться на узел.
- Узел видит порт
30800
и пересылает запрос службеNodePort
с соответствующим портом. - Сервис пересылает запрос в pod, переводя порт с
30800
на80
.
Теперь давайте увеличим количество стручков в нашем развертывании до двух:
k scale deployment nginx --replicas=2
k get pods -o wide
Kubernetes сбалансирует кластер таким образом, чтобы каждый pod располагался на отдельном узле:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-b69d8877c-w7db4 1/1 Running 0 129m 10.244.2.2 kind-worker <none> <none>
nginx-b69d8877c-z5kqs 1/1 Running 0 38m 10.244.1.2 kind-worker2 <none> <none>
На какой узел/под будет отправляться запрос?
while true; do curl localhost:30800; done
host: nginx-b69d8877c-w7db4
IP: 10.244.2.2
host: nginx-b69d8877c-w7db4
IP: 10.244.2.2
host: nginx-b69d8877c-z5kqs
IP: 10.244.1.2
host: nginx-b69d8877c-z5kqs
IP: 10.244.1.2
host: nginx-b69d8877c-w7db4
IP: 10.244.2.2
host: nginx-b69d8877c-w7db4
IP: 10.244.2.2
Служба балансирует запросы между всеми доступными узлами.
Абстракция балансировки нагрузки
NodePort
позволяет обращаться к любому узлу кластера. LoadBalancer
— это фасад над кластером, который выполняет… балансировку нагрузки. Это абстрактный объект, предоставляемый Kubernetes; каждый облачный провайдер реализует его по-разному, в зависимости от своих особенностей, хотя поведение одинаково.
LoadBalancer: Выставляет службу на внешний рынок, используя балансировщик нагрузки облачного провайдера. NodePort и ClusterIP Сервиса, к которым маршрутизируется внешний балансировщик нагрузки, создаются автоматически.
— Службы публикации (LoadBalander)
Во-первых, нам нужна реализация LoadBalancer
. Kind имеет встроенную интеграцию с MetalLB:
MetalLB — это реализация балансировщика нагрузки для пустых кластеров Kubernetes, использующая стандартные протоколы маршрутизации.
— MetalLB
Нет смысла пересказывать отличную документацию Kind о том, как установить MetalLB. Мы можем обновить манифест соответствующим образом:
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
type: LoadBalancer
ports:
- port: 80
targetPort: 30800
Давайте посмотрим на сервисы:
k get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 4h37m
nginx LoadBalancer 10.96.216.126 127.0.0.240 8080:31513/TCP 82m # 1
- У него есть внешний IP!
К сожалению, как я уже упоминал выше, на Mac (и Windows) Docker работает в виртуальной машине. Следовательно, мы не можем получить доступ к «внешнему» IP с хоста. Читатели с соответствующими системами Linux должны получить к нему доступ.
В зависимости от облачного провайдера, LoadBalancer
может предоставлять дополнительные проприетарные возможности.
Ingress, когда вам нужна маршрутизация
Ingress
фокусируется на маршрутизации запросов к сервисам в кластере.
Он разделяет некоторые аспекты с LoadBalancer
:
- Он перехватывает входящий трафик
- Это зависит от реализации, и реализации предлагают различные возможности, например, Nginx, Traefik, HAProxy и т.д.
Однако это не Service
.
Ingress открывает HTTP и HTTPS маршруты извне кластера к сервисам внутри кластера. Маршрутизация трафика контролируется правилами, определенными на ресурсе Ingress.
— Что такое Ingress?
Установка Ingress
во многом зависит от реализации. Единственным общим фактором является то, что она включает CRDs.
Для демонстрации я буду использовать контроллер Apache APISIX Ingress. Я не буду пересказывать инструкции по установке. Единственное отличие заключается в установке NodePort
в заданное значение:
helm install apisix apisix/apisix
--set gateway.type=NodePort
--set gateway.http.nodePort=30800
--set ingress-controller.enabled=true
--namespace ingress-apisix
--set ingress-controller.config.apisix.serviceNamespace=ingress-apisix
Обратите внимание, что хотя в документации упоминается Minikube, она применима к любому локальному кластеру, включая Kind.
Следующие службы должны быть доступны в пространстве имен ingress-apisix
:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
apisix-admin ClusterIP 10.96.98.159 <none> 9180/TCP 22h
apisix-etcd ClusterIP 10.96.80.154 <none> 2379/TCP,2380/TCP 22h
apisix-etcd-headless ClusterIP None <none> 2379/TCP,2380/TCP 22h
apisix-gateway NodePort 10.96.233.74 <none> 80:30800/TCP 22h
apisix-ingress-controller ClusterIP 10.96.125.41 <none> 80/TCP 22h
Для демонстрации у нас будет два сервиса: каждый из них будет иметь базовое развертывание одного стручка. Запрос /left
попадет на один сервис и вернет left
; /right
, right
.
Давайте обновим топологию соответствующим образом:
apiVersion: apps/v1
kind: Deployment
metadata:
name: left
labels:
app: left
spec:
replicas: 1
selector:
matchLabels:
app: left
template:
metadata:
labels:
app: left
spec:
containers:
- name: nginx
image: nginx:1.23
volumeMounts:
- name: conf
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
readOnly: true
volumes:
- name: conf
configMap:
name: left-conf
items:
- key: nginx.conf
path: nginx.conf
---
apiVersion: v1
kind: Service
metadata:
name: left
spec:
selector:
app: left
ports:
- port: 80
---
apiVersion: v1
kind: ConfigMap
metadata:
name: left-conf
data:
nginx.conf: |
events {
worker_connections 1024;
}
http {
server {
location / {
default_type text/plain;
return 200 "leftn";
}
}
}
Приведенный выше фрагмент описывает только путь left
; аналогичная конфигурация должна быть и для пути right
.
На данном этапе мы можем создать конфигурацию для маршрутизации путей к службам:
apiVersion: apisix.apache.org/v2beta3 # 1
kind: ApisixRoute # 1
metadata:
name: apisix-route
spec:
http:
- name: left
match:
paths:
- "/left"
backends:
- serviceName: left # 2
servicePort: 80 # 2
- name: right
match:
paths:
- "/right"
backends:
- serviceName: right # 3
servicePort: 80 # 3
- Использовать CRD
ApisixRoute
, созданный при установке - Перенаправить запрос на службу
left
- Переадресовать запрос на службу
right
.
Вот как это должно выглядеть. Обратите внимание, что я решил представить только левый
путь и один узел, чтобы не перегружать диаграмму.
Чтобы проверить, что все работает, давайте снова выполним скручивание.
curl localhost:30800
{"error_msg":"404 Route Not Found"}
Это хороший знак: APISIX отвечает.
Теперь мы можем попробовать скрутить путь right
, чтобы убедиться, что он будет перенаправлен на соответствующий блок.
curl localhost:30800/right
right
/left
, работает также.
Заключение
В этой заметке я описал несколько способов доступа к стручкам вне кластера: NodePort
и LoadBalancer
сервисы и Ingress
. Что касается Ingress
, вы могли заметить, что объект ApisixRoute
является проприетарным CRD. Чтобы избежать этого, Kubernetes стремится предоставить абстракцию; CNCF работает над проектом Gateway API.
Я опишу его в одном из следующих постов.
Полный исходный код этой заметки можно найти на GitHub:
ajavageek / k8s-access
Идем дальше:
- Сервисы, балансировка нагрузки и сетевое взаимодействие
- Службы Kubernetes простое визуальное объяснение
- Kubernetes NodePort vs LoadBalancer vs Ingress? Когда что использовать?
- Понимание Kubernetes LoadBalancer vs NodePort vs Ingress
- В чем разница между типами служб ClusterIP, NodePort и LoadBalancer в Kubernetes?
- Обзор контроллера Kubernetes Ingress
- Начало работы с Apache APISIX Ingress Controller
- FAQ по Apache APISIX Ingress Controller
Первоначально опубликовано на A Java Geek 7 августаth, 2022