Ця стаття вже п'ята з серії статей про Kubernetes. Перед її прочитанням рекомендую ознайомитись хоча б з базовими об'єктами кластеру та їх використанням для вирішення задач.
Сервіси Kubernetes надають можливість створення постійних точок входу до контейнерів додатків, що працюють в подах, проте IP адреси сервісів обираються з діапазону оверлейної мережі, і тому є видимими лише в межах кластеру. Тож у разі необхідності доступу до таких додатків ззовні існують наступні варіанти:
• hostNetwork: true. Под, створений із такою опцією, матиме можливість бачити мережеві інтерфейси Kubernetes хосту, де його було запущено.
apiVersion: v1
kind: Pod
metadata:
name: influxdb
spec:
hostNetwork: true
containers:
- name: influxdb
image: influxdb
Порт такого додатку буде прослуховуватись на всіх інтерфейсах вузла. Використання цієї опції не рекомендовано, адже в цьому випадку вичерпується простір портів хост-машини, що зі зростанням кількості працюючих додатків може з легкістю призвести до конфліктів. Більш того, у разі перестворення поду, він може "переселитись" на інший вузол, що додасть складності в його пошуках. Виключенням можуть слугувати поди утиліт, котрим дійсно необхідний вищезгаданий доступ задля управлінням мережею і т.п.
• hostPort: integer. Ця опція дозволяє поду активувати лише один порт на всіх інтерфейсах вузла Kubernetes. Yaml для створення такого поду буде виглядати наступним чином:
apiVersion: v1
kind: Pod
metadata:
name: influxdb
spec:
containers:
- name: influxdb
image: influxdb
ports:
- containerPort: 8086
hostPort: 8086
Його, як і попередній варіант, дуже не рекомендовано використовувати по тим же причинам.
• 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, а, по-друге, виділення окремої IP-адреси на кожен додаток може коштувати не дешево.
UPD. Metallb надає можливість використання LoadBalancer опції у bare-metal інсталяціях. Для його роботи необхідний окремий діапазон IP-адрес, що не буде пересікатись із адресами інших вузлів в цій мережі.
MetalLB може працювати в 2-х режимах: Layer 2 та BGP (в ідеалі потребує роутера, що підтримує цей протокол). Кожна IP адреса буде виділятись одному сервісу з активованою опцією LoadBalancer-у. У режимі Layer 2 всі ці адреси лежатимуть на одному воркері/ноді Kubernetes, котрий виборов цю можливість. Це буде виглядати як купа IP-адрес призначені одному інтерфейсу. Тобто фактично це не забезпечує балансування між нодами, а лише процедуру failover.
Принцип роботи Layer 2 режиму MetalLB наступний. MetalLB Controller у разі зміни вузла, на котрому лежать IP-адреси LoadBalancer-ів (чи створення нового LoadBalancer-а) буде надсилати широкомовні ARP анонси (gratuitous ARP messages), що відбулись зміни і вузли, виходячи із отриманих анонсів, будуть змінювати власний локальний ARP-кеш (таблиця відповідністі MAC/Ethernet та IP адрес). Тобто ARP таблиця кожного вузла буде динамічно перебудовуватись, у разі міграції адреси на інший воркер, наприклад, через падіння попереднього.
Моє розуміння процесу може бути неточним. Ліпше за все переглянути офіційну документацію:
https://metallb.universe.tf/tutorial/
https://metallb.universe.tf/tutorial/layer2/
https://metallb.universe.tf/concepts/layer2/
https://metallb.universe.tf/concepts/
MetalLB наразі перебуває у статусі альфа.
• Ingress. Це ресурс кластеру Kubernetes, додатковий об’єкт, що є дещо більшим ніж попередні варіанти. Окрім адресації трафіку на кінцеві поди, Ingress також має функціонал virtual хостингу (доступ до додатків Kubernetes по доменним іменам), балансування трафіку між подами, SSL-termination/sticky sessions та ін. Зупинимось детальніше на цьому варіанті.
Для роботи Ingress ресурсів необхідний відповідний контролер, який у разі bare-metal інсталяцій (така як наша з Kubespray чи Kubeadm) не встановлений по-замовчуванню. Є велика множина таких контролерів, серед яких хотілося б відзначити їх реалізації HAProxy (2 незалежних реалізації), Traefik та Nginx. У цій статті ми розглянемо лише останній варіант, а з повним списком можливих Ingress контролерів можна ознайомитись за посиланням https://github.com/kubernetes/ingress-nginx/blob/master/docs/catalog.md.
Для всіх наступних інструкцій я буду використовувати вже існуючий Kubernetes кластер версії 1.8.4, котрий залишився після роботи над попереднею статею. Цього разу ми ознайомимось з тим, як установити Nginx Ingress контролер та почнемо його використовувати.
Спочатку створимо новий неймспейс:
$ vim namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx
$ kubectl apply -f namespace.yaml
namespace "ingress-nginx" created
Опишемо деплоймент та сервіс для default backend. Ці поди в деплойменті будуть відповідати 404 кодом у разі невірних запитів до ingress контролеру. Для цілей високої доступності ми створимо 2 таких поди:
$ vim default-backend.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: default-http-backend
labels:
app: default-http-backend
namespace: ingress-nginx
spec:
replicas: 2
template:
metadata:
labels:
app: default-http-backend
spec:
terminationGracePeriodSeconds: 60
containers:
- name: default-http-backend
# Any image is permissable as long as:
# 1. It serves a 404 page at /
# 2. It serves 200 on a /healthz endpoint
image: gcr.io/google_containers/defaultbackend:1.4
livenessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 30
timeoutSeconds: 5
ports:
- containerPort: 8080
resources:
limits:
cpu: 10m
memory: 20Mi
requests:
cpu: 10m
memory: 20Mi
---
apiVersion: v1
kind: Service
metadata:
name: default-http-backend
namespace: ingress-nginx
labels:
app: default-http-backend
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: default-http-backend
$ kubectl apply -f default-backend.yaml
deployment "default-http-backend" created
service "default-http-backend" created
Через деякий час можна спостерігати новостворені поди:
$ kubectl get pods -n ingress-nginx -o wide
NAME READY STATUS RESTARTS AGE IP
default-http-backend-66b447d9cf-hcscc 1/1 Running 0 17s 10.233.118.47
default-http-backend-66b447d9cf-vjh9t 1/1 Running 0 17s 10.233.120.233
Додаткові конфігураційні налаштування для майбутнього контролера будуть представлені у вигляді configmap-ів:
$ vim configmaps.yaml
kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-configuration
namespace: ingress-nginx
labels:
app: ingress-nginx
data:
enable-vts-status: 'true'
---
kind: ConfigMap
apiVersion: v1
metadata:
name: tcp-services
namespace: ingress-nginx
---
kind: ConfigMap
apiVersion: v1
metadata:
name: udp-services
namespace: ingress-nginx
Єдине, що ми збираємось активувати - це додаткові метрики, що надає traffic status module. Проте за необхідності є можливість через configmap-и активувати і інші опції Nginx:
$ kubectl apply -f configmaps.yaml
configmap "nginx-configuration" created
configmap "tcp-services" created
configmap "udp-services" created
Ми проводимо налаштування для кластеру із вже активованим RBAC, тому перед виливкою контролера необхідно додатково задеплоїти ролі:
$ vim rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: nginx-ingress-clusterrole
rules:
- apiGroups:
- ""
resources:
- configmaps
- endpoints
- nodes
- pods
- secrets
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- "extensions"
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- "extensions"
resources:
- ingresses/status
verbs:
- update
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
name: nginx-ingress-role
namespace: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- configmaps
- pods
- secrets
- namespaces
verbs:
- get
- apiGroups:
- ""
resources:
- configmaps
resourceNames:
# Defaults to "<election-id>-<ingress-class>"
# Here: "<ingress-controller-leader>-<nginx>"
# This has to be adapted if you change either parameter
# when launching the nginx-ingress-controller.
- "ingress-controller-leader-nginx"
verbs:
- get
- update
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: nginx-ingress-role-nisa-binding
namespace: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: nginx-ingress-role
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: nginx-ingress-clusterrole-nisa-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: nginx-ingress-clusterrole
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
$ kubectl apply -f rbac.yaml
serviceaccount "nginx-ingress-serviceaccount" created
clusterrole "nginx-ingress-clusterrole" created
role "nginx-ingress-role" created
rolebinding "nginx-ingress-role-nisa-binding" created
clusterrolebinding "nginx-ingress-clusterrole-nisa-binding" created
І, нарешті, задеплоїмо Ingress контролер. У цьому випадку варто звернути увагу на аргументи запуску бінарного файлу nginx-ingress-controller в однойменному контейнері, а саме, що було передано ім’я дефолтного бекенду, вказані імена configmap-ів та префікс анотацій. Звісно, що ці значення мають збігатись з попередньо створеними ресурсами.
$ vim nginx-ingress-controller_with_rbac.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
spec:
replicas: 2
selector:
matchLabels:
app: ingress-nginx
template:
metadata:
labels:
app: ingress-nginx
annotations:
prometheus.io/port: '10254'
prometheus.io/scrape: 'true'
spec:
serviceAccountName: nginx-ingress-serviceaccount
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.9.0
args:
- /nginx-ingress-controller
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
- --annotations-prefix=nginx.ingress.kubernetes.io
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
У свою чергу annotations-prefix має збігатись зі значенням annotations кожного майбутнього Ingress-a додатка. Суть в тому, що Kubernetes кластер може мати довільну кількість Ingress контролерів і annotations - це спосіб розмежовувати відповідальність між ними. Якщо ж кластер має лише один Ingress контролер - annotations змінну для Ingress-ів додатків можна не вказувати.
$ kubectl apply -f nginx-ingress-controller_with_rbac.yaml
deployment "nginx-ingress-controller" created
Знову ж, тут 2 репліки задля забезпечення високої доступності, тож падіння одного поду контролера не буде критичним.
$ kubectl get pods -n ingress-nginx | grep nginx-ingress-controller
nginx-ingress-controller-8dcfb95b9-58vsk 1/1 Running 0 22s
nginx-ingress-controller-8dcfb95b9-dz655 1/1 Running 0 22s
Усередині відповідних подів буде працювати nginx, конфігурувати який буде nginx-ingress-controller:
$ kubectl exec nginx-ingress-controller-8dcfb95b9-58vsk -n ingress-nginx -i -t -- /bin/bash
root@nginx-ingress-controller-8dcfb95b9-58vsk:/# ps aux | grep nginx
root 1 0.0 0.0 4048 748 ? Ss 06:54 0:00 /usr/bin/dumb-init /nginx-ingress-controller --default-backend-service=ingress-nginx/default-http-backend --configmap=ingress-nginx/nginx-configuration --tcp-services-configmap=ingress-nginx/tcp-services --udp-services-configmap=ingress-nginx/udp-services --annotations-prefix=nginx.ingress.kubernetes.io
root 7 0.4 1.4 37156 25572 ? Ssl 06:54 0:00 /nginx-ingress-controller --default-backend-service=ingress-nginx/default-http-backend --configmap=ingress-nginx/nginx-configuration --tcp-services-configmap=ingress-nginx/tcp-services --udp-services-configmap=ingress-nginx/udp-services --annotations-prefix=nginx.ingress.kubernetes.io
root 16 0.0 1.4 82304 25624 ? S 06:54 0:00 nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf
nobody 26 0.0 1.6 351504 29356 ? Sl 06:54 0:00 nginx: worker process
nobody 27 0.0 1.6 351504 29332 ? Sl 06:54 0:00 nginx: worker process
dumb-init - це система ініціалізації та нагляду за процесами в контейнер-середовищах.
Версію вилитого контролера можна дізнатись так https://github.com/kubernetes/ingress-nginx/blob/master/docs/deploy/index.md#detect-installed-version
Основне, що наразі цікавого можна побачити в конфігураційному файлі контролера /etc/nginx/nginx.conf - це умова переадресації на default backend поди:
...
upstream upstream-default-backend {
# Load balance algorithm; empty for round robin, which is the default
least_conn;
keepalive 32;
server 10.233.118.47:8080 max_fails=0 fail_timeout=0;
server 10.233.120.233:8080 max_fails=0 fail_timeout=0;
}
...
server {
server_name _ ;
listen 80 default_server reuseport backlog=511;
listen [::]:80 default_server reuseport backlog=511;
set $proxy_upstream_name "-";
listen 443 default_server reuseport backlog=511 ssl http2;
listen [::]:443 default_server reuseport backlog=511 ssl http2;
...
location / {
set $proxy_upstream_name "upstream-default-backend";
set $namespace "";
set $ingress_name "";
set $service_name "";
port_in_redirect off;
client_max_body_size "1m";
proxy_set_header Host $best_http_host;
...
proxy_pass http://upstream-default-backend;
}
# health checks in cloud providers require the use of port 80
location /healthz {
access_log off;
return 200;
}
...
}
Отже у разі запиту будь-якого віртуального хосту (server_name _ ), окрім прямо описаного в nginx.conf, відбудеться адресація на upstream-default-backend upstream, котрий в свою же чергу покаже 404 помилку. Варто зауважити, що перераховані сервера upstream-а збігаються з адресами подів, котрі були вилиті як default-http-backend деплоймент.
Окрім того тут же описано повернення 200 коду на запит по шляху /healthz, що буде використовуватись у якості хелсчека для поду Kubernetes.
Трішки нижче в /etc/nginx/nginx.conf йде мова про активовану через configmap статистику nginx:
# default server, used for NGINX healthcheck and access to nginx stats
server {
listen 18080 default_server reuseport backlog=511;
listen [::]:18080 default_server reuseport backlog=511;
set $proxy_upstream_name "-";
...
location /nginx_status {
set $proxy_upstream_name "internal";
vhost_traffic_status_display;
vhost_traffic_status_display_format html;
}
...
}
Створимо новий сервіс для переадресації на локальні порти Ingress-у, адже інакше доступ до його ресурсів буде закритим:
$ vim ingress-service-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx
namespace: ingress-nginx
spec:
type: NodePort
ports:
- name: http
port: 80
nodePort: 30080
protocol: TCP
- name: http-mgmt
port: 18080
nodePort: 32000
protocol: TCP
selector:
app: ingress-nginx
$ kubectl apply -f ingress-service-nodeport.yaml
service "ingress-nginx" created
У випадку з підтримуваними cloud-провайдерами, можна використовувати опцію ExternalIP.
За посиланням http://адреса_вузла_kubernetes:32000/nginx_status з'явиться вже згадана сторінка статистики:
Ingress-контролер налаштовано, і наразі виллємо простий додаток для демонстрації його роботи. У якості нього скористаємось готовим образом dockersamples/static-site. Його деплоймент та сервіс для Kubernetes матимуть наступний вигляд:
$ vim app1.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: app1
spec:
replicas: 2
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
$ kubectl apply -f app1.yaml
deployment "app1" created
service "appsvc1" created
І тепер опишемо і створимо Ingress для цього додатку:
$ vim app1-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: app1-ingress
spec:
rules:
- host: app1.com
http:
paths:
- backend:
serviceName: appsvc1
servicePort: 80
Отже, всі запити, що будуть надіслані на віртуальний хост app1.com - будуть переадресовані на бекенд-сервіс appsvc1.
$ kubectl apply -f app1-ingress.yaml
ingress "app1-ingress" created
$ kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
app1-ingress app1.com 80 27s
На цьому етапі перевіримо як оновився конфігураційний файл Ingress-контролера. З'явився новий upstream для запитів на домен app1.com:
upstream default-appsvc1-80 {
# Load balance algorithm; empty for round robin, which is the default
least_conn;
keepalive 32;
server 10.233.118.49:80 max_fails=0 fail_timeout=0;
server 10.233.120.235:80 max_fails=0 fail_timeout=0;
}
Та власне запис сервера, що обслуговуватиме вже вищезгаданий домен:
server {
server_name app1.com ;
listen 80;
listen [::]:80;
set $proxy_upstream_name "-";
location / {
set $proxy_upstream_name "default-appsvc1-80";
set $namespace "default";
set $ingress_name "app-ingress";
set $service_name "";
...
proxy_pass http://default-appsvc1-80;
}
}
Знову ж адреси серверів в апстрімі співпадають з адресами подів додатку app1:
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
app1-54cf69ff86-4jkzj 1/1 Running 0 1h 10.233.118.49 k8s-s1
app1-54cf69ff86-mjjcj 1/1 Running 0 1h 10.233.120.235 k8s-s2
Тобто адреса сервіса не бере участь у адресації, а трафік буде надсилатись прямо на кінцеві поди. Ім’я сервісу використовується лише задля побудови API-запитів по створенню коректних upstream-ів Nginx.
Маштабування додатку звісно також вплине на вигляд Nginx апстріму default-appsvc1-80.
Звісно резолвінг всіх доменів, що будуть приведені в цій статті, мають виконуватись в адреси вузлів (або вузла) Kubernetes. Я це робив через /etc/hosts.
Переглянемо роботу щойно створеного додатку за посиланням http://app1.com:30080/
30080 - це порт сервісу, котрий було відкрито як nodePort для переадресації на локальний порт інгресу.
Як я вже згадував, через анотації можна активувати додаткові опції інгреса. Наприклад, таким чином можна переадресувати url-додаток на адресу http://app-redirect.com:30080/redirect:
$ vim app1-ingress-redirect.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
name: app1-ingress-redirect
spec:
rules:
- host: app-redirect.com
http:
paths:
- backend:
serviceName: appsvc1
servicePort: 80
path: /redirect
З усім багатством можливих анотацій можна ознайомитись за посиланням https://github.com/kubernetes/ingress-nginx/blob/nginx-0.9.0/docs/user-guide/annotations.md. Проте, якщо і цього замало, варто редагувати Nginx-темплейт.
Перше, що варто усвідомити, це те, що ingress-nginx розвивається дуже активно. В статті розглянуто установку версії 0.9.0, проте нові версії можуть мати іншу процедуру інсталяції та конфігурації. Наприклад, останній Ingress-nginx (0.16.2) має зовсім інший формат анотацій для активації basic auth, редіректів і інших опцій. Тому не варто бездумно копіювати конфігураційні файли, а ліпше звернутися до останніх версій документації.
Другу річ, котру варто усвідомити - це що навіть Ingress-nginx (тобто Ingress контролер на основі Nginx) може мати різні реалізації. У статті наведено роботу із Ingress-nginx від Kubernetes, проте є, наприклад, від компанії Nginx Inc. Вони мають купу відмінностей і це варто розуміти, щоб не втратити даремно час на конфігурацію.
Налаштування доступу до додатків по HTTPS-протоколу дещо відрізняється від варіанту без шифрування. Для цього згенеруємо самопідписний TLS-сертифікат та ключ, CN яких має збігатись із віртуальним доменом майбутнього додатка:
# openssl req -x509 -nodes -days 3650 -newkey rsa:4096 -keyout app2-tls.key -out app2-tls.crt -subj "/CN=app2.com/O=appsvc2"
Generating a 4096 bit RSA private key
...
writing new private key to 'app2-tls.key'
-----
Або ж використаємо вже згенеровану коректну пару і додамо їх як Kubernetes секрет:
$ kubectl create secret tls app2-tls-secret --key app2-tls.key --cert app2-tls.crt
secret "app2-tls-secret" created
У якості додатка задеплоїмо новий app2, з того ж образу dockersamples/static-site:
$ vim app2.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: app2
spec:
replicas: 2
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
$ kubectl apply -f app2.yaml
deployment "app2" created
service "appsvc2" created
А вже при стоворенні Ingress-а для нього укажемо додані до секрету app2-tls-secret сертифікат та ключ:
$ vim app2-ingress-tls.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: app2-ingress-tls
spec:
tls:
- hosts:
- app2.com
# This assumes tls-secret exists and the SSL
# certificate contains a CN for app2.com
secretName: app2-tls-secret
rules:
- host: app2.com
http:
paths:
- backend:
# This assumes appsvc2 exists and routes to healthy endpoints
serviceName: appsvc2
servicePort: 80
$ kubectl apply -f app2-ingress-tls.yaml
ingress "app2-ingress-tls" created
Відредагуємо ingress-nginx сервіс і додамо до нього секцію https із новим портом переадресування 30443:
$ vim ingress-service-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx
namespace: ingress-nginx
spec:
type: NodePort
ports:
- name: http
port: 80
nodePort: 30080
protocol: TCP
- name: http-mgmt
port: 18080
nodePort: 32000
protocol: TCP
- name: https
port: 443
nodePort: 30443
protocol: TCP
selector:
app: ingress-nginx
Новий додаток має з'явитись за посиланням https://app2.com:30443/
Окрім нового upstream default-appsvc2-80, з'явився також новий сервер, що вже працюватиме зі згенерованим сертифікатом і ключем:
server {
server_name app2.com ;
listen 80;
listen [::]:80;
set $proxy_upstream_name "-";
listen 443 ssl http2;
listen [::]:443 ssl http2;
# PEM sha: 91523b09cd85d2ffe0e638ce6a0f1329f4647677
ssl_certificate /ingress-controller/ssl/default-app2-tls-secret.pem;
ssl_certificate_key /ingress-controller/ssl/default-app2-tls-secret.pem;
ssl_trusted_certificate /ingress-controller/ssl/default-app2-tls-secret-full-chain.pem;
...
location / {
set $proxy_upstream_name "default-appsvc2-80";
set $namespace "default";
set $ingress_name "app2-ingress-tls";
set $service_name "";
...
# In case of errors try the next upstream server before returning an error
proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;
proxy_pass http://default-appsvc2-80;
}
}
Отже, у нас є додатки, на які адресування відбувається лише у разі запитів на відповідні доменні імена та 30080/30443 порти. Але використання для http(s) портів відмінних від 80/443 зовсім не зручна практика та і не зрозуміло що робити із точкою входу для кожного домену додатку... Можна просто встановити зовнішній балансувальник (на кшталт HAProxy, Nginx), як радить стаття, попереду Kubernetes кластеру, а домени додатків на DNS-сервері прив'язувати до IP адреси балансувальника. У пулах балансувальника будуть описані адреси Kubernetes воркерів та nodePort сервісів до інгресу додатків.
Поcилання:
https://habr.com/company/southbridge/blog/358824/
https://kubernetes.io/docs/concepts/services-networking/ingress/
https://github.com/kubernetes/ingress-nginx
https://github.com/kubernetes/ingress-nginx/blob/master/deploy/README.md#mandatory-commands
https://github.com/kubernetes/ingress-nginx/blob/master/deploy/README.md#baremetal
https://github.com/kubernetes/ingress-nginx/tree/master/docs/examples/tls-termination
https://crondev.com/kubernetes-nginx-ingress-controller/
http://alesnosek.com/blog/2017/02/14/accessing-kubernetes-pods-from-outside-of-the-cluster/
https://daemonza.github.io/2017/02/13/kubernetes-nginx-ingress-controller/
https://medium.com/@cashisclay/kubernetes-ingress-82aa960f658e
https://docs.traefik.io/configuration/backends/kubernetes/
http://docs.traefik.io/user-guide/kubernetes/
https://medium.com/@patrickeasters/using-traefik-with-tls-on-kubernetes-cb67fb43a948
https://github.com/kubernetes/ingress-nginx/blob/master/docs/catalog.md
https://www.haproxy.com/blog/haproxy_ingress_controller_for_kubernetes/
https://www.shellhacks.com/create-csr-openssl-without-prompt-non-interactive/
Сервіси Kubernetes надають можливість створення постійних точок входу до контейнерів додатків, що працюють в подах, проте IP адреси сервісів обираються з діапазону оверлейної мережі, і тому є видимими лише в межах кластеру. Тож у разі необхідності доступу до таких додатків ззовні існують наступні варіанти:
• hostNetwork: true. Под, створений із такою опцією, матиме можливість бачити мережеві інтерфейси Kubernetes хосту, де його було запущено.
apiVersion: v1
kind: Pod
metadata:
name: influxdb
spec:
hostNetwork: true
containers:
- name: influxdb
image: influxdb
Порт такого додатку буде прослуховуватись на всіх інтерфейсах вузла. Використання цієї опції не рекомендовано, адже в цьому випадку вичерпується простір портів хост-машини, що зі зростанням кількості працюючих додатків може з легкістю призвести до конфліктів. Більш того, у разі перестворення поду, він може "переселитись" на інший вузол, що додасть складності в його пошуках. Виключенням можуть слугувати поди утиліт, котрим дійсно необхідний вищезгаданий доступ задля управлінням мережею і т.п.
• hostPort: integer. Ця опція дозволяє поду активувати лише один порт на всіх інтерфейсах вузла Kubernetes. Yaml для створення такого поду буде виглядати наступним чином:
apiVersion: v1
kind: Pod
metadata:
name: influxdb
spec:
containers:
- name: influxdb
image: influxdb
ports:
- containerPort: 8086
hostPort: 8086
Його, як і попередній варіант, дуже не рекомендовано використовувати по тим же причинам.
• 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, а, по-друге, виділення окремої IP-адреси на кожен додаток може коштувати не дешево.
UPD. Metallb надає можливість використання LoadBalancer опції у bare-metal інсталяціях. Для його роботи необхідний окремий діапазон IP-адрес, що не буде пересікатись із адресами інших вузлів в цій мережі.
MetalLB може працювати в 2-х режимах: Layer 2 та BGP (в ідеалі потребує роутера, що підтримує цей протокол). Кожна IP адреса буде виділятись одному сервісу з активованою опцією LoadBalancer-у. У режимі Layer 2 всі ці адреси лежатимуть на одному воркері/ноді Kubernetes, котрий виборов цю можливість. Це буде виглядати як купа IP-адрес призначені одному інтерфейсу. Тобто фактично це не забезпечує балансування між нодами, а лише процедуру failover.
Принцип роботи Layer 2 режиму MetalLB наступний. MetalLB Controller у разі зміни вузла, на котрому лежать IP-адреси LoadBalancer-ів (чи створення нового LoadBalancer-а) буде надсилати широкомовні ARP анонси (gratuitous ARP messages), що відбулись зміни і вузли, виходячи із отриманих анонсів, будуть змінювати власний локальний ARP-кеш (таблиця відповідністі MAC/Ethernet та IP адрес). Тобто ARP таблиця кожного вузла буде динамічно перебудовуватись, у разі міграції адреси на інший воркер, наприклад, через падіння попереднього.
Моє розуміння процесу може бути неточним. Ліпше за все переглянути офіційну документацію:
https://metallb.universe.tf/tutorial/
https://metallb.universe.tf/tutorial/layer2/
https://metallb.universe.tf/concepts/layer2/
https://metallb.universe.tf/concepts/
MetalLB наразі перебуває у статусі альфа.
• Ingress. Це ресурс кластеру Kubernetes, додатковий об’єкт, що є дещо більшим ніж попередні варіанти. Окрім адресації трафіку на кінцеві поди, Ingress також має функціонал virtual хостингу (доступ до додатків Kubernetes по доменним іменам), балансування трафіку між подами, SSL-termination/sticky sessions та ін. Зупинимось детальніше на цьому варіанті.
Для роботи Ingress ресурсів необхідний відповідний контролер, який у разі bare-metal інсталяцій (така як наша з Kubespray чи Kubeadm) не встановлений по-замовчуванню. Є велика множина таких контролерів, серед яких хотілося б відзначити їх реалізації HAProxy (2 незалежних реалізації), Traefik та Nginx. У цій статті ми розглянемо лише останній варіант, а з повним списком можливих Ingress контролерів можна ознайомитись за посиланням https://github.com/kubernetes/ingress-nginx/blob/master/docs/catalog.md.
Для всіх наступних інструкцій я буду використовувати вже існуючий Kubernetes кластер версії 1.8.4, котрий залишився після роботи над попереднею статею. Цього разу ми ознайомимось з тим, як установити Nginx Ingress контролер та почнемо його використовувати.
Спочатку створимо новий неймспейс:
$ vim namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx
$ kubectl apply -f namespace.yaml
namespace "ingress-nginx" created
Опишемо деплоймент та сервіс для default backend. Ці поди в деплойменті будуть відповідати 404 кодом у разі невірних запитів до ingress контролеру. Для цілей високої доступності ми створимо 2 таких поди:
$ vim default-backend.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: default-http-backend
labels:
app: default-http-backend
namespace: ingress-nginx
spec:
replicas: 2
template:
metadata:
labels:
app: default-http-backend
spec:
terminationGracePeriodSeconds: 60
containers:
- name: default-http-backend
# Any image is permissable as long as:
# 1. It serves a 404 page at /
# 2. It serves 200 on a /healthz endpoint
image: gcr.io/google_containers/defaultbackend:1.4
livenessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 30
timeoutSeconds: 5
ports:
- containerPort: 8080
resources:
limits:
cpu: 10m
memory: 20Mi
requests:
cpu: 10m
memory: 20Mi
---
apiVersion: v1
kind: Service
metadata:
name: default-http-backend
namespace: ingress-nginx
labels:
app: default-http-backend
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: default-http-backend
$ kubectl apply -f default-backend.yaml
deployment "default-http-backend" created
service "default-http-backend" created
Через деякий час можна спостерігати новостворені поди:
$ kubectl get pods -n ingress-nginx -o wide
NAME READY STATUS RESTARTS AGE IP
default-http-backend-66b447d9cf-hcscc 1/1 Running 0 17s 10.233.118.47
default-http-backend-66b447d9cf-vjh9t 1/1 Running 0 17s 10.233.120.233
Додаткові конфігураційні налаштування для майбутнього контролера будуть представлені у вигляді configmap-ів:
$ vim configmaps.yaml
kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-configuration
namespace: ingress-nginx
labels:
app: ingress-nginx
data:
enable-vts-status: 'true'
---
kind: ConfigMap
apiVersion: v1
metadata:
name: tcp-services
namespace: ingress-nginx
---
kind: ConfigMap
apiVersion: v1
metadata:
name: udp-services
namespace: ingress-nginx
Єдине, що ми збираємось активувати - це додаткові метрики, що надає traffic status module. Проте за необхідності є можливість через configmap-и активувати і інші опції Nginx:
$ kubectl apply -f configmaps.yaml
configmap "nginx-configuration" created
configmap "tcp-services" created
configmap "udp-services" created
Ми проводимо налаштування для кластеру із вже активованим RBAC, тому перед виливкою контролера необхідно додатково задеплоїти ролі:
$ vim rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: nginx-ingress-clusterrole
rules:
- apiGroups:
- ""
resources:
- configmaps
- endpoints
- nodes
- pods
- secrets
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- "extensions"
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- "extensions"
resources:
- ingresses/status
verbs:
- update
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
name: nginx-ingress-role
namespace: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- configmaps
- pods
- secrets
- namespaces
verbs:
- get
- apiGroups:
- ""
resources:
- configmaps
resourceNames:
# Defaults to "<election-id>-<ingress-class>"
# Here: "<ingress-controller-leader>-<nginx>"
# This has to be adapted if you change either parameter
# when launching the nginx-ingress-controller.
- "ingress-controller-leader-nginx"
verbs:
- get
- update
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: nginx-ingress-role-nisa-binding
namespace: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: nginx-ingress-role
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: nginx-ingress-clusterrole-nisa-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: nginx-ingress-clusterrole
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
$ kubectl apply -f rbac.yaml
serviceaccount "nginx-ingress-serviceaccount" created
clusterrole "nginx-ingress-clusterrole" created
role "nginx-ingress-role" created
rolebinding "nginx-ingress-role-nisa-binding" created
clusterrolebinding "nginx-ingress-clusterrole-nisa-binding" created
І, нарешті, задеплоїмо Ingress контролер. У цьому випадку варто звернути увагу на аргументи запуску бінарного файлу nginx-ingress-controller в однойменному контейнері, а саме, що було передано ім’я дефолтного бекенду, вказані імена configmap-ів та префікс анотацій. Звісно, що ці значення мають збігатись з попередньо створеними ресурсами.
$ vim nginx-ingress-controller_with_rbac.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
spec:
replicas: 2
selector:
matchLabels:
app: ingress-nginx
template:
metadata:
labels:
app: ingress-nginx
annotations:
prometheus.io/port: '10254'
prometheus.io/scrape: 'true'
spec:
serviceAccountName: nginx-ingress-serviceaccount
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.9.0
args:
- /nginx-ingress-controller
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
- --annotations-prefix=nginx.ingress.kubernetes.io
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
У свою чергу annotations-prefix має збігатись зі значенням annotations кожного майбутнього Ingress-a додатка. Суть в тому, що Kubernetes кластер може мати довільну кількість Ingress контролерів і annotations - це спосіб розмежовувати відповідальність між ними. Якщо ж кластер має лише один Ingress контролер - annotations змінну для Ingress-ів додатків можна не вказувати.
$ kubectl apply -f nginx-ingress-controller_with_rbac.yaml
deployment "nginx-ingress-controller" created
Знову ж, тут 2 репліки задля забезпечення високої доступності, тож падіння одного поду контролера не буде критичним.
$ kubectl get pods -n ingress-nginx | grep nginx-ingress-controller
nginx-ingress-controller-8dcfb95b9-58vsk 1/1 Running 0 22s
nginx-ingress-controller-8dcfb95b9-dz655 1/1 Running 0 22s
Усередині відповідних подів буде працювати nginx, конфігурувати який буде nginx-ingress-controller:
$ kubectl exec nginx-ingress-controller-8dcfb95b9-58vsk -n ingress-nginx -i -t -- /bin/bash
root@nginx-ingress-controller-8dcfb95b9-58vsk:/# ps aux | grep nginx
root 1 0.0 0.0 4048 748 ? Ss 06:54 0:00 /usr/bin/dumb-init /nginx-ingress-controller --default-backend-service=ingress-nginx/default-http-backend --configmap=ingress-nginx/nginx-configuration --tcp-services-configmap=ingress-nginx/tcp-services --udp-services-configmap=ingress-nginx/udp-services --annotations-prefix=nginx.ingress.kubernetes.io
root 7 0.4 1.4 37156 25572 ? Ssl 06:54 0:00 /nginx-ingress-controller --default-backend-service=ingress-nginx/default-http-backend --configmap=ingress-nginx/nginx-configuration --tcp-services-configmap=ingress-nginx/tcp-services --udp-services-configmap=ingress-nginx/udp-services --annotations-prefix=nginx.ingress.kubernetes.io
root 16 0.0 1.4 82304 25624 ? S 06:54 0:00 nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf
nobody 26 0.0 1.6 351504 29356 ? Sl 06:54 0:00 nginx: worker process
nobody 27 0.0 1.6 351504 29332 ? Sl 06:54 0:00 nginx: worker process
dumb-init - це система ініціалізації та нагляду за процесами в контейнер-середовищах.
Версію вилитого контролера можна дізнатись так https://github.com/kubernetes/ingress-nginx/blob/master/docs/deploy/index.md#detect-installed-version
Основне, що наразі цікавого можна побачити в конфігураційному файлі контролера /etc/nginx/nginx.conf - це умова переадресації на default backend поди:
...
upstream upstream-default-backend {
# Load balance algorithm; empty for round robin, which is the default
least_conn;
keepalive 32;
server 10.233.118.47:8080 max_fails=0 fail_timeout=0;
server 10.233.120.233:8080 max_fails=0 fail_timeout=0;
}
...
server {
server_name _ ;
listen 80 default_server reuseport backlog=511;
listen [::]:80 default_server reuseport backlog=511;
set $proxy_upstream_name "-";
listen 443 default_server reuseport backlog=511 ssl http2;
listen [::]:443 default_server reuseport backlog=511 ssl http2;
...
location / {
set $proxy_upstream_name "upstream-default-backend";
set $namespace "";
set $ingress_name "";
set $service_name "";
port_in_redirect off;
client_max_body_size "1m";
proxy_set_header Host $best_http_host;
...
proxy_pass http://upstream-default-backend;
}
# health checks in cloud providers require the use of port 80
location /healthz {
access_log off;
return 200;
}
...
}
Отже у разі запиту будь-якого віртуального хосту (server_name _ ), окрім прямо описаного в nginx.conf, відбудеться адресація на upstream-default-backend upstream, котрий в свою же чергу покаже 404 помилку. Варто зауважити, що перераховані сервера upstream-а збігаються з адресами подів, котрі були вилиті як default-http-backend деплоймент.
Окрім того тут же описано повернення 200 коду на запит по шляху /healthz, що буде використовуватись у якості хелсчека для поду Kubernetes.
Трішки нижче в /etc/nginx/nginx.conf йде мова про активовану через configmap статистику nginx:
# default server, used for NGINX healthcheck and access to nginx stats
server {
listen 18080 default_server reuseport backlog=511;
listen [::]:18080 default_server reuseport backlog=511;
set $proxy_upstream_name "-";
...
location /nginx_status {
set $proxy_upstream_name "internal";
vhost_traffic_status_display;
vhost_traffic_status_display_format html;
}
...
}
Створимо новий сервіс для переадресації на локальні порти Ingress-у, адже інакше доступ до його ресурсів буде закритим:
$ vim ingress-service-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx
namespace: ingress-nginx
spec:
type: NodePort
ports:
- name: http
port: 80
nodePort: 30080
protocol: TCP
- name: http-mgmt
port: 18080
nodePort: 32000
protocol: TCP
selector:
app: ingress-nginx
$ kubectl apply -f ingress-service-nodeport.yaml
service "ingress-nginx" created
У випадку з підтримуваними cloud-провайдерами, можна використовувати опцію ExternalIP.
За посиланням http://адреса_вузла_kubernetes:32000/nginx_status з'явиться вже згадана сторінка статистики:
1. HTTP CONNECTION
Ingress-контролер налаштовано, і наразі виллємо простий додаток для демонстрації його роботи. У якості нього скористаємось готовим образом dockersamples/static-site. Його деплоймент та сервіс для Kubernetes матимуть наступний вигляд:
$ vim app1.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: app1
spec:
replicas: 2
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
$ kubectl apply -f app1.yaml
deployment "app1" created
service "appsvc1" created
І тепер опишемо і створимо Ingress для цього додатку:
$ vim app1-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: app1-ingress
spec:
rules:
- host: app1.com
http:
paths:
- backend:
serviceName: appsvc1
servicePort: 80
Отже, всі запити, що будуть надіслані на віртуальний хост app1.com - будуть переадресовані на бекенд-сервіс appsvc1.
$ kubectl apply -f app1-ingress.yaml
ingress "app1-ingress" created
$ kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
app1-ingress app1.com 80 27s
На цьому етапі перевіримо як оновився конфігураційний файл Ingress-контролера. З'явився новий upstream для запитів на домен app1.com:
upstream default-appsvc1-80 {
# Load balance algorithm; empty for round robin, which is the default
least_conn;
keepalive 32;
server 10.233.118.49:80 max_fails=0 fail_timeout=0;
server 10.233.120.235:80 max_fails=0 fail_timeout=0;
}
Та власне запис сервера, що обслуговуватиме вже вищезгаданий домен:
server {
server_name app1.com ;
listen 80;
listen [::]:80;
set $proxy_upstream_name "-";
location / {
set $proxy_upstream_name "default-appsvc1-80";
set $namespace "default";
set $ingress_name "app-ingress";
set $service_name "";
...
proxy_pass http://default-appsvc1-80;
}
}
Знову ж адреси серверів в апстрімі співпадають з адресами подів додатку app1:
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
app1-54cf69ff86-4jkzj 1/1 Running 0 1h 10.233.118.49 k8s-s1
app1-54cf69ff86-mjjcj 1/1 Running 0 1h 10.233.120.235 k8s-s2
Тобто адреса сервіса не бере участь у адресації, а трафік буде надсилатись прямо на кінцеві поди. Ім’я сервісу використовується лише задля побудови API-запитів по створенню коректних upstream-ів Nginx.
Маштабування додатку звісно також вплине на вигляд Nginx апстріму default-appsvc1-80.
Звісно резолвінг всіх доменів, що будуть приведені в цій статті, мають виконуватись в адреси вузлів (або вузла) Kubernetes. Я це робив через /etc/hosts.
Переглянемо роботу щойно створеного додатку за посиланням http://app1.com:30080/
30080 - це порт сервісу, котрий було відкрито як nodePort для переадресації на локальний порт інгресу.
Як я вже згадував, через анотації можна активувати додаткові опції інгреса. Наприклад, таким чином можна переадресувати url-додаток на адресу http://app-redirect.com:30080/redirect:
$ vim app1-ingress-redirect.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
name: app1-ingress-redirect
spec:
rules:
- host: app-redirect.com
http:
paths:
- backend:
serviceName: appsvc1
servicePort: 80
path: /redirect
З усім багатством можливих анотацій можна ознайомитись за посиланням https://github.com/kubernetes/ingress-nginx/blob/nginx-0.9.0/docs/user-guide/annotations.md. Проте, якщо і цього замало, варто редагувати Nginx-темплейт.
Перше, що варто усвідомити, це те, що ingress-nginx розвивається дуже активно. В статті розглянуто установку версії 0.9.0, проте нові версії можуть мати іншу процедуру інсталяції та конфігурації. Наприклад, останній Ingress-nginx (0.16.2) має зовсім інший формат анотацій для активації basic auth, редіректів і інших опцій. Тому не варто бездумно копіювати конфігураційні файли, а ліпше звернутися до останніх версій документації.
Другу річ, котру варто усвідомити - це що навіть Ingress-nginx (тобто Ingress контролер на основі Nginx) може мати різні реалізації. У статті наведено роботу із Ingress-nginx від Kubernetes, проте є, наприклад, від компанії Nginx Inc. Вони мають купу відмінностей і це варто розуміти, щоб не втратити даремно час на конфігурацію.
2. HTTPS (TLS) CONNECTION
Налаштування доступу до додатків по HTTPS-протоколу дещо відрізняється від варіанту без шифрування. Для цього згенеруємо самопідписний TLS-сертифікат та ключ, CN яких має збігатись із віртуальним доменом майбутнього додатка:
# openssl req -x509 -nodes -days 3650 -newkey rsa:4096 -keyout app2-tls.key -out app2-tls.crt -subj "/CN=app2.com/O=appsvc2"
Generating a 4096 bit RSA private key
...
writing new private key to 'app2-tls.key'
-----
Або ж використаємо вже згенеровану коректну пару і додамо їх як Kubernetes секрет:
$ kubectl create secret tls app2-tls-secret --key app2-tls.key --cert app2-tls.crt
secret "app2-tls-secret" created
У якості додатка задеплоїмо новий app2, з того ж образу dockersamples/static-site:
$ vim app2.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: app2
spec:
replicas: 2
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
$ kubectl apply -f app2.yaml
deployment "app2" created
service "appsvc2" created
А вже при стоворенні Ingress-а для нього укажемо додані до секрету app2-tls-secret сертифікат та ключ:
$ vim app2-ingress-tls.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: app2-ingress-tls
spec:
tls:
- hosts:
- app2.com
# This assumes tls-secret exists and the SSL
# certificate contains a CN for app2.com
secretName: app2-tls-secret
rules:
- host: app2.com
http:
paths:
- backend:
# This assumes appsvc2 exists and routes to healthy endpoints
serviceName: appsvc2
servicePort: 80
$ kubectl apply -f app2-ingress-tls.yaml
ingress "app2-ingress-tls" created
Відредагуємо ingress-nginx сервіс і додамо до нього секцію https із новим портом переадресування 30443:
$ vim ingress-service-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx
namespace: ingress-nginx
spec:
type: NodePort
ports:
- name: http
port: 80
nodePort: 30080
protocol: TCP
- name: http-mgmt
port: 18080
nodePort: 32000
protocol: TCP
- name: https
port: 443
nodePort: 30443
protocol: TCP
selector:
app: ingress-nginx
Новий додаток має з'явитись за посиланням https://app2.com:30443/
Окрім нового upstream default-appsvc2-80, з'явився також новий сервер, що вже працюватиме зі згенерованим сертифікатом і ключем:
server {
server_name app2.com ;
listen 80;
listen [::]:80;
set $proxy_upstream_name "-";
listen 443 ssl http2;
listen [::]:443 ssl http2;
# PEM sha: 91523b09cd85d2ffe0e638ce6a0f1329f4647677
ssl_certificate /ingress-controller/ssl/default-app2-tls-secret.pem;
ssl_certificate_key /ingress-controller/ssl/default-app2-tls-secret.pem;
ssl_trusted_certificate /ingress-controller/ssl/default-app2-tls-secret-full-chain.pem;
...
location / {
set $proxy_upstream_name "default-appsvc2-80";
set $namespace "default";
set $ingress_name "app2-ingress-tls";
set $service_name "";
...
# In case of errors try the next upstream server before returning an error
proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;
proxy_pass http://default-appsvc2-80;
}
}
Отже, у нас є додатки, на які адресування відбувається лише у разі запитів на відповідні доменні імена та 30080/30443 порти. Але використання для http(s) портів відмінних від 80/443 зовсім не зручна практика та і не зрозуміло що робити із точкою входу для кожного домену додатку... Можна просто встановити зовнішній балансувальник (на кшталт HAProxy, Nginx), як радить стаття, попереду Kubernetes кластеру, а домени додатків на DNS-сервері прив'язувати до IP адреси балансувальника. У пулах балансувальника будуть описані адреси Kubernetes воркерів та nodePort сервісів до інгресу додатків.
Поcилання:
https://habr.com/company/southbridge/blog/358824/
https://kubernetes.io/docs/concepts/services-networking/ingress/
https://github.com/kubernetes/ingress-nginx
https://github.com/kubernetes/ingress-nginx/blob/master/deploy/README.md#mandatory-commands
https://github.com/kubernetes/ingress-nginx/blob/master/deploy/README.md#baremetal
https://github.com/kubernetes/ingress-nginx/tree/master/docs/examples/tls-termination
https://crondev.com/kubernetes-nginx-ingress-controller/
http://alesnosek.com/blog/2017/02/14/accessing-kubernetes-pods-from-outside-of-the-cluster/
https://daemonza.github.io/2017/02/13/kubernetes-nginx-ingress-controller/
https://medium.com/@cashisclay/kubernetes-ingress-82aa960f658e
https://docs.traefik.io/configuration/backends/kubernetes/
http://docs.traefik.io/user-guide/kubernetes/
https://medium.com/@patrickeasters/using-traefik-with-tls-on-kubernetes-cb67fb43a948
https://github.com/kubernetes/ingress-nginx/blob/master/docs/catalog.md
https://www.haproxy.com/blog/haproxy_ingress_controller_for_kubernetes/
https://www.shellhacks.com/create-csr-openssl-without-prompt-non-interactive/
Немає коментарів:
Опублікувати коментар