24 kontenery na jednym VPS: architektura techniczna o wysokiej gęstości dla DevOpsów średniego i wyższego poziomu
Jeden serwer, 6 GB RAM, 24 kontenery — a wszystko działa stabilnie od ponad roku. To nie eksperyment, ale infrastruktura produkcyjna dla siedmiu projektów operacyjnych: bota EdTech, serwisu antyspamowego, backendu AI, metaprzeszukiwarki, lokalnej inferencji LLM oraz dwóch usług pomocniczych. Żaden z nich nie generuje przychodów, ale wszystkie są krytyczne dla codziennej działalności. Celem nie jest oszczędność dla samej oszczędności, lecz świadomy wybór kompromisu: ręczne zarządzanie zamiast usług zarządzanych, przewidywalne obciążenie zamiast elastyczności chmury, głęboka integracja zamiast abstrakcji.
Zasady architektoniczne: izolacja, obserwowalność, odporność
Kluczowe podejście to rygorystyczna izolacja sieciowa. Każdy projekt umieszczony jest w własnej sieci Docker (bridge), co eliminuje zależności między projektami i minimalizuje powierzchnię ataku. Jedyna wspólna sieć shared służy tylko do obsługi usług systemowych (np. monitoringu czy wspólnego Redis). Wewnętrzne porty (9200, 8088, 9222) dostępne są wyłącznie przez 127.0.0.1, a zewnętrzny dostęp blokowany jest na poziomie iptables i konfiguracji nginx.
Odporność zapewniona jest poprzez zależności healthcheck w pliku docker-compose.yml. Na przykład serwisy PHP w projekcie EdTech nie uruchamiają się, dopóki tunel SSH do zdalnej bazy danych nie przejdzie sprawdzenia nc -z 127.0.0.1 3306. To gwarantuje, że aplikacja nie spróbuje połączyć się z niedostępną bazą i nie zatka kolejki błędami.
Obserwowalność została zbudowana bez Prometheus/Grafana — ze względu na ograniczenia pamięci RAM. Zamiast tego stosowany jest skrypt bash wykonujący się co 15 minut:
- Sprawdzanie statusów HTTP publicznych punktów końcowych (
/health,/webhook/advert) - Poszukiwanie niezdrowych kontenerów za pomocą
docker ps --filter "health=unhealthy" - Monitoring zużycia miejsca na dysku i swapu
- Powiadomienia wysyłane są do czatu Telegrama za pomocą curl do API Bota
To daje minimalną, ale wystarczającą widoczność dla jednego administratora.
Krytyczne komponenty: Elasticsearch, Chrome Headless i tunel SSH
Trzy komponenty definiują granice wydajności VPS:
- Elasticsearch 8.12.2 zużywa 1,47 GB RAM. Bez wyraźnego
memory: 2gwdeploy.resources.limitsJVM zaczyna wypierać sąsiednie procesy do swapu, co prowadzi do niestabilności. Dla indeksu liczącego około 50 tys. dokumentów i logowania to optymalny balans między funkcjonalnością a obciążeniem. Alternatywa — Meilisearch — wymaga migracji istniejących indeksów i zmiany kodu klienta, co w obecnym etapie oceniamy jako zadanie o wysokim nakładzie pracy i niskim zwrocie inwestycji.
- Chrome Headless (zenika/alpine-chrome) zajmuje 636 MB RAM, choć używany jest tylko raz na godzinę do scrapowania renderingu JS. Uruchamianie
on-demandprzezdocker run --rmpozwoliłoby zaoszczędzić pamięć, ale dodawałoby 5–10 sekund opóźnienia na każde wywołanie. Dla usług z surowymi SLA to nie do przyjęcia; tutaj jednak jest to akceptowalny kompromis.
- Tunel SSH realizowany jest w osobnych kontenerach alpine z
autossh. Zapewniają one bezpieczny dostęp do MySQL na hostingu shared bez otwierania portów na zewnątrz. Konfiguracja zawieraServerAliveInterval=30,AUTOSSH_GATETIME=0oraz healthcheck za pomocąnc. Rozwiązanie to działa z każdym hostingiem obsługującym SSH i w pełni zastępuje płatne usługi zarządzanych baz.
Optymalizacja Nginx i PHP: redukcja latencji o milisekundy
Nginx skonfigurowany jest wyłącznie do obsługi webhooków i API — żadnych statycznych plików poza /health. Kluczową optymalizacją jest przeniesienie przetwarzania webhooków Telegrama do oddzielnego pliku PHP (webhook-advert.php), który nie ładuje Laravel bootstrap. Dzięki temu czas odpowiedzi skraca się z 80–120 ms do 15–25 ms, co jest kluczowe przy masowych wywołaniach webhooków z API Telegrama (maksymalny timeout — 60 sekund).
Konfiguracja obejmuje również:
- Przymusowe przekierowanie HTTP → HTTPS
- Odrzucenie przetwarzania wszelkich ścieżek poza
/health,/webhook/i/api/telegram/ - Zwiększenie
fastcgi_read_timeoutdo 120 sekund dla długotrwałych zadań - Użycie
realpath_rootdla poprawnego przetwarzaniaindex.php
SSL i bezpieczeństwo: trzy strategie na jednym hostu
Terminacja SSL organizowana jest hybrydowo:
- getssl + Let's Encrypt — dla domen wymagających end-to-end szyfrowania i niekorzystających z Cloudflare
- Cloudflare Proxy — dla większości domen: SSL kończy się na brzegu, a ruch do VPS idzie po HTTP, ale jest chroniony filtracją IP (tylko ASN Cloudflare)
- Ręczne aktualizacje — tymczasowe rozwiązanie dla backendu AI, który jest w trakcie automatyzacji za pomocą Caddy
Konfiguracja Cloudflare obejmuje obligatoryjne włączenie Always Use HTTPS, WAF i Rate Limiting (100 requestów/min na IP), co kompensuje brak WAF po stronie VPS.
Co jest ważne
- Rygorystyczna izolacja sieciowa poprzez oddzielne sieci Docker zapobiega kaskadowym awariom i ułatwia audyt bezpieczeństwa.
- Zależności healthcheck w docker-compose gwarantują, że serwisy startują tylko po uprzednim przygotowaniu swoich zależności (np. tunelu SSH do bazy danych).
- Elasticsearch wymaga surowego limitu pamięci (2 GB), inaczej JVM wypiera sąsiednie kontenery do swapu, co prowadzi do działania OOM-killer.
- Chrome Headless warto trzymać w trybie always-on przy niskiej częstotliwości użytkowania, jeśli opóźnienie uruchomienia jest krytyczne dla logiki biznesowej.
- Monitorowanie bash z alertami Telegram — funkcjonalna alternatywa dla Prometheus/Grafana przy deficycie pamięci RAM, pod warunkiem jasno określonych SLO.
— Editorial Team
Brak komentarzy.