RailsConf 2016
Packaging and Shipping Rails Applications in Docker

Packaging and Shipping Rails Applications in Docker

by Allan Espinosa

In his presentation at RailsConf 2016, Allan Espinosa discusses strategies for packaging and shipping Ruby on Rails applications using Docker. He emphasizes the need for optimizations and performance tuning to enhance both the deployment process and application performance. Espinosa, drawing from his extensive operations engineering background, highlights the importance of continuous feedback from production to refine application performance.

Key discussion points include:

- Optimization Techniques: Espinosa argues that optimizing Rails applications involves improving overall customer experience through better response times, efficient middleware, and fine-tuning server settings alongside optimizing SQL queries.

- Effective Logging and Instrumentation: He stresses the significance of integrating logging mechanisms and application metrics to gauge user interactions, which informs scaling decisions based on real user data.

- Streamlined Deployment Process: The speaker points out that slow deployments can negate optimization efforts. He suggests streamlining the deployment pipeline, leveraging Docker to minimize deployment times, and using build caching effectively.

- Managing Dependencies: Espinosa emphasizes that developers must manage external dependencies, advocating for a strategy that involves vendoring where possible and configuring proxy services for faster local access to dependencies.

- Building Docker Images: He explains how to structure a Dockerfile for Rails applications, promoting practices that lead to quicker builds by organizing files in a way that maximizes cache efficiency.

- Testing and Quality Assurance: He advises running thorough tests on Docker images before deployment to ensure they are production-ready.

- Graceful Degradation: The importance of implementing strategies for applications to maintain functionality during failures is highlighted, promoting resilience in the face of issues.

- Importance of Infrastructure: Espinosa concludes by advising that a reliable infrastructure is foundational, noting that understanding the underlying principles of the technologies used helps developers adapt over time.

Overall, the presentation teaches that while Docker simplifies packaging and shipping applications, optimizing the performance of Rails applications and refining the deployment process remain essential for maintaining effective operational workflows.

00:00:10.040 Hello everyone, let's start the presentation. Thank you for coming.
00:00:15.360 Welcome to this engineered sponsored talk. I'm Allan Espinosa, and I’m a Support Engineer at Engineered. Today, I'll be discussing various aspects of deploying Rails applications with Docker.
00:00:23.269 My background is primarily in operations engineering. For the past few years, I've mostly worked with Ruby in my day-to-day work. I used Ruby back in the day when it was more popular for building systems before Go became prevalent. However, Ruby still remains my go-to language.
00:00:39.780 Though I’m not strictly a Rails developer, my last experience with Rails was six or eight years ago. I do a lot of Ruby development outside of web applications, and I'm also the author of the 'Docker High-Performance' book from Packt Publishing.
00:01:07.650 Given that Docker changes frequently, much of the book's content may become obsolete in the coming months. While writing the book, I attempted to identify concepts that would endure through Docker updates, and I’ll share some of those in this session.
00:01:28.200 Specifically, I'll be addressing optimization techniques for Docker images and tailoring it for Rails deployment. It's essential to consider how to optimize the rollout of Rails applications in Docker.
00:01:49.829 When we talk about optimization, many of us think primarily about achieving faster response times or ensuring that our controllers respond quickly. While asynchronous workers help with speed, it’s important to remember that performance ultimately comes down to improving the overall customer experience.
00:02:41.380 By tracing the value stream from our customers, we can begin refactoring controllers and business logic based on feedback from direct production traffic.
00:03:07.080 Additionally, we can optimize the middleware and fine-tune our server settings, such as configuring Unicorn workers or Puma threads, to better utilize memory allocation.
00:03:42.760 Likewise, optimizing SQL queries can lead to faster database responses. All these tuning techniques should not be performed in isolation; rather, they should be informed by instrumentation data gathered from your application and infrastructure.
00:04:12.580 Integrating effective logging mechanisms and application metrics helps gauge user interactions. Even tools like Google Analytics can provide valuable insights that can be correlated with system metrics, subsequently informing scaling decisions.
00:04:24.940 By scaling the application to meet customer demand and enhancing the overall architecture with caching and increased capacity, it becomes crucial to understand when it's appropriate to spin up new instances of your Rails application.
00:04:51.390 Establishing a well-defined operational strategy is vital for optimizing deployment techniques. Therefore, while refining your application for improved performance is essential, it’s equally important to ensure that your deployment processes are efficient.
00:05:15.199 A slow deployment process can render any optimization efforts moot by the time they are implemented. Thus, in this talk, I'll focus more on expediting the software delivery process to production.
00:05:45.110 Despite concentrating on the delivery aspect, the underlying concepts I discuss will be applicable across various paradigms. Many early adopters have embraced the hype surrounding containers, and we are now starting to refine our practices.
00:06:17.210 Ultimately, while it's beneficial to package applications in Docker containers, we must always focus on the application's inherent value. Docker is merely a tool that enhances how we deliver our products.
00:06:49.030 Earlier, I spoke to several attendees at our booth at Engineered about their usage of Docker. Many users shared that they were just beginning their journey with Docker, often facing resistance when trying to implement it in production environments.
00:07:12.470 As an operations engineer, I can understand the hesitancy surrounding changes to the technology stack. However, knowing what changing to a container-based architecture means could help alleviate these concerns. Ultimately, it all comes down to how effectively you can deliver your application.
00:07:34.360 When discussing application delivery, we typically consider the deployment phase, but it’s essential to recognize the build phase as well. It's common to perceive the build phase as compiling code into binaries, such as those created with C or Go.
00:08:14.690 This perspective might seem counterintuitive for Rails and Ruby developers since Ruby is interpreted. However, in Rails, there is an equivalent to binaries, which includes everything necessary for the application to run in a production environment.
00:08:36.590 Understanding what components will be deployed is critical. For instance, when I am paged at 3:00 a.m., I need to know where to look for issues. In Rails applications, your gem packages play a crucial role in defining these components.
00:09:09.380 When you perform a 'gem install' in production, it's important to remember that there may be native bindings and shared object files involved. Essentially, the code in your Rails app forms a part of the binary alongside dependencies resulting from your 'gem install' or 'bundle install' commands.
00:09:40.480 Furthermore, Rails assets also comprise components of what it takes to run your application. Consequently, Docker provides an effective way to bundle these elements together.
00:09:58.360 The notion of a Docker image encapsulates everything required for your application.
00:10:00.550 During the Docker build process, you specify in the Dockerfile how the image will be constructed. For those just entering this field, I'll outline a basic Dockerfile for defining your Rails image. You typically start from an official Ruby image, such as Ruby 2.2.
00:10:42.900 Next, include your application's current directory and all files it requires, execute a 'bundle install' to resolve dependencies, and compile any necessary assets. Lastly, define how to run your application, which in a production setting should use Unicorn, Passenger, or Puma instead of WEBrick.
00:11:04.400 For example, when you run the Docker build command while naming your image, you can observe the process of compiling the Docker image. Initially, the Docker build command adds necessary directories as well as dependencies by executing 'bundle install'.
00:11:47.770 If you encounter native bindings during this process, the build could take a bit longer due to compilation. The initial build could take about a minute and a half to complete, but a crucial feature within Docker is the build cache.
00:12:56.120 When you run the same build without changing any code, Docker utilizes caching, allowing the process to finish in just one second. If adjustments are made to the application’s code, however, the build takes longer to finish as Docker creates new image layers.
00:13:15.590 This may not be a concern early on, but as your teams grow or if significant refactoring takes place, running 'bundle install' frequently can become a dragging task.
00:13:48.070 One effective solution is to optimize your Docker builds by organizing your application into separate sections based on how often they need updates. For example, you can separate your Gemfile from other application files to exploit caching more effectively.
00:14:15.200 After all, the strategy should be similar to dividing your unit and integration tests. Unit tests are generally quicker, while integration tests might require additional resources.
00:14:57.720 The initial build might take the same time, around a minute and a half, but if subsequent changes are limited to the application code, the build can complete as if nothing had happened, significantly improving efficiency.
00:15:31.080 The main takeaway when it comes to building your Rails application is to be as prompt as possible in receiving feedback regarding the readiness of the artifacts or binaries for deployment.
00:16:09.950 After constructing the Docker image, it's important to run it through a series of tests, such as unit tests and integration tests, in your delivery pipeline before marking it as good to go.
00:16:32.990 Upon confirming that the image is suitable for production, the next stage involves deployment. I've observed a phenomenon where many teams associate compiling with deploying, which can lead to underestimating how long deployments might take.
00:17:07.750 Customers have reached out for support, expressing frustration over deployment durations of around 30 minutes. If responses are slow during this process, the feedback loop is delayed, preventing teams from understanding whether their changes benefit users.
00:17:27.360 In my talk, I will outline several factors that can lead to deployment delays and suggest ways to improve efficiency, particularly as an operations engineer.
00:17:43.950 I particularly dislike manual deployment processes where one would log into the server, pull the latest code, and run 'bundle install.' While some might argue for parallel execution across the fleet, issues arise when large changes are introduced.
00:18:07.600 Implementing a full redeployment aspect limits how much can be updated at once, thereby hindering the process. In contrast, deploying Docker images simplifies the task.
00:18:33.050 By executing a 'docker pull' command, one simply downloads the latest image from the repository. The deployment workflow becomes more straightforward, where the focus narrows to the speed of downloading from the Docker registry.
00:19:05.360 It’s important to note that in this scenario, relying on other sources such as RubyGems can be slower compared to Docker Hub.
00:19:29.960 Furthermore, while many packages make our applications function, we ultimately hold responsibility for the availability of our services. An insightful website that became famous in the tech community is WhoOwnsMyAvailability.com. It provides random articles about availability and reliability.
00:20:04.520 This site succinctly captures the essence of being accountable for your application's uptime and emphasizes the importance of understanding various dependencies.
00:20:39.500 For instance, customers rely on our services to maintain uptime, and we in turn depend on external services to provide our functionality. If we utilize RubyGems or depend on proprietary Debian mirrors, we expose ourselves to potential inconsistencies.
00:21:13.610 Integrating Docker introduces another layer of dependency by relying on Docker Hub for images. This situation raises concern among operations teams as it can complicate deployment processes that might already be fragile.
00:21:43.840 To mitigate this, it's beneficial to vendor dependencies whenever feasible, ensuring that our deployment process remains resilient regardless of any external service failures.
00:22:06.910 Incorporating proxies into your environment may also aid significantly. Developers often dislike configuring proxy settings, which creates friction.
00:22:34.490 Recently at the conference, Jamie detailed the importance of understanding operational challenges and empathizing with different teams.
00:22:50.989 In my personal setup, I have multiple proxy servers running to streamline development. This includes a mirror for my application, one for Docker registry, and another for RubyGems, allowing for efficient software development without the constant need to download images.
00:23:34.620 By utilizing proxy services like Nexus or Artifactory for Docker images, I can alleviate dependency on external sources. As a result, I can more easily manage installations, thereby speeding up development as opposed to repeatedly pulling external resources.
00:24:32.140 Such provisions apply equally to databases. If your primary database is down, your Rails application should ideally maintain some level of functionality by reading from a secondary, perhaps read-only, source.
00:25:05.200 Thus, graceful degradation is crucial. Measures should be in place to allow your application to provide service in a reduced capacity during failures.
00:25:34.010 In conclusion, while Rails and containers clarify much of the complexity away, having a good and reliable infrastructure is fundamental. Building applications upon a shaky foundation will lead to issues. Although Ruby and Rails inspired and attracted many into the field, understanding the underlying principles of what we use can prepare us to adapt when changes occur, especially during deployments.
00:26:48.030 Developing the ability to troubleshoot effectively during crises is essential. Knowing when problems arise and how to resolve them will ultimately lead to more operable, reliable application environments.
00:27:25.180 By understanding these concepts, we can focus on serving the actual needs of our users without compromise. Thank you for listening to my talk, and if you have any questions, please feel free to ask.
00:27:50.880 Also, if you visit our Engineered booth, we have limited copies of my Docker book available. I would love to chat about your experiences using Docker or encouraging management to adopt it.