Minor text updates
[gitorious:gitorious-book.git] / gitorious-org-setup.org
1 * The setup on gitorious.org
2   As you probably know, gitorious.org runs the exact same version of
3   the Gitorious mainline repository as distributed on
4   gitorious.org. The setup we're running on those servers may be a bit
5   more complex than what you need to setup yourself, but in case
6   you're curious or plan to operate a Gitorious site with hundreds of
7   thousands of users, this chapter is for you.
8
9 ** Deployment
10    We use [[https://github.com/capistrano/capistrano/wiki][Capistrano]] to deploy to the gitorious.org servers. We keep
11    our Capfile and deploy.rb in a separate Git repository, and deploy
12    from that repository.
13
14    The configuration files for the gitorious.org servers
15    (database.yml, gitorious.yml etc) are kept in this repository and
16    pushed (via Capistrano's =upload= task) to the =app/shared=
17    directory on the server after the code is updated; which in turn
18    are symlinked into =app/current/config=. The rest of the deployment
19    process is fairly standard; but we have added Capistrano tasks for
20    starting/stopping/re-indexing Thinking Sphinx, starting/stopping
21    Resque workers etc.
22
23    Our Sphinx tasks look like this:
24 #+BEGIN_SRC ruby
25   namespace :sphinx do
26     desc "Configure Thinking Sphinx"
27     task :configure, :roles => :app do
28       run "sudo #{current_path}/bin/rake ts:configure"
29     end
30
31     desc "Update Index"
32     task :update_index, :roles => :app do
33       run "sudo #{current_path}/bin/rake ts:reindex"
34     end
35
36     desc "Stop Sphinx"
37     task :stop, :roles => :app do
38       run "sudo #{current_path}/bin/rake ts:stop"
39     end
40
41     desc "Start Sphinx"
42     task :start, :roles => :app do
43       run "sudo #{current_path}/bin/rake ts:start"
44     end
45
46     desc "Restart Sphinx"
47     task :restart, :roles => :app do
48       run "sudo #{current_path}/bin/rake ts:restart"
49     end
50   end
51
52 #+END_SRC
53
54    While the Capistrano recipes for controlling Resque are quite
55    minimal (they simply call Upstart's =status= command, since our
56    Resque workers are managed by Upstart:
57
58 #+BEGIN_SRC ruby
59   namespace :resque do
60     desc "Restart the resque workers"
61     task :restart, :roles => :app do
62       run "sudo /sbin/restart gitorious/resque-worker"
63     end
64
65     desc "Status of resque workers"
66     task :status, :roles => :app do
67       run "/sbin/status gitorious/resque-worker"
68     end
69   end
70 #+END_SRC
71
72    The =restart= task we use is a bit special. Since we use Unicorn
73    (more on that later), we don't do the =touch tmp/restart.txt=
74    maneuver, and we want to reindex the search index after
75    deploying. In our previous setup we ran the indexing from
76    Capistrano, which caused some really long-running deployments with
77    a lot of output. Our current =restart= task emits an Upstart event
78    which triggers a =post-deploy= Upstart task to run on the
79    server. The last action performed is to send a USR2 to the Unicorn
80    master, which results in reloading the server. When the
81    =post-deploy= process has ended on the server, a deployment report
82    is sent by email to us with a result of what happened (still work
83    is still not 100% done).
84
85 ** Web servers
86    Our web/app server setup looks like this:
87
88 *** Varnish
89     We run [[https://www.varnish-cache.org/][Varnish]] for caching on gitorious.org. Varnish is basically
90     a zero-config setup, and will do wonderful things to the
91     responsiveness of your app provided you take care of two things:
92
93     - Any request with a =Set-Cookie= response header will not be cached
94       by Varnish
95     - As long as the =Cache-Control= response header is set to public,
96       Varnish will cache the request for as long as specified by the
97       =max-age= parameter.
98
99     Varnish is set up to handle port 80 (HTTP) on our servers, and is
100     set up with a single backend: the private Nginx server mentioned
101     below. This means that Varnish will cache as much as it can of any
102     requests on port 80 of gitorious.org.
103
104 *** Nginx
105     We run [[http://nginx.org/][Nginx]] on port 443, since Varnish doesn't run SSL. The
106     server running on gitorious.org:443 will serve any static files
107     directly from =Rails.root= on the server, and proxy any other
108     request to the public Varnish server on port 80. By sending these
109     requests through Varnish, we get to use the same cache for HTTP
110     and HTTPS.
111
112     Nginx is also set up to listen on a private port, where it
113     receives requests from (only) Varnish. Like the HTTPS Nginx
114     server, this will deliver any static assets directly, and pass all
115     other requests over a UNIX socket to Unicorn.
116
117     Nginx is also set up to deliver sending of other files,
118     intercepting the =X-Accel-Redirect= response headers emitted by
119     Gitorious; equivalent of Apache mod_x_sendfile's =X-Sendfile=
120     headers. To enable this, we have =frontend_server:nginx= in the
121     =gitorious.yml= file on gitorious.org, and the configuration in
122     Nginx looks like this:
123
124 #+BEGIN_SRC conf
125   # Will deliver /srv/gitorious/tarballs-cache/filename.tar.gz
126   location /tarballs/ {
127     internal;
128     alias /srv/gitorious/tarballs-cache/;
129   }
130
131   location /git-http/ {
132     internal;
133     alias /srv/gitorious/repositories/;
134   }
135 #+END_SRC
136
137     If a user requests
138     https://gitorious.org/gitorious/mainline/archive-tarball/master
139     Gitorious will (once the tarball has been generated) respond with
140     an =X-Accel-Redirect= header like
141     =/tarballs/gitorious-mainline-$sha1.tar.gz= (=$sha1= is which SHA1
142     the master branch points to at request time), which is picked up
143     by Nginx by the first rule above. Nginx will resolve this to the
144     file =/srv/gitorious/tarballs-cache/$sha1.tar.gz= and deliver this
145     file directly.
146
147     The =/tarballs/= locations are marked as private in Nginx, which
148     means a user isn't allowed to request them directly. Using Apache
149     with =mod_x_sendfile= the =X-Sendfile= header would contain the
150     full path to the repository, while Nginx lets us maintain a
151     symbolic mapping resolved by Nginx itself.
152
153     The same mechanism is used for Git over HTTP.
154
155 *** Unicorn
156     [[http://unicorn.bogomips.org/][Unicorn]] is a Ruby based HTTP server leaning heavily on fundamental
157     UNIX concepts. Unicorn works by starting a master process which
158     loads the full Rails environment. Once this is done, it will run
159     fork(2) to create 16 child processes (this is how many workers we
160     have running on gitorious.org). These child processes will inherit
161     the socket set up by the master process, which means the kernel
162     will take care of load balancing the requests between the active
163     worker processes.
164
165     Unicorn is designed for chaotic situations, like the one we have
166     on gitorious.org. An IO intensive application like Gitorious will
167     run into problematic situations caused by things like IO load all
168     the time, and our previous setup (Apache and Passenger) would end
169     up with some really CPU and memory hungry processes running for a
170     long time. Our Unicorn setup has a strict timeout of 30 seconds
171     for any request, which means that any request that takes more than
172     30 seconds to complete will cause the worker process to be
173     killed. And once the worker is killed, the master will immediately
174     fork again, with the new child process ready to serve requests
175     right away.
176
177     Like the good UNIX citizen Unicorn is, the easiest way to
178     communicate with it is using signals. We use the following signals
179     on gitorious.org:
180     - We send a USR2 to the master process after deploying a new
181       version of Gitorious. This causes the master process to spawn a
182       new master process; using the newly deployed code. Once the new
183       master is started, it looks for a PID file for the "old" master
184       process in =pid_dir/unicorn.pid.oldbin=. If this file exists, it
185       sends a QUIT signal to that, which causes it do shut down itself
186       and all its worker processes. This gives us a zero downtime
187       deployment, which is a big deal for us.
188     - We send a USR1 to the master process after rotating the logs
189       (done by =logrotate=). This causes the master and worker
190       processes to reopen the log files.
191
192     The Unicorn configuration file we use on gitorious.org is
193     practically identical to the one [[https://gitorious.org/gitorious/mainline/blobs/master/config/unicorn.sample.rb][in Gitorious mainline]], except we
194     use a full path in =RAILS_ROOT= since expanding a relative path
195     would resolve to Capistrano's =app/releases= directory.
196
197 ** Message queue and consumers
198    gitorious.org has been using [[http://activemq.apache.org/][Apache ActiveMQ]] since 2009, and we
199    have not had a single problem with using that. No messages dropped,
200    no crashes, no problems at all. The [[http://code.google.com/p/activemessaging/wiki/ActiveMessaging][ActiveMessaging]] Rails plugin
201    we've been running with, however, has never worked really
202    well. Some considerable memory leaks forced us to use [[http://mmonit.com/monit/][Monit]] to kill
203    =script/poller= processes consuming more than a few hundred
204    megabytes of RAM, and killing these processes has often led to
205    zombie processes on the server; potentially even zombies still
206    connected to ActiveMQ.
207
208    When setting up the new servers for gitorious.org we chose to go
209    with [[https://github.com/defunkt/resque][Resque]] instead, which has been supported in Gitorious for a
210    year or so. Resque uses the [[http://redis.io/][Redis key-value store]] as its
211    queue. Resque works similarly to Unicorn by setting up a master
212    worker polling for new messages from Redis and forking a child
213    process to process each message. Once the child is done processing
214    it exits, which means we don't leak memory.
215
216    Switching to Redis/Resque is done in a few simple steps:
217
218 *** Install Redis
219     On Ubuntu/Debian servers:
220 #+BEGIN_EXAMPLE
221 sudo apt-get install redis-server
222 update-rc.d redis-server defaults
223 sudo service start redis-server
224 #+END_EXAMPLE
225
226     On RHEL/CentOS-like systems:
227 #+BEGIN_EXAMPLE
228 sudo yum install redis
229 sudo chkconfig redis on
230 sudo /etc/init.d/redis start
231 #+END_EXAMPLE
232
233 *** Configure Gitorious to use Resque
234     This is a simple setting in gitorious.yml:
235 #+BEGIN_SRC yaml
236 messaging_adapter: resque
237 #+END_SRC
238
239 *** Restart the app server
240     This depends on which server you're running. If you're using Passenger:
241 #+BEGIN_EXAMPLE
242 touch tmp/restart.txt
243 #+END_EXAMPLE
244
245     If you're using Unicorn
246
247 #+BEGIN_EXAMPLE
248 kill -USR2 /path/to/unicorn.pid
249 #+END_EXAMPLE
250
251 *** Start a worker
252     The =bin/rake= script shipping with Gitorious will run a rake task
253     from anywhere, setting up the correct =RAILS_ENV=, =HOME=
254     environment variables and ensuring the task is run as the user
255     specified as =gitorious_user= in =gitorious.yml=, and Resque
256     workers are run with Rake:
257
258 #+BEGIN_EXAMPLE
259 QUEUE=* /path/to/gitorious/bin/rake resque:work
260 #+END_EXAMPLE
261
262     To run dedicated workers for single queues, change the =QUEUE=
263     environment variable, eg.
264
265 #+BEGIN_EXAMPLE
266 QUEUE="/queue/GitoriousPush" /path/to/gitorious/bin/rake resque:work
267 #+END_EXAMPLE
268
269     Since the =bin/rake= task can be called directly, we simply added
270     an Upstart script with an =exec= stanza (no shell required) to
271     control the Resque workers:
272
273 #+BEGIN_SRC conf
274 description "Run a Resque worker on all queues"
275 author "Marius MÃ¥rnes Mathiesen <marius@gitorious.com>"
276
277 start on started gitorious/unicorn
278 stop on runlevel [06]
279
280 env QUEUE=*
281 env PIDFILE=/path/to/gitorious/pids/resque-worker1.pid
282 exec /path/to/gitorious/bin/rake resque:work
283
284 #+END_SRC
285
286 ** Init scripts and process babysitting
287    We're still a little on the fence with regards to
288    babysitting/monitoring processes. Our experience with
289    ActiveMessaging has made us set up Monit, but we're not using it
290    yet. We start all the services using some really simple Upstart
291    scripts. This was the main motivation for shipping the =bin/=
292    scripts with Gitorious, since these set up everything themselves we
293    don't need to spawn a shell to start them (eg. to set up
294    environment variables, dropping privileges etc.). Spawning a shell
295    would confuse Upstart, which relies on counting fork calls and
296    keeping track of PID files.
297
298    In particular, the way Unicorn is used for hot deployment would
299    lead Upstart to try to track the PID of the old master once a new
300    master was started. Instructing Upstart to =respawn= Unicorn would
301    get us into trouble when using the =USR2= technique to reload
302    Unicorn.
303
304    Monit keeps track of PID files, which would work better with
305    Unicorn.
306
307 ** Git proxying
308    We run a stack of native git daemon processes listening on port
309    9400 on the servers, and have set up Gitorious' git-proxy script to
310    proxy requests to these (this proxy will translate the incoming
311    paths to the paths on the file system before passing them on to the
312    native git daemons). The git-proxy process listens on 127.0.0.1:9418.
313
314    We've set up [[http://haproxy.1wt.eu/][HAProxy]] in front of the git-proxy process, listening
315    on the public interfaces (gitorious.org:9418, ssh.gitorious.org:443
316    and 2a02:c0:1014::1:9418). Running haproxy in front of these may
317    not be strictly necessary, but we found it easier to set up the
318    public facing addresses/ports to listen to in the HAProxy
319    configuration; and we're a little more comfortable running HAProxy
320    to the public as it gives us fine-grained control over
321    server/client timeouts.
322
323    Again, we used Upstart to start the git:// protocol handlers, since
324    Upstart lets us specify the dependency between them. Our
325    git-daemons Upstart recipe is set up like this:
326
327 #+BEGIN_SRC conf
328 start on started gitorious/unicorn
329 #+END_SRC
330
331    which means it's started once the Unicorn process is running. The
332    Upstart recipe for our git-proxy, which requires the git-daemons to
333    be running, is like this.
334
335 #+BEGIN_SRC conf
336 start on started gitorious/git-daemons
337 stop on stopped gitorious/git-daemons
338 #+END_SRC
339
340    This way the native git daemons will be started as soon as the web
341    app is ready, and the git proxy will be started once the git
342    daemons are ready.