Hẳn các bạn cũng biết rằng, để deploy một ứng dụng lên server, chúng ta phải trải qua rất nhiều bước. Việc viết từng lệnh một mỗi khi cần triển khai, update một ứng dụng khiến chúng ta mệt mỏi, tốn thời gian, công sức, cũng như việc tiềm tàng xảy ra các lỗi không đáng có.
Vậy nên hôm nay mình sẽ giới thiệu với mọi người về Capistrano để deploy ứng dụng lên server. Đây là một công cụ rất nổi tiếng, giúp chúng ta có thể deploy một ứng dụng thông qua 1 câu lệnh !
Chú ý: Mình sẽ mặc định khi mọi người đọc bài này là đã biết rõ về cách deploy một ứng dụng Ruby. Capistrano chỉ có tác dụng khi mọi người hiểu về deploy, còn nếu như chưa biết về deploy, hãy đọc qua tutorial này đã nhé !
Một vài khái niệm cơ bản
Giới thiệu qua
Capistrano là 1 công cụ cho phép việc tự động thực hiện câu lệnh trên 1 remote server bằng cách sử dụng SSH. Capistrano hoạt động trên máy tính cá nhân của bạn (chứ không phải trên server) sau đó sẽ truy cập vào 1 hoặc 1 vài server (do chúng ta cấu hình) thông qua SSH để thực hiện các câu lệnh được chúng ta thiết lập sẵn. Capistrano sử dụng Ruby làm ngôn ngữ để viết script.
Capistrano sẽ yêu cầu chúng ta phải cung cấp cho nó tối thiểu những thứ sau:
- Một Capistrano script, trong đó bao gồm những câu lệnh Capistrano phải thực hiện, cũng như server nào cần thực hiện những câu lệnh đó. File script này sử dụng Ruby để viết.
- Một hoặc một vài configuration file để cung cấp thông tin các server, cũng như cách đăng nhập vào các server đó.
Capistrano còn có thể chạy các câu lệnh đồng thời trên các server khác nhau, điều này rất có lợi khi các bạn triển khai các ứng dụng lớn lên nhiều server khác nhau.
Workflow
- Đầu tiên, dĩ nhiên rồi, chúng ta cần phải cài đặt và chỉnh sửa Capistrano trên máy tính cá nhân của mình (lưu ý là máy tính cá nhân chứ không phải server nhé)
- Mỗi khi cần deploy một version mới của ứng dụng, chúng ta cần phải:
- Commit và push toàn bộ code lên git repo (mà chúng ta đã cung cấp trong Capistrano)
- Chạy câu lệnh deploy của Capistrano
Recipes:
Capistrano rất hữu dụng, nhưng sức mạnh thực sự của nó nằm ở các recipes mà cộng đồng xây dựng cho nó.
Ví dụ, nếu chúng ta sử dụng capistrano/deploy
recipe, trong đó sẽ chứa các câu lệnh giúp chúng ta tự động git clone
, git pull
, cũng như hỗ trợ cho việc rollback rất dễ dàng. Tất cả những điều chúng ta cần phải làm đó là cung cấp cho Capistrano URL của git repo.
Hoặc nếu như bạn muốn deploy một ứng dụng rails, capistrano-rails
sẽ giúp bạn chạy câu lệnh bundle install
, compile Rails assets, chạy db migrate,…
Cấu trúc directory của Capistrano:
Sau khi deploy app lên server bằng Capistrano, directory sẽ có cấu trúc như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | myapp ├── releases │ ├── <span class="token number">20150080072500</span> │ ├── <span class="token number">20150090083000</span> │ ├── <span class="token number">20150100093500</span> │ ├── <span class="token number">20150110104000</span> │ └── <span class="token number">20150120114500</span> │ ├── <span class="token operator"><</span>checked out files from <span class="token constant">Git</span> repository<span class="token operator">></span> │ └── config │ ├── database<span class="token punctuation">.</span>yml <span class="token operator">-</span><span class="token operator">></span> <span class="token operator">/</span>var<span class="token operator">/</span>www<span class="token operator">/</span>myapp<span class="token operator">/</span>shared<span class="token operator">/</span>config<span class="token operator">/</span>database<span class="token punctuation">.</span>yml │ └── secrets<span class="token punctuation">.</span>yml <span class="token operator">-</span><span class="token operator">></span> <span class="token operator">/</span>var<span class="token operator">/</span>www<span class="token operator">/</span>myapp<span class="token operator">/</span>shared<span class="token operator">/</span>config<span class="token operator">/</span>secrets<span class="token punctuation">.</span>yml │ ├── current <span class="token operator">-</span><span class="token operator">></span> <span class="token operator">/</span>var<span class="token operator">/</span>www<span class="token operator">/</span>myapp<span class="token operator">/</span>releases<span class="token regex">/20150120114500/</span> ├── repo │ └── <span class="token operator"><</span><span class="token constant">VCS</span> related data<span class="token operator">></span> └── shared ├── <span class="token operator"><</span>linked_files <span class="token keyword">and</span> linked_dirs<span class="token operator">></span> └── config ├── database<span class="token punctuation">.</span>yml └── secrets<span class="token punctuation">.</span>yml |
releases
chứa toàn bộ các phiên bản của app, được đánh dấu bằng các timestamped folder. Mỗi khi bạn bảo với Capistrano deploy phiên bản mới lên, Capistrano sẽ clone từ Git repo về và lưu nó trong 1 thư mục con củareleases
.current
đơn giản thì nó là một symlink chỉ đến version mới nhất được chứa trongreleases
(dựa theo timestamp). Symlink này chỉ update khi quá trình deploy hoàn tất, nếu quá trình deploy fail ở bất kỳ bước nào, symlink này vẫn chỉ đến version cũ trước đó.repo
lưu giữ một bản copy của GIt repo, để lần sau có thể thực hiện pull về nhanh hơn.shared
đây là 1 thư mục rất hay ho của Capistrano, để mình lấy cho các bạn một ví dụ:
Bởi vì Capistrano hoạt động bằng việc clone ứng dụng từ Git về, vì thế nên chỉ những file được chúng ta upload lên Git mới có thể được clone về. Vậy ví dụ với một ứng dụng Rails, có những file mà ta chỉ có thể giữ ở môi trường local (database.yml, secrets.yml,…) thì làm sao ta có thể sử dụng được đây ? Và Capistrano đưa ra giải pháp, đó là người dùng có thể cấu hình các file, thậm chí là cả các folder mà có thể chia sẻ trực tiếp giữa môi trường development và môi trường production (máy cá nhân và server). Những file này sẽ luôn tồn tại dù cho chúng ta có đưa phiên bản thứ bao nhiêu lên đi chăng nữa.
Để cấu hình các file, folder được chia sẻ, Capistrano cung cấp cho ta 2 configuration options đó là linked_files
và linked_dirs
Cài đặt Capistrano
Khởi tạo
Đầu tiên chúng ta cần phải cài đặt Capistrano trên chính ứng dụng Ruby mà chúng ta đang làm việt. Mở Gemfile
ra và thêm vào:
1 2 3 4 5 6 7 8 9 10 11 12 | group <span class="token symbol">:development</span> <span class="token keyword">do</span> gem <span class="token string">'capistrano'</span> gem <span class="token string">'capistrano-bundler'</span> gem <span class="token string">'capistrano-passenger'</span><span class="token punctuation">,</span> <span class="token string">'>= 0.1.1'</span> <span class="token comment"># Remove the following if your app does not use Rails</span> gem <span class="token string">'capistrano-rails'</span> <span class="token comment"># Remove the following if your server does not use RVM</span> gem <span class="token string">'capistrano-rvm'</span> <span class="token keyword">end</span> |
Sau đó hãy chạy 2 câu lệnh:
1 2 3 4 | bundle <span class="token function">install</span> bundle <span class="token function">exec</span> cap <span class="token function">install</span> |
Chỉnh sửa Capfile:
Capfile
là nơi đầu tiên Capistrano đọc đến, trong file này sẽ define recipes nào bạn muốn sử dụng.
Khi mới mở Capfile, nó sẽ trông như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <span class="token comment"># Load DSL and set up stages</span> <span class="token keyword">require</span> <span class="token string">'capistrano/setup'</span> <span class="token comment"># Include default deployment tasks</span> <span class="token keyword">require</span> <span class="token string">'capistrano/deploy'</span> <span class="token comment"># Include tasks from other gems included in your Gemfile</span> <span class="token comment">#</span> <span class="token comment"># For documentation on these, see for example:</span> <span class="token comment">#</span> <span class="token comment"># https://github.com/capistrano/rvm</span> <span class="token comment"># https://github.com/capistrano/rbenv</span> <span class="token comment"># https://github.com/capistrano/chruby</span> <span class="token comment"># https://github.com/capistrano/bundler</span> <span class="token comment"># https://github.com/capistrano/rails</span> <span class="token comment"># https://github.com/capistrano/passenger</span> <span class="token comment">#</span> <span class="token comment"># require 'capistrano/rvm'</span> <span class="token comment"># require 'capistrano/rbenv'</span> <span class="token comment"># require 'capistrano/chruby'</span> <span class="token comment"># require 'capistrano/bundler'</span> <span class="token comment"># require 'capistrano/rails/assets'</span> <span class="token comment"># require 'capistrano/rails/migrations'</span> <span class="token comment"># require 'capistrano/passenger'</span> <span class="token comment"># Load custom tasks from `lib/capistrano/tasks' if you have any defined</span> <span class="token builtin">Dir</span><span class="token punctuation">.</span><span class="token function">glob</span><span class="token punctuation">(</span><span class="token string">'lib/capistrano/tasks/*.rake'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword">each</span> <span class="token punctuation">{</span> <span class="token operator">|</span>r<span class="token operator">|</span> import r <span class="token punctuation">}</span> |
Có thể thấy rằng những recipes hay dùng đã được comment sẵn lại ở đây, khi ta cần sử dụng recipe nào, chỉ cần uncomment là xong.
Chỉnh sửa file config/deploy.rb
Khi mới mở ra, file này trông như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | <span class="token comment"># config valid only for current version of Capistrano</span> lock <span class="token string">'3.3.5'</span> set <span class="token symbol">:application</span><span class="token punctuation">,</span> <span class="token string">'my_app_name'</span> set <span class="token symbol">:repo_url</span><span class="token punctuation">,</span> <span class="token string">'<a href="/cdn-cgi/l/email-protection" class="__cf_email__">[email protected]</a>:me/my_repo.git'</span> <span class="token comment"># Default branch is :master</span> <span class="token comment"># ask :branch, proc { `git rev-parse --abbrev-ref HEAD`.chomp }.call</span> <span class="token comment"># Default deploy_to directory is /var/www/my_app_name</span> <span class="token comment"># set :deploy_to, '/var/www/my_app_name'</span> <span class="token comment"># Default value for :scm is :git</span> <span class="token comment"># set :scm, :git</span> <span class="token comment"># Default value for :format is :pretty</span> <span class="token comment"># set :format, :pretty</span> <span class="token comment"># Default value for :log_level is :debug</span> <span class="token comment"># set :log_level, :debug</span> <span class="token comment"># Default value for :pty is false</span> <span class="token comment"># set :pty, true</span> <span class="token comment"># Default value for :linked_files is []</span> <span class="token comment"># set :linked_files, fetch(:linked_files, []).push('config/database.yml')</span> <span class="token comment"># Default value for linked_dirs is []</span> <span class="token comment"># set :linked_dirs, fetch(:linked_dirs, []).push('bin', 'log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')</span> <span class="token comment"># Default value for default_env is {}</span> <span class="token comment"># set :default_env, { path: "/opt/ruby/bin:$PATH" }</span> <span class="token comment"># Default value for keep_releases is 5</span> <span class="token comment"># set :keep_releases, 5</span> namespace <span class="token symbol">:deploy</span> <span class="token keyword">do</span> after <span class="token symbol">:restart</span><span class="token punctuation">,</span> <span class="token symbol">:clear_cache</span> <span class="token keyword">do</span> on <span class="token function">roles</span><span class="token punctuation">(</span><span class="token symbol">:web</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">in</span><span class="token punctuation">:</span> <span class="token symbol">:groups</span><span class="token punctuation">,</span> limit<span class="token punctuation">:</span> <span class="token number">3</span><span class="token punctuation">,</span> wait<span class="token punctuation">:</span> <span class="token number">10</span> <span class="token keyword">do</span> <span class="token comment"># Here we can do anything such as:</span> <span class="token comment"># within release_path do</span> <span class="token comment"># execute :rake, 'cache:clear'</span> <span class="token comment"># end</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
- Đầu tiên mọi người sẽ để ý đến trong file có rất nhiều câu lệnh bắt đầu bằng
set
,set
ở đây chính là các configuration values của Capistrano, dùng để điều chỉnh các giá trị cài đặt cho Capistrano. Có thể thấy trong file mặc định đã có rất nhiều ví dụ mẫu, giải thích ý nghĩa của dòng lệnh, các bạn có thể thử uncomment xem hiệu quả của các dòngset
đó ra sao, hoặc có thể tìm hiểu thêm ở đây. - Điều thứ 2 chúng ta cần để ý tới đó là
namespace
block ở trong file này. Chúng ta có thể sử dụng block này để tạo thêm các câu lệnh cần xử lý trong quá trình deploy. Đối với các app đơn giản thì chỉ cần sử dụng theo các recipe đã đề cập ở trên, nhưng đối với các app phức tạp thì chuyện chúng ta phải tự mình thêm các câu lệnh là chuyện hoàn toàn có thể xảy ra.
Khi ứng dụng được deploy, nó sẽ phải chạy qua cácsteps
đã được định nghĩa ở recipecapistrano/deploy
, cácsteps
đó như sau:
- Clone code về release directory
- Cài đặt linked file và directory
- Chạy các lệnh như bundle install, db migrate,…
- Chuyển đổi
current
symlink - Khởi động lại server
Như trong ví dụ tồn tại sẵn trong file, nó có nghĩa là chúng ta sẽ tạo thêm 1 task với tên gọi :clear_cache
sau khi ứng dụng chạy bước restart
(bước số 5). Trong block này ta có thể viết gì tùy muốn, nó cũng giống như các hàm callback ta hay viết trong model vậy.
Chỉnh sửa file config/deploy/production.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | <span class="token comment"># Simple Role Syntax</span> <span class="token comment"># ==================</span> <span class="token comment"># Supports bulk-adding hosts to roles, the primary server in each group</span> <span class="token comment"># is considered to be the first unless any hosts have the primary</span> <span class="token comment"># property set. Don't declare `role :all`, it's a meta role.</span> role <span class="token symbol">:app</span><span class="token punctuation">,</span> <span class="token string">%w{<a href="/cdn-cgi/l/email-protection" class="__cf_email__">[email protected]</a>}</span> role <span class="token symbol">:web</span><span class="token punctuation">,</span> <span class="token string">%w{<a href="/cdn-cgi/l/email-protection" class="__cf_email__">[email protected]</a>}</span> role <span class="token symbol">:db</span><span class="token punctuation">,</span> <span class="token string">%w{<a href="/cdn-cgi/l/email-protection" class="__cf_email__">[email protected]</a>}</span> <span class="token comment"># Extended Server Syntax</span> <span class="token comment"># ======================</span> <span class="token comment"># This can be used to drop a more detailed server definition into the</span> <span class="token comment"># server list. The second argument is a, or duck-types, Hash and is</span> <span class="token comment"># used to set extended properties on the server.</span> server <span class="token string">'example.com'</span><span class="token punctuation">,</span> user<span class="token punctuation">:</span> <span class="token string">'deploy'</span><span class="token punctuation">,</span> roles<span class="token punctuation">:</span> <span class="token string">%w{web app}</span><span class="token punctuation">,</span> my_property<span class="token punctuation">:</span> <span class="token symbol">:my_value</span> <span class="token comment"># Custom SSH Options</span> <span class="token comment"># ==================</span> <span class="token comment"># You may pass any option but keep in mind that net/ssh understands a</span> <span class="token comment"># limited set of options, consult[net/ssh documentation](http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start).</span> <span class="token comment">#</span> <span class="token comment"># Global options</span> <span class="token comment"># --------------</span> <span class="token comment"># set :ssh_options, {</span> <span class="token comment"># keys: %w(/home/rlisowski/.ssh/id_rsa),</span> <span class="token comment"># forward_agent: false,</span> <span class="token comment"># auth_methods: %w(password)</span> <span class="token comment"># }</span> <span class="token comment">#</span> <span class="token comment"># And/or per server (overrides global)</span> <span class="token comment"># ------------------------------------</span> <span class="token comment"># server 'example.com',</span> <span class="token comment"># user: 'user_name',</span> <span class="token comment"># roles: %w{web app},</span> <span class="token comment"># ssh_options: {</span> <span class="token comment"># user: 'user_name', # overrides user setting above</span> <span class="token comment"># keys: %w(/home/user_name/.ssh/id_rsa),</span> <span class="token comment"># forward_agent: false,</span> <span class="token comment"># auth_methods: %w(publickey password)</span> <span class="token comment"># # password: 'please use keys'</span> <span class="token comment"># }</span> |
File này được sử dụng để chỉ ra các server mà Capistrano sẽ chạy command để deploy lên. Như các bạn có thể thấy trong file, có 2 cách chính để chỉ định các server cũng như cách đăng nhập vào các server đó.
- Đầu tiên là sử dụng 1 global config cho toàn bộ các server. Điều này có nghĩa là các server của chúng ta đều sử dụng chung 1 mã rsa để đăng nhập thông qua ssh từ máy local.
- Cách thức thứ 2 là nếu như có server nào sử dụng 1 mã rsa khác với những server còn lại, thì ta có thể định nghĩa riêng cho server đó.
Tài liệu tham khảo
https://www.phusionpassenger.com/library/deploy/standalone/automating_app_updates/ruby/