Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Работа с 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 и проверили его работу.
Надеюсь было интересно.