Работа с Ingress-контроллерами обычно предполагает работу с Kubernetes в облаке, где внешние ip присваиваются автоматически. Я изучаю Kubernetes, обходясь обычным ноутбуком за NAT, на котором в виртуальных машинах запущены разные разновидности Kubernetes. Когда я разбирался с Ingress-контроллером, у меня возникло непреодолимое желание завести в него публичный ip и обратиться к нему извне. Давайте посмотрим, как это можно сделать.
Публичный ip я решил позаимствовать у vps. Для этого в reg.ru (не реклама, просто здесь все заработало) я арендовал на пару часов виртуалку с ubuntu20.04 на борту и парой ip адресов. Один будем использовать для доступа по ssh, второй снимем с интерфейса виртуальной машины и заведем в наш Kubernetes (работу можно организовать и проще, DNATами, но так интересней). Понятно, что публичные ip адреса, указанные далее, у каждого будут свои, и их необходимо заменить соответственно.
VPS
Состояние vps на начальном этапе:
# ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 52:54:00:73:f5:f6 brd ff:ff:ff:ff:ff:ff inet 95.163.241.96/24 brd 95.163.241.255 scope global eth0 valid_lft forever preferred_lft forever inet 89.108.76.161/24 brd 89.108.76.255 scope global eth0 valid_lft forever preferred_lft forever inet6 2a00:f940:2:4:2::51d4/64 scope global valid_lft forever preferred_lft forever inet6 fe80::5054:ff:fe73:f5f6/64 scope link valid_lft forever preferred_lft forever 3: eth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether 52:54:00:9a:da:36 brd ff:ff:ff:ff:ff:ff
Послушав eth0 убеждаемся, что гипервизор регулярно посылает arp запросы для подтверждения ip адресов. В дальнейшем мы отвяжем ip адрес 89.108.76.161 от интерфейса и запустим демон, который будет отвечать на эти arp запросы, изображая наличие ip адреса:
# tcpdump -i eth0 -n -v arp tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 14:53:20.229845 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 95.163.241.96 tell 37.140.193.29, length 28 14:53:20.229879 ARP, Ethernet (len 6), IPv4 (len 4), Reply 95.163.241.96 is-at 52:54:00:73:f5:f6, length 28 14:54:05.031046 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 89.108.76.161 tell 37.140.193.29, length 28 14:54:05.031103 ARP, Ethernet (len 6), IPv4 (len 4), Reply 89.108.76.161 is-at 52:54:00:73:f5:f6, length 28 14:54:09.126771 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 95.163.241.96 tell 37.140.193.29, length 28 14:54:09.126827 ARP, Ethernet (len 6), IPv4 (len 4), Reply 95.163.241.96 is-at 52:54:00:73:f5:f6, length 28 14:54:49.573563 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 89.108.76.161 tell 37.140.193.29, length 28 14:54:49.573615 ARP, Ethernet (len 6), IPv4 (len 4), Reply 89.108.76.161 is-at 52:54:00:73:f5:f6, length 28 14:54:54.693462 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 95.163.241.96 tell 37.140.193.29, length 28 14:54:54.693493 ARP, Ethernet (len 6), IPv4 (len 4), Reply 95.163.241.96 is-at 52:54:00:73:f5:f6, length 28
Прокинем туннель с vps до домашнего ноута с помощью wireguard. Инструкций полно на просторах интернета, так что здесь ничего особенного:
# apt update # apt install wireguard # wg genkey | tee /etc/wireguard/private.key # chmod go= /etc/wireguard/private.key # cat /etc/wireguard/private.key | wg pubkey | tee /etc/wireguard/public.key # cat > /etc/wireguard/wg0.conf <<EOF [Interface] Address = 10.15.0.1/24 SaveConfig = true ListenPort = 51820 PrivateKey = gFzlk6/oBAkRnqTSqRQ0A03IR8iX2NY0Q9518xMTDmI= EOF
Поднимаем wireguard:
# systemctl start wg-quick@wg0.service
Удаляем внешний ip с интерфейса:
# ip addr del 89.108.76.161/24 brd 89.108.76.255 dev eth0
Добавляем маршрутизацию к внешнему ip через туннель:
# ip r add 89.108.76.161 via 10.15.0.2
Команда ниже нужна, чтобы ноутбук не остался без доступа интернету, т.к. далее мы завернем весь его трафик в туннель:
# iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE
Разрешаем доступ к внешнему ip и адресу ноутбука в сети wireguard через туннель:
# wg set wg0 peer hd7clB/uztrTOlsWTrHCF7mu9g6ECp+FhE2lhohWf1s= allowed-ips 89.108.76.161,10.15.0.2
Разрешаем форвардинг между интерфейсами:
# sysctl -w net.ipv4.ip_forward=1
и убеждаемся, что цепочка FORWARD не заблокирована:
# iptables-save | grep FORWARD :FORWARD ACCEPT [450722:544073659] :FORWARD ACCEPT [4633:3846037]
После запуска wireguard в системе появится интерфейс wg0:
# ip a 4: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000 link/none inet 10.15.0.1/24 scope global wg0 valid_lft forever preferred_lft forever
Ноутбук (Ubuntu20.04)
Устанавливаем wireguard и генерируем ключи по аналогии:
# cat > /etc/wireguard/wg2.conf <<EOF [Interface] PrivateKey = Some private key Address = 10.15.0.2/24 Table = off [Peer] PublicKey = aU3tLYzJPTKCtelYgVTtAfgnvixWdNK5jC2wnXgvemw= AllowedIPs = 0.0.0.0/0 Endpoint = 95.163.241.96:51820 PersistentKeepalive = 25 EOF
Поднимаем туннель:
# systemctl start wg-quick@wg2.service
Проверяем наличие интерфейса wireguard:
# ip a 221: wg2: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000 link/none inet 10.15.0.2/24 scope global wg2 valid_lft forever preferred_lft forever
и связности с сервером:
# ping 10.15.0.1 PING 10.15.0.1 (10.15.0.1) 56(84) bytes of data. 64 bytes from 10.15.0.1: icmp_seq=1 ttl=64 time=16.3 ms 64 bytes from 10.15.0.1: icmp_seq=2 ttl=64 time=8.91 ms 64 bytes from 10.15.0.1: icmp_seq=3 ttl=64 time=9.00 ms
Для первоначальной проверки повесим внешний ip на loopback ноутбука:
# ip addr add 89.108.76.161 dev lo
Направляем весь трафик ноутбука через туннель, чтобы доходили обратные пакеты до клиентов, которые будут обращаться к 89.108.76.161 (192.168.88.1 — шлюз ноутбука по умолчанию):
# ip r add 95.163.241.96/32 via 192.168.88.1 # ip r add default via 10.15.0.1
Убедимся, что цепочка FORWARD не заблокирована:
# iptables-save | grep FORWARD :FORWARD ACCEPT [67644779:42335638975] :FORWARD ACCEPT [149377:28667150]
и
# sysctl -w net.ipv4.ip_forward=1
VPS
Проверяем доступность 89.108.76.161 с VPS:
# ping 89.108.76.161 PING 89.108.76.161 (89.108.76.161) 56(84) bytes of data. 64 bytes from 89.108.76.161: icmp_seq=1 ttl=64 time=6.90 ms 64 bytes from 89.108.76.161: icmp_seq=2 ttl=64 time=38.7 ms 64 bytes from 89.108.76.161: icmp_seq=3 ttl=64 time=59.9 ms
Запускаем демон, который будет отвечать на arp запросы:
# farpd -d -i eth0 89.108.76.161
Теперь заработает ping 89.108.76.161 из внешней сети (например, с телефона, подключенного к сети оператора).
Ноутбук
Напомним, на ноутбуке (гипервизор) запущена виртуальная машина (ВМ), в которой бегает minikube. Она соединена с бриджем virbr0 гипервизора:
# ip a 19: virbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 52:54:00:c3:6e:e6 brd ff:ff:ff:ff:ff:ff inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0 valid_lft forever preferred_lft forever
Удалим внешний адрес с lo:
# ip addr del 89.108.76.161 dev lo
Настроим маршрутизацию пакетов к 89.108.76.161 в сторону ВМ:
# ip r add 89.108.76.161 via 192.168.122.245
ВМ
Интерфейсы ВМ:
l@minikube2:~$ ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 52:54:00:a5:b3:df brd ff:ff:ff:ff:ff:ff inet 192.168.122.245/24 brd 192.168.122.255 scope global dynamic enp1s0 valid_lft 2292sec preferred_lft 2292sec inet6 fe80::5054:ff:fea5:b3df/64 scope link valid_lft forever preferred_lft forever 3: br-5b72cdfd77e4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:01:94:a2:a5 brd ff:ff:ff:ff:ff:ff inet 192.168.58.1/24 brd 192.168.58.255 scope global br-5b72cdfd77e4 valid_lft forever preferred_lft forever inet6 fe80::42:1ff:fe94:a2a5/64 scope link valid_lft forever preferred_lft forever
Состояние форвардинга:
l@minikube2:~$ sysctl -w net.ipv4.ip_forward net.ipv4.ip_forward = 1 l@minikube2:~$ sudo iptables-save | grep FORWARD :FORWARD ACCEPT [2663492:1312451658] :FORWARD ACCEPT [6299:278761]
На машине запущен миникуб с тремя нодами, которые представляют из себя контейнеры, соединенные бриджем br-5b72cdfd77e4:
l@minikube2:~$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d672c95f6adc gcr.io/k8s-minikube/kicbase:v0.0.37 "/usr/local/bin/entr…" 5 days ago Up 34 hours 127.0.0.1:49197->22/tcp, 127.0.0.1:49196->2376/tcp, 127.0.0.1:49195->5000/tcp, 127.0.0.1:49194->8443/tcp, 127.0.0.1:49193->32443/tcp helm-m03 6eac7091ea0c gcr.io/k8s-minikube/kicbase:v0.0.37 "/usr/local/bin/entr…" 5 days ago Up 34 hours 127.0.0.1:49192->22/tcp, 127.0.0.1:49191->2376/tcp, 127.0.0.1:49190->5000/tcp, 127.0.0.1:49189->8443/tcp, 127.0.0.1:49188->32443/tcp helm-m02 c02b9bb12c98 gcr.io/k8s-minikube/kicbase:v0.0.37 "/usr/local/bin/entr…" 5 days ago Up 34 hours 127.0.0.1:49187->22/tcp, 127.0.0.1:49186->2376/tcp, 127.0.0.1:49185->5000/tcp, 127.0.0.1:49184->8443/tcp, 127.0.0.1:49183->32443/tcp helm
Маршрутизируем пакеты на третью ноду:
l@minikube2:~$ sudo ip r add 89.108.76.161 via 192.168.58.4
Зайдем на нее:
l@minikube2:~$ minikube ssh -n helm-m03
Повесим внешний адрес на lo:
docker@helm-m03:~$ sudo ip addr add 89.108.76.161 dev lo docker@helm-m03:~$ ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet 89.108.76.161/32 scope global lo valid_lft forever preferred_lft forever 21: eth0@if22: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:c0:a8:3a:04 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 192.168.58.4/24 brd 192.168.58.255 scope global eth0 valid_lft forever preferred_lft forever
Поставим питон для проверки связности:
docker@helm-m03:~$ sudo apt update docker@helm-m03:~$ sudo apt install python
и запустим сервер на порту 8080:
docker@helm-m03:~$ python -m http.server
Проверим доступ к 89.108.76.161 извне по http://89.108.76.161:8000.
Переходим к Ingress-контроллеру. Добавляем его в кластер:
l@minikube2:~$ minikube addons enable ingress
Внесем внешний ip в ingress controller:
l@minikube2:~$ k patch svc -n ingress-nginx ingress-nginx-controller -p '{"spec":{"externalIPs":["89.108.76.161"]}}'
и у нас автоматически добавляется DNAT на pod, отвечающий за работу с ingress-nginx-controller:
l@minikube2:~$ sudo iptables-save | grep 89.108.76.161 -A KUBE-SERVICES -d 89.108.76.161/32 -p tcp -m comment --comment "ingress-nginx/ingress-nginx-controller:http external IP" -m tcp --dport 80 -j KUBE-EXT-CG5I4G2RS3ZVWGLK -A KUBE-SERVICES -d 89.108.76.161/32 -p tcp -m comment --comment "ingress-nginx/ingress-nginx-controller:https external IP" -m tcp --dport 443 -j KUBE-EXT-EDNDUDH2C75GIR6O
Развернем сервис whoami в Kubernetes:
l@minikube2:~$ cat > deployment.yaml <<EOF apiVersion: apps/v1 kind: Deployment metadata: name: whoami labels: app: whoami spec: replicas: 3 selector: matchLabels: app: whoami template: metadata: labels: app: whoami spec: containers: - name: whoami image: traefik/whoami ports: - containerPort: 80 EOF
l@minikube2:~$ cat > service.yaml <<EOF apiVersion: v1 kind: Service metadata: name: extip spec: ports: - port: 80 targetPort: 80 selector: app: whoami EOF
l@minikube2:~$ cat ingress.yaml <<EOF apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: extip spec: ingressClassName: nginx rules: - host: extip.yourdomainhere http: paths: - path: / pathType: Prefix backend: service: name: extip port: number: 80 EOF
l@minikube2:~$ k apply -f deployment.yaml l@minikube2:~$ k apply -f service.yaml l@minikube2:~$ k apply -f ingress.yaml
Пропишем в A записи домена extip.yourdomainhere внешний ip адрес 89.108.76.161. Обращаемся извне на http://extip.yourdomainhere, все работает!
curl extip.yourdomainhere Hostname: whoami-75d55b64f6-7q894 IP: 127.0.0.1 IP: 10.244.0.17 RemoteAddr: 10.244.0.3:50120 GET / HTTP/1.1 Host: extip.yourdomainhere User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/109.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.5 Upgrade-Insecure-Requests: 1 X-Forwarded-For: 192.168.58.4 X-Forwarded-Host: extip.yourdomainhere X-Forwarded-Port: 80 X-Forwarded-Proto: http X-Forwarded-Scheme: http X-Real-Ip: 192.168.58.4 X-Request-Id: f3c1f071b171b2ab1036241410acebcb X-Scheme: http
Итак, мы позаимствовали публичный ip у vps, завели его в Kubernetes, организовали маршрутизацию и связность до этого адреса, развернули сервис в Kubernetes и проверили его работу.
Надеюсь было интересно.
