Until recently, I’ve always worked with a few people or even alone on different web development projects. Back then, we used SVN (Subversion) as VCS (version control system) which worked great (most of the time) for small projects with not many people working in the same files at once.
Our SVN repository was setup with two different directories, one for released versions (tags/) and one for development version (trunk/). The main problem this created for us was that there were no possible way to develop a function without commiting part of the code. Once we commit code to the trunk-directory, it would be part of the next release.
Our deployment method has at best been a SVN post-commit hook that copys the files from tags (prod) or trunk (dev) to a specific directory, and possibly runs a upgrade script after.
I decided to set up a GIT-repository instead of SVN for version control. For now, my new repo is only local on my machine, but is it possible to share the repository with your team via a lot of different protocols. The one I probably will be using in the future is HTTP or HTTPS, but right now I have SSH set up for the deployment (more of this later).
My GIT-repository is set up so I have two different main-“branches”. One for development called develop. This branch would be equal to my old trunk-folder. Everything in the develop-branch will be in the next release of the system.
One branch called master, which is where all the released versions will be tagged (correctly this time). Once we decide to release a version, the development branch will be merged with the master branch and tagged.
Each new feature that we would like to develop in the system will not actually be developed in the develop-branch, but in a seperate new branch for each feature. That feature will branch of develop-branch when we start working on it, and once it is finished, it will be merged back into develop.
By working in this way, we secure the possibility of developing functions that takes years, but still be able to make releases of the system when we need to.
I wanted a solution for automatically testing my application once I commit something to the repository, and if no bugs were found, I even wanted to deploy this to our test-environment and possibly even the production-environment.
In my search for this kind of solution I stumbled upon a term called “Continuous integration“, which was exactly what I wanted.
The CI system that I found, which was open source and widely used was Jenkins.
I set up Jenkins on a virtual machine running Debian Wheezy (stable) by following the directions at http://pkg.jenkins-ci.org/debian/, which installed without any trouble at all. After this I fired up the web browser and redirected it to the virtual machines IP address, port 8080.
The basic setup of Jenkins can do a lot of nice things, and with the massive plugin support it can do almost everything you can think of.
My first “job” was created so it pulled a specific branch of my new GIT repository (over ssh) and “build” it. The first thing I thought of when I saw the term “build”, was compile, which is not necessary for PHP applications. Later on I came to realize that “build” in this case means a lot of things, including (but not limited to):
- Pull branch from VCS (SVN, GIT, file?)
- Run automatic tests and statistics gathering like: PHPLOC (Lines of code) PHPUnit (Unit testing of classes), PHPMD (Mess detector), PHPCS (Code sniffer, validate your coding standards) and so on.
- Generate documentation (phpdox, Doxygen)
- Push to test (over SCP)
- Run upgrade scripts on test-server (ex. for database changes, restarting daemons and so on)
- Generate log files and e-mails
Actually, in Jenkins you only tell it to first poll the GIT repository (how and when), then you tell it to “Invoke Ant”, which isn’t really something I had any clue what it was. Ant is essentially a glorified task manager. It takes a XML-file that describes what to do, which commands to run and in what order. It also takes the exit codes from each application ran within Ant, to determine if the build was a success or not.
After Ant has run, we get to the deployment phase. Here I actually installed another plugin called “Send build artifacts over SSH” and what this allows me to do, is to send the built application over SCP/SFTP (big surprise, right?). I tell the setup to which SSH server I would like to send my files and then I create a “Transfer Set” containing which source files should we send (in my case, I only choose the actual application, not the documentation or statistics files that PHPLOC, PHPMD and so on created). When specifying the source files, be aware that a asterisk (*) is only “all files”, not including directories. To get all files and all directories, you enter two asterisks (**). For me the “source files” ended up being “trunk/web/**” (since my source files for the web application was in directory trunk/web/, normally you have the application files in /src-directory).
The remote directory is specified as a relative path to what is configured in the SSH server setup (done in general Jenkins setup, not the job it self).
After this you are allowed to enter a “Exec command”, which is really neat. In my case I have a daemon running in a screen for each installation of my application. When the daemon starts, it writes its PID to a file on the system, which I then read when I’m upgrading the application. The first command I run is to kill the old daemon, by using:
kill -9 $(cat /folder/to/my/application/pid)
After the daemon is dead, I then upgrade the database (by a script deployed with application) and then restart the daemon, by using:
screen -S name_of_screen -X screen sh -c "cd /folder/to/my/application/server ; php runner.php"
This loads up the runner into a existing screen but in a new tab, meaning my application has been upgraded and is back online.
I have two different jobs, one for development (develop-branch in GIT) and one for production (master-branch in GIT). For the development job, I only push to one site (test) but for the production job I push to all my production installations (4 at this time), upgrading databases and reloading daemons automatically.
A different (better?) solution would be to have two jobs, one for test and one for production, where the job for production only build the project (including documentation and testing). Once it is done, it will trigger other jobs for the deployment phase it self. Meaning that you may have one job per installation (if one fails, it won’t break the whole setup?).
Another neat thing about Jenkins is that it gives you the ability to track build times, failure or success and it even pulls out the commit messages for each build.
This setup enables me to sit back and relax while Jenkins does all my testing and deployment for me, once I commit something to the “develop” or “master”-branches in GIT.