Skip to content
← Wszystkie wpisy
4 min czytania Dawid Skłodowski

Deploye Capistrano bez łez

Capistrano automatyzuje wdrożenie po SSH z atomowymi releasami i rollbackiem na jedną komendę. Praktyczny przewodnik po modelu releases/current, plikach współdzielonych, łańcuchu zadań Rails, konfiguracji multistage i pułapkach wartych poznania.

Wdrażanie aplikacji Rails ręcznie — git pull, bundle, migrate, assets:precompile, restart i modlitwa — działa do dnia, w którym nie działa, a dzień, w którym nie działa, to zwykle dzień, w którym najbardziej tego potrzebowałeś. Capistrano zastępuje ten rytuał jedną powtarzalną komendą, która robi te same kroki w tej samej kolejności za każdym razem, czyni przełączenie na nowy kod atomowym i pozwala natychmiast się wycofać, gdy coś jest nie tak. Dla zespołów Ruby to domyślne narzędzie deploy, a gdy raz jest skonfigurowane, wdrażanie przestaje być rzeczą, której się boisz. Oto jak działa i jak je skonfigurować bez łez, których tytuł obiecuje oszczędzić.

Wszystko w Capistrano nabiera sensu, gdy zrozumiesz jego układ katalogów na serwerze. Nie wdraża do jednego katalogu i go nadpisuje. Zamiast tego trzyma folder releases/ z datowanymi katalogami, po jednym na deploy, oraz symlink current wskazujący na aktywny:

/var/www/myapp/
├── releases/
│   ├── 20150518093000/
│   ├── 20150519140500/
│   └── 20150520101500/   <- najnowszy
├── current -> releases/20150520101500/
├── repo/                  <- lustro git
└── shared/                <- przetrwa między releasami

Deploy klonuje nowy kod do świeżego datowanego katalogu, uruchamia tam wszystkie kroki budowania, podczas gdy stary release wciąż obsługuje ruch, i dopiero na samym końcu przełącza symlink current na nowy katalog w jednej atomowej operacji. To funkcja, która liczy się najbardziej: przełączenie to pojedyncza zmiana symlinka, więc użytkownicy nigdy nie widzą na wpół zbudowanego release’u. A rollback to po prostu przełączenie current z powrotem na poprzedni znacznik czasu — cap production deploy:rollback — co jest natychmiastowe, bo stary release wciąż siedzi tuż obok na dysku.

Pliki i katalogi współdzielone

Model datowanych release’ów rodzi oczywiste pytanie: a co z rzeczami, które muszą przetrwać deploy — wgrane pliki, logi, master.key, skompilowane assety, które chcesz reużyć? Te żyją w shared/, a Capistrano symlinkuje je do każdego nowego release’u:

# config/deploy.rb
set :linked_files, %w[config/database.yml config/secrets.yml]
set :linked_dirs,  %w[log tmp/pids tmp/cache public/system public/uploads]

linked_files i linked_dirs to mechanizm trzymający stan poza jednorazowymi katalogami release’ów. Twój produkcyjny database.yml i secrets.yml żyją raz w shared/config/ (umieszczone tam poza obiegiem, nigdy commitowane), a każdy release symlinkuje do nich. Wgrania użytkowników w public/uploads przetrwają, bo też żyją w shared/. Dyscyplina odzwierciedla to, czego nauczyliśmy się, konteneryzując usługi ze stanem: release jest jednorazowy, dane współdzielone są cenne, a mają różne cykle życia.

Łańcuch zadań Rails

Capistrano to runner zadań, a jego wtyczki wpinają znane kroki deploy we właściwe miejsce cyklu życia release’u:

# Gemfile
gem "capistrano-rails"     # assety + migracje
gem "capistrano-bundler"   # bundle install
gem "capistrano-rbenv"     # właściwe Ruby
gem "capistrano3-puma"     # restart serwera aplikacji

Z nimi wymaganymi deploy uruchamia, po kolei: pobranie kodu do nowego release’u, bundle install, uruchomienie oczekujących migracji, precompilację assetów, potem symlink current i restart serwera aplikacji. Dwie wygody warte wiedzy: capistrano-rails jest na tyle mądry, by pominąć precompilację assetów, gdy nic pod app/assets się nie zmieniło (duża oszczędność czasu, bo precompilacja to najwolniejszy krok), a migracje uruchamiają się wobec nowego kodu, zanim symlink się przełączy. Cały łańcuch to jedna komenda:

cap production deploy

Multistage: staging i produkcja z jednej konfiguracji

Niemal zawsze wdrażasz na więcej niż jedno środowisko, a wsparcie multistage w Capistrano trzyma je uczciwymi. Współdzielone ustawienia żyją w config/deploy.rb; specyfika per środowisko żyje w config/deploy/staging.rb i config/deploy/production.rb:

# config/deploy/production.rb
server "web1.example.com", user: "deploy", roles: %w[app web db]
set :branch, "master"

# config/deploy/staging.rb
server "staging.example.com", user: "deploy", roles: %w[app web db]
set :branch, ENV["BRANCH"] || "develop"

Teraz cap staging deploy i cap production deploy używają tego samego łańcucha zadań wobec różnych hostów i gałęzi — co jest dokładnie sposobem, w jaki zapewniasz, że staging naprawdę próbuje produkcję, zamiast dryfować w inny proces.

Pułapki warte poznania

Kilka rzeczy, które kosztują ludzi popołudnie za pierwszym razem:

  • Pierwszy deploy potrzebuje plików współdzielonych na miejscu. Capistrano symlinkuje linked_files, ale ich nie tworzy — shared/config/database.yml musi istnieć na serwerze przed pierwszym deployem, inaczej zawiedzie. Umieść je tam ręcznie (albo jednorazowym zadaniem) z góry.
  • deploy:check przed deployem. Weryfikuje strukturę katalogów, pliki linkowane i dostęp SSH bez wdrażania. Uruchom go najpierw na nowym serwerze i napraw, na co narzeka.
  • Migracje nie są automatycznie zero-downtime. Capistrano czyni przełączenie kodu atomowym; nie czyni destrukcyjnej migracji bezpieczną. Migracja usuwająca kolumnę, której stary release wciąż używa, popsuje rzeczy w krótkim nakładaniu. Migracje wstecznie kompatybilne (dodaj kolumny, zanim ich użyjesz, usuń je deploy później) wciąż są twoim zadaniem.
  • Symlink current przełącza się ostatni, ale serwer aplikacji musi go przeładować. Upewnij się, że twój restart Pumy/Unicorna faktycznie podejmuje nowy current — phased restart trzymający stare workery wskazujące na stary release to klasyczne źródło „wdrożyłem, ale nic się nie zmieniło”.
  • Trzymaj rozsądny keep_releases. Stare release’y piętrzą się na dysku; set :keep_releases, 5 przycina je, trzymając wystarczająco historii na rollback.

Werdykt

Capistrano zamienia wdrożenie z lękliwego ręcznego rytuału w nudną, powtarzalną komendę — co jest dokładnie tym, czym chcesz, by wdrożenie było. Model releases-i-symlink daje ci atomowe przełączenia i natychmiastowe rollbacki za darmo, shared/ trzyma twój stan bezpiecznym między deployami, łańcuch wtyczek obsługuje specyfikę Rails, a multistage trzyma staging i produkcję zestrojone. Skonfiguruj pliki współdzielone raz, pisz migracje wstecznie kompatybilne, a cap production deploy staje się komendą, którą uruchamiasz bez zastanowienia. W późniejszej epoce będziemy wdrażać kontenerami i pipeline’ami, ale idee, które Capistrano wbija — atomowe release’y, oddzielony stan, powtarzalne kroki, łatwy rollback — to te, na których wciąż zbudowany jest każdy system deploy po nim.