13 May 2013

Trying Continuous Delivery - Part 1

Please note: I am making this stuff up as I go along, so I may have to update each post at a later stage to reflect some changes that I think might make things easier to manage.

In a previous post I introduced this series looking at continuous delivery from a practical standpoint, taking a simple web application and automating its lifecycle. This post outlines the initial setup of the continuous delivery system. These are the steps I will follow:

  1. Install VirtualBox, Subversion and Tomcat on my laptop
  2. Prepare Ubuntu Server virtual machine with Subversion and Tomcat
  3. Set up SVN repository and user access list on my laptop
  4. SVN checkin Tomcat settings from virtual server
  5. SVN checkout Tomcat settings onto my laptop
  6. SVN checkin modified Tomcat settings from my laptop
  7. SVN update Tomcat settings on the virtual server
  8. Restart Tomcat on the virtual server and verify changes
Installing VirtualBox and Subversion on my Ubuntu laptop is straightforward with apt-get,
but the Ubuntu Software Centre makes it even easier.


I can then prepare the virtual server by downloading the relevant ISO image and hooking it up to the virtual DVD of a new virtual machine. After installing Ubuntu Server I have a new environment ready to be configured as my integration box.


By default, the virtual machine has a NAT network adapter and later I will configure it to have a host-only connection with a static IP address to my new virtual server, so I can work with it also when I'm not connected to my home network. A host-only connection is also useful to simulate environments that wouldn't normally be connected to the Internet and that's ultimately the kind of adapter that I want to be working with. Before I do that, I need to install/update the relevant tools on the virtual machine: SSH Server, Subversion, Tomcat6, and MySQL, plus a few basic packages to facilitate administration and remote view.

VM$ sudo apt-get update
VM$ sudo apt-get install openssh-server subversion tomcat6 mysql-server X.org zerofree make gcc linux-headers-$(uname -r)

After I got my baseline software on the virtual machine I can create a host-only adapter in VirtualBox (File / Preferences / Network), so I will have two adapters: one NAT adapter (the default) and one host-only adapter.
After all updates are installed, I won't need the NAT adapter any more (at least until the next major package upgrade), so I can either disable it in VirtualBox, or disconnect it (untick the "Cable Connected" check box in the VM settings) without actually removing it altogether. If I ever need to install a new package or perform a major upgrade, I just re-enable the NAT adapter very easily and then disable it right away.


I need my host-only adapter to have a fixed IP address (no DHCP). The reason for using fixed IP addresses is twofold: firstly, because I want my continuous delivery scripts to be simple and deterministic without having to install a name server/DNS just for this system; and secondly, because a production-like infrastructure would most likely use fixed/reserved IP addresses anyway. I don't want to install DNS or any other name resolution service, and I only need to set up just a few virtual machines, so I'll just maintain all hostnames and addresses in /etc/hosts.

127.0.0.1       localhost
192.168.56.1    VM-host      # The VM host
192.168.56.2    UbuntuSRV    # The baseline VM
192.168.56.3    Integration  # The integration VM
192.168.56.4    UAT          # The user acceptance VM
192.168.56.5    PROD         # The production VM

I decide to assign IP address 192.168.56.1/24 to the host-only adapter in VirtualBox, for no particular reason other
than because it's the default address that VirtualBox is using for me. It follows that the IP address on my virtual
server will have to be in the format 192.168.56.xxx in order to communicate with the host (my laptop).
Change the interface configuration on the VM.

VM$ sudo vi /etc/network/interfaces

...
auto eth0
iface eth0 inet static
address 192.168.56.2
netmask 255.255.255.0
gateway 192.168.56.1
...

Restart the network interface on the VM.

VM$ sudo ifdown eth0
VM$ sudo ifup eth0
ssh stop/waiting
ssh start/running, process 1234

Test communication from virtual machine to laptop host.

VM$ ping -c 3 VM-host
PING VM-host (192.168.56.1) 56(84) bytes of data.
64 bytes from 192.168.56.1: icmp_req=1 ttl=64 time=0.828 ms
64 bytes from 192.168.56.1: icmp_req=2 ttl=64 time=1.03 ms
64 bytes from 192.168.56.1: icmp_req=3 ttl=64 time=1.893 ms

--- VM-host ping statistics ---
3 packets trasmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 0.828/0.919/1.037/0.090 ms


laptop$ ping -c 3 UbuntuSRV
PING UbuntuSRV (192.168.56.2) 56(84) bytes of data.
64 bytes from 192.168.56.2: icmp_req=1 ttl=64 time=1.54 ms
64 bytes from 192.168.56.2: icmp_req=2 ttl=64 time=0.760 ms
64 bytes from 192.168.56.2: icmp_req=3 ttl=64 time=0.737 ms

--- UbuntuSRV ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.737/1.012/1.540/0.374 ms

Now we can use the NAT adapter for internect connections, and the host-only adapter for connections to 192.168.56.xxx addresses , so the routing table needs to reflect this separation.

VM$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.56.1    0.0.0.0         UG    100    0        0 eth1
10.0.2.0        0.0.0.0         255.255.255.0   U     0      0        0 eth0
192.168.56.0    0.0.0.0         255.255.255.0   U     0      0        0 eth1

VM$ ping -c 3 www.google.com
PING www.google.com (74.125.237.178) 56(84) bytes of data.

--- www.google.com ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2014ms
It looks like the default route is through the host-only adapter, and no route leads to the Internet. I need to change that, so I'm going to use the VirtualBox NAT adapter 10.0.2.2 as a gateway for the default route. This way I can have a host-only connection and a NAT connection, both working, and with Internet access.
When I want a strict host-only connection, all I have to do is untick the "Cable Connected" box in the VirtualBox settings for my virtual server's NAT adapter.

VM$ sudo route del default
VM$ sudo route add default gw 10.0.2.2 dev eth0
VM$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.0.2.2        0.0.0.0         UG    100    0        0 eth1
10.0.2.0        0.0.0.0         255.255.255.0   U     0      0        0 eth0
192.168.56.0    0.0.0.0         255.255.255.0   U     0      0        0 eth1

VM$ ping -c 3 www.google.com
PING www.google.com (74.125.237.178) 56(84) bytes of data.
64 bytes from 74.125.237.178: icmp_req=1 ttl=64 time=19.9 ms
64 bytes from 74.125.237.178: icmp_req=2 ttl=64 time=10.7 ms
64 bytes from 74.125.237.178: icmp_req=3 ttl=64 time=12.6 ms

VM$ ping -c 3 VM-host
PING VM-host (192.168.56.1) 56(84) bytes of data.
64 bytes from 192.168.56.1: icmp_req=1 ttl=64 time=0.834 ms
64 bytes from 192.168.56.1: icmp_req=2 ttl=64 time=1.11 ms
64 bytes from 192.168.56.1: icmp_req=3 ttl=64 time=1.876 ms
Looks good, so I'll just make this route persistent and move on.

VM$ sudo vi /etc/network/interfaces

...
...
up route add default gw 10.0.2.2 dev eth0
...
...

VM$ sudo service networking restart
So far so good. Let's get Subversion running and test connectivity from the virtual machine. We'll need a local repository first of all, then run the standalone Subversion server listening on the adapter used for the
VirtualBox host-only connection.

laptop$ mkdir -p ~/svnrepo/mywebapp
laptop$ svnadmin create ~/svnrepo/mywebapp
laptop$ svnserve -d -r ~/svnrepo --foreground --log-file ~/svnserve.log --listen-host 192.168.56.1

VM$ svn info svn://VM-host/mywebapp
Path: mywebapp
URL: svn://VM-host/mywebapp
Repository Root: svn://VM-host/mywebapp
Repository UUID: cbcda313-59ce-438c-844d-f6a9db7a4d30
Revision: 0
Node Kind: directory
Last Changed Rev: 0
Last Changed Date: 2013-05-13 12:57:36 +1000 (Mon, 13 May 2013)
It looks like I have a good baseline virtual server that I can use as a template to create my other virtual environment. Let's start with the Integration environment (INT). I only have to clone the baseline VM in VirtualBox using the 'linked clone' option, also making sure I reinitialise the MAC address of all network cards.





What I want to do now is to import the existing Tomcat configuration from the INT virtual machine into Subversion. This way I will be able to control any modifications to these settings from my development laptop. When I check in new versions of the settings, I will shut down the relevant services on the virtual machine, update the settings' working copies, and restart the services in order to pick up the new settings. In fact, I can even manage these changes from Eclipse, together with the application source code. It is easy to create an empty folder structure in Eclipse on my laptop host and then commit it to Subversion. I can then checkout the relevant folders in my virtual machine, svn add the relevant configuration, and check it all in.

INT$ svn import /etc/tomcat6 svn://VM-host/mywebapp/trunk/envsetup/INT/tomcat6 -m "Initial import for tomcat6 config"
INT$ sudo svn co svn://VM-host/mywebapp/trunk/envsetup/INT/tomcat6 /etc/tomcat6 --force

I need to sudo these commands because these directories are usually owned by root, and adding them to Subversion is an operation that needs write access because it will automatically generate some synchronisation metadata files and folders in the working copy directory. Back on the laptop host, I now need to update the Eclipse project and I will be able to manage configuration changes from there.
To test that this arrangement actually works, I can use Eclipse to change the Tomcat HTTP port on the guest virtual machine. I'll change it back afterwards.
[server.xml (snippet)]
...
    <!-- changed from port 8080 to port 8081 -->
    <Connector port="8081" protocol="HTTP/1.1"
               connectionTimeout="20000"
               URIEncoding="UTF-8"
               redirectPort="8443" />
...


INT$ sudo /etc/init.d/tomcat6 stop
 * Stopping Tomcat servlet engine tomcat6 [OK]

INT$ sudo svn update /etc/tomcat6
U   /etc/tomcat6/server.xml
Updated to revision 6

INT$ sudo /etc/init.d/tomcat6 start
 * Starting Tomcat servlet engine tomcat6 [OK]

Now pointing my browser from the laptop host to the guest address http://Integration:8081 shows the Tomcat welcome page.


This means that now I can successfully drive any Tomcat configuration changes from my development environment.
Also, it means that I can safely revert to any previous revision of the Tomcat configuration if something goes wrong.
In fact, I'll just test that now.

[in my workspace folder]
laptop$ svn merge -r COMMITTED:PREV .
--- Reverse-merging r16 into '.':
U    envsetup/INT/tomcat6/server.xml

laptop$ svn commit -m "Reverting previous commit"
--- Reverse-merging r16 into '.':
U    envsetup/INT/tomcat6/server.xml

INT$ sudo svn update /etc/tomcat6
U    /etc/tomcat6/server.xml
Updated to revision 17.

INT$ sudo /etc/init.d/tomcat6 restart
 * Stopping Tomcat servlet engine tomcat6 [OK]
 * Starting Tomcat servlet engine tomcat6 [OK]
Now pointing my browser from the laptop host to the guest address http://Integration:8081 spits out a browser connection error, but http://Integration:8080 shows the Tomcat welcome page as expected.
To recap, this is what I have so far:
  • a physical host (my laptop) with Tomcat, Eclipse, and VirtualBox
  • a Subversion server running on the physical host, using a local directory as a repository
  • a virtual server (my baseline) with Tomcat, Subversion and MySQL, connected with the physical host using a host-only adapter
  • a virtual server (my integration server) cloned from my baseline, with its Tomcat configuration in the Subversion repository

and this is what I can do with the current setup:

  • change the Tomcat configuration of the virtual server from my development IDE on the physical host
  • easily pick up the latest Tomcat configuration changes on the virtual server
  • easily revert to any previous revision of the Tomcat configurations

Next post will introduce the element of automation in configuration changes.

No comments: