Сервіси Kubernetes надають можливість створення постійних точок входу до контейнерів додатків, що працюють в подах, проте IP адреси сервісів обираються з діапазону оверлейної мережі, і тому є видимими лише в межах кластеру. Тож у разі необхідності доступу до таких додатків ззовні існують наступні варіанти:
• hostNetwork: true. Под, створений із такою опцією, матиме можливість бачити мережеві інтерфейси Kubernetes хосту, де його було запущено.
Порт такого додатку буде прослуховуватись на всіх інтерфейсах вузла. Використання цієї опції не рекомендовано, адже в цьому випадку вичерпується простір портів хост-машини, що зі зростанням кількості працюючих додатків може з легкістю призвести до конфліктів. Більш того, у разі перестворення поду, він може "переселитись" на інший вузол, що додасть складності в його пошуках. Виключенням можуть слугувати поди утиліт, котрим дійсно необхідний вищезгаданий доступ задля управлінням мережею і т.п.
influxdb 1/1 Running 0 38h 10.30.0.141 k8s-s-a
NAME STATUS ROLES AGE VERSION INTERNAL-IP
...
k8s-s-a Ready <none> 4d16h v1.23.4 192.168.1.48
tcp 0 0 127.0.0.1:4222 0.0.0.0:* LISTEN 949214/influxd
tcp6 0 0 :::8086 :::* LISTEN 949214/influxd
• hostPort: integer. Ця опція дозволяє поду активувати лише один порт на всіх інтерфейсах вузла Kubernetes. Yaml для створення такого поду буде виглядати наступним чином:
Його, як і попередній варіант, дуже не рекомендовано використовувати по тим же причинам.
• nodePort. Сервіс, створений з даною опцією, активує порт з діапазону 30000-32767 на всіх вузлах Kubernetes, який в свою чергу проксується на порт додатку. Такий сервіс і под виглядатимуть так:
kind: Service
apiVersion: v1
metadata:
name: influxdb
spec:
type: NodePort
ports:
- port: 8086
nodePort: 30000
selector:
name: influxdb
---
apiVersion: v1
kind: Pod
metadata:
name: influxdb
labels:
name: influxdb
spec:
containers:
- name: influxdb
image: influxdb
ports:
- containerPort: 8086
Після створення цих об’єктів, порт 30000 вузла Kubernetes буде переадресований в 8086 порт поду.
Цього варіанту могло би бути достатньо у разі використання окремого балансувальника попереду кластера, але це зовсім не зручно: у разі, наприклад, додавання нового вузла Kuberenetes чи додатка списки балансувальника необхідно оновити власноруч. Було б чудово звести таку роботу до мінімуму.
• LoadBalancer. Опція для сервісів Kubernetes, що має сенс на cloud-платформах AWS, Azure, CloudStack, GCE та OpenStack. Якщо описати сервіс наступним чином:
kind: Service
apiVersion: v1
metadata:
name: influxdb
spec:
type: LoadBalancer
ports:
- port: 8086
selector:
name: influxdb
Kubernetes створить випадковий NodePort на внутрішній адресі оверлейної мережі (ClusterIP), потім запитає створення балансувальника cloud-платформи із зовнішньою IP-адресою і вказаним портом. Це буде виглядати так:
$ kubectl get svc influxdb
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
influxdb 10.97.121.42 10.13.242.236 8086:30051/TCP 39s
Отже запит на IP-адресу балансувальника 10.13.242.236 і порт 8086 переадресується на внутрішню адресу кластера 10.97.121.42 і порт 30051, котрий вже після переадресується на адресу поду і порт додатка.
Мінусом цього способу є те, що, по-перше, bare-metal рішення та менш популярні cloud-провайдери не мають вбудованої підтримки функціональності LoadBalancer, а, по-друге, виділення окремої балансувальника на кожен додаток може коштувати не дешево.
• Ingress. Це ресурс кластеру Kubernetes, додатковий об’єкт, що є дещо більшим ніж попередні варіанти. Окрім адресації трафіку на кінцеві поди, Ingress також має функціонал virtual-хостингу (доступ до додатків Kubernetes по доменним іменам), балансування трафіку між подами, SSL-termination/sticky sessions та ін. Зупинимось детальніше на цьому варіанті.
Для роботи Ingress ресурсів необхідний відповідний контролер, який у разі bare-metal інсталяцій (така як наша з Kubespray чи Kubeadm) не встановлений по-замовчуванню. Є велика множина таких контролерів, серед яких хотілося б відзначити їх реалізації HAProxy, Traefik та Nginx (2 реалізації: одна від F5 Networks, а інша офіційна). У цій статті ми розглянемо лише останній варіант.
Як я вже згадував, по-замовчуванню bare-metal інсталяції не мають можливості створювати сервіси із типом LoadBalancer, адже Kubernetes її не надає. Але із цим може допомогти проект MetalLB. У такому разі LoadBalancer/ExternalIP ти сервісу забезпечить прямий доступ до додатку, не треба буде використовувати проброс порту (nodePort) на хост-ноду.
MetalLB буде також корисний для Ingress-інсталяції, адже це власне такий самий сервіс,що потребує точки входу. Кожен додаток, доступ до якого буде надаватись через Ingress, в кінцевому рахунку буде використовувати лише один LoadBalancer сервіс. Скористуймося Helm пакет для установки MetalLB:
$ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
$ chmod 700 get_helm.sh
$ ./get_helm.sh
$ helm version
version.BuildInfo{Version:"v3.8.0", GitCommit:"d14138609b01886f544b2025f5000351c9eb092e", GitTreeState:"clean", GoVersion:"go1.17.5"}
Звісно, Helm можна установити багатьма способами.
$ helm upgrade --install metallb metallb \
--repo https://metallb.github.io/metallb \
--namespace metallb-system \
--create-namespace
Опишемо конфігураціний файл для MetalLB та застосуємо його:
$ cat metallb_pool.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: first-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.70-192.168.1.80
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: example
namespace: metallb-system
$ kubectl apply -f metallb_pool.yaml
ipaddresspool.metallb.io/first-pool created
l2advertisement.metallb.io/example created
Основне на що варто звернути увагу - це перелік адрес, що будуть використовуватись у якості LoadBalancer-ів для сервісів. Я його обрав із того ж діапазону, що використовував для установки вузлів Kubernetes, але можна описувати і декілька діапазонів. Кожна із цих адрес може бути виділена для нового сервісу.
Також доречно було б зупинитись на режимах роботи MetalLB, котрих є 2: Layer 2 та BGP (потребує роутера, що підтримує цей протокол).
Принцип роботи Layer 2 режиму MetalLB наступний. MetalLB у разі зміни вузла, на котрому лежать IP-адреси LoadBalancer-ів (чи створення нового LoadBalancer-а) буде надсилати широкомовні ARP анонси (gratuitous ARP messages), що відбулись зміни і всі вузли мережі (не лише Kubernetes-вузли), виходячи із отриманих анонсів, будуть змінювати власний локальний ARP-кеш (таблиця відповідністі MAC/Ethernet та IP адрес). Тобто ARP таблиця кожного вузла буде динамічно перебудовуватись, у разі міграції адреси на інший воркер, наприклад, через падіння попереднього. Одна із адрес вказаного діапазону для LoadBalancer сервісу буде призначена на один із вузлів/воркерів Kubernetes, але не у якості IP-адреси на додатковий інтерфейс (як у випадку Corosync/Pacemaker/Heartbeat), а вузол буде просто відповідати на ARP-запити із необхідною MAC-адресою (за допомогою MetalLB speaker, що присутній на кожній ноді).
Для всіх наступних інструкцій я буду використовував вже існуючий Kubernetes кластер версії 1.23.4, котрий я установив за допомогою kubeadm:
$ kubectl get all -n metallb-system
NAME READY STATUS RESTARTS AGE
pod/metallb-controller-777cbcf64f-m76qh 1/1 Running 0 43s
pod/metallb-speaker-6k6vq 1/1 Running 0 43s
pod/metallb-speaker-f5t76 1/1 Running 0 43s
pod/metallb-speaker-gqxkj 1/1 Running 0 43s
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/metallb-speaker 3 3 3 3 3 kubernetes.io/os=linux 43s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/metallb-controller 1/1 1 1 43s
NAME DESIRED CURRENT READY AGE
replicaset.apps/metallb-controller-777cbcf64f 1 1 1 43s
Тепер проінсталюємо Ingress:
$ helm upgrade --install ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
--namespace ingress-nginx \
--create-namespace
Перевіримо чи необхідні ресурси створились:
# kubectl get all -n ingress-nginx
NAME READY STATUS RESTARTS AGE
pod/ingress-nginx-controller-5b6f946f99-62hq2 1/1 Running 0 50s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/ingress-nginx-controller LoadBalancer 10.109.253.43 192.168.1.70 80:31543/TCP,443:30438/TCP 50s
service/ingress-nginx-controller-admission ClusterIP 10.105.215.196 <none> 443/TCP 50s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/ingress-nginx-controller 1/1 1 1 50s
NAME DESIRED CURRENT READY AGE
replicaset.apps/ingress-nginx-controller-5b6f946f99 1 1 1 50s
Тут особливо має зацікавити адреса Ingress контролера 192.168.1.70, котру забезпечив MetalLB. Важливо одразу зрозуміти, що MetalLB - це не балансувальник в класичному розумінні цього слова, адже трафік буде надходити лише на один із вузлів кластеру, котрий буде відповідати за цю адресу, і лише потім він буде переадресований на сервіс, де і буде відбуватись його розподіл по кінцевим подам.
Наразі створимо якийсь сервіс із використанням Ingress:
$ vim app1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app1
spec:
replicas: 2
selector:
matchLabels:
app: app1
template:
metadata:
labels:
app: app1
spec:
containers:
- name: app1
image: dockersamples/static-site
env:
- name: AUTHOR
value: app1
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: appsvc1
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: app1
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app1-ingress
spec:
rules:
- host: app1.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: appsvc1
port:
number: 80
ingressClassName: nginx
$ kubectl apply -f app1.yaml
$ kubectl get ing
NAME CLASS HOSTS ADDRESS PORTS AGE
app1-ingress nginx app1.example.com 192.168.1.70 80 86s
Тепер якщо прив'язати домен app1.example.com до 192.168.1.70, наприклад через hosts, в браузері відкриється відповідна сторінка:
Із TLS-з'єднанням все приблизно так само. Всі ресурси окрім ingress виглядатимуть аналогічно:
$ vim app2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app2
spec:
replicas: 2
selector:
matchLabels:
app: app2
template:
metadata:
labels:
app: app2
spec:
containers:
- name: app2
image: dockersamples/static-site
env:
- name: AUTHOR
value: app2
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: appsvc2
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: app2
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app2-ingress
spec:
tls:
- hosts:
- app2.example.com
# This assumes tls-secret exists and the SSL
# certificate contains a CN for app2.example.com
secretName: tls-secret
ingressClassName: nginx
rules:
- host: app2.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
# This assumes appsvc2 exists and routes to healthy endpoints
service:
name: appsvc2
port:
number: 80
Але також треба завантажити сертифікат-ключ, на який ми щойно зробили посилання:
$ kubectl create secret tls tls-secret --key tls.key --cert tls.crt
Для генерації самопідписного сертифікату можна використати наступну команду (якщо це необхідно):
$ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=*.example.com/O=nginxsvc"
Власне все, можна запускати створення нового сервісу і інгреса для нього:
$ kubectl apply -f app2.yaml
$ kubectl get ing
NAME CLASS HOSTS ADDRESS PORTS AGE
...
app2-ingress nginx app2.example.com 192.168.1.70 80, 443 3h12m
Як і минулого разу новий домен також треба прив'язати до ExternalIP Ingress-контролера, після чого можна спостерігати роботу додатка:
Перевіривши локальний arp-кеш, можна дійсно пересвідчитись у тому на який вузол вказує 192.168.1.70:
$ arp -n
Address HWtype HWaddress Flags Mask Iface
192.168.1.1 ether cc:5d:4e:4e:4a:78 C wlp0s20f3
192.168.1.70 ether 08:00:27:38:70:31 C wlp0s20f3
192.168.1.45 ether 08:00:27:38:70:31 C wlp0s20f3
192.168.1.41 ether b8:70:f4:ad:28:f1 C wlp0s20f3
Отже, виявляється цей IP обслуговує мережева карта вузла, що у моєму випадку виступає як control plane.
Також можемо переглянути nginx-конфігураційний файл, котрим оперує Ingress:
$ kubectl get pods -n ingress-nginx
NAME READY STATUS RESTARTS AGE
ingress-nginx-controller-5b6f946f99-62hq2 1/1 Running 0 3d
$ kubectl exec ingress-nginx-controller-5b6f946f99-62hq2 -n ingress-nginx -- cat /etc/nginx/nginx.conf
Так виглядає секція для нашого домену app2.example.com:
server {
server_name app2.example.com ;
listen 80 ;
listen 443 ssl http2 ;
set $proxy_upstream_name "-";
ssl_certificate_by_lua_block {
certificate.call()
}
location / {
set $namespace "default";
set $ingress_name "app2-ingress";
set $service_name "appsvc2";
set $service_port "80";
set $location_path "/";
set $global_rate_limit_exceeding n;
rewrite_by_lua_block {
lua_ingress.rewrite({
force_ssl_redirect = false,
ssl_redirect = true,
force_no_ssl_redirect = false,
preserve_trailing_slash = false,
use_port_in_redirects = false,
...
})
balancer.rewrite()
plugins.run()
}
...
proxy_pass http://upstream_balancer;
proxy_redirect off;
}
}
Це власне все, про що хотілось написати. Надіюсь Ingress в Kubernetes уже набув певної стабільності і необхідності знову переписувати всю статтю не буде.
Посилання:
https://habr.com/en/company/southbridge/blog/358824/
https://kubernetes.io/docs/concepts/services-networking/ingress/
https://alesnosek.com/blog/2017/02/14/accessing-kubernetes-pods-from-outside-of-the-cluster/
https://kubernetes.github.io/ingress-nginx/
https://kubernetes.github.io/ingress-nginx/examples/tls-termination/
https://kubernetes.github.io/ingress-nginx/deploy/
https://kubernetes.github.io/ingress-nginx/deploy/baremetal/
https://itnext.io/configuring-routing-for-metallb-in-l2-mode-7ea26e19219e
https://metallb.universe.tf/
https://metallb.universe.tf/installation/
Немає коментарів:
Дописати коментар