TL;DR: Click for the example service unit configuration file.
I love Gerrit for code review at $work (Not my current work mind - personal opinion and all). GitHub and GitLab are great tools (I’m sure I’ve missed others). The pull request (PR) workflow is more suited to contributors with varying backgrounds, but Gerrit’s workflow feels faster. I think this is because a PR can be multiple commits, whereas in Gerrit a changeset is roughly equivalent to a commit (once merged), plus the changeset contains all revisions as patchsets. Alice reviews a patchset, Bob amends the commit, pushes it and Alice can see both patchsets, and crucially the diff of both patchsets also. I’m not saying other tools don’t do this, but Gerrit worked for our team.
Deployment however is a bit of a hassle. Oh, and the interface is ugly.
Note: This is for Gerrit v1, not v2 which was recently released.
Short intro
Gerrit is a Java program. I’ll be comparing it to Go CD,
because I’m messing about deploying that at the moment, but a fairer comparison would
probably be Jenkins.
As far as Java programs go, it’s pretty light weight, certainly compared to Go CD or Eclipse. Although more users will quickly eat into resources such as file descriptors - to be expected!
Go CD has packages for different platforms (and yum
/apt
repositories), and
installs itself in fairly standard locations, plus the documentation is top notch.
Gerrit comes as a .war
file, so you’ll have to check the documentation to get
it deployed. Gerrit’s documentation
isn’t great, and whatever you do, don’t click the Wiki
link. Very confusing.
Deploying Gerrit
Turns out you have to “initialise a site” via:
java -jar gerrit.war init -d "<site>"
This creates a directory structure similar to the Filesystem Hierarchy Standard
under <site>
, including bin
, cache
, data
, etc
, lib
, logs
, and
tmp
. Not what I want as a sysadmin. FYI, the PID file lives in logs
.
To be fair, some of this is tweak-able, e.g. the location of git repositories
can be configured via gerrit.basePath
. However, you have to do this before
running init
, otherwise the default repositories All-Projects.git
and
All-Users.git
aren’t created. Also, make sure the folder has the correct
permissions - I’ve had instances where Gerrit hasn’t set up the default projects
and shown no errors. Re-running init
will not create these default
repositories if the site already exists.
Also keep in mind that you need to run the reindex
operation every time you
modify the Gerrit config file - and for this, the Gerrit instance can’t be
running.
Webserver woes
Other gotchas include that the built-in Java webserver Jetty is hard to set up for SSL. In this day and age that should be unacceptable, although luckily the solution is also straight-forward: Run Gerrit behind Nginx. At that point, I’d rather have Nginx do the authentication - if you know how to do this, please get in touch.
From init.d script to systemd service unit
The script to manage Gerrit is a) huge, b) hack-y, and c) an init.d script. I
wanted a systemd script, because it’s usually simpler and even though systemd
does support init.d scripts, bad things can happen if you interact via the
script itself. With gerrit, this is particularly bad, as the script doesn’t
live in /etc/init.d/
, but in <site>/bin/gerrit.sh
.
This gerrit.sh
script actually reads from /etc/default/gerritcodereview
,
environmental variables, and the gerrit config file, and debugging this is a
pain.
Instead, the systemd service unit configuration file (service file) lets us set many of these options ourself, and makes it much easier to support multiple sites (although why you would do this on the same machine/VM/container is beyond me, as Gerrit has fully featured auth and permissions). The options systemd inherits or replaces are:
- Java executable path (JAVA_HOME)
- Gerrit site path (GERRIT_SITE)
- Gerrit PID file (GERRIT_PID)
- Java command line options (container.javaOptions, container.heapLimit)
- Gerrit user to run as (container.user)
- Gerrit
.war
path (container.war) - Gerrit slave setting (container.slave)
- Gerrit daemon setting (container.daemonOpt)
i.e. these are set in the service unit config, and not in the Gerrit config with this approach (unlike the init.d script which actually reads the config file). I think that’s all of them.
The gerrit.sh
script also has a section called “Configure sane ulimits for
a daemon our size”, which as a sysadmin makes me worried. This isn’t a good
thing to happen automatically. But again, systemd makes it easy to set the
ulimits
for Gerrit if you wish to do so.
In the end, the systemd service unit configuration file is about 20-30 lines, instead of 565 for a hack-y script. Granted, the service file is less… general? But well worth it for consistency.
The service unit configuration, in all its glory:
[Unit]
Description=Web based code review and project management for Git based projects
After=syslog.target network.target remote-fs.target
[Service]
Type=simple
User={{ gerrit_user }}
ExecStart=/usr/bin/java -DGerritCodeReview=1 -jar {{ gerrit_home }}/bin/gerrit.war daemon -d {{ gerrit_home }}
ExecStop=/bin/kill -s SIGINT $MAINPID
# stupid java exit codes
SuccessExitStatus=130 SIGINT
SuccessExitStatus=143 SIGTERM
# default gerrit ulimits set by the init.d script... your choice
LimitCORE=0
LimitDATA=unlimited
LimitFSIZE=unlimited
LimitRSS=unlimited
# NOFILE : GERRIT_FDS, determined by "core.packedGitOpenFiles" in the script
LimitNOFILE=1024
LimitCPU=unlimited
LimitAS=unlimited
LimitLOCKS=unlimited
[Install]
WantedBy=multi-user.target