Version 16.1 by David Avendasora on 2010/11/30 05:51

Show last authors
1 Capistrano is a deployment system written on Ruby. Actually you don't have to know ruby if you want to use Capistrano - you'll still be able to implement your basic tasks. But if you want to gain real power and control on your deployment scenarios, some knowledge or ruby will greatly help.
2
3 = How to install Capistrano =
4
5 Here is the official Capistrano installation instructon page: http:~/~/www.capify.org/install. On Leopard all you need to do is to run the following command with root privileges:
6
7 {{noformat}}
8
9 gem install -y capistrano
10
11 {{/noformat}}
12
13 On 10.4 you'll have to install ruby and rubygems prior to running this command. The simplest way to do this is to use binary installers.
14
15 = Must-read article about Capistrano =
16
17 Basics of using Capistrano are well described on its official site: http:~/~/www.capify.org/getting-started/basics
18
19 = Writing simple deployment recipe =
20
21 Before we write simple deployment recipe, I'd like to highlight 3 things about Capistrano (they are always highlighted in most number of articles about Capistrano, but still...):
22
23 * Capistrano reads /etc/capistrano.conf before any (well, almost any) execution. You can use this file to define your roles, for example - in order not to duplicate hostnames across multiple files.
24 * Capistrano executes all ssh-commands in parallel on several deployment servers. In order to make your deployment recipes simple you better have exactly equal deployment configurations (folder structure, applications versions and so on) on each deployment server.
25 * Capistrano tasks can be used for anything - not only for deployment. They can run arbitrary ssh commands or, instead, work locally without them.
26
27 Ok, back to the deployment recipe. By saying deployment we usually mean "copying already built application or framework to the deployment server". So let's assume that we have an example WO application, called (surprisingly) BugTracker. The build recipes usually have ".cap" extension. Let's create file BugTracker.cap and put it into the BugTracker folder. As we are going to write deployment recipe, we have nothing to do with build process. So let's assume that ant was executed and build product (BugTracker.woa) was placed into the BugTracker/build folder.
28
29 So let's start our recipe file:
30
31 {{code}}
32
33 task :deploy, roles => :app do
34 end
35
36 {{/code}}
37
38 This is the empty definition of task "deploy" that will run on application servers (see, roles :app). We need to define the :app role in order to make the recipe usable:
39
40 {{code}}
41
42 role :app, "localhost"
43 task :deploy, roles => :app do
44 end
45
46 {{/code}}
47
48 Ok - this is not much, but at least something. Capistrano recipes are executed using "cap" command. So now you should be able to execute the following:
49
50 {{noformat}}
51
52 cap -f BugTracker.cap deploy
53
54 {{/noformat}}
55
56 The output should be:
57
58 {{noformat}}
59
60 * executing `deploy'
61
62 {{/noformat}}
63
64 Now let's fill our recipe with some real code. Basically the simplest (not usable in production, but good as example) way of deploying is:
65
66 1. Pack the BugTracker.woa into tar.gz archive.
67 1. Copy the archive to the remote server.
68 1. Remove previous deployment on the remote server.
69 1. Unpack the archive.
70
71 Let's implement the :deploy task to do this:
72
73 == 1. Pack the BugTracker.woa into tar.gz archive. ==
74
75 This step should be done locally - we don't need to use Capistrano's main feature of executing ssh-commands in parallel on multiple servers.
76 So the code will be:
77
78 {{code}}
79
80 system "tar -C build -czvf BugTracker.woa.tar.gz BugTracker.woa"
81 raise "failed to create an archive" unless $?.exitstatus == 0
82
83 {{/code}}
84
85 "system" call is one of the standard ruby ways of executing shell commands. The second line checks that exit status of the previous command is 0 (which means success for shell commands). The second line also shows 2 things about ruby:
86
87 * The postfix style of conditional operators - it's a kind of syntactic sugar that makes code more readable.
88 * The $? variable which contains the status of the last executed shell command. You can find out more about this variable from Ruby documentation by executing the following command: "ri Process::Status" ($? contains the object of class Process::Status).
89
90 == 2. Copy the archive to the remote server. ==
91
92 Here's how it can be done:
93
94 {{code}}
95
96 upload "BugTracker.woa.tar.gz", "/tmp/BugTracker.woa.tar.gz"
97
98 {{/code}}
99
100 This will copy BugTracker.woa.tar.gz to all servers specified for :app role (because our :deploy task is associated with this role). Capistrano will ask you for username and password to do the copying. It's quite inconvenient to enter username and password each time - so you probably should setup SSH public key authentication.
101
102 == 3. Remove previous deployment on the remote server. ==
103
104 This will look like this:
105
106 {{code}}
107
108 run "rm -rf /Library/WebObjects/Applications/BugTracker.woa"
109
110 {{/code}}
111
112 This will remove the /Library/WebObjects/Applications/BugTracker.woa folder on all servers specified for :app role. Notice that we've used "run" instead of "system". There is a great difference between these commands. "system" executes commands locally whereas "run" executes them on all remote servers specified for the current role. Another thing to notice the --f flag used for rm command. Capistrano will throw an exception and exit immediately if one of the commands executed on remote servers fail. Without --f flag rm will fail when there's no "/Library/WebObjects/Applications/BugTracker.woa" folder. This can happen during the first deployment, for example.
113
114 == 4. Unpack the archive. ==
115
116 Nothing new in this code:
117
118 {{code}}
119
120 run "tar -C /Library/WebObjects/Applications -xzvf /tmp/BugTracker.woa.tar.gz"
121
122 {{/code}}
123
124 So, right now we have the following deployment script:
125
126 {{code}}
127
128 role :app, "localhost"
129 task :deploy, roles => :app do
130 # creating BugTracker.woa.tar.gz
131 system "tar -C build -czvf BugTracker.woa.tar.gz BugTracker.woa"
132 raise "failed to create an archive" unless $?.exitstatus == 0
133
134 # copy the BugTracker.woa.tar.gz to remote server
135 upload "BugTracker.woa.tar.gz", "/tmp/BugTracker.woa.tar.gz"
136
137 # remove previous /Library/WebObjects/Applications/BugTracker.woa
138 run "rm -rf /Library/WebObjects/Applications/BugTracker.woa"
139
140 # unpack /tmp/BugTracker.woa.tar.gz to /Library/WebObjects/Applications"
141 run "tar -C /Library/WebObjects/Applications -xzvf /tmp/BugTracker.woa.tar.gz"
142 end
143
144 {{/code}}
145
146 You can change "localhost" to any hostname you want, or even specify several hostnames separated with commas. This script is the simplest one, but it already does plenty of stuff - and can be used in some simple scenarios. Let's complicate things a bit.
147
148 == Cleanup ==
149
150 Let's write a cleanup task in order not to leave tar.gz archives both on our local machine and on remote servers. The task can look like this:
151
152 {{code}}
153
154 task :cleanup, roles => :app do
155 FileUtils.rm_f "BugTracker.woa.tar.gz"
156
157 run "rm -f /tmp/BugTracker.woa.tar.gz"
158 end
159
160 {{/code}}
161
162 The new part here is FileUtils.rm//f call. This is the way to delete files in ruby.
163 Now we can check that :cleanup task actually works by executing the following command~://
164
165 {{noformat}}
166
167 cap -f BugTracker.cap cleanup
168
169 {{/noformat}}
170
171 It's great to have a cleanup task, but it would be even better if it would run after the deployment. Capistrano has a "hooks" feature that will help us with that:
172
173 {{code}}
174
175 after :deploy, :cleanup
176
177 {{/code}}
178
179 Yes, that's all. Now if :deploy tasks finishes successfully :cleanup task will be executed automatically.
180
181 == Using variables ==
182
183 You can use variable in capistrano scripts. You can set then with the "set" command:
184
185 {{noformat}}
186
187 set <variable name>, <variable value> - this commands says for itself. Some examples:
188
189 {{/noformat}}
190
191 {{code}}
192
193 set "var1", "some data"
194 set :var2, 10
195
196 {{/code}}
197
198 Note also that you can use the identifiers starting with ":" as variable names. This is the ruby way of specifying unique identifiers (called symbols in ruby). Using symbols is a bit faster than using strings, besides you can easily see identifiers in your code, as they won't be quoted - and will not look like string literals. Anyway these calls are absolutely equal:
199
200 {{code}}
201
202 set :var1, "some data"
203 set "var1", "some data"
204
205 {{/code}}
206
207 After the variable is set, you can use it in string literals using in the traditional ruby way:
208
209 {{code}}
210
211 run "echo #{var1}"
212 run "echo #{var2}"
213
214 {{/code}}
215
216 So let's generalize our script with some variables usage:
217
218 {{code}}
219
220 role :app, "localhost"
221
222 set :app_name, "BugTracker.woa"
223 set :archive_name, "#{app_name}.tar.gz"
224 set :tmp_archive_path, "/tmp/#{archive_name}"
225 set :wo_apps_path, "/Library/WebObjects/Applications"
226 set :app_path, "#{wo_apps_path}/#{app_name}"
227
228 task :deploy, roles => :app do
229 # creating BugTracker.woa.tar.gz
230 system "tar -C build -czvf #{archive_name} #{app_name}"
231 raise "failed to create an archive" unless $?.exitstatus == 0
232
233 # copy the BugTracker.woa.tar.gz to remote server
234 upload archive_name, tmp_archive_path
235
236 # remove previous /Library/WebObjects/Applications/BugTracker.woa
237 run "rm -rf #{app_path}"
238
239 # unpack /tmp/BugTracker.woa.tar.gz to /Library/WebObjects/Applications"
240 run "tar -C #{wo_apps_path} -xzvf #{tmp_archive_path}"
241 end
242
243 task :cleanup, roles => :app do
244 FileUtils.rm_f archive_name
245
246 run "rm -f #{tmp_archive_path}"
247 end
248 after :deploy, :cleanup
249
250 {{/code}}
251
252 Note that in //upload// and //FileUtils.rm//f// calls variable names are used without any additional symbols - that's because they're not the part of any string literal - so they're used as simple ruby variables (well actually things are much more complicated - but at least they look like simple ruby variables).//
253
254 == Moving global definitions to /etc/capistrano.conf ==
255
256 Capistrano processes /etc/capistrano.conf file before processing any recipe. If you use several recipes for multiple projects that are hosted on the same deployment server, you will still have to specify :app role in every recipe. To avoid such duplication you can move the role definition to /etc/capistrano.conf. Also some general variable definitions can be moved there. In our case it's the :wo//apps//path variable.
257
258 = Conclusion =
259
260 Actually, with this brief overview of Capistrano features, you'll be able to write quite complicated deployment recipes. But it won't come as a surprise if I say that Capistrano can do a lot more. You can embed capistrano scripts into the ruby code, define multiple deployment configurations in single capistrano file, process output from servers and more and more... I'll write about these topics as soon as possible.