Continuous X in a pinch
I had the great opportunity to speak at NCDevCon recently about how we implemented Continuous Delivery at Dude Solutions. Last year, we published a post about our process and toolset. Recently, we’ve made a lot of progress at the Dude in improving our #ContinuousX process, we’ve even added a bunch of DevOps engineers (#JoinTheDude). I had a little bit of a different challenge for NCDevCon though. How do you take the entire continuous integration and delivery system and demo it to a room full of people from a single laptop?
We touted the fact that we separated the build and deployment roles in our tools, which is great for our team, not so great for a single laptop demo environment. Imagine spinning up eight or more virtual machines just to get the job done! Needless to say I wanted to take a stab at making it as simple as possible. I’ll try to run you through what I did…
VMs, Vagrant, Docker, Cloud Foundry, oh my!
I had fiddled around with Vagrant a bit during one of our hackathons to stand up an active directory Windows server. It worked well at the time. However, here at the Dude we’re having serious discussions around using Docker and Cloud Foundry. I wanted to try and get as much of the environment running in Docker as possible. Cloud Foundry was doable but can be a pain to run locally and Docker images for our tools are mostly readily available.
First I needed to find some good Docker images to use for the project. To recap, the tools we use internally are:
Atlassian’s Bitbucket Server
Jetbrains’ Teamcity Server
Octopus Deploy
With Teamcity and Octopus I also fully expected to need additional servers to handle builds as well as deployment targets. Here’s where the first hurdle appeared. Bitbucket and Teamcity are both Java/Tomcat based apps with vendor-published Docker images. Octopus is currently a Windows only tool. The deployment targets (tentacles) are capable of running Windows or Linux but the core server needs Windows. I also discovered that a core Teamcity plugin we use to handle communications with Octopus also only ran on a Windows box.
Alright, so I couldn’t just get by with Docker Linux images. I checked out some of Microsoft’s Server 2016 documentation on using Docker on Windows but honestly did not have the time to try it out. I ended up deciding to use Docker on OS X for all Linux apps and Parallels Desktop to host my Windows VMs.
Build All The Thingz
To start, I grabbed the latest install of Docker for OS X. The install was straight-forward and the CLI tools were added to my local PATH. A simple check of the Docker version from terminal and things were all set.
Setting up Parallels was even more straight forward. I grabbed a Windows 2012 R2 ISO from my Dude provided MSDN subscription and started spinning up VMs. It’s also good to note that it’s very important to make sure you set reasonable CPU and RAM limits on your VMs. Parallels by default allocates your entire machine to each VM. I was able to get by with 1 CPU and 1 GB of RAM assigned to each VM. Bad things happened to my Mac when I didn’t do this (see Cmd+Option+Esc).
So I spun up all of the needed machines. I ended up with a few. You could change or scale this out as needed to support whatever test lab you needed or better yet, what your machine could run.
Ok, but Docker?
Docker was the easy part. Each of our tools has a vendor-published image. This means that whenever Atlassian or Jetbrains publish a new version of the product a new Docker image shows up around the same time. They even provide you with the basic commands to get started. I ran into a problem with networking at first. Docker creates a single virtual network (a.k.a. default0) that all of your Docker images run on. By default, this is an isolated environment where containers cannot interact. This doesn’t work well in a CI/CD environment where every tool is talking to another.
I created a new bridge network that allowed communication between the instances. Moreover, since Parallels also creates its own subnet the parallels machines could talk to Docker containers and vice versa. Pretty sweet! Setting up the bridge network was as easy as running this command:
docker network create --driver bridge ncdevcon
Then I started running docker run commands to begin standing up containers. Both Atlassian and Jetbrains recommend mounting volumes from the host to store persistent storage elements like configs, etc.
Bitbucket
docker run --name=bitbucket \ -itd \ --network=ncdevcon \ -v /<your datadir>:/var/atlassian/application-data/bitbucket \ -p 8080:7990 \ -p 8081:7999 \ atlassian/bitbucket-server
Teamcity
docker run --name=teamcity \ -itd \ --network=ncdevcon \ -v /<data dir>:/data/teamcity_server/datadir \ -v /<log dir>:/opt/teamcity/logs \ -p 8085:8111 \ jetbrains/teamcity-server
Teamcity Linux Agent
docker run --name=teamcity-agent-2 \ -itd \ --network=ncdevcon \ -v /<data dir>:/data/teamcity_agent/conf \ -e SERVER_URL="http://teamcity:8111" \ -e AGENT_NAME="NCDevCon-Agent02" \ brentpabst/teamcity-agent
Wait, that last Docker Image is from you!
Yep, it is! My sample application for the demo was a .NET Core web app (grab the source here). We’re also starting to toy around with moving to .NET Core so I wanted to base the sample app on it. I ran into an issue however where the Docker image Jetbrains currently uses for the Teamcity Agent is based on Ubuntu 15.x. .NET Core currently only supports Ubuntu 14.x or 16.x. I spun up the Jetbrains image first and then ran a distro-upgrade on the machine to get it to Ubuntu 16. Once I did that and installed .NET Core on the container I packaged the image and shipped it to the Docker Hub for others to use. If you don’t need .NET Core on your agent, just grab jetbrains/teamcity-agent!
I should also point out at this time the reference to other containers in some of the Docker run commands. Specifically, the SERVER_URL parameter passed to the Teamcity Agent. Docker is able to self-resolve other containers in the same network. Essentially the DNS server exposed to the containers resolves other containers in the same network, hence the custom bridge network I created first. However, the port number is the port number the service runs on and not the port number that is exposed back to me, the user, running at the OS X layer. A little meta for sure!
The End Result
Between Parallels and Docker, I was able to get enough containers/VMs running to give the demo. There is always a fair amount of work that goes into configuring each tool, let alone getting the tools to talk to each other. The cool thing with the Docker containers and the mounted folders is that the configuration can be copied and cloned to other containers or saved for later, regardless of how many times you rebuild the containers. Parallels allows for some snapshotting but it’s not perfect. Hopefully Octopus will head down the .NET Core path and have their own Docker image in the future so a few more steps could be removed from this process.
Missed the Talk?
Check the NCDevCon.com site for updates and eventually the video recording of the session.
You can also check out the sample app and grab the slide deck from https://github.com/brentpabst/ncdevcon16
Got a #ContinuousX environment running on my local machine for NCDevCon... find out how!










