Cargo აპლიკაციის სერვერზე Rust ვებ სერვისის deploy-ისთვის ოთხი რამ უნდა იყოს სწორად: რეპოზიტორია, რომლის ძირში არის Cargo.toml და დაკომიტებული Cargo.lock; პროგრამა, რომელიც უსმენს 0.0.0.0-ს და SERVER_PORT გარემოს ცვლადში მითითებულ პორტს; ერთი სტრიქონი standard output-ში, რომელსაც პროგრამა ბეჭდავს მოსმენის დაწყებისას (პანელი ამით ხვდება, რომ სერვერი ჩაირთო); და ტარიფი საკმარისი მეხსიერებით, რომ დამოკიდებულებების მთელი ხე დაკომპილირდეს - ეს რიცხვი სრულიად განსხვავდება იმისგან, რაც მზა binary-ს სჭირდება. როცა ეს ოთხი რამ წესრიგშია, დანარჩენი GitHub-ის მიერთება და კონსოლის კითხვის ჩვევაა. სხვათა შორის, აქ საუბარია Rust ენაზე - თუ სათამაშოდ გინდოდა survival თამაში, გახსენი Rust dedicated server-ის გზამკვლევი.
ეს პოსტი გადის ყველაფერს იმ თანმიმდევრობით, როგორც შეგხვდება: რა ხდება სერვერის ჩართვისას, რეპოზიტორიის მომზადება, პორტი და ჩართვის ამომცნობი სტრიქონი, რამდენ ხანს გრძელდება build და რამდენ RAM-ს ხარჯავს თითოეულ ტარიფზე, კონფიგურაცია და საიდუმლოებები, deploy GitHub-იდან და ლოგების კითხვა, როცა რამე არ გამოდის. თვითონ სერვისის უფრო ღრმა დაზუსტებისთვის - tokio-ს worker thread-ები, allocator-ის ქცევა, graceful shutdown, connection pool-ები - გვაქვს ცალკე პოსტი Rust ვებ სერვისის deploy-ზე axum-ით. ეს კი ეხება იმას, როგორ მივიდე რეპოზიტორიიდან გაშვებულ და ხელმისაწვდომ პროცესამდე.
რა ხდება Cargo სერვერის ჩართვისას#
RE:NODE-ზე Rust (Cargo) სერვერი არის კონტეინერი, რომელშიც არის Rust toolchain და შენი პროექტი ფაილებში. ის არ უშვებს შენი აპლიკაციის წინასწარ აწყობილ image-ს. ყოველ ჩართვაზე ის აგებს და უშვებს შენს კოდს cargo-თი, რაც ერთ რამეს გულისხმობს და უმჯობესია თავიდანვე გაითვალისწინო: ჩართვა არის კომპილაცია. რესტარტი არის კომპილაცია. deploy არის კომპილაცია. Cargo კარგად ახერხებს უკვე გაკეთებული სამუშაოს არ გამეორებას, ამიტომ უმეტესი კომპილაცია მოკლეა, მაგრამ პირველი არასდროს არის მოკლე.
თუ GitHub რეპოზიტორიას მიაერთებ, ყოველ ჩართვაზე თანმიმდევრობა ასეთია: მოაქვს არჩეული branch, შემდეგ build, შემდეგ გაშვება. დიაგრამა მთელ ციკლს აჩვენებს.
ახალი სერვერი ცარიელი არ არის. სანამ deploy-ს გააკეთებ, მასში დევს პატარა Rust პროექტი, მხოლოდ სტანდარტული ბიბლიოთეკით - Cargo.toml და src/main.rs, რომელიც თავის მისამართზე აჩვენებს გვერდს "რა უნდა გააკეთო შემდეგ". ის იმისთვისაა, რომ პირველ ჩართვაზე მუშა რამ დაინახო და არა crash, და ის იწერება მხოლოდ ცარიელ სერვერზე; შენს ფაილებს არასდროს გადააწერს. გახსენი მისამართი ერთხელ, დარწმუნდი, რომ სერვერი პასუხობს, და შემდეგ შეცვალე პროექტი შენით.
თვითონ გაშვების ბრძანება ჩანს Startup ჩანართზე. წაიკითხე ერთხელ, რომ იცოდე, release რეჟიმში აგებს თუ არა და რომელ binary-ს უშვებს - ქვემოთ მოცემული სექციები ვარაუდობს release build-ს, ერთადერთს, რომლის deploy-იც ღირს.
რეპოზიტორიის მომზადება#
Cargo-ს ძალიან ცოტა სჭირდება, მაგრამ სწორ ადგილას. სერვერი აგებს იმის ძირიდან, რაც ჩამოტვირთა, ამიტომ Cargo.toml რეპოზიტორიის სათავეში უნდა იყოს და არა backend/ საქაღალდეში. თუ შენი სერვისი workspace-შია, workspace-ის Cargo.toml დადე ძირში და დარწმუნდი, რომ ნაგულისხმევად სწორედ ის binary ეშვება, რომელიც გინდა.
ჩამონათვალი:
- დაკომიტე `Cargo.lock`. binary-სთვის ის რეპოზიტორიაში უნდა იყოს. მის გარეშე ყოველი build დამოკიდებულებებს თავიდან ადგენს და ერთი და იგივე commit-ის ორმა deploy-მა შეიძლება სხვადასხვა პროგრამა მოგცეს.
- დააიგნორე `target/`. ეს build cache-ია, ხშირად რამდენიმე გიგაბაიტი, და კონკრეტულ მანქანაზეა მიბმული. ის
.gitignore-შია და არასდროს commit-ში. - აირჩიე ნაგულისხმევი binary. თუ პაკეტს ერთზე მეტი binary აქვს (
src/bin/*.rsპლუსsrc/main.rs),cargo runვარაუდს არ აკეთებს. მიუთითეdefault-run[package]-ში, რომ ვარაუდი არ დასჭირდეს. - არასდროს დაკომიტო `.env`. საიდუმლოებები სერვერზე ინახება და არა Git-ში. ამაზე ქვემოთ უფრო ვრცლად.
- დააფიქსირე toolchain, თუ ახალ ფუნქციაზე ხარ დამოკიდებული.
rust-versionCargo.toml-ში იწვევს იმას, რომ ძველი კომპილატორი გასაგები შეტყობინებით ჩავარდება და არა ახსნის გარეშე შეცდომების მთელი გვერდით.
მინიმალური Cargo.toml axum სერვისისთვის, release პროფილით, რომელიც პატარა სერვერზე build-ისთვისაა მორგებული და არა სიჩქარის ბოლო რამდენიმე პროცენტისთვის:
[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ვერსიების ნომრები მაგალითებია; გამოიყენე ის, რასაც cargo add მოგცემს იმ დღეს. შეხედე, რა არ არის ამ პროფილში: არც lto = true, არც codegen-units = 1. ორივე binary-ს ოდნავ უფრო სწრაფს და პატარას ხდის, ხოლო build-ს - გაცილებით ნელს და მეტად ხარბს. 1 GB ტარიფზე მათ შეუძლიათ build, რომელიც სრულდება, იმად აქციონ, რომელიც არ სრულდება. დაამატე მოგვიანებით, როცა დარწმუნდები, რომ build ეტევა.
და .gitignore, რომლითაც ყოველი Rust რეპოზიტორია უნდა დაიწყოს:
/target.envმიბმა გამოყოფილ პორტზე#
შენს ტარიფს ერთი გამოყოფილი პორტი მოჰყვება. პანელი მას გარემოში SERVER_PORT-ად ათავსებს და შენმა პროგრამამ ზუსტად ამ პორტზე უნდა უსმინოს 0.0.0.0-ზე. ორი შეცდომა ფარავს "მუშაობს, მაგრამ ვერ ვუკავშირდები" ტიპის მოხსენებების უმეტესობას:
- მოსმენა `127.0.0.1`-ზე ან `localhost`-ზე. კონტეინერის შიგნით ეს ნიშნავს "ხელმისაწვდომია მხოლოდ ამ კონტეინერის შიგნიდან". ლოგი ამბობს, რომ უსმენს, და გარედან ვერავინ უკავშირდება.
- პორტის ხელით ჩაწერა კოდში. სახელმძღვანელოებს უყვართ
3000და8080. შენი გამოყოფილი პორტი არცერთია, თუ შემთხვევით არ დაემთხვა.
წაიკითხე პორტი გარემოდან და ხმამაღლივად ჩავარდი, თუ ის არ არის, ნაცვლად იმისა, რომ ჩუმად დაბრუნდე პორტზე, რომელსაც არავინ გადაამისამართებს:
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();}actix-web-ის ექვივალენტი იგივე იდეაა, სხვა builder-ით:
#[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) პატარა ტარიფზე მნიშვნელოვანია. actix-web ნაგულისხმევად ყოველ ხილულ ლოგიკურ CPU-ზე ერთ worker-ს ქმნის, ხოლო ნახევარ ბირთვზე შეზღუდულ კონტეინერს შეიძლება მაინც მთელი მანქანის ბირთვების რაოდენობა ჩანდეს. ორი worker ქვედა ტარიფებზე სავსებით საკმარისია.
თუ იგივე კოდის შენს ლეპტოპზე გაშვებაც გინდა, შეინახე fallback მხოლოდ ლოკალური დეველოპმენტისთვის, ცალკე ცვლადის ან cfg(debug_assertions) შემოწმების უკან, რომ სერვერზე release build არასდროს მიება ჩუმად არასწორ პორტს.
ჩართვის ამომცნობი სტრიქონი#
ეს ის ნაბიჯია, რომელზეც თითქმის ყველა ერთხელ წაიფორხილებს. პანელი გადაწყვეტს, რომ სერვერმა ჩართვა დაამთავრა, კონსოლში კონკრეტული ტექსტის დანახვით. სანამ ამ ტექსტს არ დაინახავს, სერვერი "Starting" სტატუსში რჩება, თუნდაც შენი სერვისი შესანიშნავად მუშაობდეს. Cargo სერვერზე ამ ტექსტს თვითონ აყენებ Startup ჩანართზე და ის უნდა იყოს სტრიქონი, რომელსაც შენი პროგრამა რეალურად ბეჭდავს.
ყველაზე მარტივი ვარიანტია ის, რაც ზემოთ მაგალითებშია: დაბეჭდე ერთი უბრალო, სტაბილური სტრიქონი listener-ის მიბმის შემდეგ და მისი ფრაგმენტი გამოიყენე ამომცნობ ტექსტად.
listening on 0.0.0.0:რამდენიმე დეტალი ამას საიმედოს ხდის:
- დაბეჭდე `bind`-ის წარმატების შემდეგ, და არა
main-ის დასაწყისში. მაშინ "ჩაირთო" ნიშნავს "იღებს კავშირებს", სწორედ ამას უნდა აჩვენებდეს პანელი. - გამოიყენე `println!` და არა მხოლოდ `tracing`. Rust-ის standard output ხაზობრივად ბუფერიზებულია, ამიტომ
println!კონსოლში მაშინვე ხვდება.tracing-ის სტრიქონიც მუშაობს, მაგრამ მისი ფორმატი subscriber-ის კონფიგურაციაზეა დამოკიდებული და ფორმატის უწყინარმა ცვლილებამ (დროის ნიშნულები, ფერები, JSON გამოტანა) შეიძლება ტექსტის დამთხვევა გააფუჭოს. - ამომცნობ ტექსტში ნუ ჩაწერ პორტის ნომერს, თუ დარწმუნებული არ ხარ, რომ ის არასდროს შეიცვლება. ფრაგმენტი
listening on 0.0.0.0:პორტის შეცვლას უძლებს; სრული სტრიქონი - არა. - არ დაუშვა, რომ ის build-ის გამოტანაში გამოჩნდეს. Cargo build-ისას ბეჭდავს
CompilingდაFinishedსტრიქონებს. არასდროს გამოიყენო ტექსტი, რომელსაც თვითონ cargo ბეჭდავს, თორემ პანელი სერვერს ჩართულად ჩათვლის მანამ, სანამ შენი პროგრამა გაეშვება.
Build-ის დრო და მეხსიერება თითოეულ ტარიფზე#
აი, Rust-ის ჰოსტინგის უსიამოვნო ნაწილი. პროგრამა, რომელსაც deploy-ს უკეთებ, უქმად ათეულობით მეგაბაიტს იკავებს. კომპილატორს, რომელიც მას აგებს, შეიძლება გიგაბაიტზე მეტი დასჭირდეს, წუთების განმავლობაში, პიკით ბოლო link-ზე. ტარიფი build-ს უნდა ერგებოდეს და არა მხოლოდ binary-ს.
Rust (Cargo) ტარიფები RE:NODE-ზე იწყება თვეში $4-დან და ხუთი დონისაა:
| ტარიფი | RAM / დისკი / CPU | Build სერვერზე |
|---|---|---|
| Starter | 1 GB / 5 GB / 0.5 vCPU | პატარა სერვისები, ერთდროულად ერთი სამუშაო |
| Standard | 2 GB / 10 GB / 1 vCPU | ტიპური axum ან actix სერვისი |
| Advanced | 4 GB / 20 GB / 1.5 vCPU | დიდი ხეები, sqlx, ცოტა LTO |
| Pro | 6 GB / 35 GB / 2 vCPU | მძიმე ხეები, LTO უშფოთველად |
| Premium | 8 GB / 50 GB / 3 vCPU | Workspace-ები, რამდენიმე binary |
მარჯვენა სვეტი ჩვენი მიახლოებითი წესია და არა გარანტია. build-ის ფასი დამოკიდებულია იმაზე, რამდენი crate არის Cargo.lock-ში, რამდენად macro-ებით დატვირთულია ისინი (serde derive, sqlx, ყველაფერი procedural macro-ებით) და უნდა თუ არა C დამოკიდებულებების კომპილაცია. გამოცდილებით, ორიენტირისთვის: პატარა სერვისი რამდენიმე ათეული crate-ით პირველ release build-ს ერთ ბირთვზე რამდენიმე წუთში აკეთებს; ტიპურ ვებ სერვისს ორასი-სამასი crate-ით უფრო მეტი სჭირდება, ხოლო ნახევარ ბირთვზე შეიძლება ათი წუთი ან მეტიც. შემდგომი build-ები, რომლებიც მხოლოდ შენს კოდს ეხება, გაცილებით სწრაფია, რადგან დაკომპილირებული დამოკიდებულებები target/-შია და თავიდან გამოიყენება.
მნიშვნელოვანია, რა ხდება, როცა build ტარიფისთვის ძალიან დიდია, რადგან ის შეცდომას არ ჰგავს. RE:NODE-ზე კონტეინერს, რომელიც მეხსიერების ლიმიტს მიაღწევს, kernel აჩერებს და სუფთად რესტარტავს, swap-ის ნებართვის ნაცვლად. ამიტომ მეხსიერებას გადაცილებული build ჩანს ასე: კონსოლი Compiling-ის შუაში ჩერდება და სერვერი თავიდან იწყებს, ხშირად იმავე კედელს ეჯახება. თუ ამ სურათს ხედავ, ეს მეხსიერებაა და გამოსწორების სამი გზა არსებობს, უპირატესობის მიხედვით:
- ააგე ერთდროულად ერთი crate. Cargo ნაგულისხმევად crate-ებს პარალელურად აკომპილირებს და ყოველი პარალელური სამუშაო საკუთარ მეხსიერებას იკავებს. jobs-ის ერთზე დაყენება პიკს მკვეთრად ამცირებს, ფასად - უფრო დიდი ფაქტობრივი დრო.
- ამოიღე LTO და `codegen-units = 1` release პროფილიდან. ისინი link ნაბიჯის ყველაზე ძვირი პარამეტრებია.
- გადადი ერთი დონით ზემოთ. თუ სერვისს სამასი crate სჭირდება, მას ტარიფი სჭირდება, რომელსაც მათი კომპილაცია შეუძლია. ტარიფის შეცვლა სერვერს თავიდან არ აგებს.
პირველი გამოსწორება შეიძლება რეპოზიტორიაში ცხოვრობდეს, რომ კოდთან ერთად გადაადგილდეს და სერვერზე ყოველ build-ზე გავრცელდეს:
[build]jobs = 1დისკსაც იგივე ყურადღება სჭირდება. საშუალო ვებ სერვისის target/ საქაღალდე ჩვეულებრივ ერთიდან სამ გიგაბაიტამდეა, ხოლო თუ მასში debug და release არტეფაქტებიც მოხვდა, უფრო მეტი. Starter ტარიფის 5 GB დისკი მხოლოდ build cache-მა შეიძლება შეავსოს. თუ სერვერზე ოდესმე debug build გაუშვი, target/debug-ის წაშლა ფაილ მენეჯერში მარტივი გიგაბაიტია. არ წაშალო მთელი target/ ჩვევით - ეს დამოკიდებულებების cache-ს გაანადგურებს და შემდეგი ჩართვა ყველაფერს თავიდან დააკომპილირებს.
ძალიან პატარა ტარიფებისთვის არსებობს კანონიერი ალტერნატივაც: release binary ააგე CI-ში ან შენს მანქანაზე x86_64-unknown-linux-gnu-სთვის (ან -musl-ისთვის, სტატიკური binary-სთვის), ატვირთე SFTP-ით და გაუშვი ის. კარგავ წყაროდან ყოველ ჩართვაზე build-ის მოხერხებულობას და ეს მუშაობს მხოლოდ იმ შემთხვევაში, თუ გაშვების ბრძანება cargo-ს კი არა, შენს binary-ს უშვებს, ამიტომ სანამ სცდი, შეამოწმე Startup ჩანართი.
გარემოს ცვლადები და საიდუმლოებები#
კონფიგურაცია გარემოს ცვლადებშია, იკითხება ერთხელ ჩართვისას, და პროგრამა უარს ამბობს გაშვებაზე, თუ რომელიმე აკლია. ასე დავიწყებული database URL deploy-ს კონსოლში აჩერებს და არა დილის სამ საათზე, პირველივე მოთხოვნაზე, რომელსაც ის სჭირდება.
Cargo სერვერზე მნიშვნელობების ჩასაწერად ორი ადგილია:
- Startup ჩანართი. ის აჩვენებს სერვერის ცვლადებს, მათ შორის
SERVER_PORT-ს. გამოიყენე იმ ცვლადებისთვის, რომლებსაც პანელი იქ გამოაქვს. - `.env` ფაილი სერვერის ფაილებში. შექმენი ფაილ მენეჯერით ან ატვირთე SFTP-ით და ჩატვირთე
dotenvycrate-ითmain-ის დასაწყისში. ის არასდროს მიდის Git-ში.
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() { // Loads .env if it exists; real environment variables win. let _ = dotenvy::dotenv(); // ...}dotenvy::dotenv() არ გადააწერს ცვლადებს, რომლებიც გარემოში უკვე არის დაყენებული, ამიტომ პანელის SERVER_PORT ყოველთვის მოიგებს ფაილში შემთხვევით მოხვედრილ მნიშვნელობას. სწორედ ეს ქცევაა საჭირო.
თუ რეპოზიტორიას GitHub-თან აკავშირებ, GitHub ჩანართზე pre-flight შემოწმება დაკომიტებულ .env ფაილს სახელით აღნიშნავს, მისი გახსნის გარეშე. თუ ოდესმე დაგიფიქსირებს, ამოიღე ის რეპოზიტორიიდან და შეცვალე ყველა საიდუმლო, რომელსაც შეიცავდა, რადგან ისტორიაში ის მაინც რჩება. ზოგადი წესები - სად ინახება საიდუმლოებები, როგორ იცვლება, რას არ უნდა ჩაეწეროს ლოგში - არის პოსტში გარემოს ცვლადები და საიდუმლოებები.
აპლიკაციის ტარიფები მოიცავს მონაცემთა ბაზის სლოტებს: შექმენი პანელში და მიიღებ გენერირებულ host-ს, მომხმარებელსა და პაროლს, რომლებიც DATABASE_URL-ში ჩასვა. თუ შენი სერვისი იყენებს sqlx-ის კომპილაციისას შემოწმებად მოთხოვნებს, build-ს სჭირდება ან ცოცხალი მონაცემთა ბაზა, ან მომზადებული მოთხოვნების მონაცემები: გაუშვი cargo sqlx prepare ლოკალურად, დაკომიტე .sqlx საქაღალდე და დააყენე SQLX_OFFLINE=true, რომ სერვერის build კომპილაციისას ბაზას არ მიაკითხოს. ცალკე PostgreSQL სერვერი, თუ სლოტი გაგიპატარავდა, არის database ჰოსტინგის ტარიფი.
Deploy GitHub-იდან#
წყაროს SFTP-ით ატვირთვა მუშაობს, მაგრამ Cargo სერვერზე ცხოვრებას სასიამოვნოს GitHub deploy-ები ხდის. პანელი უკავშირდება GitHub App-ით, ხანმოკლე token-ებით, ამიტომ კერძო რეპოზიტორიები მუშაობს და არსად არ გჭირდება personal access token-ის ჩასმა. GitHub ერთადერთი მხარდაჭერილი პროვაიდერია.
სერვერის GitHub ჩანართზე აკავშირებ რეპოზიტორიას და branch-ს და არის ორი გადამრთველი:
- Pull ყოველ ჩართვაზე. ყოველი ჩართვა build-მდე branch-ს ჩამოტვირთავს. პანელიდან რესტარტი მაშინ განახლებაც არის.
- Deploy push-ზე. branch-ზე push მუშა სერვერს რესტარტავს, ან გამორთულს რთავს, რაც ჩამოტვირთავს და ააგებს ახალ commit-ს.
ყოველი deploy ტოვებს ჩანაწერს, ამიტომ ხედავ, რომელი commit და როდის წავიდა. მიერთებამდე pre-flight შემოწმება კითხულობს რეპოზიტორიას - ხის ჩამონათვალს და რამდენიმე პატარა ფაილს, არასდროს სრულ clone-ს - და ჩამოთვლის, რა გაფუჭდება, ყველაზე მძიმიდან დაწყებული, ჩასასმელი გამოსწორებით: დაკომიტებული .env, არასწორი host-ის ან პორტის მიბმა და მსგავსი შეცდომები, რომლებიც სხვაგვარად მხოლოდ პირველი ჩართვის შემდეგ გამოჩნდებოდა. ის ამოწმებს აშკარა გაფუჭების სცენარებს; შენს კოდს არ აკომპილირებს, ამიტომ ტიპის შეცდომა მაინც კონსოლში გამოჩნდება.
ორი რამ Rust-ისთვისაა სპეციფიკური და წინასწარ დაგეგმვის ღირსია:
- deploy არის რესტარტი, ხოლო რესტარტი არის build. ძველი პროცესი ჩერდება, ახალი commit კომპილირდება და მხოლოდ ამის შემდეგ იწყებს ახალი binary მოსმენას. შენი კოდის ცვლილებისთვის ეს ჩვეულებრივ წამებიდან ერთ წუთამდეა;
Cargo.lock-ის ცვლილებისთვის, რომელიც ფართოდ გამოყენებულ დამოკიდებულებას ანახლებს, შეიძლება რამდენიმე წუთი გასტანოს, და ამ დროს სერვისი გამორთულია. დამოკიდებულებების განახლებები შეაგროვე და ნუ გააგზავნი push-ით სათითაოდ, და გაუშვი მაშინ, როცა სერვისს არავინ ეყრდნობა. - გატეხილი build ნიშნავს სერვერს, რომელიც არ ჩაირთვება. თუ ახალი commit არ კომპილირდება, ამასობაში მომსახურე ძველი binary არ არსებობს. გაუშვი
cargo build --releaseდა ტესტები ლოკალურად ან CI-ში, სანამ push deploy branch-ს მიაღწევს, და განიხილე deploy ცალკე branch-იდან, რომელიც მხოლოდ გავლილ merge-ებს იღებს.
ამ ფანჯრის დასაპატარავებლად zero-downtime deploy-ები პატარა სერვერზე განიხილავს, რა მიიღწევა და რა არა ერთი პროცესით ერთ პორტზე, ხოლო pre-flight შემოწმებები, რომლებიც გატეხილ deploy-ებს იჭერს GitHub ჩანართის შემოწმებას დეტალურად გადის.
ლოგები და კონსოლი#
ყველაფერი, რასაც შენი პროგრამა standard output-სა და standard error-ში წერს, პანელის კონსოლში ჩანს, ცოცხლად და ფილტრის გარეშე, ტარიფის ლიმიტების მიმართ მეხსიერების, CPU-ს და დისკის გრაფიკების გვერდით. ეს კონსოლი პირველი ადგილია ამ პოსტის ყოველი პრობლემის სანახავად: build-ის გამოტანა, ჩართვის ამომცნობი სტრიქონი, panic-ები და შენი საკუთარი ლოგები - ყველაფერი იქ ხვდება.
სტრუქტურირებული ლოგირებისთვის სტანდარტული არჩევანია tracing და tracing-subscriber, რომელიც standard output-ში წერს, ხოლო RUST_LOG ცვლადი განსაზღვრავს, რა გაივლის:
RUST_LOG=info,tower_http=debug,sqlx=warn,hyper=warnფორმატი არის ნაგულისხმევი დონე, შემდეგ crate-ების მიხედვით გადაფარვები. tower_http=debug router-ზე TraceLayer-ით ყოველ მოთხოვნას სტატუსითა და დაყოვნებით ლოგავს, რაც debug-ისას ყველაზე სასარგებლო ერთი სტრიქონია, რომლის დამატებაც შეგიძლია. დაამთავრებ და დააბრუნე info-ზე; debug დონის მოთხოვნების ლოგები დატვირთულ სერვისზე მხოლოდ ხმაურია.
დააყენე RUST_BACKTRACE=1. ის არაფერი ღირს, სანამ რამე panic-ს არ გამოიწვევს, და მერე "thread 'tokio-runtime-worker' panicked"-ს გარდაქმნის სტეკად, რომელზეც შეგიძლია იმოქმედო. თუ ამ backtrace-ებში ხაზების ნომრები გინდა, release პროფილში დააყენე debug = "line-tables-only"; ის binary-ს ზრდის და არა პროცესის მადას.
თვალი ადევნე მეხსიერების გრაფიკს პირველი build-ისას და ტრაფიკის პირველ საათში. build-ის პიკი გეუბნება, სწორად არის თუ არა ტარიფი შერჩეული. გაშვებული პროცესი ლიმიტზე გაცილებით დაბლა ბრტყელ ხაზზე უნდა დადგეს; ხაზი, რომელიც მუდმივი დატვირთვისას სტაბილურად იზრდება, ლიმიტამდე მისვლამდე გამოკვლევის ღირსია. ლოგები, რომლებიც ღირს შენახვად აღწერს, რა და რამდენ ხანს შეინახო, როცა სერვისი სტაბილურ რეჟიმშია.
დომენი და HTTPS#
გამოყოფილ პორტზე მიაწოდე უბრალო HTTP და TLS proxy სლოტს მიანდე. აპლიკაციის ტარიფები ერთს მოიცავს: მიუთითე დომენის A ჩანაწერი ნაჩვენებ მისამართზე და სერტიფიკატი ავტომატურად გაიცემა და განახლდება, 21-დღიან ფანჯარაში. შენი სერვისი სერტიფიკატის ფაილს არასდროს ეხება.
proxy-ს უკან კლიენტის რეალური მისამართი X-Forwarded-For header-შია და არა კავშირში, ამიტომ axum-ის ConnectInfo ვიზიტორს კი არა, proxy-ს გაძლევს. თუ IP-ით rate-limit-ს ან ლოგირებას აკეთებ, header-ი წაიკითხე. ყველგან, სადაც აბსოლუტურ URL-ებს აგებ - გადამისამართებები, OAuth callback-ები, ბმულები წერილებში - გამოიყენე კონფიგურირებული საბაზო URL, მაგალითად APP_BASE_URL, და არა მოთხოვნიდან გამოცნობა. რას აკეთებს reverse proxy header-ების დანარჩენ ჯაჭვს განმარტავს, ხოლო websocket-ები reverse proxy-ს უკან მნიშვნელოვანია, თუ axum::extract::ws-ს იყენებ.
პრობლემების გადაჭრა#
სერვერი "Starting"-ზე სამუდამოდ რჩება. Startup ჩანართზე ამომცნობი ტექსტი არაფერს ემთხვევა იმისგან, რასაც შენი პროგრამა ბეჭდავს. დააკოპირე მოსმენის სტრიქონის ფრაგმენტი და გადატვირთე.
კონსოლი `Compiling`-ის შუაში ჩერდება და სერვერი რესტარტდება. build-მა მეხსიერების ლიმიტს მიაღწია. .cargo/config.toml-ში დააყენე jobs = 1, ამოიღე LTO release პროფილიდან, ან გადადი ერთი დონით ზემოთ.
`error: could not find Cargo.toml`. პროექტი რეპოზიტორიის ქვესაქაღალდეშია. გადაიტანე ძირში, ან იქ დადე workspace-ის Cargo.toml.
`cargo run` could not determine which binary to run. პაკეტს რამდენიმე binary აქვს. დაამატე default-run [package]-ში.
მუშაობს, მაგრამ მისამართი არ პასუხობს. ის მიბმულია 127.0.0.1-ზე ან ხელით ჩაწერილ პორტზე. მიაბი 0.0.0.0-ს და წაიკითხე SERVER_PORT.
`Address already in use`. იმავე პროცესის ხეში რაღაც სხვას უკვე უკავია პორტი - ხშირად მეორე listener, რომელიც metrics-ისთვის იმავე პორტზე დაამატე. ერთ ტარიფს ერთი გამოყოფილი პორტი აქვს; metrics მიაწოდე მთავარი router-ის route-ზე.
ყოველ deploy-ს პირველივით დიდი დრო სჭირდება. ვიღაც ასუფთავებს target/-ს, ან Cargo.lock დაკომიტებული არ არის და დამოკიდებულებები ყოველ ჯერზე სხვაგვარად წყდება. დაკომიტე lock ფაილი და cache-ს ნუ ეხები.
`set DATABASE_URL to use query macros`. sqlx ცდილობს მოთხოვნების კომპილაციისას შემოწმებას. დაკომიტე .sqlx საქაღალდე cargo sqlx prepare-დან და დააყენე SQLX_OFFLINE=true.
დისკი სავსეა. ჩვეულებრივ target/. წაშალე target/debug, თუ არსებობს, და დარწმუნდი, რომ debug და release გვერდიგვერდ არ გაგიშვია.
FAQ#
ეს Rust თამაშისთვისაა თუ Rust პროგრამირების ენისთვის?
ენისთვის. Rust (Cargo) ჰოსტინგი უშვებს პროგრამებს, რომლებიც Rust-ზეა დაწერილი და cargo-თია აგებული - ვებ სერვისებს, ბოტებს, worker-ებს. survival თამაში სხვა პროდუქტია საკუთარი დაყენებით, რომელიც აღწერილია ჩვენს Rust dedicated server-ის გზამკვლევში.
რომელი ტარიფი მჭირდება axum სერვისისთვის?
თვითონ სერვისი უმცირეს ტარიფზეც იმუშავებდა. გადამწყვეტი build-ია. პატარა სერვისი 1 GB-ზე აშენდება ერთდროულად ერთი კომპილაციის სამუშაოთი; ტიპური ვებ სერვისი რამდენიმე ასეული crate-ით გაცილებით კომფორტულია 2 GB დონეზე ან მეტზე. პირველი build-ისას უყურე მეხსიერების გრაფიკს და ტარიფი იმის მიხედვით აირჩიე, რასაც აჩვენებს.
რატომ გრძელდება რესტარტი ასე დიდხანს?
იმიტომ, რომ ყოველი ჩართვა პროექტს გაშვებამდე cargo-თი აგებს. როცა დამოკიდებულებები target/-შია cache-ირებული, კოდის ცვლილების გარეშე რესტარტი ძირითადად cargo-ს დადასტურებაა, რომ არაფრის თავიდან აგებაა საჭირო, ხოლო შენი კოდის ცვლილება მხოლოდ შენს crate-ს აკომპილირებს. რესტარტს ისევ ნელს Cargo.lock-ის ცვლილება ხდის.
შემიძლია ვებ სერვერის გარდა სხვა რამის გაშვება?
დიახ. Discord ან Telegram ბოტი, queue worker ან დაგეგმილი სამუშაოების გამშვები ერთნაირად მუშაობს: პროგრამა ეშვება, ბეჭდავს ჩართვის ამომცნობ სტრიქონს და მუშაობს. მას პორტზე მოსმენა არ სჭირდება, თუ ვინმეს არ უნდა მასთან დაკავშირება, თუმცა პატარა health route გარედან შესამოწმებლად მაინც სასარგებლოა.
აუცილებელია deploy GitHub-იდან?
არა. წყაროს ფაილ მენეჯერით ან SFTP-ით ატვირთვა და რესტარტი შეგიძლია, და სერვერი ააგებს იმას, რაც მის ფაილებშია. GitHub deploy-ები მოსახერხებელი გზაა და ერთადერთი Git პროვაიდერი, რომელსაც პანელი უკავშირდება.
შეიძლება nightly toolchain-ის გამოყენება?
სერვერი აგებს მისი image-ის toolchain-ით და nightly-ზე გადართვის გზას ჩვენ არ ვაღწერთ. თუ nightly ფუნქციაზე ხარ დამოკიდებული, პრაქტიკული გზაა სხვაგან აგება და binary-ს ატვირთვა - ჯერ გაშვების ბრძანება შეამოწმე, რომ მზა binary-ს გაუშვებს.


