აპლიკაცია, რომელიც შენს ლეპტოპზე flask run-ით ან uvicorn main:app --reload-ით მუშაობს, production-ს იმაზე ახლოს არის, ვიდრე ხალხს ეშინია. მიაბი 0.0.0.0-ს localhost-ის ნაცვლად, პორტი აიღე გარემოდან, განვითარების სერვერი გამოცვალე gunicorn-ით ან uvicorn-ით, აირჩიე worker-ების ისეთი რაოდენობა, რომლის ღირებულებასაც შენი მეხსიერება ნამდვილად გაუძლებს, და აპლიკაციას უთხარი, რომ proxy-ის უკან დგას. ეს ხუთი რამ არის მთელი საქმე. ქვემოთ დარჩენილი ყველაფერი მათი დეტალებია: რიცხვები, რომლებზეც worker-ები უნდა გაზომო, პარამეტრები, რომლებიც ქცევას ცვლის, და შეცდომები, რომლებსაც დაახლოებით იმ თანმიმდევრობით შეხვდები, რა თანმიმდევრობითაც ისინი ჩნდება.
ორი framework-ი ერთი რამით განსხვავდება და სწორედ ეს წყვეტს თითქმის ყველა მომდევნო არჩევანს. Flask სინქრონულია და WSGI-ზე მუშაობს. FastAPI ასინქრონულია და ASGI-ზე მუშაობს. ეს რომ გაიგე, დანარჩენი თავისით მოსდევს.
ASGI თუ WSGI: რა სჭირდება შენს framework-ს#
WSGI ძველი, სინქრონული Python ვებ ინტერფეისია: სერვერი აპლიკაციას მოთხოვნას გადასცემს და პასუხს ელოდება. ერთი worker ერთდროულად ერთ მოთხოვნას ამუშავებს. ის მარტივია, გამოცდილია და websocket-ის ღია შენახვა არ შეუძლია.
ASGI ასინქრონული შემცვლელია. ერთ პროცესს შეუძლია ათასობით კავშირი ერთდროულად გაიხსნას, სანამ მის შიგნით გაშვებული კოდი შეყვანა-გამოყვანის მოლოდინში კონტროლს გადასცემს. სწორედ ეს ხდის შესაძლებელს websocket-ებს, server-sent events-სა და long-polling-ს ერთ პროცესში.
| Framework | ინტერფეისი | რომელი სერვერი გამოვიყენო | Import string |
|---|---|---|---|
| Flask | WSGI | gunicorn | wsgi:app |
| Django | WSGI (ASGI არჩევითია) | gunicorn | myproject.wsgi:application |
| FastAPI | ASGI | uvicorn | app.main:app |
| Starlette | ASGI | uvicorn | app.main:app |
Flask 2.0-დან async def view ფუნქციებს მხარს უჭერს და ამაში ზედმეტის ამოკითხვა ადვილია. Flask ასინქრონულ view-ს ისე უშვებს, რომ ამ ერთი მოთხოვნისთვის event loop-ს ქმნის და მის დასრულებას ელოდება, ამიტომ worker მთელი მოთხოვნის განმავლობაში დაკავებული რჩება. სინტაქსს იღებ, პარალელურობას - არა. თუ გჭირდება, რომ ერთმა პროცესმა ბევრი ნელი მოთხოვნა ერთდროულად ემსახუროს, გჭირდება ASGI framework-ი და არა async view WSGI-ში.
საპირისპირო შემთხვევაც ღირს ხსენებად. თუ შენი აპლიკაცია ბაზის წინ მდგარი რამდენიმე CRUD endpoint-ია, Flask gunicorn-ზე რამდენიმე thread-ით მას სავსებით კარგად ემსახურება და ნაკლები მექანიზმია, რომელიც შეიძლება გაფუჭდეს. FastAPI აირჩიე ტიპების ვალიდაციისა და გენერირებული OpenAPI სქემის გამო და არა იმიტომ, რომ async ქაღალდზე უფრო სწრაფია.
Requirements, virtualenv და ინსტალაციის ნაბიჯი#
ყველაფერი, რასაც სერვერი უშვებს, მოდის დაფიქსირებული requirements ფაილიდან, რომელიც აპლიკაციის საკუთრებაში არსებულ virtual environment-ში ინსტალირდება. არა სისტემურ Python-ში და არა იმაში, რასაც pip-მა იმ დღეს გადაწყვიტა.
fastapi==0.115.0uvicorn[standard]==0.30.6gunicorn==22.0.0pydantic-settings==2.4.0psycopg[binary]==3.2.1$ python -m venv .venv$ .venv/bin/pip install --upgrade pip$ .venv/bin/pip install --no-cache-dir -r requirements.txtგარემოს გააქტიურების ნაცვლად .venv/bin/pip-ისა და .venv/bin/python-ის პირდაპირ გამოძახება „ჩემს shell-ში მუშაობს“ ტიპის პრობლემების მთელ კატეგორიას აქრობს - shell-ის მდგომარეობა არ არსებობს, რომ არასწორი გამოვიდეს. --no-cache-dir პატარა გეგმაზე მნიშვნელოვანია: pip-ის wheel ქეში home დირექტორიაში ინახება და ჩუმად ასობით მეგაბაიტამდე იზრდება დისკზე, რომელიც შეიძლება სულ 5 GB იყოს. Python-ის requirements და virtualenv-ები განიხილავს ვერსიების დაფიქსირებას, lock ფაილებსა და პაკეტებს, რომლებიც ნახევარბირთვიან მანქანაზე საკუთარი თავის კომპილაციას ცდილობენ.
uvicorn-ზე [standard] დამატება ღირს. ის მოაქვს uvloop-სა და httptools-ს, რომლებიც წმინდა Python-ის event loop-სა და HTTP პარსერს C იმპლემენტაციებით ცვლიან, და ასევე websockets ბიბლიოთეკას. ეს FastAPI აპლიკაციისთვის ხელმისაწვდომი ყველაზე იაფი გამტარუნარიანობის გაუმჯობესებაა და ერთ ხაზს ჯდება.
სად გაეშვას ინსტალაცია, ეს გადაწყვეტილებაა. თუ pip install -r requirements.txt შენი start ბრძანების წინ დგას, ყოველი გადატვირთვა თავიდან აინსტალირებს, რაც ნელია, მაგრამ იძლევა გარანტიას, რომ გაშვებული კოდი და გამოცხადებული დამოკიდებულებები ერთმანეთს ემთხვევა. ერთხელ ხელით ინსტალაცია უფრო სწრაფად იტვირთება, მაგრამ იმ წუთას იწყებს გადახრას, როცა ვიღაც პაკეტს დაამატებს და დაივიწყებს. ჰოსტზე, რომელიც შენს repository-ს start-ზე ტვირთავს, პირველი ვარიანტი ჩვეულებრივ სწორია: pip უკვე დაკმაყოფილებულს გამოტოვებს, ამიტომ გადატვირთვა, სადაც დამოკიდებულებები არ შეცვლილა, რამდენიმე წამს ამატებს და არა რამდენიმე წუთს.
Start ბრძანება: მიბმა, პორტები და ერთი რამ, რაც ტყდება#
აქ ვარდება პირველი deploy-ების უმეტესობა და ყოველთვის ერთსა და იმავე მიზეზით. კონტეინერის შიგნით 127.0.0.1 თვით კონტეინერს ნიშნავს. გარედან ვერაფერი მისწვდება. სერვერი უნდა მიებას 0.0.0.0-ს.
# FastAPI, single process$ uvicorn app.main:app --host 0.0.0.0 --port 8000# Flask, four gunicorn workers$ gunicorn wsgi:app --bind 0.0.0.0:8000 --workers 4# FastAPI under gunicorn, which supervises uvicorn workers$ gunicorn app.main:app -k uvicorn.workers.UvicornWorker \ --bind 0.0.0.0:8000 --workers 4ეს მესამე ფორმა რამდენიმე uvicorn worker-ის გაშვების ტრადიციული გზაა და ის მიგრაციის შუაშია. uvicorn 0.30-დან ჩაშენებული uvicorn.workers მოდული მოძველებულად ითვლება ცალკე uvicorn-worker პაკეტის სასარგებლოდ, რომლის კლასია uvicorn_worker.UvicornWorker. ხაზის კოპირებამდე შეამოწმე, რომელი ვერსია დააფიქსირე. uvicorn-ს ასევე შეუძლია საკუთარი worker-ების გაშვება --workers 4-ით და gunicorn საერთოდ არ სჭირდება, რაც ერთი დამოკიდებულებით ნაკლებია იმავე შედეგისთვის.
import string არის module:attribute, სადაც მოდულის გზა წერტილებს იყენებს, ატრიბუტი კი აპლიკაციის ობიექტია. app.main:app ნიშნავს „ცვლადი app ფაილში app/main.py“. თუ შენი Flask აპლიკაცია factory შაბლონს იყენებს, gunicorn მას თავად გამოიძახებს: gunicorn "myapp:create_app()". uvicorn-ს ამის ნაცვლად --factory უნდა.
პორტი აიღე გარემოდან და არ ჩაწერო კოდში მყარად, რადგან პორტი, რომელსაც პანელი ან პლატფორმა გამოყოფს, ყოველთვის 8000 არ არის:
$ uvicorn app.main:app --host 0.0.0.0 --port ${SERVER_PORT:-8000}Pterodactyl-ზე აგებული პანელები გამოყოფილ პორტს გარემოს ცვლადად აჩვენებს Startup ტაბზე; სხვა პლატფორმები PORT-ს იყენებენ. შეხედე ტაბს, გამოიყენე სახელი, რომელსაც იპოვი, და შენი მანქანისთვის დატოვე გონივრული ნაგულისხმევი.
Worker-ები, thread-ები და რა უჯდება თითოეული მეხსიერებაში#
gunicorn-ის დოკუმენტაცია (2 x cores) + 1 worker-ს გვთავაზობს. ეს რჩევა ვარაუდობს მანქანას, რომლის ბირთვებიც შენია. კონტეინერზე, რომელსაც CPU-ს მყარი წილი ნახევარი ბირთვია, ის ისეთ რიცხვს იძლევა, რომელიც მეხსიერებას ამოწურავს დიდი ხნით ადრე, სანამ ვინმეს რამეს მოუტანს, რადგან თითოეული worker ცალკე ოპერაციული სისტემის პროცესია, რომელსაც საკუთარი ინტერპრეტატორის ასლი და ყველა დაიმპორტებული მოდული უჭირავს.
ერთხელ გაზომე შენს აპლიკაციაზე, ps -o rss= -p PID-ით მას შემდეგ, რაც გარკვეული ტრაფიკი მოემსახურა. საწყისი წერტილი: პატარა Flask აპლიკაცია worker-ზე დაახლოებით 50-80 MB-ზე დგას; FastAPI აპლიკაცია pydantic მოდელებითა და ბაზის დრაივერით 80-150 MB-ს უახლოვდება; ყველაფერი, რაც numpy-ს, pandas-ს ან machine-learning ბიბლიოთეკას შემოიტანს, სხვა ლიგაშია და მხოლოდ გაზომვით უნდა შეფასდეს.
| გეგმის მეხსიერება | CPU წილი | Flask (სინქრონული worker-ები) | FastAPI (ასინქრონული worker-ები) |
|---|---|---|---|
| 1 GB | 0.5 ბირთვი | 1-2 | 1 |
| 2 GB | 1 ბირთვი | 2-3 | 1-2 |
| 4 GB | 1.5 ბირთვი | 3-4 | 2 |
| 6-8 GB | 2-3 ბირთვი | 4-6 | 2-3 |
ეს სვეტები დაახლოებით 150 MB-ს ვარაუდობს worker-ზე და სივრცეს ტოვებს request body-ებისთვის, ქეშებისა და დროდადრო დიდი პასუხისთვის. ასინქრონული worker დაბალი რიცხვით ჩანს, რადგან მეგობრები არ სჭირდება: ერთ uvicorn პროცესს შეუძლია ასობით ერთდროულ მოთხოვნას ემსახუროს, როცა სამუშაო ბაზის მოლოდინია. იმ CPU წილზე მეტი worker, რომელიც შეიძინე, არაფერს გაძლევს.
ორი პარამეტრი ამ სურათის ფორმას ცვლის:
--worker-class gthread --threads 4სინქრონულ gunicorn worker-ს ოთხ thread-ს აძლევს. ოთხი მოთხოვნა, რომლებიც ყველა ბაზას ელოდება, ერთდროულად შეიძლება მიმდინარეობდეს ერთი პროცესის მეხსიერების ფასად. I/O-ზე დამოკიდებული Flask აპლიკაციისთვის ეს საუკეთესო ღირებულების პარამეტრია.--max-requests 1000 --max-requests-jitter 100worker-ს დაახლოებით ათას მოთხოვნაზე ათავისუფლებს და ახალს უშვებს. ეს უხეში იარაღია და ასევე ისაა, რითაც გადაურჩები ნელ გაჟონვას დამოკიდებულებაში, რომელსაც ვერ აკონტროლებ. jitter ყველა worker-ს ერთდროულად გადატვირთვისგან იცავს.
FastAPI-სთვის არსებობს ერთი წესი, რომელიც ყველაფერს აღემატება: არასოდეს დაბლოკო event loop. სინქრონული ბაზის გამოძახება, requests.get ან CPU-ზე მძიმე ციკლი async def handler-ის შიგნით ამ პროცესის ყველა სხვა მოთხოვნას აჩერებს, სანამ არ დასრულდება. FastAPI ნაწილობრივ გიცავს იმით, რომ ჩვეულებრივ def handler-ებს threadpool-ში უშვებს რამდენიმე ათეული სლოტით, ამიტომ სინქრონული endpoint უსაფრთხოა; async def endpoint, რომელიც დამბლოკავ კოდს შეიცავს, - არა.
worker-ების ჭარბი რაოდენობა კონტეინერის ჰოსტებზე კონკრეტული სახის შეცდომას იძლევა. RE:NODE-ზე სერვერი, რომელიც მეხსიერების ლიმიტს მიაღწევს, kernel-ის მიერ ჩერდება და სუფთად თავიდან იტვირთება swap-ში ჩაგდების ნაცვლად, ამიტომ ერთი ზედმეტი worker აპლიკაციად ჩანს, რომელიც დატვირთვისას კვდება და არა აპლიკაციად, რომელიც ნელდება. crash watcher განმეორებით გადატვირთვებს ამჩნევს და საათში სამის შემდეგ ticket-ს ხსნის, რაც სასარგებლო სიგნალია იმისა, რომ შენი worker-ების რაოდენობა და გეგმა ერთმანეთს არ ემთხვევა.
Reverse proxy-ის უკან: HTTPS, კლიენტის IP-ები და timeout-ები#
production-ში TLS-ს შენს აპლიკაციამდე რაღაც წყვეტს და მასთან უბრალო HTTP გადმოაქვს. ეს რაღაც სამ header-ს ადგენს - X-Forwarded-For, X-Forwarded-Proto და X-Forwarded-Host - და შენი აპლიკაცია ყველა მათგანს უგულებელყოფს, სანამ არ ეტყვი, რომ ასე არ ქნას.
uvicorn მიმდინარე ვერსიებში forwarded header-ებს ნაგულისხმევად კითხულობს, მაგრამ მხოლოდ იმ მისამართებიდან, რომლებიც --forwarded-allow-ips-შია ჩამოთვლილი და რომლის ნაგულისხმევი მნიშვნელობაა 127.0.0.1. თუ შენი proxy სხვა მისამართზეა, header-ები იგნორირდება და ყველა კლიენტი proxy-ს ჰგავს. მიუთითე proxy-ს მისამართი, ან *, როცა პორტამდე proxy-ს გარდა ვერავინ აღწევს.
Flask-ს middleware სჭირდება:
from flask import Flaskfrom werkzeug.middleware.proxy_fix import ProxyFixapp = Flask(__name__)app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)რიცხვები ამბობს, რამდენ proxy-ს სიღრმეზე უნდა ენდო. ერთი proxy ნიშნავს 1-ს. მათი იმაზე მაღლა დაყენება, ვიდრე proxy-ების რეალური რაოდენობაა, კლიენტს საშუალებას აძლევს, header თვითონ გამოაგზავნოს და საკუთარი მისამართი გააყალბოს, რაც სტანდარტული გზაა, რომლითაც ხალხი შემთხვევით საკუთარ rate limiting-ს ამარცხებს. რას აკეთებს reverse proxy header-ების ჯაჭვს უფრო დეტალურად ფარავს.
კიდევ ორი proxy-სთან დაკავშირებული პრობლემა. გადამისამართების ციკლები ჩნდება, როცა აპლიკაცია ფიქრობს, რომ მოთხოვნა HTTP იყო, HTTPS-ზე გადაამისამართებს, და proxy იმავე მოთხოვნას თავიდან გადაგზავნის - proto header-ის გასწორება ციკლს აგვარებს. ხოლო websocket-ებს upgrade header-ების გატარება სჭირდება და read timeout, რომელიც უმოქმედო კავშირისთვის საკმარისად გრძელია, რაც ცალკე თემაა websocket-ები reverse proxy-ის უკან.
RE:NODE-ზე აპლიკაციის გეგმები proxy slot-ს შეიცავს: მიუთითე A ჩანაწერი ნაჩვენებ მისამართზე და სერტიფიკატი ავტომატურად გაიცემა და განახლდება 21-დღიან ფანჯარაში, ხოლო კლიენტის რეალური მისამართი X-Forwarded-For-ში მოდის. ეს ნიშნავს, რომ ზემოთ აღწერილი კონფიგურაცია იქ არჩევითი არ არის - ეს ერთადერთი გზაა, რომ შენს ლოგებში ერთი განმეორებადი მისამართის გარდა სხვა რამეც ჩანდეს.
გარემოს ცვლადები და პარამეტრები#
კონფიგურაცია გარემოშია და არა repository-ში. შაბლონი, რომელიც გუნდთან შეხვედრას უძლებს, ტიპიზებული settings ობიექტია, რომელიც start-ზე ხმამაღლა ვარდება, თუ რამე აკლია:
from pydantic_settings import BaseSettings, SettingsConfigDictclass Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env") database_url: str secret_key: str debug: bool = Falsesettings = Settings()დაკარგული DATABASE_URL ახლა პროცესს import-ზე ჩერდება გასაგები შეტყობინებით, იმის ნაცვლად, რომ პირველ მოთხოვნაზე საათის შემდეგ კავშირის შეცდომა გამოიდოს. Flask-ს იგივე იდეის უფრო მცირე ვერსია აქვს: app.config.from_prefixed_env() ყველა ცვლადს, რომელიც FLASK_-ით იწყება, config ობიექტში ტვირთავს.
DATABASE_URL=postgresql://app:secret@db.example.net:5432/appSECRET_KEY=change-meDEBUG=falsePYTHONUNBUFFERED=1.env git-დან გარეთ დატოვე და რეალური მნიშვნელობები სერვერზე დააყენე. ყველაფერი, რაც პანელის Startup ტაბში ჩაიწერება, ხილულია ყველასთვის, ვისაც ამ სერვერის გახსნა შეუძლია, ამიტომ თანამშრომლებს მიეცი როლი, რომელიც მას გამორიცხავს, თუ მათ მხოლოდ console სჭირდებათ - გარემოს ცვლადები და საიდუმლოებები დანარჩენს განიხილავს, იმათ შორის იმასაც, რა უნდა გააკეთო მას შემდეგ, რაც გასაღები იქ ჩაჯდა, სადაც არ უნდა ყოფილიყო.
სტატიკური ფაილები, ატვირთვები და ბაზა#
Flask საკუთარ static/ საქაღალდეს თავად ემსახურება. FastAPI ამას mount-ით აკეთებს:
from fastapi.staticfiles import StaticFilesapp.mount("/static", StaticFiles(directory="static"), name="static")ფაილების Python-იდან მიწოდება პატარა მასშტაბზე კარგია. ის აღარ არის კარგი, როცა worker დაბლოკილია 40 MB ფაილის წაკითხვაზე ნელ კავშირზე მყოფი ვიღაცისთვის, რაც ერთ ან ორ worker-იან გეგმაზე რეალური ფასია. თუ წინ proxy გიდგას, მიეცი მას ყველაფრის ქეშირების საშუალება, რაც შეუძლია, და ყველაფერზე, რაც ფაილის სახელში hash-ს შეიცავს, გრძელი Cache-Control მნიშვნელობები გააგზავნე.
მომხმარებლების ატვირთვები არ უნდა მოხვდეს /tmp-ში და არც სადმე, სადაც deploy-ზე იცვლება. აირჩიე დირექტორია მუდმივ დისკზე, ჩაწერე იქ და გახსოვდეს, რომ ის გეგმის დისკში ითვლება - 5 GB ყველაზე პატარა აპლიკაციის დონეზე, 50 GB ზედაზე. ატვირთვები ისაა, რაც ხალხს backup-ში ავიწყდება, რადგან ისინი არც repository-შია და არც ბაზაში.
ბაზისთვის რიცხვი, რომელსაც უნდა უყურო, კავშირებია და არა მოთხოვნები. ყოველი gunicorn worker საკუთარ connection pool-ს ქმნის, ამიტომ ჯამი არის worker-ები გამრავლებული pool-ის ზომაზე:
engine = create_engine( settings.database_url, pool_size=5, max_overflow=5, pool_pre_ping=True,)ოთხ worker-ს ამ კონფიგურაციით დატვირთვისას ორმოცი კავშირის გახსნა შეუძლია, რაც პატარა PostgreSQL სერვერის ამოწურვას თავისთავად ჰყოფნის. pool-ის ზომა შეამცირე, როცა worker-ებს ზრდი, და წაიკითხე connection pool-ები და ლიმიტები, სანამ დაუშვებ, რომ ნაგულისხმევი უსაფრთხოა. პანელის ბაზის სლოტებს გენერირებული host, მომხმარებელი და პაროლი მოჰყვება; ცალკე PostgreSQL ან MongoDB ინსტანცია ბაზის ჰოსტინგის გეგმაა.
migration-ები გაუშვი სერვერის start-მდე და არა მის შიგნით:
$ .venv/bin/alembic upgrade head && .venv/bin/uvicorn app.main:app \ --host 0.0.0.0 --port ${SERVER_PORT:-8000}ლოგები, health check-ები და სუფთა გამორთვა#
Python stdout-ს ბუფერავს, როცა ის ტერმინალზე არ არის მიბმული. პანელის console-ზე ეს ჩანს როგორც აპლიკაცია, რომელიც ათი წუთი არაფერს ბეჭდავს და შემდეგ ყველაფერს ერთბაშად გამოსცემს, და ეს აპლიკაციების ჰოსტინგში ყველაზე გავრცელებული ყალბი განგაშია. დააყენე PYTHONUNBUFFERED=1 და გაქრება.
gunicorn error ლოგებს ნაგულისხმევად stderr-ში წერს, მაგრამ access ლოგს საერთოდ არ წერს, სანამ არ მოითხოვ: --access-logfile - მას stdout-ში აგზავნის. uvicorn access ხაზებს ნაგულისხმევად წერს და --no-access-log მათ გამორთავს, რაც ღირს, თუ health check ყოველ ხუთ წამში გეკითხება და ყველა დანარჩენს ჩრდილავს.
health endpoint იაფი უნდა იყოს და ბაზას არ უნდა შეეხოს:
@app.get("/healthz")async def healthz(): return {"status": "ok"}მიზეზი ისაა, რომ liveness check, რომელიც ბაზას ეკითხება, შენს აპლიკაციას თავიდან გაუშვებს, როცა ბაზას ცუდი წუთი აქვს, და პატარა შეფერხებას crash loop-ად აქცევს. თუ გინდა შემოწმება, რომელიც დამოკიდებულებებსაც მოიცავს, გააკეთე ის მეორე endpoint-ად და მისი შედეგი ინფორმაციად აღიქვი და არა თავიდან გაშვების საფუძვლად.
ორივე სერვერი SIGTERM-ს სწორად ამუშავებს: ახალ კავშირებს აღარ იღებს, მიმდინარე მოთხოვნებს ასრულებინებს და გადის. gunicorn worker-ებს კლავს --graceful-timeout წამის შემდეგ (ნაგულისხმევად 30). FastAPI-ის lifespan shutdown სწორედ ამ დროს სრულდება, სადაც pool-ებს ხურავ და ყველაფერს, რაც ბუფერშია, ჩაწერ. Graceful shutdown და health check-ები სრულ შაბლონს შეიცავს, იმის ჩათვლით, რატომ არის მოთხოვნა, რომელიც graceful timeout-ზე დიდხანს გრძელდება, დიზაინის პრობლემა და არა კონფიგურაციის.
რა შეიძლება არასწორად წავიდეს#
ლოგი წერს „Uvicorn running“, მაგრამ არაფერი უკავშირდება. host არის 127.0.0.1. შეცვალე 0.0.0.0-ზე და გადატვირთე.
start-ზე „Address already in use“. წინა პროცესს პორტი ისევ უჭირავს. გამოიყენე პანელის Kill, ან მოძებნე ss -ltnp-ით მანქანაზე, სადაც shell გაქვს.
`ModuleNotFoundError` deploy-ის შემდეგ, რომელიც ლოკალურად მუშაობდა. პაკეტი, რომელიც თვეების წინ ხელით დააინსტალირე, requirements.txt-ში არასოდეს მოხვედრილა. ლოკალურად ფაილიდან თავიდან შექმენი შენი virtualenv და ნახე, როგორ ვარდება ზუსტად ისევე.
ყველაფერი კარგადაა, სანამ ოთხი ადამიანი ერთდროულად არ გამოიყენებს. ერთი სინქრონული worker thread-ების გარეშე. დაამატე --threads 4, შემდეგ worker-ები, თუ მეხსიერება იძლევა.
მოთხოვნები ზუსტად ოცდაათ წამზე კვდება. gunicorn-ის --timeout, რომელიც worker-ს კლავს, თუ მან არ დაასრულა. გაზარდე, თუ endpoint კანონიერად ნელია, მაგრამ ოცდაათწამიანი ვებ მოთხოვნა ჩვეულებრივ ფონურ სამუშაოდ უნდა იქცეს.
Too many redirects. აპლიკაცია ფიქრობს, რომ მოთხოვნა HTTP-ით მოვიდა. გაასწორე X-Forwarded-Proto-ს დამუშავება, სანამ გადამისამართების კოდს შეეხები.
კლიენტის IP ყველა მოთხოვნაზე ერთი და იგივეა. forwarded header-ებს არ ენდობი. დააყენე --forwarded-allow-ips ან დაამატე ProxyFix.
მეხსიერება მთელი დღე იზრდება და სერვერი ღამით თავიდან იტვირთება. რაღაც გაჟონავს. --max-requests დროს გიგებს, სანამ პოულობ; console-ის მეხსიერების გრაფიკი გეუბნება, დახრილობაა თუ საფეხური.
FAQ#
მჭირდება gunicorn, თუ uvicorn-ს უკვე ვიყენებ?
არა. uvicorn-ს საკუთარი worker-ების მართვა --workers-ით შეუძლია და უმეტესი deploy-ისთვის ეს საკმარისია. gunicorn-ის დამატება ღირს, როცა გინდა მისი პროცესების ზედამხედველობა, access ლოგის ფორმატი, ან ისეთი პარამეტრები, როგორიცაა --max-requests, რომელიც uvicorn-ს არ აქვს.
რამდენი worker ეტევა 1 GB-ში?
ერთი ან ორი. FastAPI აპლიკაცია ჩვეულებრივ 80-150 MB-ია worker-ზე, სანამ რამეს გააკეთებს, და კონტეინერს request body-ებიც და ნებისმიერი ქეშიც უნდა დაატიოს. ყველაზე პატარა გეგმებზე ერთი worker thread-ებით ან async პარალელურობით ორ worker-ს სჯობს, რომლებიც ერთი და იმავე მეხსიერებისთვის იბრძვიან.
Flask-ის async მხარდაჭერა FastAPI-სნაირია?
არა. Flask async view-ს ახალ event loop-ში უშვებს და მთელი მოთხოვნის განმავლობაში მაინც worker-ს იკავებს, ამიტომ სინტაქსს იღებ პარალელურობის გარეშე. FastAPI event loop-ზე მუშაობს, რომელიც პროცესის ყველა კავშირს ერთდროულად ემსახურება.
შემიძლია ფონური worker იმავე სერვერზე გავუშვა?
კი, თუ ის პატარაა: გაუშვი იმავე ბრძანებიდან &-ით, რომ ორივე პროცესი ერთ კონტეინერში ცხოვრობდეს. ის გეგმის CPU-ს და მეხსიერებას ვებ პროცესთან ინაწილებს, ამიტომ ყველაფერს, რაც მძიმეა, საკუთარი სერვერი ეკუთვნის. პერიოდული სამუშაოსთვის და არა რიგისთვის, აპლიკაციის შიგნით გაშვებული scheduler ჩვეულებრივ უფრო იაფი პასუხია.
მჭირდება nginx uvicorn-ის წინ?
არა, თუ სხვა რამ უკვე წყვეტს TLS-ს და შენამდე გადმოაქვს. uvicorn კომპეტენტური HTTP სერვერია. proxy ამატებს სერტიფიკატებს, სტატიკური ფაილების ქეშირებას, request-ის ზომის ლიმიტებსა და rate limit-ის ადგილს - თუ ერთი უკვე გაქვს წინ, მეორე hop-ს ამატებს და სხვა არაფერს.
რატომ არ აჩვენებს პანელის console არაფერს, სანამ აპლიკაცია არ დაინგრევა?
Python stdout-ს ბუფერავს, რადგან ის ტერმინალი არ არის. დააყენე PYTHONUNBUFFERED=1 გარემოში, ან გადაეცი -u ინტერპრეტატორს, და გამოტანა იმავე წამს გამოჩნდება, როცა იწერება.




კომენტარები
სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.