RE:NODE

Webhosting14 min čtení

Nasazení Laravelu do produkce: fronty a cron

Co aplikace v Laravelu potřebuje na skutečném serveru: soubor .env a klíč aplikace, zapisovatelné storage, cache konfigurace a rout, migrace, queue workery a scheduler.

2 čtenáři

Nasazení Laravelu se skládá ze šesti kroků v pevném pořadí: dostat kód na server, nainstalovat produkční závislosti, vedle nich vytvořit skutečný .env s klíčem aplikace, zpřístupnit pro zápis storage a bootstrap/cache, sestavit cache a spustit migrace. Všechno, co se potom pokazí, je jen variace na to, že jeden z těch šesti kroků chybí, proběhl ve špatném pořadí nebo na stroji s jinými cestami, než má ten, který obsluhuje provoz. Tenhle návod obsahuje celou posloupnost, nastavení, která rozhodují o tom, jestli je web rychlý, nebo jen funguje, a chyby, které by tě jinak stály celý večer.

Počítá se serverem s Linuxem, nginxem a PHP-FPM, což je většina sdíleného i panelového hostingu. Příkazy artisan jsou stejné na jakémkoli hostingu, i tam, kde máš jen SFTP.

Než nahraješ kód: PHP, rozšíření a document root#

Laravel 10 vyžaduje PHP 8.1 nebo novější. Laravel 11 a 12 vyžadují PHP 8.2 nebo novější. Pokud vybíráš teď, vezmi nejnovější PHP, které tvé závislosti podporují. Přechod ze 7.4 na 8.x je největší bezplatné zrychlení, jaké PHP aplikace může dostat, a OPcache v 8.x lépe drží zkompilovaný kód v paměti.

Rozšíření, která Laravel dokumentuje jako povinná, jsou ctype, curl, dom, fileinfo, filter, hash, mbstring, openssl, pcre, pdo, session, tokenizer a xml. Skoro každá reálná aplikace chce navíc intl pro formátování dat a čísel, zip pro práci s archivy a gd nebo imagick, pokud pracuje s obrázky. Zkontroluj, co máš, ještě než cokoli nahraješ:

bash
$ php -v$ php -m$ php -i | grep -E "opcache.enable|memory_limit|upload_max_filesize"

Document root je nastavení, na kterém narazí každý, kdo přichází z CMS. Musí ukazovat na adresář public/ uvnitř projektu, ne na kořen projektu. Všechno nad public/ - tvůj .env, vendor, storage, app - má být přes HTTP nedostupné. Pokud je root špatně, uvidíš jeden ze dvou příznaků: výpis adresáře se zdrojovým kódem, nebo stránku, která se načte, ale na každé routě kromě / vrací 404. Ani jedno není nenápadné, když už víš, kam se dívat.

Pokud hosting document root pevně nastavuje a nedá se změnit, máš dvě poctivé možnosti: dát obsah public/ do obsluhovaného adresáře a upravit index.php, aby require mířil na skutečné cesty o úroveň výš, nebo si vybrat hosting, kde root ovládáš. První řešení funguje a je ošklivé; spousta produkčních webů takhle běží.

Soubor .env a klíč aplikace#

.env se nenasazuje spolu s kódem. Vytvoří se jednou na serveru a nikdy se necommituje. Minimum pro produkci:

.env
APP_NAME="Example"APP_ENV=productionAPP_KEY=base64:...APP_DEBUG=falseAPP_URL=https://example.comLOG_CHANNEL=stackLOG_LEVEL=warningDB_CONNECTION=pgsqlDB_HOST=db.example.netDB_PORT=5432DB_DATABASE=appDB_USERNAME=appDB_PASSWORD=...SESSION_DRIVER=databaseCACHE_STORE=databaseQUEUE_CONNECTION=database

APP_DEBUG=false není volitelné. Při zapnutí zobrazí každá neodchycená výjimka stránku s cestami k souborům, dotazem a velkou částí prostředí. Nastav to a ověř si to záměrnou chybou 500, při které uvidíš obyčejnou chybovou stránku.

APP_KEY je 32bajtový klíč používaný pro každou šifrovanou cookie a každé volání Crypt::. Na serveru ho vygeneruj jednou:

bash
$ php artisan key:generate --force

Pozdější změna zneplatní všechny existující relace a zašifrované sloupce přestanou jít přečíst, takže ho vygeneruj před spuštěním a pak ho nech být. Zálohu si ulož jinam než na server.

Laravel obsahuje databázové ovladače mysql, mariadb, pgsql, sqlite a sqlsrv. Použij ten, který odpovídá databázi, kterou skutečně máš, a hostitele, port, název, uživatele a heslo vezmi z toho, co ti hosting vygeneroval, ne z odhadu. Webové tarify na RE:NODE obsahují sloty pro databáze vytvářené z panelu, s vygenerovaným hostitelem, uživatelem a heslem, a tlačítko „Open in phpMyAdmin“, které tě přihlásí tokenem platným 60 sekund. Samostatná databázová řada je PostgreSQL nebo MongoDB, dostupné na vlastním hostiteli a portu.

Ovladače fronty, cache a relací si zaslouží rozhodnutí, ne výchozí hodnotu. database pro všechny tři je správný začátek na jednom malém serveru: nepotřebuje nic navíc, přežije restart a snadno se kontroluje. Relace v file fungují, ale nechávají tisíce souborů v storage/framework/sessions a sdílená cache přestane být sdílená ve chvíli, kdy spustíš dva procesy. Pozor: Laravel 11 přejmenoval klíč cache z CACHE_DRIVER na CACHE_STORE; ve verzi 10 a starších je pořád CACHE_DRIVER a nastavení špatného z nich tě tiše nechá na výchozí hodnotě.

Dva adresáře musí být zapisovatelné pro uživatele, pod kterým běží PHP-FPM, a jenom ty dva: storage a bootstrap/cache. Všechno ostatní může být jen pro čtení.

bash
$ chmod -R ug+rwX storage bootstrap/cache$ chown -R www-data:www-data storage bootstrap/cache

Jméno uživatele se liší podle hostingu. V kontejneru to často vůbec není www-data a na sdíleném hostingu je uživatel SFTP obvykle stejný jako uživatel PHP, takže tento krok nic nemění. 777 je odpověď, kterou lidé najdou na fórech, a je špatná: každý nahraný soubor se tím stane spustitelným pro kohokoli na stroji.

Soubory nahrané aplikací jdou ve výchozím nastavení do storage/app/public, což není uvnitř document rootu. Zpřístupní je symlink:

bash
$ php artisan storage:link

Vytvoří public/storage ukazující na storage/app/public. Je to skutečný symlink, takže nepřežije nasazení, které přes SFTP přepíše adresář public/ novým, a některé nástroje na archivy ho odmítnou vytvořit. Pokud po nasazení vrací nahrané obrázky 404 a soubory na disku jsou, je to důvod. Spusť příkaz znovu nebo vytvoř odkaz ručně.

Pokud hosting symlinky nepovoluje vůbec, nastav FILESYSTEM_DISK=public a v config/filesystems.php změň root disku na adresář uvnitř public/. Není to elegantní, ale je to lepší než rozbitá knihovna médií.

Composer, assety a cache, které web zrychlí#

Závislosti nainstaluj produkčním způsobem:

bash
$ composer install --no-dev --optimize-autoloader --no-interaction

--no-dev přeskočí všechno z require-dev. Pokud se web rozbije chybou „Class not found“ s názvem ladicího nebo testovacího balíčku, ten balíček se používá v produkčním kódu a patří do require, ne do require-dev. --optimize-autoloader vytvoří mapu tříd, takže PHP přestane při každém autoloadu prohledávat souborový systém, což u velké aplikace měřitelně zkrátí odezvu.

Front-endové assety se sestavují přes Vite a výstup skončí v public/build. V tarifu s 1 GB je npm run build na velkém projektu docela možný způsob, jak narazit na limit paměti a nechat kontejner zastavit, takže sestavení lokálně nebo v CI a nahrání public/build je rychlejší i bezpečnější. Server pak Node vůbec nepotřebuje.

Pak cache. Každá z nich promění hromadu čtení souborů a parsování v jediné načtené PHP pole:

PříkazCo ukládá do cacheKdy je bezpečné spustit
config:cacheVšechny soubory z config/ do jednoho poleVždy, v produkci
route:cacheVšechny definice routPokud routy nepoužívají closure
view:cachePředem zkompilované šablony BladeVždy
event:cacheZjištěné eventy a listeneryVždy
optimizeSpustí předchozí sadu, podle verzeVždy, v produkci
bash
$ php artisan optimize

V novějších verzích php artisan optimize spustí cache konfigurace, eventů, rout a view dohromady; ve starších dělal méně. Spusť php artisan optimize --help ve své verzi a nespoléhej na předpoklady. Opačný příkaz, když potřebuješ cache přestavět, je php artisan optimize:clear.

Důsledek, na který lidé doplácejí: jakmile je konfigurace v cache, `env()` vrací všude null kromě souborů v `config/`. Cachované pole se sestavuje z konfiguračních souborů, kde je env() už vyhodnocené. Pokud tvá aplikace volá env('STRIPE_KEY') v controlleru, lokálně to funguje a v produkci to vrací null. Oprava je přidat hodnotu do konfiguračního souboru a volat místo toho config('services.stripe.key'). Je to jedna ze dvou nejčastějších příčin hlášky „u mě to funguje“.

Cache rout selže hlasitě, pokud některá routa používá closure místo controlleru. Chyba pojmenuje soubor. Přesuň closure do controlleru, nebo route:cache vynech a smiř se s cenou.

Migrace bez rozbití webu#

V produkci se migrace nesmí na nic ptát:

bash
$ php artisan migrate --force

Bez --force se artisan zeptá na potvrzení a v neinteraktivním nasazovacím skriptu buď zamrzne, nebo skončí. --force znamená jen „neptej se“; nic nepřeskakuje.

Záleží na pořadí. Nasazení, které dodá nový kód dřív, než existuje sloupec, který potřebuje, dává každému návštěvníkovi chybu 500 po celou dobu té mezery. Vyhnout se tomu jde dvěma postupy:

  1. Režim údržby pro malé weby, kde je minuta výpadku přijatelná. php artisan down --secret=some-long-string zobrazí všem stránku 503, ale tebe pustí na https://example.com/some-long-string. Nasaď, spusť migrace a pak php artisan up.
  2. Zpětně kompatibilní migrace pro všechno ostatní. Přidej sloupec a nasaď kód, který toleruje, že je null, doplň data, pak nasaď kód, který ho vyžaduje, a starý sloupec smaž až v pozdějším vydání. Jsou to tři nasazení místo jednoho a nikdo neuvidí chybu. Postup pořádně rozebírá Migrace bez výpadku.

Před každou migrací, která cokoli maže nebo přejmenovává, udělej zálohu databáze. php artisan migrate:rollback funguje jen tehdy, když má migrace funkční metodu down(), a překvapivě mnoho z nich ji nemá. Na RE:NODE zálohu vytvoří záložka Backups na požádání nebo podle plánu a obnoví ji tlačítkem; zálohy se ukládají mimo stroj, který chrání, což je jediný druh, který pomůže, když je problém ve stroji. Zbytek najdeš v článku Zálohy databáze a jejich obnova.

Fronty a scheduler#

Cokoli pomalého ve webovém požadavku - odeslání e-mailu, změna velikosti obrázku, volání cizího API - patří do fronty. Fronta potřebuje worker, tedy proces, který přežije požadavek:

bash
$ php artisan queue:work --queue=high,default --tries=3 --max-time=3600 --sleep=3

queue:work je dlouho běžící PHP proces, takže drží tvůj kód v paměti: po nasazení dál běží se starým kódem, dokud mu neřekneš jinak. php artisan queue:restart pošle všem workerům signál, aby dokončili aktuální úlohu a skončili, a tvůj správce procesů je spustí znovu s novým kódem. Dej ten řádek do nasazovacího skriptu a už na to nemysli.

--max-time=3600 způsobí, že worker skončí po hodině bez ohledu na cokoli, což je nejlevnější obrana proti pomalému úniku paměti v balíčku, který neovládáš. --tries=3 zabrání tomu, aby se trvale selhávající úloha opakovala donekonečna; neúspěšné úlohy skončí v tabulce failed_jobs a php artisan queue:retry all je vrátí do fronty. queue:listen používej jen při vývoji: restartuje framework při každé úloze, což je pohodlné a několikrát pomalejší.

Scheduler je druhý proces. Scheduler Laravelu je jediný záznam v cronu, který běží každou minutu a sám rozhodne, co je na řadě:

bash
* * * * * cd /var/www/example && php artisan schedule:run >> /dev/null 2>&1

V kontejneru bez crontabu dělá totéž php artisan schedule:work jako proces v popředí, který necháš běžet. To bývá lepší řešení pro panelový hosting, kde zůstává naživu hlavně serverový proces. Neplánuj stejnou úlohu ze dvou strojů: Laravel má pro to přesně withoutOverlapping() a onOneServer(), přičemž to druhé potřebuje sdílené úložiště cache.

Na RE:NODE záložka Schedules přijímá výraz pro cron a spouští seřazené úlohy proti serveru - příkaz v konzoli, zálohu, akci napájení - takže je to správné místo pro noční zálohu a týdenní restart. Pokud je syntaxe pro tebe nová, pět polí vysvětluje Výrazy cronu vysvětleně.

HTTPS, proxy a generování URL#

Skoro každá produkční aplikace v Laravelu stojí za něčím, co ukončuje TLS: reverzní proxy, load balancer, CDN. Ten stroj mluví s návštěvníkem přes HTTPS a s tvou aplikací přes obyčejné HTTP, což vede ke dvěma předvídatelným chybám.

První je smíšený obsah. Aplikace vidí požadavek HTTP, takže url(), asset() a route() generují odkazy http://, které prohlížeč na stránce s HTTPS zablokuje. Druhá je, že každý návštěvník vypadá, jako by přicházel z adresy proxy, což potichu rozbije omezování počtu požadavků, logování i všechno, co závisí na poloze.

Obojí se opraví tím, že proxy důvěřuješ, takže Laravel čte X-Forwarded-Proto a X-Forwarded-For. V Laravelu 11 a 12 se to dělá v bootstrap/app.php:

bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {    $middleware->trustProxies(at: '*');})

V Laravelu 10 a starším je to $proxies = '*'; v app/Http/Middleware/TrustProxies.php. Důvěřovat * je správně, když je jediná cesta k aplikaci přes proxy, a špatně, když je port originu dostupný z internetu, protože pak může hlavičku podvrhnout kdokoli. Nastavení APP_URL=https://example.com pokrývá generování URL v konzolových příkazech a ve frontových úlohách, které nemají požadavek, z něhož by schéma přečetly.

Tarify RE:NODE pro aplikace a web obsahují slot pro proxy: nasměruj záznam A na zobrazenou adresu a certifikát se vystaví a obnovuje za tebe, v okně 21 dní, přičemž skutečná adresa klienta přichází v X-Forwarded-For. Cestu požadavku vysvětluje Co dělá reverzní proxy a v článku HTTPS a Let's Encrypt vysvětleně najdeš, proč vystavení selže, když DNS ještě není připravené.

Opakovatelné nasazení#

Posloupnost si zapiš jednou a pokaždé spusť to samé. Ruční nasazení se rozcházejí a právě to rozcházení se pokazí o půlnoci.

bash
$ php artisan down --secret="$(cat /var/www/deploy-secret)"$ git pull --ff-only origin main$ composer install --no-dev --optimize-autoloader --no-interaction$ php artisan migrate --force$ php artisan optimize$ php artisan queue:restart$ php artisan up

Tam, kde nemáš shell, proběhnou stejné kroky přes SFTP a konzoli, kterou hosting nabízí, ve stejném pořadí. Jedno varování: cache sestavuj na stroji, který je obsluhuje. bootstrap/cache/packages.php a zkompilované šablony obsahují absolutní cesty, takže cache vygenerované na notebooku a nahrané na server mohou ukazovat do adresářů, které na serveru neexistují.

Nech si poslední funkční vydání. Nejrychlejší rollback je předchozí adresář plus migrate:rollback, který jsi opravdu vyzkoušel, a druhý nejrychlejší je obnova ze zálohy, kterou jsi udělal před pěti minutami. Vzor se symlinkovanými vydáními, kdy je samo nasazení neviditelné, popisuje Nasazení bez výpadku na malém serveru.

Řešení potíží#

Prázdná stránka nebo holá chyba 500 bez podrobností. To je APP_DEBUG=false, které dělá svou práci. Podrobnosti jsou v storage/logs/laravel.log. Pokud je ten soubor prázdný, PHP do něj nemohlo zapsat: zkontroluj oprávnění výše a chybový log PHP-FPM.

„No application encryption key has been specified“. Chybí APP_KEY, nebo byla cache konfigurace sestavena dřív, než existoval. Spusť php artisan key:generate --force a pak znovu php artisan config:cache.

Všechno kromě domovské stránky vrací 404. Document root není public/, nebo nginx nemá v bloku location řádek try_files $uri $uri/ /index.php?$query_string;.

419 Page Expired u každého formuláře. Cookie relace se nevrací. Obvyklé příčiny: SESSION_DOMAIN nastavené na špatného hostitele, SESSION_SECURE_COOKIE=true, zatímco aplikace si stále myslí, že je na HTTP, nebo cache konfigurace z jiného prostředí.

Změna nastavení nic neudělá. Cache konfigurace je zastaralá. php artisan optimize:clear a pak ji sestav znovu. Platí to i pro úpravu .env: když je konfigurace v cache, .env se při požadavku vůbec nečte.

Nahrané obrázky vrací 404, ale na disku jsou. Symlink public/storage zmizel. Spusť znovu php artisan storage:link.

Fronta po nasazení nic nezpracovává. Worker spadl a nic ho znovu nespustilo, nebo pořád drží starý kód. Zkontroluj, jestli proces běží, a pak spusť php artisan queue:restart.

Web je pod zátěží pomalejší a pak se restartuje. Narážíš na limit paměti. Na RE:NODE se kontejner na limitu zastaví a restartuje načisto, místo aby se nechal swapovat, což se rychleji zotavuje, ale znamená to ztrátu požadavků, které právě běží. Než začneš vinit framework, spočítej počet workerů PHP-FPM krát jejich skutečnou spotřebu paměti a přečti si Dimenzování webové aplikace na den spuštění.

FAQ#

Potřebuju mít na serveru nainstalovaný Composer?

Ne nutně. Můžeš spustit composer install --no-dev --optimize-autoloader lokálně nebo v CI a nahrát adresář vendor, pokud verze PHP, proti které jsi sestavoval, odpovídá verzi na serveru. Spuštění na serveru je jednodušší a tomu rozdílu se vyhneš.

Proč env() v produkci vrací null?

Protože je konfigurace v cache. php artisan config:cache jednou vyhodnotí každé volání env() uvnitř config/ a zapíše výsledek, a env() mimo tyto soubory pak čte prostředí, které už není naplněné. Přesuň hodnotu do konfiguračního souboru a čti ji přes config().

Kolik paměti potřebuje web v Laravelu?

Malá aplikace se v 1 GB pohodlně vejde, včetně workerů PHP-FPM a databáze na stejném stroji. Paměť přidávej, když přidáváš souběžnost, ne funkce: každý worker PHP-FPM drží vlastní kopii aplikace, takže čtyři workery po 80 MB jsou 320 MB ještě před čímkoli dalším.

Můžu spustit scheduler bez cronu?

Ano. php artisan schedule:work běží v popředí a každou minutu spouští úlohy, které jsou na řadě, což je praktická odpověď na hostingu, kde nemůžeš upravit crontab. Musí zůstat spuštěný jako každý jiný dlouhodobý proces.

Mají worker fronty a webový server sdílet tarif?

U malého webu ano - nečinný worker stojí skoro nic. Rozděl je, když je úloha tak náročná, že připravuje webové požadavky o CPU, protože nárazová fronta a špička návštěvnosti, které přijdou zároveň, jsou způsob, jak se setup s jedním kontejnerem zhroutí.

Co mám dělat s .env při změně hostingu?

Zkopíruj ho ručně, ne spolu s kódem. Přepiš přihlašovací údaje k databázi na nové, APP_KEY nech úplně stejný, aby relace a šifrovaná data přežily, a pak na novém stroji cache vymaž a sestav znovu.


Komentáře

Zcela anonymně: žádný účet, žádný e-mail, žádné cookie. Ukládáme jen jméno, které napíšeš, text a čas - nic dalšího. Počet odkazů je omezený a značkování se nevykresluje.

0/2000