Capistrano je nástroj psaný v jazyce Ruby. Pokud nejste kamarádi s jeho ekosystémem, určitě si přečtěte článek Ekosystém Ruby: přehled nástrojů. Pokud ještě nemáte, nainstalujte si Ruby a Bundler.
Než se do toho pustíme, tak několik základních předpokladů, které by Vaše aplikace a Váš server měl splňovat:
- Aplikace je verzovaná ve VCS, ideálně v Gitu.
- K serveru máme SSH přístup a server má přístup do repository, kde máme aplikaci (tj. na serveru je platný klíč, nebo máme správně nastavený SSH Agent Forwarding).
- Máme možnost změnit nastavení HTTP serveru, zejména pak document root pro naši aplikaci.
- V repositáři nejsou uložena hesla, ani jiná část konfigurace specifická pro produkční server.
Dále budu uvažovat, že aplikaci máme v Git repository. Také budeme předpokládat, že jde o Nette aplikaci využívající Composer se standardní adresářovou strukturou, tj.:
. ├── app ├── composer.json ├── composer.lock ├── log ├── temp ├── tests ├── vendor └── www
Instalace a inicializace aplikace
Capistrano budeme instalovat právě pomocí nástroje Bundler. Zajistíme tak, že budeme v aplikaci později využívat vždy tu verzi, pro kterou máme deployment proces připravený a hlavně vyzkoušený, nikoliv tu, která se nám nainstalovala globálně do systému.
Vytvoříme tedy soubor Gemfile, ve kterém budeme deklarovat verzi 3.2.0 a vyšší:
source 'https://rubygems.org' gem 'capistrano', '~> 3.2.0'
Nainstalujeme jednoduše příkazem bundle install
.
Příprava aplikace
Na začátek bude potřeba vytvořit základní konfiguraci deploymentu. Spustíme následující příkaz:
bundle exec cap install
Příkaz vytvoří soubor Capfile
, složku config
(pokud neexistuje) a v ní následující soubory a adresáře:
config/ ├── deploy/ │ ├── production.rb │ └── staging.rb └── deploy.rb
Soubor deploy.rb
obsahuje globální nastavení deploymentu. Ve složce deploy
jsou pak nastavení pro jednotlivá prostředí, na která můžeme aplikaci deployovat. Můžeme tak snadno použít jeden nástroj pro nasazení aplikace do testovacího i produkčního prostředí.
Globální nastavení
Podívejme se nejprve na soubor deploy.rb
. Capistrano nám připravilo okomentovanou šablonu, ze které můžeme vycházet:
# config valid only for Capistrano 3.1 lock '3.2.1' set :application, 'my_app_name' set :repo_url, 'git@example.com:me/my_repo.git' # Default branch is :master # ask :branch, proc { `git rev-parse --abbrev-ref HEAD`.chomp }.call # Default deploy_to directory is /var/www/my_app # set :deploy_to, '/var/www/my_app' # Default value for :scm is :git # set :scm, :git # Default value for :format is :pretty # set :format, :pretty # Default value for :log_level is :debug # set :log_level, :debug # Default value for :pty is false # set :pty, true # Default value for :linked_files is [] # set :linked_files, %w{config/database.yml} # Default value for linked_dirs is [] # set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system} # Default value for default_env is {} # set :default_env, { path: "/opt/ruby/bin:$PATH" } # Default value for keep_releases is 5 # set :keep_releases, 5 namespace :deploy do desc 'Restart application' task :restart do on roles(:app), in: :sequence, wait: 5 do # Your restart mechanism here, for example: # execute :touch, release_path.join('tmp/restart.txt') end end after :publishing, :restart after :restart, :clear_cache do on roles(:web), in: :groups, limit: 3, wait: 10 do # Here we can do anything such as: # within release_path do # execute :rake, 'cache:clear' # end end end end
Na začátku bychom měli nastavit název aplikace a cestu ke Git repository:
set :application, 'test-application' set :repo_url, 'git@git.lynt.cz:testing/test-app.git'
Další nastavení určuje branch, která se bude deployovat. Výchozí je master
, a můžeme se na ní buď dotázat uživatele při deploymentu odkomentováním řádku s
ask :branch, proc { `git rev-parse --abbrev-ref HEAD`.chomp }.call
nebo můžeme větev nastavit přímo:
set :branch, :development
Do testovacího prostředí však budeme pravděpodobně chtít deployovat jinou branch, než na produkci, proto toto nastavení necháme do souborů pro jednotlivá prostředí.
Další důležité nastavení je složka, do které se má aplikace deployovat. Můžeme jí opět nastavit globálně, nebo pro každé prostředí zvlášť. Můžeme také nastavit výchozí hodnotu a tu pak jen podle potřeby přepsat:
set :deploy_to, '/data/htdocs/testing/test-application'
V této složce Capistrano vytváří následující adresářovou strukturu:
/data/htdocs/testing/test-application ├── current -> ./releases/YYYYMMDDHHMMSS ├── releases │ └── YYYYMMDDHHMMSS ├── repo ├── revisions.log └── shared
Nejdůležitější je složka releases
. Pro každý deployment vytvoří Capistrano novou složku s časovým razítkem v názvu. Do ní z Gitu nahraje požadované soubory. Aby se data pořád nemusela stahovat ze vzdáleného repositáře, udržuje jeho mirror ve složce repo
. Každý release také zapíše do souboru revisions.log
.
Ve složce shared
jsou uloženy soubory a složky, které jsou sdílené pro všechny releases. Jedná se zejména o uživatelská data, soubory sessions a další data, o která by nebylo dobré s každým release přijít. Po stažení souborů jsou nasymlinkovány do složky konkrétního release.
A konečně, složka current
není složka, ale symlink na poslední úspěšný release. Pokud nedojde k chybě, tak je v posledním kroku deploymentu symlink změněn. Document root aplikace bychom měli nastavit právě do current/www
.
Dalším zajímavým nastavením je nastavení sdílených souborů a složek. Do každého release budeme chtít nalinkovat připravený app/config/config.local.neon
, který obsahuje nastavení připojení k databázi, a složky log
, temp/sessions
a www/attachments
. Nastavení bude vypadat následovně:
set :linked_files, %w{app/config/config.local.neon} set :linked_dirs, %w{log temp/sessions www/attachments}
Konec konfiguračního souboru tvoří příklad vlastních úloh, které se mají v rámci deployment procesu spouštět. K těm se dostaneme později.
Nastavení prostředí
Nyní přejdeme k nastavení cílového prostředí. Začněme nastavením prostředí staging, které by mělo sloužit k revizi aplikace před uvedením do produkce. Nastavení produkčního prostředí pak bude analogické.
I v deployment/staging.rb
si pro nás Capistrano přichystalo kostru konfigurace (sekce s nastavením SSH pro přehlednost vyjmuta):
# Simple Role Syntax # ================== # Supports bulk-adding hosts to roles, the primary server in each group # is considered to be the first unless any hosts have the primary # property set. Don't declare `role :all`, it's a meta role. role :app, %w{deploy@example.com} role :web, %w{deploy@example.com} role :db, %w{deploy@example.com} # Extended Server Syntax # ====================== # This can be used to drop a more detailed server definition into the # server list. The second argument is a, or duck-types, Hash and is # used to set extended properties on the server. server 'example.com', user: 'deploy', roles: %w{web app}, my_property: :my_value # ...
V kostře najdeme nastavení serverů, na které bude probíhat deployment. Ty mohou být rozděleny do skupin. Na každé skupině pak mohou být aplikovány jiné příkazy. Pro jednoduchost budeme uvažovat jediný server s rolemi web
a app
. Pro výchozí nastavení stejně role serverů nehrají moc roli, protože výchozí deployment proces probíhá pro všechny role stejně. Více o rolích a jejich filtrování naleznete v kapitole Role filtering oficiální dokumentace.
Konfigurace jednoho vypadá následovně:
server 'deploy.lynt.cz', user: 'deploy', roles: %w{web app}
Capistrano tedy bude přistupovat pomocí SSH na server depoly.lynt.cz
jako uživatel deploy
. Dále nastavíme branch, která se bude deployovat, a cestu:
set :branch, :staging set :deploy_to, '/data/htdocs/testing/test-application'
Pokud jsme všechno nastavili správně, měli bychom již nyní být schopni provést deployment:
bundle exec cap staging deploy
Jako první argument uvedeme prostředí, se kterým chceme pracovat, a jako druhý úlohu, kterou chceme provést na jeho serverech. V našem případě deploy
.
Příkaz by měl vytvořit na vzdáleném serveru všechny potřebné složky, ale skočit na neexistenci souboru app/config/config.local.neon
. Do složky shared/app/config
tedy nahrajeme produkční konfiguraci a příkaz spustíme znovu, nyní by již měl deployment proběhnout korektně a měl by být vytvořen symlink current
.
Composer
Pokud používáme Composer pro správu závislostí a nemáme závislosti v Git repository, tak se naše aplikace sice nahrála na vzdálený server, ale bez závislostí. Pro capistrano naštěstí existuje plugin, které podporu Composeru doplní.
Do Gemfile
stačí přidat řádek gem 'capistrano-composer'
, soubor tedy bude vypadat následovně:
source 'https://rubygems.org' gem 'capistrano', '~> 3.2.0' gem 'capistrano-composer'
Nové závislosti nainstalujeme opět příkazem bundle install
. Plugin je ještě nutné povolit v souboru Capfile
. Za řádek require 'capistrano/deploy'
doplníme require 'capistrano/composer'
, celý soubor bude vypadat například následovně:
# Load DSL and Setup Up Stages require 'capistrano/setup' # Includes default deployment tasks require 'capistrano/deploy' # Includes tasks from other gems included in your Gemfile require 'capistrano/composer' # Loads custom tasks from `lib/capistrano/tasks' if you have any defined. Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
Nyní by již v rámci deploymentu měla proběhnout i instalace závislostí. Na serveru samozřejmě musí být Composer nainstalovaný a dostupný z proměnné PATH
. Tu můžeme upravit v konfiguraci jednotlivých prostředí přidáním nastavení :default_env
. Pokud chceme použít složku bin
v domovském adresáři uživatele, který provádí deployment, přidáme následující řádku:
set :default_env, { path: "$HOME/bin:$PATH" }
Vlastní úlohy
V rámci deploymentu jistě budeme chtít spouštět vlastní úlohy, například nastavení oprávnění. Úlohy můžeme definovat v souboru config/deploy.rb
. Úlohy jsou členěny do namespaces. Úloha pro nastavení oprávnění může vypadat například takto:
namespace :deploy do desc 'Fix application permissions.' task :fix_permissions do on roles(:app) do |host| execute "chmod -R u+w,g+wX #{release_path} ; true" execute "chmod -R u+w,g+wX #{shared_path} ; true" execute "chgrp -R http #{release_path} ; true" execute "chgrp -R http #{shared_path} ; true" end end end
V úlohy příkazu můžeme určit, s jakými servery budeme pracovat. Uvedením on roles(:app)
zajistíme, že se příkaz vykoná jen na aplikačních serverech. V proměnné host
pak budeme mít uložené informace o serveru, se kterým právě pracujeme.
Příkaz execute
vykoná na vzdáleném serveru zadaný příkaz. Příkaz můžeme zadat jako řetězec, nebo jako seznam řetězců oddělených čárkami – při spouštění se spojí a jednotlivé části správně escapují. Můžeme používat proměnné release_path
a shared_path
, které obsahují cesty ke složce s aktuálním release a ke sdíleným souborům.
Uvedený příkaz končí vždy příkazem true
, protože ve sdílené složce mohou být soubory, ke kterým uživatel pro deployment nemá oprávnění přistupovat – například přístupové logy serveru, nebo soubory vytvořené uživateli aplikace. chmod
a chgrp
by v takovém případě skončily chybou (návratový kód 1
) a celý deployment by se přerušil.
Takto nadefinovanou úlohu však musíme spustit ručně z příkazové řádky:
bundle exec cap staging deploy:fix_permissions
Bylo by dobré ji začlenit do deployment procesu. Pomocí příkazů before
a after
jej můžeme naplánovat na automatické spuštění před nebo po určitém příkazu. V rámci příkazu deploy
se postupně spouští následující úlohy (převzato z Capistrano Flow):
deploy:starting - start a deployment, make sure everything is ready deploy:started - started hook (for custom tasks) deploy:updating - update server(s) with a new release deploy:updated - updated hook deploy:publishing - publish the new release deploy:published - published hook deploy:finishing - finish the deployment, clean up everything deploy:finished - finished hook
Opravu oprávnění naplánujeme před úlohu deploy:publishing
, definice příkazu bude vypadat následovně:
namespace :deploy do desc 'Fix application permissions.' task :fix_permissions do on roles(:app) do |host| execute "chmod -R u+w,g+wX #{release_path} ; true" execute "chmod -R u+w,g+wX #{shared_path} ; true" execute "chgrp -R http #{release_path} ; true" execute "chgrp -R http #{shared_path} ; true" end end before :publishing, :fix_permissions end
Pokud spustíme bundle exec cap staging deploy
, proběhne před vytvořením symlinku current
i nastavení oprávnění.
Závěr
V článku jsme si ukázali nastavení deployment procesu pro jednoduchou aplikaci. Vycházeli jsme z univerzálního deployment procesu, který je nadefinovaný v nástroji Capistrano. Pro složitější scénáře je možné proces snadno rozvinout přidáváním vlastních příkazů.
Capistrano používá knihovnu SSHKit, která umožňuje pokročilé sestavování příkazů, jejich paralelní spouštění na serverech odpovídajících daným kritériím (např. rolím) a mnoho dalšího.
Pro více informací se podívejte do oficiální dokumentace k nástroji Capistrano.
Já to tedy řeším o dost jinak. U malých aplikací to nemá smysl rozebírat, tak je jen jednoduchý kopírovací skript, ale u takových aplikací se moc nepočítá s dalším updatem. U větších je to buď balíček, tzn "apt-get update", nebo u aplikací které je třeba takto deploynout třeba několikrát za den to řeším přes SH skript.
Ten funguje tak, že se nejdříve zabalí a vyřeší faktury a jiné věci které s tím úplně nesouvisejí, ale chci je mít v bezpečí. Pak se natáhne z produkční větve z GITu obsah do nějaké úplně oddělené složky na serveru a nainstalují se závislosti. Pak se nastaví práva, configy, doctrine proxy, webloader minifikované soubory a takové věci. Vše je napsáno tak, aby když to selže, tak se deploy okamžitě zastavil. Teď tedy když je aplikace připravená, tak se pomocí rsync šoupne na správné umístění, ještě se v tomto umístění dogeneruje cache (dojde tedy ke kompilaci jako takové) a když je vše ok, tak se to publikuje.
Ještě se nestalo, aby aplikace nenastartovala, nebo se provedl deploy s rozbitou aplikací a celé (to co nás zajímá) trvá pár sekund. Navíc je to otázkou jednoho příkazu (nepočítaje instalace gitu a composeru)... (-:
Díky! Převzal jsem několik projektů v Nette (na svoje používám Capifony) a bál jsem se, že si budu muset napsat Capistrano script sám.
Martin Zlámal: ve výsledku je to řešení pomocí SH skriptu velmi podobné. Capistrano dělá v podstatě totéž: nejprve z gitu aplikaci natáhne do vlastní složky, provede vše potřebné (určeno deployment skriptem: instalace závislostí, generování doctrine proxies, nastavení oprávnění apod.), když cokoliv selže, tak se zastaví. Na závěr jen nepřesouvá rsyncem a publikací se rozumí změna symlinku. Celé to také trvá jen pár sekund (nejdelší na celém procesu je instalace závislostí).
Vytváření balíčků (v našem případě RPM - jedeme na CentOSu) jsem také zvažoval, ale nakonec mi to přišlo jako moc velký overhead. Navíc deployovat pomocí Capistrana z libovolného klienta není problém a není nutné dávat vývojářům oprávnění roota, stačí přidat jejich SSH klíč nějakému omezenému uživateli.