Привет всем,
Как уже стало понятным из описания, в этой заметке разбираемся с предоставлением доступа для внешних клиентов на сервисы запущенные в кластере.
В качестве тестового стенда у меня поднят кластер из 3х нод. Две ноды в роли воркеров, и одна нода в роли мастер-ноды.
Создаем деплоймент
Для тестирования доступов, предварительно развернем тестовое приложение. В нашем случаи это будем обычный деплоймент, который поднимает 3 экземпляра nginx. При запросах на веб-сервера будет отдаваться ответ с текущем именем пода.
Для начала пишем конфиг для nginx:
[root@k8s-node1 nginx-app]# cat nginx-app.conf
---
server {
listen 80 default_server;
server_name _;
default_type text/plain;
location / {
return 200 'Hi from pod: $hostname.\n';
}
}
Как видно из описания, у нас есть единственный локейшен, при попадании на который будет отдаваться 200 http-код с текстовым приветом.
Сохраняемся, и создаем новый конфигмапу из файла:
[root@k8s-node1 nginx-app]# kubectl create cm nginx-conf --from-file=./nginx-app.conf
Далее напишем новый деплоймент:
[root@k8s-node1 nginx-app]# vim nginx-app-dp.yml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-ing
spec:
replicas: 3
selector:
matchLabels:
app: nginx-ing
strategy:
rollingUpdate:
maxSurge: 30%
maxUnavailable: 30%
type: RollingUpdate
template:
metadata:
labels:
app: nginx-ing
spec:
containers:
- image: nginx:1.20
name: nginx
ports:
- containerPort: 80
readinessProbe:
failureThreshold: 3
successThreshold: 1
periodSeconds: 10
timeoutSeconds: 3
httpGet:
port: 80
path: /
livenessProbe:
failureThreshold: 3
successThreshold: 1
periodSeconds: 10
timeoutSeconds: 3
httpGet:
port: 80
path: /
resources:
requests:
cpu: 200m
memory: 250Mi
limits:
cpu: 200m
memory: 250Mi
volumeMounts:
- name: nginx-conf
mountPath: /etc/nginx/conf.d/
volumes:
- name: nginx-conf
configMap:
name: nginx-conf
Как можно понять из деплоймента, поднимается 3 экземпляра с nginx версией 1.20. В контексте volumes, подключаем ранее созданный конфигмап. Затем монтируем его во внутрь пода.
Применяем наш деплоймент к кластеру:
[root@k8s-node1 nginx-app]# kubectl apply -f nginx-app-dp.yml
deployment.apps/nginx-ing created
Проверяем статус нашего деплоймента:
[root@k8s-node1 nginx-app]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-ing-7944c5499c-46529 1/1 Running 0 11s
nginx-ing-7944c5499c-7dmdd 1/1 Running 0 11s
nginx-ing-7944c5499c-7m2s6 1/1 Running 0 11s
Отлично, наше импровизированное приложение поднято.
Доступ через NodePort
Самый простой и быстрый способ открыть доступ на приложение, можно реализовать через NodePort сервис. При создании такого типа сервиса на всех нодах открывается рандомный порт, в диапазоне 30000-32767. За счет NAT-преобразований, наши запросы на выделенные порт пробрасываются на внутренний порт нашего сервиса.
Давайте напишем манифест для NodePort:
[root@k8s-node1 nginx-app]# vim nginx-app-nodeport.yml
---
apiVersion: v1
kind: Service
metadata:
name: nginx-svc-front
spec:
selector:
app: nginx-ing
ports:
- port: 80
nodePort: 30080
name: http
type: NodePort
Из основного, в спецификации сервиса мы указываем селектр, по которому будут определены наши end-поинты. Следом описываем порт, в данном случаи мы биндим статический порт 30080. Запросы на этот порт будут проксироваться на 80 порт подов.
Применяем манифест:
[root@k8s-node1 nginx-app]# kubectl apply -f nginx-app-nodeport.yml
service/nginx-svc-front created
Пролистим все сервисы:
[root@k8s-node1 nginx-app]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.233.0.1 <none> 443/TCP 2d3h
nginx-svc-front NodePort 10.233.51.104 <none> 80:30080/TCP 18s
Курлом проверим работу нашего сервиса:
[root@k8s-node1 nginx-app]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-ing-7944c5499c-46529 1/1 Running 0 130m
nginx-ing-7944c5499c-7dmdd 1/1 Running 0 130m
nginx-ing-7944c5499c-7m2s6 1/1 Running 0 130m
---
[root@k8s-node1 nginx-app]# curl http://10.200.70.11:30080
Hi from pod: nginx-ing-7944c5499c-7dmdd.
[root@k8s-node1 nginx-app]# curl http://10.200.70.11:30080
Hi from pod: nginx-ing-7944c5499c-7m2s6.
[root@k8s-node1 nginx-app]# curl http://10.200.70.11:30080
Hi from pod: nginx-ing-7944c5499c-46529.
В curl-запросе я указываю ip воркера вместе с портом 30080.
На схеме все будет устроено так: Все же не рекомендуется использовать в продакшене такой способ доступа к нашим приложением. Так как на один порт мы можем повесить только один сервис, и в случаи если мы открывать доступ на множество наших сервисов, получается вагон и маленькая тележка множества портов, которые дают нам много потенциальных уязвимостей.
Хотя как временное решение или для тестов такой вариант годится. Также хочу отметить, что некоторые таким способом открывают доступ к кластеру монги или к кластеру ДБ.
Доступ через Ingress
Подробно про ингрессы мы разбирались в прошлых заметках, все эксперименты там проделывались в разрезе minicube. В этой итерации будет также использовать nginx ингресс контроллер, давайте его заинсталим:
[root@k8s-node1 nginx-app]# kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.1/deploy/static/provider/baremetal/deploy.yaml
И проверяем состояние подов:
[root@k8s-node1 nginx-app]# kubectl get po -n ingress-nginx
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-s2w5m 0/1 Completed 0 2m19s
ingress-nginx-admission-patch-k46dr 0/1 Completed 0 2m19s
ingress-nginx-controller-5c778bffff-7gkmw 1/1 Running 0 2m19s
В документации описывается несколько способов использовая nginx-контроллера, рассмотрим распространненые случаи это через использование NodePort сервис или же дополнительный компонент Metallb.
Ingress через NodePort
Для доступа к нашему деплойменту со стороны ingress, создадим новый сервис типа - ClusterIP. Который используется как правило, по умолчанию. Когда одному нашему сервису нужно дать доступ к другому сервису, в разрезе только одного кластера.
Пишем деплоймент сервиса:
[root@k8s-node1 nginx-app]# vim nginx-app-clusterip.yml
---
apiVersion: v1
kind: Service
metadata:
name:
nginx-app-svc
spec:
selector:
app: nginx-ing
ports:
- port: 80
targetPort: 80
name: http
type: ClusterIP
Затем применяем манифест:
[root@k8s-node1 nginx-app]# kubectl apply -f nginx-app-clusterip.yml
Думаю из этого манифеста понятно, что мы также выбираем по селектору наши поды и пробрасываем внешний 80 порт во внутрь пода.
Теперь создадим манифест ингресс контроллера:
[root@k8s-node1 nginx-app]# vim nginx-app-ingress.yml
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-nginx-app
spec:
rules:
- host: nginx-service.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-app-svc
port:
number: 80
ingressClassName: nginx
В данном манифесте мы описываем правило при обращении на хост - nginx-service.local. Все обращения будут отправлены на ClusterIP сервис - nginx-app-svc.
Листим объекты ингресов:
[root@k8s-node1 nginx-app]# kubectl get ing
NAME CLASS HOSTS ADDRESS PORTS AGE
nginx-ingress nginx nginx-service.local 80 30s
На этом все, можем проверять доступность нашего сервиса. При установке ingress-контролера, сервис NodePort будет создан из манифеста автоматом.
Смотрим на какие порты можем подключиться:
[root@k8s-node1 nginx-app]# kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller NodePort 10.233.33.49 <none> 80:30210/TCP,443:30416/TCP 157m
ingress-nginx-controller-admission ClusterIP 10.233.24.42 <none> 443/TCP 157m
Из вывода понятно, что http поднят на порту 30210.
Теперь мы можем постучаться на любую ноду:
[root@k8s-node1 nginx-app]# curl http://10.200.70.13:30210/ -H 'Host: nginx-service.local'
Hi from pod: nginx-ing-7944c5499c-7m2s6.
[root@k8s-node1 nginx-app]# curl http://10.200.70.12:30210/ -H 'Host: nginx-service.local'
Hi from pod: nginx-ing-7944c5499c-46529.
Отлично все работает.
Использование подобной схемы, подразумевает что у вас есть какие-то внешние балансировщики. И имеет место быть такое мнение, что использование ingress поверх nodeport привносит дополнительный оверхед за счет NAT-преобразований.
На обновленной схеме, взаимодействие объектов будет таким:
Ingress + Metallb
Доступ к сервису можно предоставить за счет установки встроенного в кластер балансировщика - MetalLB. В режиме работы L2, Metallb решает проблему предоставления доступа к сервисам из локальной сети, путем назначения ноде дополнительного ip-адреса и привязки его к сервису.
В нашем текущем кейсе, мы будем предоставлять доступ к ingress-y. Для его нужно:
- Установить в кластер Metallb,
- Создать ресурсы - IPAddressPool и L2Advertisement, которые определяют пулл ip-адресов, и режим работы Metallb.
- Ну и создать сервис типа - Loadbalancer.
Если же у вас kube-proxy работает в режиме ipvs
, нужно в его настройках разрешить ARP.
[root@k8s-node1]# kubectl get configmap kube-proxy -n kube-system -o yaml | sed -e "s/strictARP: false/strictARP: true/" | kubectl apply -f - -n kube-system
Далее применяем манифест:
[root@k8s-node1]# kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.10/config/manifests/metallb-native.yaml
Создаем объекты, описывающие режим работы MetalLB и адресный пул:
[root@k8s-node1]# vim metall-lb.yml
---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: default
namespace: metallb-system
spec:
addresses:
- 10.200.70.211-10.200.70.213
autoAssign: true
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: default
namespace: metallb-system
spec:
ipAddressPools:
- default
В объекте IPAddressPool
, я определяю пулл с именем default
и маленьким адресным пулом с тремя IP (10.200.70.211-10.200.70.213). А объект L2Advertisement
опреляет режим работы MetalLB, и можете обратить внимание что в спецификации указывается какой использовать адресный пулл.
Применяем манифест:
[root@k8s-node1]# kubectl apply -f metall-lb.yml
Теперь остается создать новый сервис типа - LoadBalancer, с указанием на ingress:
[root@k8s-node1]# vim loadbalancer.yml
---
kind: Service
apiVersion: v1
metadata:
name: ingress-nginx-lb
namespace: ingress-nginx
annotations:
metallb.universe.tf/address-pool: default
spec:
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
- name: https
protocol: TCP
port: 443
targetPort: 443
selector:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
type: LoadBalancer
Из особеностей здесь в аннотациях указываем какой адресный пулл использоваться - metallb.universe.tf/address-pool: default
. Далее уже знакомые нам спецификации, описывающие порты. В селекторе описываем объекты ingress-nginx
.
Применяем манифест, и смотрим:
[root@k8s-node1]# kubectl apply -f loadbalancer.yml
[root@k8s-node1]# kubectl -n ingress-nginx get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller NodePort 10.233.33.49 <none> 80:30210/TCP,443:30416/TCP 25h
ingress-nginx-controller-admission ClusterIP 10.233.24.42 <none> 443/TCP 25h
ingress-nginx-lb LoadBalancer 10.233.31.1 10.200.70.211 80:31518/TCP,443:31822/TCP 19s
Как можете понять из вывода, на сервис с именем ingress-nginx-lb
присвоился ip - 10.200.70.211. По мимо основных портов, на этот сервис вещаются nodePort порты. (Одна из особеностей)
На обновленной схеме взаимосвязь отобразится так:
Для проверки можно также курлой постучатся на этот ip, c указанием поля host. И собственно задача решена.