Давно гемороївся з проблемою доступу до AWS EKS із офісу – нарешті психанув, і розібрався 🙂
В чому проблема: є AWS EKS кластер з Public і Private endpoint для API.
При роботі з офісного ноутбука іноді запити до нього проходять нормально – а іноді відвалюються з помилкою “i/o timeout“:
$ kk get pod [...] Get \"https://F07***D78.gr7.us-east-1.eks.amazonaws.com/api?timeout=32s\": dial tcp 10.0.64.9:443: i/o timeout" ...
Поїхали копати – бо тут нюанси і з DNS, і з роутами в мережі.
Зміст
AWS VPC DNS та мої VPN
У моєму випадку для EKS увімкнено і Public і Private endpoints – тому при DNS resolution працює split-horizon DNS:
- на запит з “публічного інтернету” AWS VPC DNS поверне Public IP
- на запит з середини VPC – то буде Private IP
Далі: я маю два активних VPN-підключення + офісний WiFi, а проблема починається, коли я додаю AWS VPC DNS до resolv.conf, бо:
- є проектний Pritunl/OpenVPN і є домени в AWS, які мають резолвитись через 10.0.0.2 – AWS VPC DNS
- є мій власний WireGuard і домени які мають резолвитись з мого домашнього MikroTik через 10.100.0.1 (див. MikroTik: налаштування WireGuard та підключення Linux peers)
- і є просто публічні DNS-зони, які мають резолвитись через 1.1.1.1
В resolv.conf це виглядає так:
nameserver 1.1.1.1 # CloudFlare DNS, returns EKS Endpoint Public IP nameserver 10.100.0.1 # my MikroTik with WireGuard, returns EKS Endpoint Public IP nameserver 10.0.0.2 # AWS VPC DNS via OpenVPN, returns EKS Endpoint Private IP EKS 10.0.64.9
Файл менеджиться з openresolv, який запускається WireGuard при старті тунелю – WireGuard прописує свої DNS:
$ sudo cat /etc/wireguard/wg0.conf ... DNS = 10.100.0.1, 10.0.0.2, 192.168.0.1 ...
В помилці з timeout бачимо, що запит до F07***D78.gr7.us-east-1.eks.amazonaws.com йде на IP 10.0.64.9 – тобто DNS resolution був через OpenVPN і AWS VPC DNS 10.0.0.2.
Linux DNS та systemd-resolved
Перевіряємо хто в системі взагалі відповідає за DNS – грепаємо файл /etc/nsswitch.conf:
$ grep hosts /etc/nsswitch.conf hosts: mymachines resolve [!UNAVAIL=return] files myhostname dns
Тут опція resolve – це використання модулю nss-resolve через D-Bus до systemd-resolved.
І він йде першим, до параметрів files (nss-files та /etc/hosts) та dns (модуль nss-dns і “класичний” glibc DNS resolver) – тому спочатку запити йдуть до systemd-resolved.
Див. Domain name resolution на Arch Wiki.
systemd-resolved, DNS resolution та network interfaces
Тепер цікава частина – як саме systemd-resolved виконує DNS resolution.
systemd-resolved використовує openresolv – дивимось його параметри:
$ resolvectl status
Global
Protocols: +LLMNR +mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: foreign
Current DNS Server: 10.0.0.2
DNS Servers: 10.0.0.2 10.100.0.1 192.168.0.1
...
Далі перевіримо що відбувається в системі – включаємо debug log для openresolv:
$ sudo resolvectl log-level debug
Потім в одному вікні відкриваємо логи:
$ sudo journalctl -u systemd-resolved -f | grep "F07***D78.gr7.us-east-1.eks.amazonaws.com"
Робимо kubectl get pod – і в логах бачимо:
...
May 20 12:06:39 setevoy-office systemd-resolved[698]: varlink-28-28: Received message: {"method":"io.systemd.Resolve.ResolveHostname","parameters":{"name":"F07***D78.gr7.us-east-1.eks.amazonaws.com","flags":0,"ifindex":0}}
...
May 20 12:06:39 setevoy-office systemd-resolved[698]: varlink-28-28: Sending message: {"parameters":{"addresses":[{"ifindex":6,"family":2,"address":[10,0,64,9]},{"ifindex":6,"family":2,"address":[10,0,65,205]}],"name":"F07***D78.gr7.us-east-1.eks.amazonaws.com","flags":1048577}}
Тут:
- Received message: запит прийшов з параметром
"ifindex":0– “пофіг, де шукати“ - Sending message: відповідь повернулась через
"ifindex":6– цеtun0, openVPN і AWS VPC DNS
Перевіряємо інтерфейси:
$ ip -o link | awk -F': ' '{print $1, $2}'
1 lo
2 enp0s31f6
4 wlan0
5 enp0s13f0u3u4u4
6 tun0
...
"ifindex":6 – це інтерфейс tun0, робочий OpenVPN, і результат, отриманий від AWS VPC DNS – "address":[10,0,64,9], бо AWS VPC DNS буде повертати приватну адресу.
Повторюємо запит – і тепер результат буде інший:
...
varlink-28-28: Sending message: {"parameters":{"addresses":[{"ifindex":4,"family":2,"address":[44,216,7,46]},{"ifindex":4,"family":2,"address":[3,***,***,161]}],"name":"F07***D78.gr7.us-east-1.eks.amazonaws.com","flags":8388609}}
...
Відповідь вже з ifindex":4 – wlan0, і маємо публічний IP.
Чому – бо в тому ж логу бачимо:
... Firing regular transaction 49587 ... IN A> scope dns on */* Firing regular transaction 59798 ... IN A> scope dns on wlan0/* ...
Тут перший запис – це запит через global pool, на всі сервери в ньому:
$ resolvectl status
Global
Protocols: +LLMNR +mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: foreign
Current DNS Server: 10.0.0.2
DNS Servers: 10.0.0.2 10.100.0.1 192.168.0.1
...
А результат повернеться від того, хто перший відповідає, див. systemd-resolved.service:
If lookups are routed to multiple interfaces, the first successful response is returned
І в цьому випадку це був wlan0:
... Added positive ... cache entry ... on wlan0/INET/10.0.0.1 ...
Раз запит через wlan0 – то і у відповідь від AWS DNS для EKS endpoint отримали публічний IP.
А при першій спробі це був tun0:
... Added positive ... cache entry ... on tun0/INET/10.0.0.2 ...
І у відповідь отримали приватний IP 10.0.64.9.
Отже:
systemd-resolvedробить запити до всіх доступних DNS- повертає результат від того, хто перший відповів
- якщо запит від
wlan0, офісної мережі – отримуємо публічний IP, і підключення проходить - якщо запит від
tun0, OpenVPN і AWS VPC DNS – отримуємо приватний IP, і підключення падає з таймаутом
Не забуваємо повернути лог info:
$ sudo resolvectl log-level info
Тепер переходимо до маршрутизації – чому саме підключення падає з timeout error?
VPN та Linux IP routes mess
Глянемо що в маршрутах на робочому ноуті:
$ route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 10.0.0.1 0.0.0.0 UG 600 0 0 wlan0 10.0.0.0 0.0.0.0 255.255.255.0 U 600 0 0 wlan0 10.0.0.2 172.16.0.1 255.255.255.255 UGH 0 0 0 tun0 10.0.6.162 172.16.0.1 255.255.255.255 UGH 0 0 0 tun0 10.0.32.0 172.16.0.1 255.255.240.0 UG 0 0 0 tun0 10.0.48.0 172.16.0.1 255.255.240.0 UG 0 0 0 tun0 10.0.66.0 172.16.0.1 255.255.255.0 UG 0 0 0 tun0 10.0.67.0 172.16.0.1 255.255.255.0 UG 0 0 0 tun0 10.100.0.0 0.0.0.0 255.255.255.0 U 0 0 0 wg0 ...
Тут:
- 10.0.0.0 через wlan0: бо це офісна мережа і у нас тут MacMini до яких треба доступ, ну і доступ в інтернет
- 10.0.0.2, 10.0.32.0, 10.0.48.0 etc – через tun0: це AWS VPC Private Subnets – сюди роутятся запити через робочий OpenVPN для доступу до AWS RDS і інших приватних ресурсів
- 10.100.0.0 через wg0: це мережа мого WireGuard через MikroTik для доступу в мої домашні мережі
А тепер – де виникає проблема: коли EKS ендпоінт резолвиться через AWS VPC DNS 10.0.0.2 – отримуємо приватну адресу 10.0.64.9.
Але для неї нема окремого роуту через OpenVPN – і вона роутиться через 10.0.0.1, офісний роутер і публічний інтернет:
$ kk get pod [...] Get \"https://F07***D78.gr7.us-east-1.eks.amazonaws.com/api?timeout=32s\": dial tcp 10.0.64.9:443: i/o timeout" ...
Перевіряємо сам маршрут – бачимо, що піде через via 10.0.0.1, офісний роутер, а не tun0 і OpenVPN:
$ ip route get 10.0.64.9
10.0.64.9 via 10.0.0.1 dev wlan0 src 10.0.0.133 uid 1000
cache
Ну і traceroute (btw, див. Unix: что такое traceroute – писав ще у 2016,але досі актуально):
$ traceroute 10.0.64.9 traceroute to 10.0.64.9 (10.0.64.9), 30 hops max, 60 byte packets 1 office.example.dev (10.0.0.1) 14.261 ms 14.614 ms 14.592 ms 2 * * * 3 * * * ...
І звісно, що запит на адресу в приватному сабнеті відправлений через публічний інтернет тупо падає.
Варіанти вирішення
Є кілька опцій – або просто додати 10.0.64.9 до OpenVPN, або налаштувати split-DNS – і правильно резолвити домени:
- просто додати 10.0.64.9 до OpenVPN – тоді він створить роут через
tun0на додачу до вже існуючих - можна налаштувати split-DNS через
systemd-resolved - можна налаштувати split-DNS на локальному Unbound чи
dnsmasq– і переключити всі DNS запити на нього
Варіант з 10.0.64.9 на OpenVPN – костиль.
Note: вже коли дописав весь пост – згадав, що Control Plane EKS живе у власних VPC Subnets, і я міг тупо додати їх до OpenVPN так само, як це роблено для RDS, але вже ок – вийшло все одно цікаво 🙂
Рішення зі split-DNS через systemd-resolved виглядає якось геморно.
А Unbound я вже крутив на FreeBSD для домашнього NAS (див. FreeBSD: Home NAS, part 4 – локальний DNS з Unbound), конфіг простий і зрозумілий, ще і викинути зі схеми systemd-resolved з його складностями – нормальний варіант.
Хоча dnsmasq як рішення для ноутбука може був би кращий – бо ще простіший конфіг, але мені Unbound дуже зайшов – тому зробив з ним.
Arch Linux та Unbound
Встановлюємо сам пакет:
$ sudo pacman -S unbound
Що треба зробити:
- всі запити до
compute.internal(AWS EC2 etc) – роутити через OpenVPN і AWS VPC DNS - всі запити до
ops.example.com– теж, бо там у нас записи для AWS RDS типуdb.prod.ops.example.com - всі запити до
grafana.net.setevoy– роутити через MikroTik, бо це моя локальна зона для домашніх хостів - решту – відправляємо на 1.1.1.1 та 8.8.8.8
Пишемо файл /etc/unbound/unbound.conf, описуємо три forward-zone з власними DNS і одну з публічними DNS:
server:
interface: 127.0.0.1
access-control: 127.0.0.0/8 allow
do-ip6: no
hide-identity: yes
hide-version: yes
prefetch: yes
# local homelab via MikroTik
forward-zone:
name: "setevoy."
forward-addr: 10.100.0.1
forward-addr: 192.168.0.1
forward-zone:
name: "compute.internal."
forward-addr: 10.0.0.2
forward-zone:
name: "ops.example.co."
forward-addr: 10.0.0.2
# everything else
forward-zone:
name: "."
forward-addr: 1.1.1.1
forward-addr: 8.8.8.8
Перевіряємо синтаксис:
$ sudo unbound-checkconf unbound-checkconf: no errors in /etc/unbound/unbound.conf
Відключення systemd-resolved
В пості Arch Linux: WireGuard Peer для підключення до MikroTik описував рішення іншої проблеми, і там для NetworkManager додавав dns=systemd-resolved.
Якщо є – прибираємо в /etc/NetworkManager/NetworkManager.conf, задаємо просто dns=none:
... [main] dns=none
Відключаємо systemd-resolved (тут інтернет відвалиться – бо нема куди слати DNS):
$ sudo systemctl disable --now systemd-resolved systemd-resolved-monitor.socket systemd-resolved-varlink.socket
Перезапускаємо NetworkManager:
$ sudo systemctl restart NetworkManager
Перевіряємо 53 порт – якщо systemd-resolve ще живий, значить щось тригерить його запуск:
$ sudo ss -tulpn | grep ':53'
...
tcp LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=723720,fd=25))
tcp LISTEN 0 4096 127.0.0.54:53 0.0.0.0:* users:(("systemd-resolve",pid=723720,fd=27))
Можна жорстко заблокувати запуск через systemctl mask:
$ sudo systemctl mask systemd-resolved $ sudo systemctl stop systemd-resolved
Ще раз перевіряємо порти, і якщо на порту 53 вже нема нікого – то запускаємо unbound.service:
$ sudo systemctl stop systemd-resolved $ sudo systemctl enable --now unbound Created symlink '/etc/systemd/system/multi-user.target.wants/unbound.service' → '/usr/lib/systemd/system/unbound.service'.
Перевіряємо порт:
$ sudo ss -tulpn | grep ':53\b'
udp UNCONN 0 0 127.0.0.1:53 0.0.0.0:* users:(("unbound",pid=727532,fd=3))
tcp LISTEN 0 256 127.0.0.1:53 0.0.0.0:* users:(("unbound",pid=727532,fd=4))
Редагуємо /etc/resolv.conf – задаємо всі DNS через нього:
nameserver 127.0.0.1
І пробуємо щось публічне:
$ dig google.com +short 216.58.207.14
Потім ендпоінт EKS – має повернути публічні IP:
$ dig F07***D78.gr7.us-east-1.eks.amazonaws.com +short 3.***.***.161 44.***.***.46
Пробуємо RDS – має повернути приватні IP із пулу VPC:
$ dig prod.db.kraken.ops.example.com +short kraken-ops-rds-prod.***.us-east-1.rds.amazonaws.com. 10.0.66.14
Редагуємо WireGuard /etc/wireguard/wg0.conf – міняємо параметр DNS:
[Interface] ... DNS = 127.0.0.1 ...
Виконуємо sudo resolvconf -u, бо робили зміни в /etc/resolv.conf вручну і WireGuard буде сваритись.
Перезапускаємо WireGuard:
$ sudo wg-quick down wg0 && sudo wg-quick up wg0
Перевіряємо файл:
$ cat /etc/resolv.conf # Generated by resolvconf nameserver 127.0.0.1
І тепер все працює, як треба.
![]()