این آزمون پنج درس بخش «عملکرد و مقیاس» را میسنجد: عملکرد: gzip و کش، load balancing، محدودسازی درخواستها، Nginx در داکر و Compose و پروژه: استقرار کامل. جواب هر سؤال همان لحظه با توضیح نشان داده میشود و نتیجهات در همین مرورگر ذخیره میشود (پیشرفت من).
روی Ubuntu با gzip on، صفحهی HTML فشرده میآید ولی CSS نه. چرا؟
gzip_types را صریح بنویس و با curl -H "Accept-Encoding: gzip" -I بررسی کن.
کدام نوع فایل را نباید در gzip_types گذاشت؟
JPEG و PNG و ویدیو خودشان فشردهاند؛ gzip فقط CPU هدر میدهد.
gzip_vary on چه فایدهای دارد؟
پاسخ یک آدرس به هدر Accept-Encoding درخواست بستگی دارد.
سیاست کش درست برای HTML و برای /_astro/style.3fa9c1.css کدام است؟
اسم فایل hashدار با هر تغییر محتوا عوض میشود؛ HTML اسم ثابت دارد.
gzip_static on دقیقاً چه میکند؟
فایل .gz را در build با سطح ۹ بساز (gzip -k -9).
در upstream بدون zone، ۹ درخواست پشتسرهم به سه سرور نامتوازن پخش شد. چرا؟
zone backend 64k; در upstream.
گزارشگیریها چند ثانیه طول میکشند و بقیهی درخواستها کوتاهاند. کدام روش پخش؟
least_conn به عضوی میفرستد که کمترین اتصال فعال را دارد.
کاربرها از پشت یک اپراتور با IP های 5.6.7.x میآیند و ip_hash داری. نتیجه؟
hash $remote_addr consistent کل IP را حساب میکند.
یکی از سه سرور خاموش شد و max_fails=2 fail_timeout=10s داری. کاربر چه میبیند؟
در error log دو خط connect() failed میبینی، کاربر نه.
اپ وسط پردازش یک POST اتصال را بست. Nginx آن را به سرور دیگر میفرستد؟
شاید سرور اول کار را انجام داده باشد؛ تکرار یعنی خطر پرداخت دوباره.
برای دیپلوی یکییکی سرورهای upstream بدون خطا، چه میکنی؟
reload بیقطعی است و down عضو را بدون شکست کنار میگذارد.
با rate=2r/s و بدون burst، دو درخواست با فاصلهی ۱۰۰ میلیثانیه. دومی؟
Nginx با دقت میلیثانیه میشمارد.
تفاوت burst=5 با burst=5 nodelay؟
برای صفحههای واقعی burst + nodelay رفتار طبیعیتری دارد.
location /x { return 200 "ok"; limit_req zone=z; } چرا محدود نمیشود؟
ترتیب مرحلهها مهم است، نه ترتیب خطها.
کلید یک limit_req_zone را عوض کردی و اسمش را نه. nginx -t موفق و systemctl reload هم کد ۰ داد. چه شده؟
وقتی کلید یا اندازهی zone عوض میشود، اسمش را هم عوض کن یا restart بزن؛ بعد از reload لاگ را ببین.
چطور IP دفتر را از محدودیت معاف میکنی؟
geo $limited { default 1; IP 0; } و map $limited $key { 0 ""; 1 $binary_remote_addr; }
در Compose نوشتهای proxy_pass http://localhost:3000 و اپ سرویس app است. نتیجه؟
هر کانتینر localhost خودش را دارد.
بعد از دیپلوی API در Compose، سایت ۵۰۲ میدهد تا nginx -s reload. علت و راهحل دائمی؟
getent hosts api داخل کانتینر IP جدید را نشان میدهد ولی Nginx هنوز قدیمی را دارد.
فایل default.conf را تنها mount کردهای و با sed -i ویرایشش کردی؛ reload هم زدی ولی تغییر اعمال نشد. چرا؟
mount پوشهی conf.d این مشکل را ندارد.
در استقرار اتمی، چرا ln -sfn new current.new و بعد mv -T current.new current؟
ln -sfn مستقیم روی current پشت صحنه پاک کن و بساز است.
certbot renew --dry-run موفق بود. آیا deploy hook (systemctl reload nginx) هم آزمایش شد؟
اجرای hook در /var/log/letsencrypt/letsencrypt.log ثبت میشود.
فردا حراج است. روی سرور، اینها را بساز و با یک اسکریپت ثابت کن:
۱. دو نسخه از یک API (روی 127.0.0.1:9431 و 9432) که در پاسخ بگوید کدام نسخه است و مسیر /api/search را جواب بدهد.
۲. Nginx برای sale.test: upstream با zone، روش least_conn، max_fails=1 fail_timeout=5s.
۳. /api/search محدود به 2r/s برای هر IP با burst=4 nodelay و کد 429؛ بقیهی /api/ بدون این محدودیت.
۴. پاسخهای JSON API فشرده (gzip) شوند.
۵. اسکریپت آزمایش: پخش بین دو نسخه، ادامهی کار وقتی یکی خاموش است، تعداد 200 و 429 در ۱۰ جستجوی همزمان، و Content-Encoding: gzip روی JSON.
راهنمایی
برای API همان الگوی درس load balancing: یک اسکریپت Node و یک unit قالبی systemd (lx-sale@9431). JSON را کمی بزرگ کن (مثلاً ۱۰۰ نتیجه) تا از gzip_min_length بیشتر شود. برای /api/search یک location = /api/search جدا با limit_req. یادت باشد پاسخ پشت proxy با gzip_proxied any فشرده میشود. برای درخواستهای همزمان: seq 10 | xargs -P 10 -I{} curl ....
راهحل
systemctl stop 'lx-sale@*' 2>/dev/nullrm -rf /srv/lx-sale && mkdir -p /srv/lx-salegrep -v '\.test$' /etc/hosts > /tmp/hosts.lx; cat /tmp/hosts.lx > /etc/hostsecho '127.0.0.1 sale.test' >> /etc/hosts
cat > /srv/lx-sale/app.js <<'EOF'// sale API: /api/search returns 100 fake resultsconst http = require('http');const port = Number(process.env.PORT);http.createServer((req, res) => { const results = Array.from({ length: 100 }, (_, i) => ({ id: i, title: `book ${i}`, price: 100000 + i * 500 })); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ instance: port, path: req.url, results }));}).listen(port, '127.0.0.1');EOFcat > /etc/systemd/system/lx-sale@.service <<'EOF'[Unit]Description=sale API on port %i
[Service]Environment=PORT=%iExecStart=/usr/bin/node /srv/lx-sale/app.jsRestart=on-failureEOFsystemctl daemon-reloadsystemctl start lx-sale@9431 lx-sale@9432
cat > /etc/nginx/sites-available/sale.test <<'EOF'upstream sale_api { zone sale_api 64k; least_conn; server 127.0.0.1:9431 max_fails=1 fail_timeout=5s; server 127.0.0.1:9432 max_fails=1 fail_timeout=5s;}limit_req_zone $binary_remote_addr zone=sale_search:10m rate=2r/s;
server { listen 80; server_name sale.test;
gzip on; gzip_proxied any; gzip_types application/json;
location /api/ { proxy_pass http://sale_api; include proxy_params; } location = /api/search { limit_req zone=sale_search burst=4 nodelay; limit_req_status 429; proxy_pass http://sale_api; include proxy_params; }}EOFln -sf /etc/nginx/sites-available/sale.test /etc/nginx/sites-enabled/sleep 1nginx -t 2>&1 | tail -n 1 && systemctl reload nginxsleep 1
inst() { curl -s "$@" | grep -o '"instance":[0-9]*' | cut -d: -f2; }echo "1) spread: $(for i in 1 2 3 4 5 6; do inst http://sale.test/api/items; done | sort | uniq -c | tr -s ' ' | tr '\n' ' ')"systemctl stop lx-sale@9431echo "2) 9431 off: $(for i in 1 2 3 4; do curl -s -o /dev/null -w '%{http_code} ' http://sale.test/api/items; done)-> $(inst http://sale.test/api/items)"systemctl start lx-sale@9431sleep 1echo "3) search: $(seq 10 | xargs -P 10 -I{} curl -s -o /dev/null -w '%{http_code}\n' 'http://sale.test/api/search?q=x' | sort | uniq -c | tr -s ' ' | tr '\n' ' ')"echo " items: $(seq 10 | xargs -P 10 -I{} curl -s -o /dev/null -w '%{http_code}\n' http://sale.test/api/items | sort | uniq -c | tr -s ' ' | tr '\n' ' ')"echo "4) gzip: $(curl -s -D - -o /dev/null -H 'Accept-Encoding: gzip' http://sale.test/api/items | grep -i '^content-encoding' | tr -d '\r'), $(curl -s -o /dev/null -w '%{size_download}' http://sale.test/api/items) -> $(curl -s -o /dev/null -w '%{size_download}' -H 'Accept-Encoding: gzip' http://sale.test/api/items) bytes"nginx: configuration file /etc/nginx/nginx.conf test is successful1) spread: 3 9431 3 94322) 9431 off: 200 200 200 200 -> 94323) search: 5 200 5 429 items: 10 2004) gzip: Content-Encoding: gzip, 4329 -> 759 bytesهر بخش ثابت شد:
- پخش: ۶ درخواست، ۳ به هر نسخه (
least_connوقتی همه بیکارند مثل round robin رفتار میکند). - خرابی: با ۹۴۳۱ خاموش، هر ۴ درخواست
200گرفتند و همه را ۹۴۳۲ جواب داد. - محدودیت: از ۱۰ جستجوی همزمان، ۵ تا قبول (۱ +
burst=4) و ۵ تا429؛ ولی ۱۰ درخواست همزمان به/api/itemsهمه200، چونlocation = /api/search(تطابق دقیق) محدودیت جدای خودش را دارد و بقیهی/api/نه. - gzip: JSON اپ (از پشت proxy، با
gzip_proxied any) فشرده شد: ۴۳۲۹ بایت به ۷۵۹ بایت.