RubyConf 2014

Containerized Ruby Applications with Docker

Containerized Ruby Applications with Docker

by Laura Frank

In the RubyConf 2014 presentation titled "Containerized Ruby Applications with Docker," Laura Frank discusses the integration of Docker technology with Ruby applications. Docker, a lightweight virtualization tool, offers a user-friendly interface for creating isolated execution environments termed containers, which package applications with low overhead. The talk is structured around three core questions: what Docker containers are, how to use Docker with Ruby, and how to architect Ruby applications effectively using Docker.

Key Points Discussed:
- Introduction to Docker: Docker provides an efficient way to package applications utilizing existing components of the Linux kernel, simplifying the development process for Ruby applications. Laura emphasizes the importance of understanding Docker as a packaging tool and its limited scope, notably that it does not handle job scheduling.
- Container vs. Virtual Machine: The efficiency of containers over traditional virtual machines (VM) is illustrated through the sharing of libraries and fast boot times, highlighting significant performance benefits.
- Technical Components of Docker: Laura introduces core components that make Docker operational: libcontainer, namespaces, control groups (cgroups), and the union file system, which together provide a lightweight and effective runtime.
- Docker Ecosystem: The talk explains the Docker Hub as a collaborative platform for sharing Docker images and resources, and how official repositories ease the process for developers.
- Using Ruby with Docker: Instruction on using Docker with Ruby includes understanding Docker images and how to construct a Dockerfile. A live demonstration is given, where Laura builds a simple Ruby application container and runs it, emphasizing the ease and speed compared to a traditional VM setup.
- Best Practices and Architecture: The discussion transitions to Docker’s architecture, which promotes a service-oriented approach. Best practices regarding the use of configurations, like avoiding hardcoded sensitive information in Dockerfiles and using environment variables during runtime, are noted. Application templating tools like Fig and Panamax are recommended for streamlining deployment.
- Conclusion and Recommendations: Laura concludes the session by sharing valuable resources available at Docker Hub and tips on optimizing Docker usage for Ruby applications. She strongly encourages leveraging tools like Boot2Docker for improved development experiences and stresses the importance of using templating for efficient project kick-offs.

The presentation offers a detailed introduction to Docker tailored specifically for Ruby developers, bridging knowledge gaps while showcasing practical applications and effective workflows.

00:00:18.520 My name is Laura Frank. I'm a senior engineer at CenturyLink Labs, which is a small R&D offshoot of CenturyLink. We primarily focus on research and development around Docker and technologies that are kind of tangentially related to Docker, such as Fleet, CoreOS, and Kubernetes.
00:00:25.480 We explore how these technologies serve the development community and work with them, hoping to improve them.
00:00:31.599 You can find me online. I also share a name with a reporter from a Colorado newspaper, but I'm not her.
00:00:37.079 As I mentioned, I work extensively with Docker, and today I'll talk about Docker and Ruby together.
00:00:44.160 Throughout this talk, I aim to answer three large and ambitious questions for you. The first is: What are Docker containers? The second is: How can you use Docker and Ruby together? Finally, how might you architect an application using Docker and Ruby to ensure it works effectively?
00:00:50.760 I assume that most of you have heard of Docker but might know very little about it, and that's perfectly fine.
00:00:56.160 I'll cover a lot of basic introductory content regarding Docker. If you're already a Docker user, hopefully, I'll shed some light on the technical underpinnings of Docker.
00:01:03.680 Has anyone here attended the Docker BoF last night? A few people? Great!
00:01:09.920 I didn't attend, as I was busy enjoying some fish, since I live in the Midwest.
00:01:15.960 So, what is Docker? We've probably heard about it because companies like Google, Spotify, Twitter, and countless others have been discussing how they're using Docker in production.
00:01:21.600 They talk about the fun and complex problems that Docker solves for them.
00:01:27.280 However, it's worth noting that the concept of containerization is not new. LXC, or Linux Containers, has been around since 2008.
00:01:34.240 But Docker gives this a nice, user-friendly front end, making it easier for everyone to use.
00:01:40.960 As the internet becomes more ubiquitous and more people use it, production workloads can fail at a much faster rate than they used to, especially before everyone had smartphones.
00:01:47.799 Docker fulfills a significant need in this context. In essence, Docker is a packaging tool.
00:01:55.560 It sits atop existing components of the Linux kernel, packaging applications into isolated execution environments.
00:02:01.759 Docker has a very limited scope; it does not schedule jobs for you. There are many things it does not do.
00:02:07.200 However, what it does is package your applications into containers.
00:02:13.040 A container is essentially a self-contained execution environment. I'll explain how this is set up shortly.
00:02:19.360 Think of it as something enclosed in a box that's isolated from everything else.
00:02:24.680 Containers share the kernel of the host system while maintaining isolation. This combination results in fast boot times and low overhead.
00:02:30.920 They operate very efficiently, reducing duplicate workloads, which often leads to significant performance benefits.
00:02:36.760 For instance, you will notice a substantial performance difference between a service running in a virtual machine versus one running in a container.
00:02:44.000 This is due to the major differences in operation between these two types of services.
00:02:50.959 Here's an example of a typical virtual machine setup, which many of you might have running right now.
00:02:56.400 In this example, we have two different services: App One and App Two, each with their instances.
00:03:01.519 In a virtual machine environment, each instance includes its libraries and a guest OS, all sitting on a hypervisor above the host OS and hardware.
00:03:09.000 However, in the Docker world, there's no need for a hypervisor. We can eliminate the guest OS in each virtual machine.
00:03:16.440 Instead, we introduce something called the Docker Engine.
00:03:21.879 This engine is the packaging tool on the host system that creates and deploys containers.
00:03:27.400 You'll notice that the libraries, instead of residing inside the container, are now beneath it.
00:03:33.239 This design allows each container to refer back to the same library, eliminating the need for duplicate copies.
00:03:39.040 A relatable analogy is the San Diego Zoo, where koalas are displayed in self-contained environments.
00:03:45.200 Each koala doesn't require its own caregiver; they can share the same food supply. It would be impractical to have a separate veterinarian for each koala.
00:03:51.360 Similarly, you don't want a separate library for each container.
00:03:56.680 Now, how does this work? Containers may seem abstract and possibly difficult to grasp, and it can be.
00:04:02.959 We've spent months working on a tool that sits on top of Docker, and we sometimes want to throw our laptops out the window.
00:04:09.120 However, there are four key components that enable Docker to function effectively and deliver those great performance benefits.
00:04:15.959 The first of these is libcontainer.
00:04:22.560 This is the container format.
00:04:29.000 Several months ago, Docker launched libcontainer, its own equivalent of LXC.
00:04:35.240 Previously, Docker relied on LXC, limiting it to Linux. With libcontainer, there is potential to run Docker anywhere.
00:04:41.800 You might see Docker thinking about running on Windows or natively on a Mac thanks to libcontainer.
00:04:48.680 For today’s examples, we'll focus on libcontainer since it simplifies the process.
00:04:54.080 The next crucial aspect is namespaces, which isolate the container from everything else.
00:05:00.800 Namespaces are about processes, not resources.
00:05:07.840 They include networking and process IDs, keeping processes in their own namespace to prevent conflicts with other containers.
00:05:14.680 On the other side, we have cgroups, or control groups, which enable containers to be good multi-tenant citizens, accessing shared resources when necessary.
00:05:21.560 Lastly, we have the union file system, which significantly increases Docker's speed.
00:05:27.400 The layered file system operates on a copy-on-write principle.
00:05:33.600 When building a Docker image, everything is done sequentially, stacking layers as you go.
00:05:39.040 Think of this process similar to your Git log; if you need to revert or rebuild an image, you can reference a previous state rather than starting from scratch.
00:05:45.000 These technical components make Docker a lightweight runtime and packaging tool that utilizes Linux kernel components.
00:05:50.640 However, Docker is more than just that; it represents a development workflow and an ecosystem.
00:05:55.680 This is why Docker is attractive to developers employed in small teams and at organizations that manage production loads, like Google.
00:06:00.960 Here's what Docker looks like at a high level.
00:06:06.400 On one side, we have the engine that handles the technical components, the code execution, and so forth.
00:06:12.800 On the other side is the Docker Hub, where people collaborate and share elements and resources related to Docker.
00:06:18.320 The cornerstone of this collaborative aspect is the Docker Registry.
00:06:24.120 You can find it at registry.hub.docker.com. This is where Docker images are stored.
00:06:30.080 If you create a Docker image that you'd like to share, you can push it to this public repository.
00:06:36.960 There's also the option to use a private registry for proprietary images.
00:06:43.120 About a third of the way down the page, you will see official repositories.
00:06:48.920 These repositories contain images from companies or communities that are using Docker. They often create an official image to help others avoid unnecessary repeated work.
00:06:55.120 For example, if you want to use MongoDB in your project, you don't have to build your own image; you can use the one created by MongoDB.
00:07:01.680 These official images usually come with extensive documentation to help bootstrap your own projects.
00:07:06.640 To utilize these resources, you must first install Docker.
00:07:13.360 One of my favorite observations about Docker is that it often faces criticism for being overly abstract.
00:07:20.000 For some, the many layers of this abstraction can be overwhelming, creating a gap between the programmer and the code running in a container.
00:07:26.000 I won't lie; overcoming this hurdle when starting with Docker can be challenging.
00:07:32.480 Fortunately, there are many tools available to help bridge this substantial gap.
00:07:39.120 For those on Linux, Docker was built primarily with Linux containers in mind. You can install Docker using official packages and get set up.
00:07:46.720 If you're using other operating systems, you will have to run a VM. I know this may sound contrary to what I've said about VMs being bloated and undesirable.
00:07:54.560 However, running a single VM that can support thousands of containers has minimal impact on performance.
00:08:00.800 Docker also provides a great tool called Boot2Docker, which you'll find in the installation instructions.
00:08:07.520 Boot2Docker is a lightweight tool that will set up a VM in the background, run Docker on it, and sync all your folders.
00:08:13.120 This allows you to interact with Docker from your Mac or Windows machine as if you were on a normal terminal.
00:08:19.360 In fact, I will be using Boot2Docker during this presentation, allowing it to appear as if I'm directly interacting with Docker.
00:08:26.000 When you eventually have Docker installed, whether through Boot2Docker or your own Vagrant file, you'll interact with Docker primarily via the CLI.
00:08:32.880 There is also a REST API for interfacing with Docker, both of which have beautifully written documentation available at docs.docker.com.
00:08:39.120 Now, let’s move to the exciting part: using Ruby with Docker.
00:08:44.640 Specifically, let’s discuss how to use the Docker API, CLI, and an image to construct a running Ruby container for your Ruby applications.
00:08:51.840 Before diving into building containers, we need to familiarize ourselves with what an image is.
00:08:57.839 Think of a Docker image as a class, while a container represents an instance of that class.
00:09:04.480 You cannot start a container without first having an image; otherwise, the container would lack any content.
00:09:10.760 Each Docker image is governed by a Dockerfile, which serves as a list of instructions.
00:09:17.080 We'll examine a Dockerfile shortly, and we will build an image together.
00:09:23.240 Images are built using the command ```docker build -t <name> .``` here, 't' signifies 'tag,' followed by the name you want to give it.
00:09:30.160 A common naming convention is your GitHub username followed by your image name, but you can name it as you wish.
00:09:36.640 The dot at the end signifies the current working directory where the Dockerfile is located.
00:09:44.600 Referring back to the Docker Registry I mentioned, whether you're using a public or private registry, you can pull an image using the format ```docker pull <username>/<imagename>```.
00:09:50.960 This also allows for optional version tagging using a colon.
00:09:57.680 For instance, when working with the Ruby image, here's a sample Dockerfile for a very simple application.
00:10:04.520 This sample is based on Ruby 2.1.2.
00:10:11.440 It executes specific tasks and ultimately culminates in a command to start the container.
00:10:19.160 Let's attempt to build this image now.
00:10:26.720 I'm currently in the directory with all necessary files to run this application, including the Dockerfile we previously reviewed.
00:10:33.760 I'll execute the command ```docker build -t hello-world .```.
00:10:40.000 You can see the layering file structure in action with various memory references being displayed as the process runs.
00:10:48.320 This may take a couple of moments, so while we wait for that...
00:10:54.560 Let’s hope that works!
00:10:59.720 Live coding or live demos can be a bit nerve-wracking for everyone, including the audience, so thank you for your patience.
00:11:05.840 Again, the Dockerfile is very simple. The `FROM` command is mandatory; every image must inherit from a base image.
00:11:13.360 Similar to the class-object analogy I mentioned before, inheritance can be utilized for Docker.
00:11:20.200 Any components the CenturyLink base image has will be included in my image because I reference it.
00:11:26.000 Notice the version identifier '2.1.2' in this instance, which corresponds to Ruby 2.1.2.
00:11:33.440 At CenturyLink, we invest significant effort in optimizing our Ruby images. You can find them on Docker Hub.
00:11:40.640 Next is the 'MAINTAINER' directive, which indicates me. If something breaks, you'll know whom to contact.
00:11:48.080 Following that is the 'EXPOSE' command, which announces that your service is running on a specific port.
00:11:54.080 This informs other containers on the same host about the exposed port.
00:12:00.080 Next up, we have the 'RUN' and 'ADD' commands. A 'RUN' command executes whatever command is provided.
00:12:07.760 In this case, we're creating a new directory named 'app'.
00:12:14.000 The 'ADD' command behaves similar to a copy command; it will copy everything in the current directory into the newly created 'app' directory.
00:12:21.120 After that, we set the 'app' directory as our working directory.
00:12:27.040 Going forward, all commands specified in the Dockerfile will execute in this working directory.
00:12:33.600 So, when we run 'bundle install', it will specifically execute for the Gemfile within that new directory.
00:12:41.200 Finally, there's the 'CMD' directive which specifies the actual operation to execute when the container runs.
00:12:47.440 In this case, we'll run a very simple Ruby script: 'hello_world.rb'.
00:12:53.120 Additionally, setting environment variables is essential.
00:12:59.760 Environment variables use the syntax of uppercase key and lowercase values. They are accessible during subsequent run commands.
00:13:06.480 We can also specify a mount point using the 'VOLUME' command. This allows for sharing code in containers by creating a mounted volume.
00:13:12.640 Now, let’s check on our image to see if it has downloaded successfully.
00:13:18.560 This 'FROM' command proves quite powerful. It's essential in determining the base image for every service.
00:13:25.040 In an ideal world, all services would use compatible Ruby versions.
00:13:30.720 However, you only truly need one base image, which you can reference from other Dockerfiles.
00:13:36.560 The advantage is that if Ruby gets updated, you can simply modify the base image that all others inherit from.
00:13:42.560 This shared infrastructure minimizes maintenance and update efforts across your Docker projects.
00:13:48.960 Okay, great! The build was successful, which is excellent news.
00:13:56.480 Let me clear the screen here so it's easier for those in the back to see.
00:14:02.040 You can view all the images on your host by using the command 'docker images'. I noticed a failed build earlier, which is why there's an odd entry.
00:14:10.000 Observe that my CenturyLink Ruby base image is regarded as a distinct image.
00:14:16.000 The hello-world image will reference everything that our base image provides.
00:14:22.240 If we delete the hello-world image now, I won't do this right now, but if we rebuild it, it eliminates redundant operations.
00:14:29.680 Docker will skip the steps needed to build the already created base image, resulting in efficiency.
00:14:37.040 In constructing a complex application with multiple services sharing a single base image, you save significant time and resources.
00:14:44.360 Now, let’s run the Docker image; it sounds like a good idea.
00:14:52.320 The command 'docker ps' allows you to view all running processes on your host.
00:14:58.680 Currently, there are none running. So let's execute a command to run our new container.
00:15:05.480 We'll include a port binding so that you can view this in your browser. This binding will map port 4567.
00:15:14.360 The final argument to pass is the name of the image, which is hello-world.
00:15:22.360 As you can see, we now have a containerized application running before your eyes!
00:15:30.360 It took mere milliseconds to start; imagine how long it would take in a traditional virtual machine.
00:15:37.760 Consider how often you restart a service while developing; with Docker, you can reduce that time from minutes to milliseconds.
00:15:44.600 Now, this container is running. I can access it through my browser, and I'll shut it down.
00:15:52.000 Let's check on the container status. It's exited, which is normal.
00:15:59.520 Docker provides exit codes for its containers. I can start it again by referencing its identifier or friendly name.
00:16:06.240 As expected, it generates a quirky name, usually a combination of an adjective and a scientist's name.
00:16:12.960 Now, that wraps up the Ruby application demonstration.
00:16:20.640 In this instance, we used the base image created by CenturyLink.
00:16:27.760 Rubу also has many official images available on Docker Hub, ranging from versions 1.9.3 and newer.
00:16:34.960 You can grab these easily with a simple 'docker pull' command. The official images come with excellent documentation.
00:16:41.840 Just as I mentioned earlier, the official Ruby images can be quite large.
00:16:49.280 My team is working diligently to optimize them, and we aspire for our images to become the official Ruby images.
00:16:56.640 You are welcome to choose either option based on your requirements.
00:17:02.960 In some cases, you might need specific Ruby gems for your project.
00:17:09.360 Swipely has done extensive work with Docker and provides several gems, one of which we use is the Docker API gem.
00:17:16.160 This gem enables interaction with the Docker API from within your application.
00:17:23.120 You can find a plethora of other gems on RubyGems.org related to Docker, with varying activity and user bases.
00:17:29.760 Debbugging in a container can indeed be a challenge. Docker faces criticism for its abstractions.
00:17:36.800 In our team, we often use Pry for debugging. We try to run things locally before deploying them in a container.
00:17:44.960 I recommend that if you haven't used Pry, start doing so as it provides a robust debugging session.
00:17:52.480 If you use Pry, you can directly use a command to enter a debug session while the application is running in the container.
00:17:59.440 You can use Docker run commands to drop yourself into a debugging session efficiently.
00:18:05.920 With that said, knowing how to address issues that arise can significantly improve your development workflow.
00:18:13.200 Now, let’s turn to architecture—a topic that often generates lively discussion.
00:18:18.880 Docker architecture is based on service-oriented architecture, which is beneficial.
00:18:25.200 Think about how quickly we got that service running—it took mere milliseconds!
00:18:31.920 When each service resides within its own container, if one fails, it can be spun up rapidly.
00:18:39.120 Scaling individual containers is also much easier than dealing with monolithic ones.
00:18:46.080 Take a look at this illustration: one service, one container, depicting a simple setup with a web framework and a database.
00:18:53.360 You might wonder how container A recognizes the existence of container B since they are isolated.
00:19:00.480 The answer lies in Docker's linking capabilities or through port mapping and environment configurations.
00:19:08.240 This sort of configuration is nothing but simple settings handled in two places: the Dockerfile and during the container's runtime.
00:19:15.440 Let's revisit that dummy application's Dockerfile and analyze best practices regarding configuration.
00:19:22.000 Essentially, it's acceptable to have everything coded in this format, but if I push it to Docker Hub, I don’t want to hardcode bindings.
00:19:29.120 It's crucial that I avoid embedding environment variables in the Dockerfile.
00:19:36.960 If someone unknowingly commits sensitive information, such as passwords, it could be exposed publicly.
00:19:43.440 Instead, set up environment variables during runtime using the Docker run command.
00:19:50.160 In this example, we again use 'docker run' followed by '-p' to declare the port mapping.
00:19:58.440 You can pass environment variables using '-e' and specify key-value pairs without hardcoding them into your Dockerfile.
00:20:05.440 The linking functionality mentioned earlier can also apply here—where one container can view and communicate with another.
00:20:11.600 As you can see, each container is executed through one command, while multiple containers can be instantiated from the same image.
00:20:19.360 You want to balance configurations between the Dockerfile and Docker run command to protect sensitive data.
00:20:27.200 Remember that you have to set configurations every time you run a container; as a lazy engineer, I look for shortcuts.
00:20:34.760 Thankfully, there is a process known as application templating that can simplify this.
00:20:42.080 My team utilizes such templates to streamline the configuration process of our Docker containers.
00:20:49.840 With this approach, after configuring once, you can define an application template containing your requirements.
00:20:56.080 Instead of executing each container separately, you just run the template that encapsulates your configurations.
00:21:03.440 A noteworthy option is Fig. Docker acquired Fig, and it is now part of the official Docker ecosystem.
00:21:10.760 Using Fig, you can organize your application specifications into a YAML file and initiate the configuration effortlessly.
00:21:17.760 If you're seeking a more visual approach, the tool my team created is called Panamax.
00:21:25.000 It’s a Docker workflow tool that may run multiple Ruby services in containers depending on how it's set up.
00:21:31.680 Panamax incorporates various Docker-related technologies, promoting service orchestration and discovery.
00:21:38.000 You may explore Panamax at panamax.io; it's maintained by the Docker community.
00:21:45.360 If you wish to download it, seek panamax.io/get-panamax. It’s an open source project built entirely in Ruby.
00:21:52.000 This is what an application template looks like on Panamax; it consists of a name, description, and support documentation.
00:22:00.640 The template lists images, like Rails, alongside their descriptions.
00:22:06.760 The configuration elements are persistent, so you need to configure them only once.
00:22:12.480 Let's navigate to the Panamax homepage. It’s directly connected to Docker registry.
00:22:19.840 Here, users can find various pre-existing templates, verified for functionality.
00:22:27.760 You can run templates with a simple click, avoiding the need for elaborate command line entries.
00:22:35.040 I’ll run a local instance of a Rails application to demonstrate.
00:22:41.680 This process involves downloading images, which will inevitably take some time.
00:22:48.560 The service should be successfully created soon.
00:22:55.040 Once it is up and running, the equivalent 'docker run' commands are displayed.
00:23:02.960 Using Panamax simplifies the user experience, while still maintaining transparency in configurations.
00:23:09.440 The application templating that Panamax offers is a transformative feature.
00:23:16.600 If you deal with multiple technologies or services in your projects, Panamax is particularly helpful.
00:23:23.680 It's a cakewalk to deploy everything at once instead of configuring each service independently.
00:23:30.080 All in all, we have discussed what Docker is and how to run Ruby applications using it.
00:23:37.040 Today, we explored how to architect applications and utilize templating to save time.
00:23:45.280 As you embark on your container journey, I recommend Boot2Docker; it's incredibly helpful.
00:23:53.200 With Boot2Docker, it won't feel like you're working on a separate VM, enabling a seamless development experience.
00:24:00.960 Extracting commonalities into a base image will yield significant efficiency gains.
00:24:07.600 Be mindful not to include passwords within your Dockerfiles; this keeps your sensitive information secure.
00:24:14.640 Lastly, employ templating as a rapid approach to kick-start your projects.
00:24:23.120 For additional resources and guidance, I highly recommend checking out the Docker Hub to explore existing images.
00:24:30.560 Utilizing available official images can save you considerable time.
00:24:37.920 Docker’s documentation is excellent, so utilize that, along with resources like Boot2Docker.
00:24:45.440 If you're interested in templating, I encourage you to explore tools such as Panamax or Fig.
00:24:53.760 My team regularly produces informative content, including longer tutorials, so check out our blog on CenturyLink's website.
00:25:02.240 Thank you for being an attentive audience today! If anyone has questions, I'd be glad to answer or help you.