Кантэйнер не дасягае API толькі праз VPN? Праксі на хосце
Тунэль жыве на хосце, таму кантэйнер не мае маршруту да прыватнага адраса. Апублікуйце вузкі зваротны проксі і звяртайцеся да яго як host.docker.internal.
Ваш сэрвіс Compose атрымлівае тайм-аўт пры звароце да API, які браўзер на той жа машыне адкрывае без ніякіх скаргаў. Гэтая розніца і ёсць падказка: запыт, які працуе, выходзіць з хоста з тунэлем VPN, а запыт, які вісіць, выходзіць з кантэйнера з уласным сеткавым стэкам. Нішто ўнутры кантэйнера не ведае маршрутаў тунэлю, таму прыватны адрас застаецца недасягальным, як бы ні пісалася імя. Выпраўленне — гэта наўмысны скачок праз хост, а не разумнейшая табліца маршрутызацыі.
Пачніце з таго, чаму два шляхі розняцца, затым вырашыце, дзе павінен жыць тунэль.
Чаму хост працуе, а кантэйнер не
Падняцце тунэлю дае хосту інтэрфейс, набор маршрутаў і, звычайна, запіс рэзалвера, які падае VPN. Кантэйнер не атрымлівае нічога з гэтага ў спадчыну: ён пачынае з адным інтэрфейсам на мосце рухавіка і маршрутам па змаўчанні, які паказвае на гэты мост. Маршруты, усталяваныя на хосце, нябачныя для ўсяго, што выконваецца ўнутры гэтай прасторы імёнаў.
Менавіта таму сімптомы выглядаюць супярэчлівымі, а не проста зламанымі. Хост разрознівае прыватнае імя і злучаецца, бо і імя, і маршрут там існуюць. Кантэйнер адпраўляе свой пакет на прыватны адрас, да якога нішто на шляху моста не дабярэцца, і запыт сканчаецца тайм-аўтам. Даданне імя ў файл hosts кантэйнера нічога не змяняе, бо сам адрас недасягальны з таго месца, дзе знаходзіцца кантэйнер.
Дасягніце хоста знутры кантэйнера
Працэс у кантэйнеры, які звяртаецца да localhost, звяртаецца да кантэйнера. Docker Desktop публікуе host.docker.internal як імя, што паказвае назад на машыну, якая запускае рухавік; рухавікі Linux пакідаюць яго нявызначаным, пакуль сэрвіс не папросіць, і менавіта гэта робіць адлюстраванне ніжэй. З гэтым праграма нацэльваецца на імя і порт замест прыватнага адраса.
services:
app:
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
INTERNAL_API_HOST: "host.docker.internal:8666"Тунэль застаецца на баку хоста гэтага скачка, а порт у гэтай зменнай — той, на якім будзе слухаць проксі. Кантэйнер, які страціў тунэль, цяпер падае на адным вядомым скачку замест спробы маршруту, якога ў яго ніколі не было.
Выберыце, дзе жыве тунэль
Для сэрвісу Compose, якому патрэбны прыватны API, магчымыя тры формы, і яны розняцца тым, які працэс валодае тунэлем. Кліент VPN можа працаваць унутры вобраза кантэйнера, што патрабуе адкрыцця прылады тунэлю і выдачы здольнасці кіравання сеткай, якая з гэтым ідзе. Праграма можа падзяляць сеткавую прастору імёнаў асобнага кантэйнера з тунэлем, тады абодва працэсы бачаць адзін стэк і адну табліцу маршрутызацыі. Або тунэль можа застацца на хосце, з вузкім перанакіравальнікам з хоста ў кантэйнер.
Першыя два цягнуць файлы профіляў, паводзіны перападключэння і палітыку рэзалвера ў праект, і яны робяць кожны сэрвіс у файле залежным ад здароўя тунэлю. Гэта занадта шмат звязвання для аднаго выкліку API. Для стэка распрацоўкі, які чытае адзін унутраны эндпойнт, перанакіравальнік на баку хоста — найменшае змяненне, якое ўсё ж пакідае ўліковыя даныя там, дзе імі кіруе каманда платформы.
Апублікуйце адзін вузкі шлях на хосце
Пачніце з таго, што ўспадкоўвае перанакіравальнік: запусціце яго пад уліковым запісам, які валодае тунэлем, і яго выходныя сокеты падхопяць маршруты хоста і рэзалвер хоста. Адзін блок location у nginx пакрывае адзін шлях.
server {
listen 8666;
location /search/ {
proxy_pass http://10.20.30.40:8090/;
proxy_set_header Host internal-api.example;
proxy_read_timeout 60s;
}
}Разглядайце адрас і порт як часовыя падстаўкі і падстаўце пару, на якой API сапраўды слухае. Кожны наступны upstream заслугоўвае ўласнага блока location, і кожны блок павінен прапускаць толькі тыя шляхі, якія выклікае гэты сэрвіс, каб проксі не стаў агульнымі дзвярыма ў прыватную сетку. Прывяжыце слухач да адраса моста для кліентаў, якія прыходзяць праз host-gateway, да loopback толькі калі кліент падзяляе сеткавую прастору імёнаў хоста, і ніколі да 0.0.0.0. Выберыце таксама порт, які яшчэ свабодны: заняты нумар пакідае проксі, які не слухае, пакуль кантэйнер працягвае атрымліваць тайм-аўты, і гэта немагчыма адрозніць ад сеткавай няспраўнасці, з якой вы пачалі.
Порты, імёны і рашэнне пра TLS
Разрозненне імёнаў дзеліцца гэтак жа, як маршрутызацыя. Публічныя запыты кантэйнера ідуць праз рэзалвер рухавіка, таму прыватнае імя, якое разрозніваецца на хосце, не будзе разрознівацца ўнутры кантэйнера. Менавіта проксі разрознівае прыватнае імя, бо ён успадкоўвае рэзалвер хоста.
TLS — гэта рашэнне, якое адкладваюць, а потым яно тлумачыць наступную памылку. Калі проксі перадае байты некранутымі, кантэйнер бачыць уласны сертыфікат API і мае патрэбу ва ўнутраным CA у сваім сховішчы даверу. Калі проксі завяршае TLS, гэты давер пераходзіць на хост, і праграма пачынае будаваць спасылкі з атрыманага загалоўка host. Выберыце адну схему і ўжывайце яе да кожнага рэсурсу, бо перанакіраванне, выдадзенае другой схемай, паказвае на імя хоста, якое кантэйнер не можа разрозніць.
Праверце знутры сэрвісу
Адпраўце запыт з кантэйнера, а не з хоста: звычайны curl па шляху проксі, затым уласная праверка здароўя праграмы. Калі проксі адказвае, а праграма ўсё яшчэ падае, параўнайце тры рэчы: базавы URL, які яна будуе, любую зменную проксі ў яе асяроддзі і CA, якому яна давярае. Усё гэта знаходзіцца ў асяроддзі і журналах кантэйнера, і выпраўленне любога з іх не кранае VPN.
Запішыце абранае рашэнне ў README праекта, пакуль яно свежае. Інакш наступны чалавек, які спаткае гэты тайм-аўт, правядзе тую ж пасляабедзенную гадзіну, зноў адкрываючы для сябе, што тунэль жыве на хосце.