Aby šla Rust webová služba nasadit na aplikačním serveru s Cargem, musí sedět čtyři věci: repozitář s Cargo.toml a commitnutým Cargo.lock v kořeni, program, který poslouchá na 0.0.0.0 a na portu z proměnné prostředí SERVER_PORT, řádek vypsaný na standardní výstup ve chvíli, kdy už poslouchá (panel podle něj pozná, že server nastartoval), a tarif s dost paměti na kompilaci celého stromu závislostí, což je úplně jiné číslo než to, co potřebuje hotový binární soubor. Když tyhle čtyři věci sedí, zbytek je připojení GitHubu a zvyk číst konzoli. Mluvíme tu o programovacím jazyce Rust - pokud jsi přišel kvůli survival hře, hledáš spíš průvodce dedikovaným serverem Rust.
Tento článek jde v pořadí, v jakém na jednotlivé věci narazíš: co se děje při startu serveru, příprava repozitáře, port a řádek pro detekci startu, jak dlouho build trvá a kolik RAM sní v jednotlivých tarifech, konfigurace a tajné hodnoty, deploy z GitHubu a čtení logů, když něco selže. Jemnější ladění samotné služby - worker vlákna tokia, chování alokátoru, řádné ukončení, pooly spojení - najdeš v samostatném článku o nasazení Rust webové služby s axum. Tenhle je o cestě od repozitáře k běžícímu a dostupnému procesu.
Co se děje při startu Cargo serveru#
Rust (Cargo) server na RE:NODE je kontejner s toolchainem Rustu a tvým projektem v souborech. Nespouští hotový image tvé aplikace. Při každém startu sestaví a spustí tvůj kód pomocí cargo, což má důsledek, který je dobré pochopit hned: start je kompilace. Restart je kompilace. Deploy je kompilace. Cargo umí dobře nedělat práci, kterou už jednou udělalo, takže většina těchto kompilací je krátká, ale ta první nikdy.
Pokud připojíš repozitář na GitHubu, pořadí při každém startu je: stáhnout zvolenou větev, pak build, pak spuštění. Diagram ukazuje celý životní cyklus.
Nový server není prázdný. Dokud nenasadíš nic svého, obsahuje drobný Rust projekt postavený jen na standardní knihovně - Cargo.toml a src/main.rs, který na adrese serveru zobrazí stránku „co dělat dál“. Je tam proto, aby první start ukázal něco funkčního místo pádu, a zapisuje se jen do prázdného serveru; žádné tvoje soubory nikdy nepřepíše. Otevři adresu jednou, ověř, že server odpovídá, a pak projekt nahraď svým.
Samotný startovací příkaz je vidět na záložce Startup. Přečti si ho jednou, abys věděl, jestli buildí v release režimu a který binární soubor spouští - následující části počítají s release buildem, což je jediný druh, který má smysl nasazovat.
Příprava repozitáře#
Cargo potřebuje velmi málo, ale na správném místě. Server buildí z kořene toho, co stáhl, takže Cargo.toml musí být na nejvyšší úrovni repozitáře, ne ve složce backend/. Pokud tvoje služba žije ve workspace, dej workspace Cargo.toml do kořene a zajisti, aby se ve výchozím stavu spouštěl ten binární soubor, který chceš.
Kontrolní seznam:
- Commitni `Cargo.lock`. U binárního programu do repozitáře patří. Bez něj se při každém buildu závislosti vyřeší znovu a dva deploye stejného commitu mohou dát různé programy.
- Ignoruj `target/`. Je to cache buildu, často o velikosti gigabajtů, a je vázaná na konkrétní stroj. Patří do
.gitignore, nikdy do commitu. - Vyber výchozí binárku. Pokud má balíček víc binárních souborů (
src/bin/*.rsplussrc/main.rs),cargo runodmítne hádat. Nastavdefault-runv[package], ať hádat nemusí. - Nikdy necommituj `.env`. Tajné hodnoty patří na server, ne do Gitu. Víc o tom níže.
- Připni toolchain, pokud závisíš na novější funkci. Položka
rust-versionvCargo.tomlzpůsobí, že starý kompilátor selže s jasnou hláškou místo stránky matoucích chyb.
Minimální Cargo.toml pro službu v axum, s release profilem laděným na build na malém serveru, ne na posledních pár procent rychlosti:
[package]name = "api"version = "0.1.0"edition = "2021"default-run = "api"[dependencies]axum = "0.8"tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal"] }tracing = "0.1"tracing-subscriber = { version = "0.3", features = ["env-filter"] }[profile.release]strip = "symbols"debug = falseČísla verzí jsou jen příklady; použij to, co ti v daný den dá cargo add. Všimni si, co v tom profilu není: žádné lto = true, žádné codegen-units = 1. Obojí udělá binárku o něco rychlejší a menší a build znatelně pomalejší a náročnější na paměť. V tarifu s 1 GB mohou z buildu, který doběhne, udělat build, který nedoběhne. Přidej je později, až budeš vědět, že se build vejde.
A .gitignore, kterým by měl začínat každý Rust repozitář:
/target.envPoslouchej na přidělený port#
K tarifu patří jeden přidělený port. Panel ho dává do prostředí jako SERVER_PORT a tvůj program musí poslouchat přesně na něm, na 0.0.0.0. Dvě chyby vysvětlují většinu hlášení „běží to, ale nedostanu se tam“:
- Poslech na `127.0.0.1` nebo `localhost`. Uvnitř kontejneru to znamená „dostupné jen zevnitř tohoto kontejneru“. Log říká, že poslouchá, a zvenku se nikdo nepřipojí.
- Pevně zadaný port. Návody milují
3000a8080. Tvůj přidělený port není ani jeden z nich, leda náhodou.
Přečti port z prostředí a když chybí, skonči hlasitě, místo abys tiše sklouzl na port, který nikdo nepřeposílá:
use axum::{routing::get, Router};use tokio::net::TcpListener;#[tokio::main]async fn main() { tracing_subscriber::fmt() .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .init(); let port: u16 = std::env::var("SERVER_PORT") .expect("SERVER_PORT is not set") .parse() .expect("SERVER_PORT is not a number"); let app = Router::new().route("/health", get(|| async { "ok" })); let listener = TcpListener::bind(("0.0.0.0", port)).await.unwrap(); println!("listening on 0.0.0.0:{port}"); axum::serve(listener, app).await.unwrap();}Ekvivalent pro actix-web je stejný nápad v jiném builderu:
#[actix_web::main]async fn main() -> std::io::Result<()> { let port: u16 = std::env::var("SERVER_PORT").expect("SERVER_PORT").parse().unwrap(); let server = HttpServer::new(|| App::new().route("/health", web::get().to(health))) .workers(2) .bind(("0.0.0.0", port))?; println!("listening on 0.0.0.0:{port}"); server.run().await}.workers(2) má na malém tarifu význam. actix-web ve výchozím stavu spustí jednoho workera na každé logické CPU, které vidí, a kontejner omezený na půl jádra může stále vidět počet jader celého stroje. Na nižších tarifech jsou dva workeři úplně dost.
Pokud chceš, aby stejný kód běžel i na tvém notebooku, nech si záložní hodnotu jen pro lokální vývoj, za explicitní proměnnou nebo kontrolou cfg(debug_assertions), aby release build na serveru nikdy tiše nenavázal na špatný port.
Řádek pro detekci startu#
Tenhle krok chytí skoro každého aspoň jednou. Panel pozná, že server dokončil start, tak, že v konzoli sleduje určitý text. Dokud ho neuvidí, server se zobrazuje jako „Starting“, ať tvoje služba běží sebelíp. Na Cargo serveru ten text nastavuješ sám na záložce Startup a musí to být řádek, který tvůj program skutečně vypisuje.
Nejjednodušší uspořádání je to z příkladů výše: po navázání listeneru vypiš jeden prostý, stabilní řádek a jeho úryvek použij jako text pro detekci startu.
listening on 0.0.0.0:Spolehlivým to dělá pár detailů:
- Vypiš ho až po úspěšném `bind`, ne na začátku
main. Pak „nastartováno“ znamená „přijímá spojení“, což je přesně to, co chceš, aby panel hlásil. - Používej `println!`, ne jen `tracing`. Standardní výstup Rustu je řádkově bufferovaný, takže
println!dorazí do konzole ve chvíli, kdy se provede. Řádek ztracingfunguje taky, ale jeho podoba závisí na konfiguraci subscriberu a nevinná změna formátu (časové značky, barvy, výstup v JSON) může způsobit, že text přestane sedět. - Nedávej do detekčního textu číslo portu, pokud si nejsi jistý, že se nikdy nezmění. Úryvek
listening on 0.0.0.0:změnu portu přežije; celý řádek ne. - Drž ho mimo výstup buildu. Cargo během buildu vypisuje řádky
CompilingaFinished. Nikdy nepoužívej text, který vypisuje samo cargo, jinak panel prohlásí server za nastartovaný dřív, než se tvůj program vůbec spustil.
Čas buildu a paměť podle tarifu#
Tady je nepříjemná stránka hostingu Rustu. Program, který nasazuješ, v klidu zabere desítky megabajtů. Kompilátor, který ho staví, může potřebovat výrazně přes gigabajt, po dobu minut, s vrcholem při závěrečném linkování. Tarif se musí vejít na build, ne jen na binárku.
Tarify Rust (Cargo) na RE:NODE začínají na $4 měsíčně a mají pět úrovní:
| Plan | RAM / disk / CPU | Build na serveru |
|---|---|---|
| Starter | 1 GB / 5 GB / 0.5 vCPU | Malé služby, jedna úloha najednou |
| Standard | 2 GB / 10 GB / 1 vCPU | Typická služba v axum nebo actix |
| Advanced | 4 GB / 20 GB / 1.5 vCPU | Velké stromy, sqlx, trochu LTO |
| Pro | 6 GB / 35 GB / 2 vCPU | Těžké stromy, LTO bez starostí |
| Premium | 8 GB / 50 GB / 3 vCPU | Workspace, více binárních souborů |
Pravý sloupec je naše orientační pravidlo, ne záruka. Náročnost buildu závisí na tom, kolik crate je v Cargo.lock, jak jsou makry nabité (serde derive, sqlx, cokoli s procedurálními makry) a jestli je potřeba kompilovat C závislosti. Hrubé vodítko z praxe: malá služba s pár desítkami crate udělá první release build za pár minut na jednom jádře; typická webová služba se dvěma nebo třemi sty crate trvá déle a na půl jádře to může být deset minut i víc. Pozdější buildy, které se dotknou jen tvého vlastního kódu, jsou mnohem rychlejší, protože zkompilované závislosti leží v target/ a znovu se využijí.
Co se stane, když je build na tarif příliš velký, je důležité, protože to nevypadá jako chyba. Kontejner, který dosáhne limitu paměti, na RE:NODE kernel zastaví a restartuje načisto, místo aby mu dovolil swapovat. Build, kterému dojde paměť, se tak projeví tím, že konzole se zastaví uprostřed Compiling a server nastartuje znovu, často do stejné zdi. Pokud vidíš tenhle vzorec, je to paměť a řešení jsou tři, seřazená podle preference:
- Buildi jednu crate po druhé. Cargo ve výchozím stavu kompiluje crate paralelně a každá paralelní úloha drží vlastní paměť. Nastavení jobs na jedna výrazně sníží vrchol za cenu delšího reálného času.
- Vyhoď LTO a `codegen-units = 1` z release profilu. Jsou to nejdražší nastavení v kroku linkování.
- Přejdi o tarif výš. Pokud služba potřebuje tři sta crate, potřebuje tarif, který je zvládne zkompilovat. Změna tarifu server nepřebuilduje.
První řešení může žít v repozitáři, takže cestuje s kódem a platí pro každý build na serveru:
[build]jobs = 1Disk si zaslouží stejnou pozornost. Adresář target/ průměrné webové služby má běžně jeden až tři gigabajty a když se do něj dostanou debug i release artefakty, tak víc. Disk 5 GB v tarifu Starter se může zaplnit jen build cache. Pokud jsi někdy na serveru spustil debug build, smazání target/debug ve správci souborů uvolní snadno gigabajt. Nemaž celé target/ z rutiny - zahodíš tím cache závislostí a další start zkompiluje všechno znovu.
Pro velmi malé tarify existuje legitimní alternativa: sestav release binárku v CI nebo na svém stroji pro x86_64-unknown-linux-gnu (nebo -musl pro statickou binárku), nahraj ji přes SFTP a spouštěj ji. Vzdáváš se pohodlí buildu ze zdrojů při každém startu a funguje to jen tehdy, když startovací příkaz spouští tvoji binárku a ne cargo, takže si před pokusem zkontroluj záložku Startup.
Proměnné prostředí a tajné hodnoty#
Konfigurace patří do proměnných prostředí, čtených jednou při startu, a program odmítne nastartovat, když nějaká chybí. Zapomenutá adresa databáze pak zastaví deploy v konzoli, místo aby selhala ve tři ráno při prvním požadavku, který ji potřebuje.
Na Cargo serveru jsou dvě místa, kam hodnoty dát:
- Záložka Startup. Zobrazuje proměnné serveru včetně
SERVER_PORT. Použij ji pro proměnné, které tam panel nabízí. - Soubor `.env` v souborech serveru. Vytvoř ho ve správci souborů nebo nahraj přes SFTP a načti ho crate
dotenvyna začátkumain. Do Gitu nikdy nepatří.
DATABASE_URL=postgres://app_user:generated-password@db-host:5432/appRUST_LOG=info,tower_http=info,sqlx=warnRUST_BACKTRACE=1APP_BASE_URL=https://api.example.comfn main() { // Načte .env, pokud existuje; skutečné proměnné prostředí mají přednost. let _ = dotenvy::dotenv(); // ...}dotenvy::dotenv() nepřepisuje proměnné, které už v prostředí nastavené jsou, takže SERVER_PORT z panelu vždy vyhraje nad zbloudilou hodnotou v souboru. Přesně takové chování chceš.
Pokud repozitář připojíš ke GitHubu, předběžná kontrola na záložce GitHub nahlásí commitnutý soubor .env jménem, aniž by ho otevřela. Když ho kdy označí, odstraň ho z repozitáře a vyměň všechny tajné hodnoty, které obsahoval, protože historie ho pořád má. Obecná pravidla - kde tajné hodnoty žijí, jak je rotovat, co nikdy nelogovat - jsou v článku o proměnných prostředí a tajných hodnotách.
Aplikační tarify zahrnují sloty pro databáze: vytvoř jeden v panelu a dostaneš vygenerovaný host, uživatele a heslo, které vložíš do DATABASE_URL. Pokud tvoje služba používá dotazy kontrolované při kompilaci v sqlx, build potřebuje buď živou databázi, nebo připravená data dotazů: spusť lokálně cargo sqlx prepare, commitni adresář .sqlx a nastav SQLX_OFFLINE=true, aby se build na serveru během kompilace nesnažil k databázi připojit. Samostatný server PostgreSQL, pokud ti slot přeroste, je tarif hostingu databází.
Deploy z GitHubu#
Nahrávání zdrojů přes SFTP funguje, ale deploye z GitHubu dělají ze života s Cargo serverem radost. Panel se připojuje přes GitHub App s krátkodobými tokeny, takže privátní repozitáře fungují, aniž bys kamkoli vkládal osobní přístupový token. GitHub je jediný podporovaný poskytovatel.
Na záložce GitHub serveru propojíš repozitář a větev a máš dva přepínače:
- Pull on every start. Každý start před buildem stáhne větev. Restart z panelu je tak zároveň aktualizace.
- Deploy on push. Push do větve restartuje běžící server, nebo nastartuje zastavený, který stáhne a zbuildí nový commit.
Každý deploy zanechá záznam, takže vidíš, který commit odešel a kdy. Než repozitář propojíš, předběžná kontrola ho přečte - výpis stromu a pár malých souborů, nikdy celý klon - a vypíše, co se rozbije, od nejhoršího, s opravou k vložení: commitnutý .env, poslech na špatném hostu nebo portu a podobné chyby, které by se jinak projevily až po prvním startu. Kontroluje zjevné způsoby selhání; kód nekompiluje, takže typová chyba se stále ukáže v konzoli.
Dvě věci jsou specifické pro Rust a vyplatí se s nimi počítat:
- Deploy je restart a restart je build. Starý proces se zastaví, nový commit se zkompiluje a teprve potom nová binárka začne poslouchat. U změny ve vlastním kódu to obvykle trvá vteřiny až minutu; u změny
Cargo.lock, která zvedne široce používanou závislost, to mohou být minuty, po které služba nejede. Aktualizace závislostí sdružuj, místo abys je pushoval po jedné, a pushuj je ve chvíli, kdy se na službu nikdo nespoléhá. - Rozbitý build znamená server, který nenaběhne. Pokud se nový commit nezkompiluje, nemá mezitím kdo obsluhovat - stará binárka neběží. Spusť
cargo build --releasea testy lokálně nebo v CI dřív, než se push dostane do deploy větve, a zvaž deploy z vyhrazené větve, která dostává jen merge, které prošly.
Víc o tom, jak to okno zkrátit, najdeš v článku deploye bez výpadku na malém serveru, který popisuje, čeho lze s jedním procesem na jednom portu dosáhnout a čeho ne, a předběžné kontroly, které zachytí rozbité deploye podrobně rozebírají kontrolu na záložce GitHub.
Logy a konzole#
Všechno, co tvůj program zapíše na standardní výstup a standardní chybový výstup, se objeví v konzoli panelu, živě a nefiltrovaně, vedle grafů paměti, CPU a disku proti limitům tarifu. Konzole je první místo, kam se podívat u každého problému v tomto článku: výstup buildu, řádek pro detekci startu, paniky i tvoje vlastní logy skončí tam.
Pro strukturované logování je standardní volbou tracing s tracing-subscriber zapisujícím na standardní výstup a proměnná RUST_LOG určuje, co projde:
RUST_LOG=info,tower_http=debug,sqlx=warn,hyper=warnFormát je výchozí úroveň a pak přepsání po jednotlivých crate. tower_http=debug s TraceLayer na routeru loguje každý požadavek s jeho statusem a latencí, což je nejužitečnější jednotlivý řádek, který můžeš při ladění přidat. Až skončíš, vrať to na info; logy požadavků na úrovni debug jsou na vytížené službě jen šum.
Nastav RUST_BACKTRACE=1. Nestojí nic, dokud nedojde k panice, a pak z „thread 'tokio-runtime-worker' panicked“ udělá stack, se kterým se dá pracovat. Pokud chceš v těch backtrace čísla řádků, nastav v release profilu debug = "line-tables-only"; zvětší to binárku, ne spotřebu paměti procesu.
Sleduj graf paměti během prvního buildu a první hodiny provozu. Vrchol buildu ti řekne, jestli je tarif dimenzovaný správně. Běžící proces by se měl ustálit na rovné čáře hluboko pod limitem; čára, která při konstantní zátěži stabilně stoupá, stojí za prošetření dřív, než narazí na strop. Logy, které stojí za uchování pokrývá, co uchovávat a jak dlouho, až je služba v ustáleném provozu.
Doména a HTTPS#
Servíruj prosté HTTP na přidělený port a TLS nech na proxy slotu. Aplikační tarify ho zahrnují: nasměruj A záznam své domény na zobrazenou adresu a certifikát se vystaví a obnovuje automaticky, v rámci 21denního okna. Tvoje služba se souboru s certifikátem nikdy nedotkne.
Za proxy je skutečná adresa klienta v hlavičce X-Forwarded-For, ne ve spojení, takže ConnectInfo v axum ti dá proxy, ne návštěvníka. Pokud omezuješ rychlost nebo logguješ podle IP, čti hlavičku. Všude, kde skládáš absolutní URL - přesměrování, OAuth callbacky, odkazy v e-mailech - používej nakonfigurovanou základní adresu, třeba APP_BASE_URL, místo hádání z požadavku. Co dělá reverzní proxy vysvětluje zbytek řetězce hlaviček a websockety za reverzní proxy se hodí, pokud používáš axum::extract::ws.
Řešení potíží#
Server visí na „Starting“ donekonečna. Text pro detekci startu na záložce Startup neodpovídá ničemu, co tvůj program vypisuje. Zkopíruj do něj úryvek svého řádku o poslechu a restartuj.
Konzole se zastaví uprostřed `Compiling` a server se restartuje. Build narazil na limit paměti. Nastav jobs = 1 v .cargo/config.toml, vyhoď LTO z release profilu, nebo přejdi o tarif výš.
`error: could not find Cargo.toml`. Projekt je v podadresáři repozitáře. Přesuň ho do kořene, nebo tam dej workspace Cargo.toml.
`cargo run` could not determine which binary to run. Balíček má víc binárních souborů. Přidej default-run do [package].
Běží to, ale adresa neodpovídá. Je navázané na 127.0.0.1 nebo na pevně zadaný port. Poslouchej na 0.0.0.0 a čti SERVER_PORT.
`Address already in use`. Port už drží něco jiného ve stejném stromu procesů - často druhý listener, který jsi přidal pro metriky na stejném portu. Jeden tarif má jeden přidělený port; metriky servíruj na route hlavního routeru.
Každý deploy trvá stejně dlouho jako ten první. Něco maže target/, nebo není commitnutý Cargo.lock a závislosti se pokaždé vyřeší jinak. Commitni lock soubor a cache nech na pokoji.
`set DATABASE_URL to use query macros`. sqlx se snaží kontrolovat dotazy při kompilaci. Commitni adresář .sqlx z cargo sqlx prepare a nastav SQLX_OFFLINE=true.
Disk je plný. Obvykle za to může target/. Smaž target/debug, pokud existuje, a zkontroluj, že nebuildíš debug a release vedle sebe.
FAQ#
Je to o hře Rust, nebo o programovacím jazyce Rust?
O jazyce. Hosting Rust (Cargo) provozuje programy napsané v Rustu a sestavené pomocí cargo - webové služby, boty, workery. Survival hra je jiný produkt s vlastním nastavením, popsaným v našem průvodci dedikovaným serverem Rust.
Jaký tarif potřebuji pro službu v axum?
Samotná služba by běžela na nejmenším tarifu. Rozhoduje build. Malá služba se na 1 GB zbuildí s jednou kompilační úlohou najednou; typická webová služba s pár stovkami crate je mnohem pohodlnější na tarifu s 2 GB nebo vyšším. Sleduj graf paměti při prvním buildu a dimenzuj podle toho, co ukáže.
Proč restart trvá tak dlouho?
Protože každý start projekt nejdřív zbuildí pomocí cargo a pak ho spustí. Jakmile jsou závislosti v cache v target/, je restart bez změn v kódu hlavně cargo, které potvrzuje, že není co přebuildovat, a změna tvého vlastního kódu překompiluje jen tvoji crate. Zpomalí to znovu až změněný Cargo.lock.
Můžu provozovat něco jiného než webový server?
Ano. Discord nebo Telegram bot, worker nad frontou nebo spouštěč naplánovaných úloh fungují stejně: program běží, vypíše řádek pro detekci startu a běží dál. Nemusí poslouchat na portu, pokud se k němu nikdo nepotřebuje připojit, i když malá health route se pořád hodí pro kontrolu zvenčí.
Musím nasazovat z GitHubu?
Ne. Zdroje můžeš nahrát ve správci souborů nebo přes SFTP a restartovat, server zbuildí cokoli, co má v souborech. Deploye z GitHubu jsou pohodlná cesta a jediný poskytovatel Gitu, ke kterému se panel připojuje.
Můžu použít nightly toolchain?
Server buildí s toolchainem ze svého image a způsob, jak ho přepnout na nightly, nedokumentujeme. Pokud závisíš na nightly funkci, praktická cesta je buildit jinde a nahrát binárku - nejdřív ale zkontroluj startovací příkaz, jestli umí spustit hotovou binárku.


