YARP'ı Production'da İşletmek: Observability, Performans, Container ve Çok Instance
YARP normal yükte medyanda yaklaşık 70 µs gecikme ekliyor; production'da asıl dikkat edilecekler trace'in gateway'den geçişi, hata türüne göre alarm, instance başına çalışan rate limit ve cache, shutdown süresi ve doğru container image'ı. OpenTelemetry ve Jaeger ile trace'leri, k6 ile gateway maliyetini, SDK ile container üretmeyi, YARP'ın nightly image'ını ve graceful shutdown'u Bookstore.Gateway üzerinde gerçek çıktılarla gösteriyoruz.
Sürüm notu: Bu yazıdaki kodlar .NET 10, Yarp.ReverseProxy 2.3 ve OpenTelemetry .NET 1.21 ile derlenip denenmiştir; load test k6 2.4, trace'ler Jaeger 2.23 ile alınmıştır.
Önce Tahmin Et
Bookstore.Gateway'i iki instance olarak çalıştırıyorsun, önlerinde istekleri sırayla dağıtan bir load balancer var. 04'teki limit hâlâ geçerli: Ada 10 saniyede en fazla 5 sipariş isteği atabilir. Ada 10 saniye içinde 12 istek atarsa kaç tanesi 429 alır?
İkinci soru: gateway'de 8 saniye süren bir istek devam ederken Kubernetes ya da sen gateway'e SIGTERM (işletim sisteminin process'ten kapanmasını isteyen sinyali) gönderiyorsun. O isteği bekleyen istemci ne görür: 200 mü, bağlantı hatası mı? Gateway'in ayarlarına hiç dokunmadığını varsay.
Trace'in Gateway'den Geçişi: OpenTelemetry
Gateway'i production'a çıkardığında ilk soru "yavaşlık nerede?" oluyor. Sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) artık bir gateway, bir BFF (Backend for Frontend; belirli bir istemci için yazılmış ara backend) ve arkasında beş service'ten oluşuyor; bir isteğin hangi adımda zaman kaybettiğini log satırlarından bulmak zor. Trace context'in ne olduğunu, traceparent header'ını ve log'a TraceId eklemeyi Logging serisinin Correlation ID ve Trace Context yazısında anlattık. Burada gateway'e özgü kısma bakıyoruz: YARP'ın (Yet Another Reverse Proxy; .NET için reverse proxy library'si) kendi span'leri ve trace'in proxy'den kopmadan geçmesi.
public static IServiceCollection AddGatewayTelemetry(this IServiceCollection services, IConfiguration configuration)
{
OpenTelemetryBuilder otel = services.AddOpenTelemetry()
.ConfigureResource(resource => resource.AddService("bookstore-gateway"))
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
// YARP'ın kendi span'leri: her proxy isteği için proxy.forwarder ve active health check probe'ları
.AddSource("Yarp.ReverseProxy"))
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation());
// yalnız bir collector ayarlıysa dışarı gönder (OTEL_EXPORTER_OTLP_ENDPOINT)
if (!string.IsNullOrEmpty(configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]))
{
otel.UseOtlpExporter();
}
return services;
}
Catalog ve Inventory'ye de aynı paketlerle birkaç satırlık bir kurulum ekledim. OTLP (OpenTelemetry Protocol; trace, metric ve log'ları collector'a gönderen protokol) ile Jaeger'a gönderip iki isteğin trace'ini Jaeger'ın API'sinden ağaç olarak çıkardım:
docker run -d --name yarpseries-jaeger -p 4327:4317 -p 16696:16686 jaegertracing/jaeger:2.23.0
# her service'i OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4327 ile başlat
bookstore-gateway GET /api/catalog/{**rest} 35.3 ms
bookstore-gateway proxy.forwarder 15.4 ms
bookstore-gateway GET 3.0 ms
bookstore-catalog GET /books/{isbn} 2.4 ms
bookstore-gateway GET /api/bff/books/{isbn} 69.0 ms
bookstore-gateway GET 2.7 ms
bookstore-catalog GET /books/{isbn} 2.0 ms
bookstore-gateway GET 37.6 ms
bookstore-inventory GET /stock/{isbn} 24.6 ms
bookstore-gateway GET 44.4 ms
İlk trace bir proxy route'u. ASP.NET Core'un server span'inin altında YARP'ın proxy.forwarder span'i, onun altında forwarder'ın backend'e attığı HTTP isteği, en altta da Catalog'un kendi server span'i var. Dört span aynı TraceId'yi taşıyor; YARP forwarder'ı traceparent'ı backend'e kendisi ekliyor, bizim bir şey yazmamız gerekmedi. proxy.forwarder span'i yalnız AddSource("Yarp.ReverseProxy") eklendiğinde oluşuyor; YARP, dinleyen yoksa bu activity'yi hiç yaratmıyor. İkinci trace 05'teki aggregation endpoint'i: üç çağrının paralel çalıştığı ve toplam sürenin en yavaş çağrıya (Ordering, 44 ms) yakın olduğu tek bakışta görülüyor. Son GET'in altında backend span'i yok, çünkü Ordering'e OpenTelemetry eklemedim. Trace o noktada bitiyor ama traceparent yine gönderiliyor; Ordering'i instrument ettiğin gün ağaç kendiliğinden tamamlanır.
Sürelerdeki büyük farklar (35 ms'lik server span, 2,4 ms'lik backend) ilk isteklerin warm-up maliyeti. Sürekli trafikte gateway'in eklediği süreyi aşağıda load test ile ölçüyoruz. Bir uyarı: YARP active health check probe'ları için de span üretiyor. Bizim ayarımızla her 5 saniyede üç probe, yani her gün on binlerce kısa trace demek. Sampling ya da collector'da bu span adlarını (proxy.cluster_health_checks, proxy.destination_health_check) filtrelemek gerekiyor. Metric, sampling ve collector kurulumunun ayrıntıları ilerideki Observability serisinin konusu.
YARP'ın Sayaçları ve ForwarderError
Trace tek bir isteği anlatıyor; gateway'in genel durumunu sayaçlar söylüyor. YARP 2.3 Yarp.ReverseProxy adında bir EventSource ile istek sayaçları yayınlıyor. Load test sırasında dotnet-counters ile okudum:
dotnet-counters collect -p <gateway-pid> --counters Yarp.ReverseProxy --format csv --duration 00:00:00:05
Timestamp,Provider,Counter Name,Counter Type,Mean/Increment
11/14/2026 14:35:26,Yarp.ReverseProxy,Requests Started,Metric,1073140
11/14/2026 14:35:26,Yarp.ReverseProxy,Requests Started Rate (Count / 1 sec),Rate,2001
11/14/2026 14:35:26,Yarp.ReverseProxy,Requests Failed,Metric,0
11/14/2026 14:35:26,Yarp.ReverseProxy,Current Requests,Metric,0
Bunlar EventCounter; OpenTelemetry metric'lerine doğrudan girmiyor. Production'da genelde ASP.NET Core'un ve System.Net.Http'nin metric'leri (yukarıdaki WithMetrics) yeterli oluyor, çünkü gateway'in gelen ve giden her isteği zaten onlardan geçiyor. İstek başına daha ayrıntılı zamanlama (bağlantı kurma, header gönderme, body kopyalama) gerekiyorsa Yarp.Telemetry.Consumption paketi bu event'leri kendi kodunda dinleyebileceğin interface'ler sunuyor.
"Requests Failed" sayacının arkasında ForwarderError var. 01'den beri log satırımıza bu değeri yazıyoruz. YARP'ın kaynak kodundaki tanımlara göre en sık göreceklerin:
| ForwarderError | Ne oldu | İstemcinin gördüğü |
|---|---|---|
Request | Bağlantı kurulamadı ya da header'lar gönderilip alınamadı | 502 |
RequestTimedOut | Aynı adımlarda ActivityTimeout doldu | 504 |
RequestCanceled | İstek iptal edildi: istemci gitti ya da request timeout middleware'i kesti (04) | 400 |
NoAvailableDestinations | Health check bütün destination'ları çıkardı | 503 |
ResponseBodyDestination | Backend cevap gövdesinin ortasında koptu | Cevap başlamadıysa 502, başladıysa bağlantı kesilir |
Alarmı status code'a değil hata türüne kur. 502 sayısı artıyorsa Request mi, ResponseBodyDestination mı olduğu farklı ekiplere gider: birincisi ağ ya da deploy, ikincisi backend'in kendi hatası. Access log için de aynı log satırı yeterli: route, cluster, destination, status ve hata türü. ASP.NET Core'un HTTP logging middleware'ini kullanacaksan request ve response body'yi loglamayı açma; gateway'deki her token, kişisel veri ve dosya log'a düşer ve log hacmi trafiğin kendisiyle yarışır.
Gateway Ne Kadar Gecikme Ekliyor?
01'de bu ölçümün sözünü vermiştik. Aynı Catalog endpoint'ini önce doğrudan, sonra gateway üzerinden k6 ile çağırdım. Gateway route'u olarak cache'siz ve timeout'suz catalog-legacy'yi seçtim; bütün uygulamalar Release build, log seviyesi Warning, hepsi ve k6 aynı makinede.
import http from 'k6/http';
import { check } from 'k6';
// kullanım: k6 run -e URL=... -e RATE=2000 rate.js (sabit istek hızı, doyurma testi değil)
export const options = {
scenarios: {
fixed: {
executor: 'constant-arrival-rate',
rate: Number(__ENV.RATE),
timeUnit: '1s',
duration: '20s',
preAllocatedVUs: 100,
},
},
};
export default function () {
const res = http.get(__ENV.URL);
check(res, { 'status is 200': (r) => r.status === 200 });
}
# saniyede 2000 istek, 20 saniye
== http://localhost:5301/books/9780441172719
http_req_duration..............: med=78µs p(95)=133µs p(99)=521µs
http_reqs......................: 40000 1999.937002/s
== http://localhost:5300/api/catalog/books/9780441172719
http_req_duration..............: med=151µs p(95)=191µs p(99)=1.07ms
http_reqs......................: 40001 1999.979201/s
# doyurma testi: 50 sanal kullanıcı, 20 saniye, olabildiğince hızlı
== http://localhost:5301/books/9780441172719
http_req_duration..............: med=317µs p(95)=722µs p(99)=1.43ms
http_reqs......................: 2485218 124257.979937/s
== http://localhost:5300/api/catalog/books/9780441172719
http_req_duration..............: med=871µs p(95)=1.66ms p(99)=2.68ms
http_reqs......................: 1016953 50838.39487/s
Normal yükte gateway medyanda yaklaşık 70 µs, p99'da yaklaşık 0,5 ms ekliyor. Bir web sayfasının toplam süresi yanında bu fark ölçülmeyecek kadar küçük. Doyurma testinde tablo değişiyor: aynı makinede gateway'li yol saniyede 51 bin, doğrudan yol 124 bin istek taşıdı. Bu sayılar "gateway üç kat yavaş" demek değil. Her istek artık iki HTTP işleminden geçiyor ve beş process (k6, gateway ve üç Catalog) aynı CPU'yu paylaşıyor; gateway'in maliyeti gecikmeden çok CPU'da. Kendi ortamında ölçerken gateway'i ayrı bir makinede çalıştır ve sabit hızlı testle başla; yük testi tasarımının ayrıntıları ilerideki Performance ve Load Testing serisinde.
Performansı belirleyen ayarların çoğu backend tarafında. YARP dokümantasyonuna göre her cluster'ın kendine ait bir HttpMessageInvoker'ı var ve bağlantılar bu invoker'ın havuzunda yeniden kullanılıyor; her istek için yeni bağlantı açılmıyor. Backend HTTP/2 konuşuyorsa tek bağlantı üzerinden çok sayıda istek gidebilir, ama 03'te gördüğümüz gibi şifresiz HTTP/2 için iki tarafın da açık ayarı gerekiyor. Body boyutu tarafında Kestrel'in varsayılan sınırı gateway'de de geçerli; YARP'ın route'ta MaxRequestBodySize ayarı bu sınırı route bazında değiştiriyor (kaynak koddaki açıklamaya göre -1 sınırı kaldırıyor). Kestrel'in limitlerini ve yavaş istemcilere karşı ayarları ASP.NET Core Güvenliği serisinin Kestrel ve Request Limitleri yazısında anlattık.
Container: Kendi Image'imiz ve YARP'ın Hazır Image'ı
Gateway'i container'a koymanın en kısa yolu .NET SDK'sının kendi container desteği; Dockerfile yazmadan image üretiyor. Angular dosyaları 05'teki gibi önce wwwroot'a build ediliyor:
cd web/bookstore-web && npm install && npx ng build && cd ../..
dotnet publish src/Bookstore.Gateway -c Release -t:PublishContainer \
-p:ContainerRepository=bookstore-gateway -p:ContainerImageTag=1.0.0
Building image 'bookstore-gateway' with tags '1.0.0' on top of base image 'mcr.microsoft.com/dotnet/aspnet:10.0'.
Pushed image 'bookstore-gateway:1.0.0' to local registry via 'docker'.
docker run -d --name yarpseries-gateway -p 5400:8080 \
-e Auth__SigningKey=dev-only-bookstore-signing-key-not-a-secret-0123456789 \
-e DestinationAddresses__catalog-1=http://host.docker.internal:5301/ \
-e DestinationAddresses__catalog-2=http://host.docker.internal:5311/ \
-e DestinationAddresses__catalog-3=http://host.docker.internal:5312/ \
-e Services__ordering__http__0=host.docker.internal:5302 \
bookstore-gateway:1.0.0
Image aspnet:10.0 tabanlı, 8080'de dinliyor ve root olmayan app kullanıcısıyla (uid 1654) çalışıyor. Signing key burada sahte bir örnek; gerçekte bir secret store'dan gelir. Adresler 02'deki DestinationAddressFilter ile environment variable'dan veriliyor ve container ilk çalıştırmada bu ayarları görmedi:
curl http://localhost:5400/api/v1/books/9780441172719
502
warn: Yarp.ReverseProxy.Health.ActiveHealthCheckMonitor[17]
Probing destination `catalog-1[localhost:5301]` on cluster `catalog` failed.
Environment variable'lar container'daydı; sorun sırada. 03'te eklediğimiz service discovery destination resolver'ı, YARP'ın kaynak koduna göre config filter'lardan önce çalışıyor ve destination ID'sini catalog-1 yerine catalog-1[localhost:5301] yapıyor. Filter da DestinationAddresses:catalog-1[localhost:5301] anahtarını arıyor ve bulamıyor. 02'deki filter, 03'te resolver eklendiği günden beri sessizce çalışmıyormuş; yerelde adresler zaten doğru olduğu için kimse fark etmemiş. Düzeltme, ID'nin köşeli parantezden önceki kısmıyla aramak:
foreach ((string id, DestinationConfig destination) in cluster.Destinations)
{
// service discovery resolver'ı config filter'dan önce çalışır ve "catalog-1"i
// "catalog-1[localhost:5301]" yapar; override'ı config'teki ID ile ara
string configuredId = id.Split('[')[0];
string? address = configuration[$"DestinationAddresses:{configuredId}"];
destinations[id] = string.IsNullOrEmpty(address)
? destination
: destination with { Address = address };
}
{"isbn":"9780441172719","title":"Dune","author":"Frank Herbert","price":420.00} 200
Destination ID'si log'da hâlâ catalog-2[localhost:5311] görünüyor ama istekler host.docker.internal'a gidiyor; log'u okurken bunu bil. Ders şu: environment'a göre değişen her ayarı container içinde, production'a benzer bir ortamda bir kez gerçekten dene.
YARP ekibinin hazır bir container image'ı da var: mcr.microsoft.com/dotnet/nightly/yarp. Kod yazmadan, bir JSON dosyasıyla gateway çalıştırıyor:
docker run -d --name yarpseries-yarp -p 5401:5000 \
-v "$(pwd)/deploy/yarp-image/yarp.json:/etc/yarp.config:ro" \
mcr.microsoft.com/dotnet/nightly/yarp:2.3.0-preview.5
HTTP/1.1 200 OK X-Catalog-Instance: catalog-1
HTTP/1.1 200 OK X-Catalog-Instance: catalog-2
HTTP/1.1 200 OK X-Catalog-Instance: catalog-1
Image'ı inceleyince karar netleşiyor. Repo yalnız nightly altında ve etiketleri preview ile latest; stable bir sürüm etiketi yok. İçinde Yarp.ReverseProxy 2.3.0, service discovery, OTLP exporter'lı OpenTelemetry ve bir environment variable'la (YARP_ENABLE_STATIC_FILES) açılan statik dosya desteği var. Shell içermeyen küçük bir taban üzerinde, root olmayan kullanıcıyla çalışıyor; config dosyasını /etc/yarp.config'ten okuyor ve 5000'de dinliyor. Ama .NET 9.0.23 runtime'ı üzerinde çalışıyor ve .NET 9'un desteği 10 Kasım 2026'da bitti. Authentication, rate limiting, output cache ya da kendi transform'larımız için bir genişleme noktası da yok. Bu image Aspire'ın AddYarp kaynağının kullandığı image; yerel geliştirmede ve basit yönlendirmede pratik, ama bu serideki gateway gibi kod içeren bir gateway için kendi image'ını üretmek gerekiyor. Aspire tarafını ilerideki Aspire ile Dağıtık Uygulamalar serisi anlatacak.
Çok Instance ve Graceful Shutdown
Production'da gateway tek instance olmamalı; tek bir gateway bütün sistemin single point of failure olur. İki instance (5300 ve 5320) başlatıp "Önce Tahmin Et"in ilk sorusunu denedim:
# Ada, 12 istek, iki instance'a sırayla
5300:200 5320:200 5300:200 5320:200 5300:200 5320:200 5300:200 5320:200 5300:200 5320:200 5300:429 5320:429
# output cache, her instance'a ikişer istek
X-Catalog-Instance: catalog-1 <- :5300
Age: 0 X-Catalog-Instance: catalog-1 <- :5300
X-Catalog-Instance: catalog-1 <- :5320
Age: 0 X-Catalog-Instance: catalog-1 <- :5320
Cevap 2: Ada 5 yerine 10 istek geçirdi. 02 ve 04'te söylediğimiz şey burada ölçülmüş oldu: rate limiter ve output cache sayaçları instance'ın belleğinde. Output cache'te her instance backend'e ayrı gidiyor; bu çoğu zaman kabul edilebilir. Rate limit'te ise limit fiilen instance sayısıyla çarpılıyor. Paylaşılan bir store ya da limiti instance sayısına bölmek gibi çözümleri ASP.NET Core Güvenliği serisinin Rate Limiting yazısında konuştuk. Route config'inin instance'lara dağıtılması da aynı soru: dosyadan okuyan her instance kendi dosyasını görür; 02'deki veritabanı provider'ı ise her instance'ın 10 saniyede bir aynı tabloyu okumasını sağlıyor. Container'ın log'unda DataProtection anahtarlarının container içinde saklandığını söyleyen bir uyarı da var. Bugün önemsiz, ama gateway cookie üretmeye başlarsa (05'te andığımız OpenID Connect cookie session'ı gibi) anahtarların instance'lar arasında paylaşılması gerekir.
İkinci soru graceful shutdown. Catalog'u 8 saniyelik gecikmeyle başlattım, gateway'e istek gönderdim ve 1 saniye sonra gateway process'ine SIGTERM gönderdim:
# varsayılan ayarlar
SIGTERM at +1s
new request at +2s: curl exit 7
long request: 200 after 8.065999s
Application is shutting down...
# ASPNETCORE_SHUTDOWNTIMEOUTSECONDS=3
SIGTERM at +1s
new request at +2s: curl exit 7
long request: 000 after 4.017660s
long request: curl exit 56
Varsayılanla açık istek 8 saniyenin sonunda 200 aldı. Host SIGTERM'i alınca yeni bağlantı kabul etmeyi bıraktı (yeni istek bağlantı hatası aldı) ve açık istekleri bekledi. Bekleme süresi ShutdownTimeout; dokümantasyona göre .NET 6'dan beri varsayılanı 30 saniye. 3 saniyeye indirdiğimde süre doldu ve açık istek bağlantı kopmasıyla bitti. Kubernetes'te bu süre, pod'un sonlandırılma süresiyle (terminationGracePeriodSeconds) uyumlu olmalı. Pod'un endpoint listesinden çıkarılması da SIGTERM ile aynı anda başlıyor; birkaç saniye boyunca yeni istekler kapanmakta olan pod'a gelebilir, bu yüzden kapanmaya başlamadan önce kısa bir bekleme yaygın bir çözüm. WebSocket gibi uzun bağlantılar için 30 saniye çoğu zaman yetmez; o bağlantıların istemci tarafında yeniden bağlanabilmesi gerekir.
Gateway'i Kubernetes'te bir Ingress'in ya da Gateway API'nin arkasına mı, yoksa onların yerine mi koyacağın ayrı bir karar. Arkasına koymak TLS'i (Transport Layer Security; HTTPS'in altındaki şifreleme protokolü) ve dış trafiği platformun standart bileşenine bırakır, gateway yalnız kod gerektiren işleri yapar; bedeli bir proxy hop'u daha ve iki yerde routing. Yerine koymak tek bir routing katmanı verir ama TLS sertifikalarını ve dış yük dengelemeyi senin yönetmen gerekir. Ingress ve Gateway API'yi Kubernetes serisinin dış trafik yazısında anlattık. Arkasına koyarsan forwarded header zincirine dikkat: YARP X-Forwarded-For'u gateway'in gördüğü istemci adresiyle yazıyor (02); gateway ingress'in arkasındaysa bu adres ingress'in adresi olur. Gateway'de ForwardedHeaders middleware'ini yalnız ingress'in ağına güvenecek şekilde ayarlamak gerekiyor; ayrıntısı Web Server'lar ve Hosting serisinin forwarded header yazısında.
Üretimde Dikkat
Gateway'in kendisi için de SLO (Service Level Objective; servisin hedeflenen güvenilirlik seviyesi) tanımla ve onu backend'lerden ayrı ölç. Gateway'in kendi ürettiği hatalar (NoAvailableDestinations, Request, rate limit 429'ları, timeout 504'leri) ile backend'den geçen hatalar ayrı grafiklerde durmalı; aksi hâlde her backend sorunu gateway ekibinin alarmı olur.
Kapasiteyi CPU'ya göre planla. Ölçtüğümüz gibi gecikme maliyeti küçük ama her istek gateway'de bir HTTP isteğini karşılayıp bir yenisini açıyor. Gateway instance'larının CPU'sunu izle ve autoscaling'i buna bağla.
Health check trafiğini hesaba kat. Üç destination, 5 saniyelik aralık ve on gateway instance'ı backend başına saniyede iki probe demek; health endpoint'i ucuz olmalı ve trace'lerde filtrelenmeli.
Deploy sırasında config ile kod arasındaki sırayı düşün. Yeni bir route yeni bir policy adı kullanıyorsa ve o policy henüz deploy edilmemiş kodda tanımlıysa, veritabanından gelen config eski instance'larda hata verir. Önce kodu, sonra config'i yayınla.
Sık Düşülen Tuzaklar
Birincisi, gateway'i tek instance olarak çalıştırmak. Gateway çökünce her şey çöker; en az iki instance ve bir health check'li load balancer şart.
İkincisi, gateway'de body'yi loglamak. Kişisel veri, token ve dosyalar log'a düşer, maliyet ve risk birlikte artar.
Üçüncüsü, instance başına çalışan özellikleri (rate limit, output cache, canary bucket'ı olmayan rastgele seçimler) tek instance'ta test edip production'da aynı davranışı beklemek.
Dördüncüsü, shutdown süresini platformla uyumlu ayarlamamak. Kısa bir ShutdownTimeout deploy'larda açık istekleri keser; çok uzun bir süre ise Kubernetes'in pod'u zorla öldürmesiyle aynı sonuca varır.
Beşincisi, hazır bir image'ı etiketine bakmadan production'a koymak. nightly ve preview etiketli, desteği bitmiş bir runtime üzerinde çalışan bir image, kod yazmamanın kazancını güvenlik güncellemesi alamamanın maliyetiyle değiştirir.
Küçük Bir Deneme
★☆☆ Gateway'i OTEL_EXPORTER_OTLP_ENDPOINT ile Jaeger'a bağla ve Ordering'e de Catalog'daki gibi OpenTelemetry ekle. 05'teki aggregation endpoint'inin trace'inde artık kaç span var? En uzun süren hangisi?
★★☆ k6 testini output cache'li /api/v1/books route'una karşı çalıştır ve doğrudan Catalog sonucuyla karşılaştır. Gateway bu sefer neden daha hızlı olabilir? Aynı testi Authorization header'ıyla tekrarla ve farkı 04'teki bilgiyle açıkla.
★★★ Gateway'e IHostApplicationLifetime.ApplicationStopping'e bağlı bir readiness endpoint'i ekle: SIGTERM geldiğinde endpoint hemen 503 dönsün, host ise 5 saniye bekledikten sonra kapanmaya başlasın. İki gateway instance'ının önüne 03'teki gibi active health check'li ikinci bir YARP koyup kapanan instance'a yeni istek gitmediğini load test sırasında ölç.
Gateway artık izlenebiliyor, ölçülmüş bir maliyeti var ve container olarak çalışıyor. Serinin son yazısında YARP'ın geçmişine bakacağız: 1.0'dan 2.3'e değişen varsayılanlar, eski bir YARP konfigürasyonunu okumak ve Ocelot tabanlı bir gateway'i YARP'a taşırken kavramların karşılıkları.