Kendi CDN sunucunuzu kurma: bir kez al, binlerce kez sun

Yayınlanma tarihi 7 dk okuma

CDN sunucusu her dosyayı kaynaktan bir kez alır ve sonrasında binlerce kez kendisi sunar. Kaynak sunucu isteklerin yalnızca küçük bir bölümünü görür, ziyaretçiler ise kısa yüklenme süreleri elde eder. Böyle bir sunucuyu şöyle kurarsınız.

Bir ziyaretçinin sitenizden yüklediği her dosyayı kaynak sunucunuz (origin) dosya sisteminden alır, bir yanıtın içine koyar ve yola çıkarır. Günde on ziyaretçide bunu kimse fark etmez. Eşzamanlı on bin ziyaretçide ise, asıl uygulamanın yapacak hiçbir işi olmadığı hâlde sitenin yavaşlamasının sebebi tam olarak budur.

CDN sunucusu bunun önüne yerleşir. Her dosyayı kaynaktan bir kez alır ve sonrasında, ne kadar gerekiyorsa o kadar, kendisi sunar. Kaynak böylece isteklerin yalnızca küçük bir bölümünü görür ve ziyaretçiler dosyalarını, sunmaktan başka hiçbir iş yapmayan bir sunucudan alır. Bu yazı bunun gerçekte ne kazandırdığını, böyle bir sunucuyu nginx ile nasıl kuracağınızı ve trafik ile lokasyonda neyin önemli olduğunu gösteriyor.

CDN sunucusu gerçekte ne yapar

Akış üç cümleyle anlatılabilir:

  • Bir ziyaretçi CDN sunucusundan bir dosya ister.
  • Dosya orada hazırsa hemen yola çıkar. Bu bir isabettir.
  • Orada yoksa CDN sunucusu dosyayı kaynaktan alır, saklar ve sunar. Bu bir ıskadır ve dosya başına, geçerlilik süresi boyunca yalnızca bir kez olur.

İlke bir stream sunucusu ile aynı: bir kez al, çok kez sun. Tek fark, stream sunucusunun akmakta olan bir veri akışını dağıtması, CDN sunucusunun ise hazır dosyaları dağıtmasıdır.

Hesap: bir önbellek yükü ne kadar azaltır

Belirleyici olan isabet oranıdır, yani önbelleğin kendi kendine yanıtladığı isteklerin payı. Günde bir milyon istekte yükün azalması şöyle görünür:

İsabet oranıKaynaktaki isteklerYük azalması
%50500.0002 kat
%80200.0005 kat
%9550.00020 kat
%9910.000100 kat

Statik dosyalarda yüzde 95 ve üzeri olağan durumdur, çünkü görseller, betikler ve yazı tipleri nadiren değişir. Yüzde 95'ten 99'a çıkış ise sayıların düşündürdüğünden daha değerlidir: Kalan yükü bir kez daha beşe böler.

Bu arada trafik yer değiştirir. Kaynak neredeyse hiçbir şey aktarmaz, CDN sunucusu ise bir o kadar fazlasını aktarır. Günde 500.000 görsel çağrısında her biri 400 KB ise bu günlük 200 GB, yani ayda yaklaşık 6 TB eder, hem de yalnızca görseller için. Bu yüzden bir CDN düğümünde önemli sayı CPU değil, bağlantı ve hacimdir.

Kendinize ait bir CDN sunucusu ne zaman değer

Ticari CDN sağlayıcıları aktarılan gigabayt başına faturalandırır. Bu rahattır ve az hacimde ucuza gelir. Belirli bir miktardan sonra ise denge tersine döner:

DurumKendinize ait CDN sunucusuGigabayt başına faturalandırma
Ayda birkaç GBpek değmezdaha ucuz
Ayda çok TBsabit aylık fiyatdoğrusal olarak büyür
Büyük indirmeler, video arşivleriaçık ara avantajlıçabucak pahalanır
Kurallar ve kayıtlar üzerinde tam denetimeksiksizkısıtlı

Buna, sayıyla ifade edilmesi zor bir nokta eklenir: Kendinize ait bir düğümde neyin önbelleğe alınacağına, ne kadar süreyle kalacağına ve ne zaman kaybolacağına kendiniz karar verirsiniz. Yabancı bir sağlayıcının kurallarına bağlı kalmazsınız ve yabancı şemalara sığmayan şeyleri de sunabilirsiniz.

nginx ile önbellek olarak kurulum

Kurulum

apt update
apt install -y nginx

Daha fazlası gerekmiyor, önbellek nginx'in içinde yerleşik. Dosyaların duracağı dizini oluşturun:

mkdir -p /var/cache/cdn
chown www-data:www-data /var/cache/cdn

Yapılandırma

Önbellek bir kez http bloğunda tanımlanır ve sonra tek tek konumlarda kullanılır:

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";

Değerler tek tek şu anlama gelir: keys_zone=cdn:100m yaklaşık 800.000 kayda yeter, max_size diskteki yeri sınırlar, inactive=30d 30 gün boyunca kimsenin istemediği dosyaları atar ve use_temp_path=off dizinler arasında gereksiz bir taşımayı önler.

Bunun altında da asıl sunucu:

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;

        # aynı anda gelen birden fazla ıskayı tek bir isteğe topla
        proxy_cache_lock on;
        proxy_cache_lock_timeout 10s;

        # kaynakta sorun olduğunda sunmayı sürdür
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
        proxy_cache_background_update on;

        # oturum çerezleri önbelleği patlatmamalı
        proxy_ignore_headers Set-Cookie;
        proxy_hide_header Set-Cookie;

        add_header X-Cache-Status $upstream_cache_status always;
    }
}

Bunlardan üç satır özel dikkat hak ediyor. proxy_cache_lock, yeni bir dosyada yüz ziyaretçinin aynı anda kaynağa sormasını değil, yalnızca birinin sorup geri kalanların beklemesini sağlar. proxy_cache_use_stale, kaynak yeniden başlarken ya da yavaş yanıt verirken sunmayı ayakta tutar. Set-Cookie başlığının gizlenmesi de oturum çerezi taşıyan tek bir yanıtın bütün önbelleği kullanılamaz hâle getirmesini engeller.

İşe yarayıp yaramadığını sınama

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

İlk çağrıda orada MISS, ikincide HIT yazar. İkinci kez de MISS yazıyorsa bu neredeyse her zaman üç olağan şüpheliden birine bağlıdır: yanıttaki bir Set-Cookie, kaynaktan gelen bir Cache-Control: no-store ya da eksik bir uzunluk bilgisi.

Neyin önbelleğe alınacağı, neyin alınmayacağı

Temiz çözüm, bunu kaynağın kendisinin söylemesidir. İçeriklerini o bilir, önbellek bilmez. Bunun için uygun başlıkları ayarlar:

İçerikKaynaktaki başlık
Adında sağlama toplamı olan dosyaCache-Control: public, max-age=31536000, immutable
Sürümlenmemiş görsel ya da indirmeCache-Control: public, max-age=86400
Herkese açık bir sayfanın HTML'iCache-Control: public, max-age=300
Oturum açılan bölüm, alışveriş sepetiCache-Control: private, no-store

Kararı önbellekte kendiniz vermek istiyorsanız bu, dosya türüne göre ayrı konumlarla olur:

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;
}

İçerikleri hedefli biçimde atma

En kolay yol, hiç silmek zorunda kalmamaktır: Değişen her görsele yeni bir dosya adı veren kişi, örneğin logo.a1b2c3.png, bu sorunu yaşamaz. Eski dosya zamanla kendiliğinden önbellekten düşer.

Bu mümkün değilse nginx'te ücretsiz ngx_cache_purge modülü yardımcı olur. Ek modül olmadan da yol, önbellek anahtarından hesaplanabilir, çünkü nginx anahtarın MD5 toplamını dosya adı olarak kullanır:

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"

Günlük kullanımda genellikle bu satırları bir yol listesi için çalıştıran küçük bir betik yeterlidir.

Büyük dosyalar ve videolar

Birkaç yüz megabaytın üzerindeki dosyalarda bloklara ayırma gerekir, aksi hâlde önbellek videodaki her atlamada bütün dosyayı yeniden indirir:

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";
}

Böylece nginx yalnızca gerçekten istenen blokları alır. Bir izleyici filmin ortasına atladığında hattan birkaç gigabayt değil, birkaç megabayt geçer.

TLS, HTTP/2 ve HTTP/3

Bir CDN düğümü çok sayıda bağlantıya çok sayıda küçük dosya sunar; yeni protokoller de tam orada karşılığını verir. HTTP/2, http2 on; ile tamamlanır. HTTP/3 ise asıl kazancını mobil bağlantılarda getirir, çünkü ağ değişimi bağlantının kopmasına yol açmaz:

listen 443 quic reuseport;
listen 443 ssl;
http2 on;
add_header Alt-Svc 'h3=":443"; ma=86400';

Bu arada güvenlik duvarında UDP 443'ü açmayı unutmayın. HTTP/3'ün kurulu olduğu hâlde kimsenin kullanmamasının en sık görülen sebebi budur.

Birden fazla lokasyon

Tek bir düğüm kazancın en büyük bölümünü, yani kaynağın yükünün azalmasını zaten sağlar. İkinci kazanç, yani ziyaretçiye giden kısa yollar, ancak birden fazla lokasyonla gelir.

Dağıtım DNS üzerinden işler: Aynı ad, ziyaretçinin nereden geldiğine göre başka bir düğümü gösterir. Bunun için coğrafi eşleme yapabilen bir DNS sağlayıcısına ihtiyacınız var. Bütün düğümler aynı yapılandırmayı ve aynı kaynağı alır, birbirlerinden haberleri olmaz ve kurulumu bu kadar dayanıklı kılan da tam olarak budur: Bir düğüm devre dışı kalırsa onu DNS'ten çıkarırsınız, geri kalanlar devralır.

KernelHost Almanya'da, ABD Batı Yakası'nda, ABD Doğu Yakası'nda, ayrıca Singapur ve Güney Kore'de lokasyonlar sunuyor. Gecikme süreleriyle birlikte genel bir bakışı sunucu lokasyonları altında bulabilirsiniz. Kaba kural: Ziyaretçi kitlenizin kayda değer bir bölümünün bulunduğu her kıtaya bir düğüm.

Tahmin yerine ölçüm

Önbellek durumunu kayda yazın, böylece isabet oranını istediğiniz zaman sayabilirsiniz:

log_format cdn '$remote_addr $status $body_bytes_sent '
               '$upstream_cache_status "$request"';
access_log /var/log/nginx/cdn.log cdn;

Bunu tek satırlık bir komutla değerlendirebilirsiniz:

awk '{print $4}' /var/log/nginx/cdn.log | sort | uniq -c | sort -rn

HIT payı statik dosyalarda yüzde 90'ın altındaysa sebebi aramak neredeyse her zaman değer: Genellikle yanıtlardaki çerezler, fazla kısa geçerlilik süreleri ya da adresin sonunda değişen parametreler taşıyan istekler söz konusudur; bunlar önbelleğe her seferinde yeni bir dosya gibi görünür.

Hangi sunucu uygun

Bir CDN düğümü bir sunum makinesidir. Önemli özelliklerin sıralaması bu yüzden bir uygulama sunucusundakinden farklı görünür:

  1. Trafik ve bağlantı. Bütün yük burada doğar. Bir düğüm işletimde düzenli olarak ayda birkaç terabayt aktarır.
  2. Disk. NVMe açık ara avantajlıdır, çünkü önbellek çok sayıda küçük ve rastgele erişimden oluşur. Boyut, elinizdeki içeriğin ne kadarının sıcak olduğuna göre belirlenir, içeriğin toplamda ne kadar büyük olduğuna göre değil.
  3. RAM. İşletim sistemi boş belleği diskin önünde ikinci bir önbellek olarak kullanır. Burada daha fazla RAM doğrudan daha fazla hız anlamına gelir.
  4. CPU. En az kritik olanı; yalnızca TLS için gerekir ve bunu modern işlemciler yan işi olarak hallediyor.

Tam olarak bu durum için yapılmış olan Sınırsız Trafik root sunucularımız: sunucu başına kısıtlama olmadan gerçek 2 × 1 Gbit/s, sabit aylık fiyata sınırsız hacim, RAID içinde datacenter NVMe ve kutudan çıktığı gibi 3,2 Tbps flood koruması. Hazırlık 30 saniyenin altında sürüyor; tam root erişimi, IPv4 ve bir /64 IPv6 ağıyla. Faturalandırma PrePaid esasına göre yapıldığı ve aylık olarak iptal edilebildiği için bir düğümle başlayıp, ziyaretçi kitleniz dağıldığında istediğiniz zaman ikinci bir lokasyon ekleyebilirsiniz.

Sıkça sorulan sorular

CDN sunucusu tam olarak ne yapar?
Ziyaretçilerinizle kaynak sunucunuzun arasında durur. Bir dosyaya gelen ilk isteği kaynağa iletir ve bu arada yanıtı saklar. Aynı dosyaya gelen bütün diğer istekleri ise kaynağı hiç rahatsız etmeden kendisi yanıtlar.
Bu gerçekte yükü ne kadar azaltır?
Bu, isabet oranına bağlı. Yüzde 95'te kaynağınız artık yalnızca her yirminci isteği görür, yani yük yirmide bire düşer. Yüzde 80'de bile hâlâ beşte bir eder. Görseller, betikler, yazı tipleri ve indirmeler gibi statik dosyalarda yüzde 95 ve üzeri olağandır.
Bunun için ticari bir sağlayıcıya ihtiyacım var mı?
Zorunlu değil. Kendinize ait bir CDN sunucusu özellikle büyük dosyalarda ve yüksek hacimde değer, çünkü ticari sağlayıcılar aktarılan gigabayt başına faturalandırır. Sınırsız trafikli bir sunucuda yüzüncü terabayt da ilki kadar tutar, yani ek bir ücret çıkmaz.
Hangi dosyaları önbelleğe almalıyım, hangilerini almamalıyım?
Bütün ziyaretçiler için aynı görünen her şeyi: görseller, videolar, CSS, JavaScript, yazı tipleri, PDF, kurulum dosyaları. Önbelleğe almamanız gerekenler ise kişiselleştirilmiş sayfalar, alışveriş sepetleri, oturum açılan bölümler ve oturum çerezi taşıyan her şeydir. Bu kararı en iyi, Cache-Control başlığı üzerinden kaynak verir.
Önbellek dosyaları ne kadar süre tutmalı?
Adında sürüm numarası ya da sağlama toplamı olan dosyalar için gönül rahatlığıyla bir yıl, bunlar hiç değişmez. Sürümlenmemiş görseller ve indirmeler için saatlerden günlere kadar olağandır. HTML için, eğer onu hiç önbelleğe alıyorsanız, dakikalar. Önemli olan tam sayı değil, tek tek dosyaları hedefli biçimde silebileceğiniz bir yolun olmasıdır.
Bir dosyayı önbellekten nasıl silerim?
En kolayı dosya adı üzerinden: Her değişiklikte yeni bir ad veren kişinin silmeye hiç ihtiyacı olmaz. Bu mümkün değilse nginx'te ya ticari purge modülü ya da ücretsiz ngx_cache_purge yardımcı olur. Gerekirse önbellek dosyasını doğrudan dosya sisteminde silersiniz, yol önbellek anahtarından hesaplanabilir.
Kaynak sunucu devre dışı kalırsa ne olur?
Doğru ayarlandığında önbellek, kayıtların süresi aslında dolmuş olsa bile sunmayı sürdürür. Bunu error ve timeout ile birlikte proxy_cache_use_stale ve ayrıca proxy_cache_background_update sağlar. Böylece kaynak bakımdayken ya da yeniden başlarken siteniz erişilebilir kalır.
Bu, büyük video dosyaları için de işler mi?
Evet, ama yalnızca slice ayarıyla. O olmadan önbellek, videodaki her atlamada bütün dosyayı kaynaktan yeniden indirir. slice ile nginx dosyayı örneğin birer megabaytlık bloklara ayırır ve yalnızca gerçekten istenen bölümleri saklar.
Kaç lokasyona ihtiyacım var?
Bir taneyle başlayın, hem de ziyaretçilerinizin çoğunun bulunduğu yerde. İkinci bir lokasyon ancak kitlenin kayda değer bir bölümü başka bir kıtada oturuyorsa değer. Dağıtım o zaman DNS üzerinden işler, her ziyaretçi kendisine en yakın düğümde sonlanır.
Önbelleğin çalıştığını nasıl anlarım?
upstream_cache_status değişkenini bir başlık olarak dışarı verin. Aynı dosyanın ikinci çağrısında orada HIT yazması gerekir. Aynı değeri bir de kayda yazarsanız isabet oranını istediğiniz zaman sayabilirsiniz. Statik dosyalarda yüzde 90'ın altında kalıyorsa, genellikle başlıklarda ya da çerezlerde bir sorun vardır.

CDN Caching nginx Reverse Proxy Performance HTTP Unlimited Traffic