Budowa własnego serwera CDN: pobrać raz, wydać tysiąc razy
Serwer CDN pobiera każdy plik raz ze źródła i potem wydaje go tysiąc razy samodzielnie. Serwer źródłowy widzi już tylko ułamek zapytań, a odwiedzający dostają krótkie czasy ładowania. Tak się taki serwer buduje.
Każdy plik, który odwiedzający ładuje z Twojej strony, Twój serwer źródłowy pobiera z systemu plików, pakuje w odpowiedź i wysyła w drogę. Przy dziesięciu odwiedzających dziennie nikt tego nie zauważy. Przy dziesięciu tysiącach jednoczesnych odwiedzających to właśnie jest powód, dla którego strona staje się wolna, choć sama aplikacja nie ma nic do roboty.
Serwer CDN staje przed nią. Pobiera każdy plik raz ze źródła, a potem wydaje go sam, tyle razy, ile jest potrzebny. Źródło widzi dzięki temu już tylko ułamek zapytań, a odwiedzający dostają swoje pliki z serwera, który nie robi nic poza wydawaniem. Ten artykuł pokazuje, ile to naprawdę daje, jak postawić taki serwer na nginx i na co zwrócić uwagę przy transferze i lokalizacji.
Co serwer CDN właściwie robi
Przebieg opowiada się w trzech zdaniach:
- Odwiedzający pyta serwer CDN o plik.
- Jeśli plik już tam leży, wychodzi natychmiast. To jest trafienie.
- Jeśli go tam nie ma, serwer CDN pobiera go ze źródła, odkłada i wydaje. To jest chybienie i zdarza się na plik tylko raz w okresie ważności.
Zasada jest ta sama co przy serwerze streamingowym: pobrać raz, wydać wielokrotnie. Różnica jest tylko taka, że serwer streamingowy rozdziela bieżący strumień danych, a serwer CDN gotowe pliki.
Rachunek: jak mocno cache odciąża
Decydujący jest współczynnik trafień, czyli udział zapytań, na które cache odpowiada sam. Przy milionie zapytań na dobę odciążenie wygląda tak:
| Współczynnik trafień | Zapytania u źródła | Odciążenie |
|---|---|---|
| 50 % | 500.000 | czynnik 2 |
| 80 % | 200.000 | czynnik 5 |
| 95 % | 50.000 | czynnik 20 |
| 99 % | 10.000 | czynnik 100 |
Dla plików statycznych 95 procent i więcej to przypadek normalny, bo zdjęcia, skrypty i fonty zmieniają się rzadko. Skok z 95 na 99 procent jest przy tym cenniejszy, niż liczby każą przypuszczać: dzieli pozostałe obciążenie jeszcze raz przez pięć.
Jednocześnie przesuwa się transfer. Źródło prawie nic już nie przesyła, serwer CDN tym więcej. Przy 500.000 wywołań zdjęć na dobę po 400 KB każde daje to 200 GB dziennie, czyli około 6 TB w miesiącu, i to tylko za zdjęcia. Dlatego przy węźle CDN ważną liczbą nie jest procesor, a łącze i wolumen.
Kiedy własny serwer CDN się opłaca
Komercyjni dostawcy CDN rozliczają każdy przesłany gigabajt. To jest wygodne i przy małym wolumenie tanie. Od pewnej ilości danych proporcje się odwracają:
| Przypadek | Własny serwer CDN | Rozliczenie za gigabajt |
|---|---|---|
| Kilka GB w miesiącu | prawie się nie opłaca | taniej |
| Wiele TB w miesiącu | stała cena miesięczna | rośnie liniowo |
| Duże pliki do pobrania, archiwa wideo | wyraźna przewaga | szybko staje się drogie |
| Pełna kontrola nad regułami i protokołami | całkowita | ograniczona |
Dochodzi do tego punkt, który trudno wyrazić w liczbach: przy własnym węźle sam decydujesz, co jest cachowane, jak długo i kiedy znika. Nie jesteś przywiązany do reguł obcego dostawcy i możesz wydawać także rzeczy, które nie mieszczą się w obcych schematach.
Budowa z nginx jako cache
Instalacja
apt update
apt install -y nginx
Więcej nie trzeba, cache jest w nginx wbudowany. Utwórz katalog, w którym mają leżeć pliki:
mkdir -p /var/cache/cdn
chown www-data:www-data /var/cache/cdn
Konfiguracja
Cache opisuje się raz w bloku http, a potem używa w poszczególnych lokalizacjach:
proxy_cache_path /var/cache/cdn
levels=1:2
keys_zone=cdn:100m
max_size=200g
inactive=30d
use_temp_path=off;
proxy_cache_key "$scheme$request_method$host$request_uri";
Wartości znaczą po kolei tyle: keys_zone=cdn:100m wystarcza na około 800.000 wpisów, max_size ogranicza miejsce na dysku, inactive=30d wyrzuca pliki, o które nikt nie pytał od 30 dni, a use_temp_path=off unika zbędnego przenoszenia między katalogami.
Poniżej właściwy serwer:
server {
listen 443 ssl;
http2 on;
server_name cdn.example.com;
ssl_certificate /etc/letsencrypt/live/cdn.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cdn.example.com/privkey.pem;
location / {
proxy_pass https://origin.example.com;
proxy_set_header Host origin.example.com;
proxy_http_version 1.1;
proxy_cache cdn;
proxy_cache_valid 200 301 302 24h;
proxy_cache_valid 404 1m;
# łączenie wielu jednoczesnych chybień w jedno zapytanie
proxy_cache_lock on;
proxy_cache_lock_timeout 10s;
# wydawanie dalej przy problemach ze źródłem
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
# ciasteczka sesji nie mogą rozsadzić cache
proxy_ignore_headers Set-Cookie;
proxy_hide_header Set-Cookie;
add_header X-Cache-Status $upstream_cache_status always;
}
}
Trzy linie z tego zasługują na szczególną uwagę. proxy_cache_lock dba o to, żeby przy nowym pliku nie pytało źródła stu odwiedzających jednocześnie, a tylko jeden, podczas gdy pozostali czekają. proxy_cache_use_stale utrzymuje wydawanie treści, gdy źródło startuje ponownie albo odpowiada wolno. A ukrycie Set-Cookie zapobiega temu, żeby jedna jedyna odpowiedź z ciasteczkiem sesji uczyniła cały cache bezużytecznym.
Sprawdzenie, czy działa
nginx -t && systemctl reload nginx
curl -sI https://cdn.example.com/bild.jpg | grep -i x-cache-status
curl -sI https://cdn.example.com/bild.jpg | grep -i x-cache-status
Przy pierwszym wywołaniu stoi tam MISS, przy drugim HIT. Jeśli i za drugim razem stoi MISS, przyczyną jest prawie zawsze jeden z trzech zwykłych podejrzanych: Set-Cookie w odpowiedzi, Cache-Control: no-store ze źródła albo brakująca informacja o długości.
Co jest cachowane, a co nie
Czyste rozwiązanie polega na tym, że mówi to samo źródło. Ono zna swoje treści, cache nie. Ustawia do tego odpowiednie nagłówki:
| Treść | Nagłówek u źródła |
|---|---|
| Plik z sumą kontrolną w nazwie | Cache-Control: public, max-age=31536000, immutable |
| Zdjęcie albo plik do pobrania bez wersjonowania | Cache-Control: public, max-age=86400 |
| HTML strony publicznej | Cache-Control: public, max-age=300 |
| Obszar po zalogowaniu, koszyk | Cache-Control: private, no-store |
Jeśli chcesz decydować w samym cache, da się to zrobić przez osobne lokalizacje dla każdego typu plików:
location ~* \.(jpg|jpeg|png|gif|webp|avif|svg|ico|css|js|woff2|mp4|zip)$ {
proxy_pass https://origin.example.com;
proxy_cache cdn;
proxy_cache_valid 200 7d;
add_header Cache-Control "public, max-age=604800";
add_header X-Cache-Status $upstream_cache_status always;
}
Celowe pozbywanie się treści
Najprostsza droga to w ogóle nie musieć usuwać: kto każdemu zmienionemu zdjęciu nadaje nową nazwę pliku, na przykład logo.a1b2c3.png, nie ma tego problemu. Stary plik kiedyś sam wypada z cache.
Jeśli to niemożliwe, przy nginx pomaga darmowy moduł ngx_cache_purge. Bez dodatkowego modułu ścieżkę da się policzyć z klucza cache, bo nginx używa jego sumy MD5 jako nazwy pliku:
KEY="httpsGETcdn.example.com/bild.jpg"
H=$(printf '%s' "$KEY" | md5sum | cut -d' ' -f1)
rm -f "/var/cache/cdn/${H: -1}/${H: -3:2}/$H"
Na codzienne potrzeby wystarcza zwykle mały skrypt, który wykonuje te linie dla listy ścieżek.
Duże pliki i wideo
Przy plikach powyżej kilkuset megabajtów potrzebujesz rozkładu na bloki, inaczej cache przy każdym skoku w filmie pobiera cały plik od nowa:
location /video/ {
proxy_pass https://origin.example.com;
proxy_cache cdn;
proxy_cache_valid 200 206 7d;
slice 1m;
proxy_set_header Range $slice_range;
proxy_cache_key "$uri$slice_range";
}
W ten sposób nginx pobiera tylko te bloki, o które naprawdę pytano. Gdy widz przeskoczy na środek filmu, przez łącze wędruje kilka megabajtów zamiast kilku gigabajtów.
TLS, HTTP/2 i HTTP/3
Węzeł CDN wydaje wiele małych plików do wielu połączeń, a właśnie tam nowsze protokoły się opłacają. HTTP/2 załatwia http2 on;. HTTP/3 daje przede wszystkim coś na połączeniach komórkowych, bo zmiana sieci nie zrywa połączenia:
listen 443 quic reuseport;
listen 443 ssl;
http2 on;
add_header Alt-Svc 'h3=":443"; ma=86400';
Nie zapomnij przy tym otworzyć w firewallu portu UDP 443. To najczęstszy powód, dla którego HTTP/3 jest skonfigurowany, a jednak nikt z niego nie korzysta.
Kilka lokalizacji
Pojedynczy węzeł daje już największą część zysku, czyli odciążenie źródła. Drugi zysk, krótkie trasy do odwiedzającego, przychodzi dopiero z kilkoma lokalizacjami.
Rozdzielanie działa przez DNS: ta sama nazwa wskazuje, zależnie od pochodzenia odwiedzającego, na inny węzeł. Potrzebujesz do tego dostawcy DNS z przypisaniem geograficznym. Wszystkie węzły dostają tę samą konfigurację i to samo źródło, nic o sobie nie wiedzą, i właśnie to czyni całą konstrukcję tak odporną: gdy jeden węzeł padnie, wyjmujesz go z DNS, a pozostałe przejmują ruch.
KernelHost oferuje lokalizacje w Niemczech, na zachodnim i wschodnim wybrzeżu USA oraz w Singapurze i Korei Południowej. Przegląd z opóźnieniami znajdziesz pod lokalizacje serwerów. Jako reguła praktyczna: jeden węzeł na każdy kontynent, na którym siedzi znacząca część Twojej publiczności.
Mierzyć, nie zgadywać
Zapisuj status cache do protokołu, wtedy możesz w każdej chwili przeliczyć współczynnik trafień:
log_format cdn '$remote_addr $status $body_bytes_sent '
'$upstream_cache_status "$request"';
access_log /var/log/nginx/cdn.log cdn;
Wynik da się ocenić jedną linijką:
awk '{print $4}' /var/log/nginx/cdn.log | sort | uniq -c | sort -rn
Jeśli udział HIT przy plikach statycznych leży poniżej 90 procent, szukanie przyczyny prawie zawsze się opłaca: zwykle są to ciasteczka w odpowiedziach, za krótkie okresy ważności albo zapytania ze zmiennymi parametrami na końcu adresu, które dla cache za każdym razem wyglądają jak nowy plik.
Jaki serwer pasuje
Węzeł CDN to maszyna do wydawania treści. Kolejność ważnych cech wygląda więc inaczej niż przy serwerze aplikacyjnym:
- Transfer i łącze. Tu powstaje całe obciążenie. Węzeł w ruchu regularnie przesyła kilka terabajtów w miesiącu.
- Dysk. NVMe ma wyraźną przewagę, bo cache składa się z wielu małych losowych odczytów. Wielkość zależy od tego, jak duża część Twoich zasobów jest gorąca, a nie od tego, jak duże są zasoby w całości.
- RAM. System operacyjny używa wolnej pamięci jako drugiego cache przed dyskiem. Więcej RAM znaczy tu wprost więcej tempa.
- Procesor. Najmniej krytyczny, potrzebny jest tylko do TLS, a to nowoczesne procesory robią przy okazji.
Dokładnie na ten przypadek zrobione są nasze serwery root z nielimitowanym transferem: prawdziwe 2 × 1 Gbit/s na serwer bez dławienia, nielimitowany wolumen w stałej cenie miesięcznej, NVMe klasy centrum danych w RAID oraz ochrona przed zalewaniem o wydajności 3,2 Tbps od razu po uruchomieniu. Udostępnienie trwa niecałe 30 sekund, z pełnym dostępem root, IPv4 i siecią IPv6 /64. Ponieważ rozliczenie działa na zasadzie przedpłaty z możliwością rezygnacji co miesiąc, możesz zacząć od jednego węzła i w każdej chwili dodać drugą lokalizację, gdy Twoja publiczność się rozłoży.
Najczęstsze pytania
Co dokładnie robi serwer CDN?
Jak mocno to naprawdę odciąża?
Czy potrzebuję do tego komercyjnego dostawcy?
Które pliki cachować, a których nie?
Jak długo cache ma trzymać pliki?
Jak usunąć plik z cache?
Co się dzieje, gdy serwer źródłowy padnie?
Czy działa to także dla dużych plików wideo?
Ile lokalizacji potrzebuję?
Po czym poznam, że cache działa?
2026 KernelHost GmbH. Wszelkie prawa zastrzeżone. Ten poradnik jest chroniony prawem autorskim. Publikowanie go w innych serwisach, w całości, we fragmentach lub w zmienionej formie, wymaga naszej pisemnej zgody. Cytaty z podaniem źródła i z linkiem są jak najbardziej mile widziane.

