The arrival of Docker container technology brought with it an amazing array of capabilities. By encapsulating an entire software package, including its dependencies and libraries, into a single, portable container, Docker has made deployment across platforms a simple and straightforward process.
But emerging practices in containerization let you break the process down even further into microservices, which divvy an application into collections of shared, virtualized services. Though microservice architecture is powerful and infinitely scaleable, it comes with new considerations and concerns about managing dependencies, security, and application resiliency. Below is a look at some common obstacles you’ll encounter when managing Docker containers and how to overcome them.
Design Considerations for Container Environments
The key to a manageable environment is clean design. When building a new environment that utilizes Docker containers, keep these basic design approaches in mind.
Containers should be lightweight.
The technology behind Docker tech isn’t new; essentially containers are virtual machine images similar to those used in all virtual computing. But the value of Docker containers comes from their tiny footprint, which can be a small fraction of that of a traditional VM.
To keep your architecture simple, limit compute processes to one per container. Combining processes within containers often defeats the purpose of microservice design and quickly leads to complications in troubleshooting the application, reviewing and managing logs, and streamlining your continuous delivery and deployment pipeline.
Similarly, containers should not be used to retain data. Containers are made to start, stop, and even disappear. Shared repositories work better for storage because they provide a specific and persistent location to house data accessed by multiple sources, and can store different data formats. While containers should remain as small and agile as possible, repositories can store databases, monitoring logs, and more in the same persistent home.
Containers should be fast
A great advantage of container tech is speed. Rather than waiting minutes for all the processes and commands of a VM to spin up, containers can launch as quickly as a single process. Linking and associating containers that pool together to perform complex compute processes greatly accelerates delivery. Read more below about optimizing container performance.
Containers should be disposable
Unlike repositories, containers were built to be disposable. Some containers exist for just milliseconds, executing a single task and then blinking out of existence as quickly as they appeared. Where traditional architecture focused on finding permanent homes for applications to live and run, container tech brings an on-demand capability that allows you to automatically create, deploy and destroy processes and avoid design bloat.
Docker Containers and Immutability
Containers are designed to be completely self sufficient, holding all the necessary code, configuration and dependencies to operate in almost any environment. Once the perfect operating state is achieved, don’t change it!
During continuous integration and testing, promote Docker images at each phase in the pipeline. It may seem tempting to create new containers, but by creating a new image you are actually changing the working production recipe, so you can’t be sure that the image that passed all the quality gates is the one that got to production.
By promoting known good images as immutable and stable binaries all the way through the quality gates to production, updated containers can be easily rolled back if problems are encountered. If they aren’t, the older container you’re replacing can be destroyed, lowering overhead.
Another key tip: beware the Docker COMMIT command, which snapshots an image of a running container. This can be useful for troubleshooting and analysis, but it does not completely reproduce the source image, leaving behind any data stored within the container. Dockerfiles, which are completely reproducible, are a much preferred approach.
Similarly, the LATEST tag can be problematic. Once an image in production receives the latest tag it cannot be rolled back. The tag also creates dependency issues, as the parent layer is replaced by a new version that is not backward compatible. The latest tag should also be avoided when deploying containers in production as you can’t track what version of the image is running.
These guidelines for immutable containers will speed up deployment, reduce operational overhead, and make troubleshooting and rollbacks a straightforward process.
Securing Your Containers
Modern Devops integrates security at every level of development and deployment, so containers should be treated no differently. Here are tips for securing your containers.
- Don’t run containers with root level access. Use the -u tag at the beginning of every container, which will default access to user, rather than administrator.
- Don’t store credentials within the container. Instead use environmental variables to manage credentials so a breach in one container won’t snowball.
- Check and manage runtime privilege and Linux capabilities. The default setting for new containers is unprivileged, which means they will not be able to access any other devices. For containers that require collaboration apply the -privileged tag, though this will allow access to all devices.
- Use security tools. Tools that scan images for known security vulnerabilities can greatly increase security. Third party applications can help add visibility into your containers and manage security through easy GUI.
- Consider private registries. Docker Hub makes available a vast array of free and shared registry options. But many companies aren’t comfortable with with the security arrangement this represents and choose to host their own registries, or turn to on-premise artifact repository services like JFrog Artifactory. Evaluate your security needs with your team and decide if public registries will work for you.
Security is a constant in a forward-facing DevOps environment, so make certain your containers are as safe as the rest of your infrastructure. Consider employing additional security measures for your Docker environment to ensure it is fully protected.
Optimizing Your Docker Environment
As mentioned above, containers should be fast. If not managed properly they will bloat, bogging down your environment and reducing the capabilities they were designed to deliver. A few ways to constrain CPU and memory allocations and optimize your environment.
- CPU Share Constraint. Dictate the percentage of processor time a container is granted with this command, which allows you to fix the amount of a time one or all available CPUs will devote to the container. This resource offers a good walkthrough for various CPU constraints.
- Block IO bandwidth (Blkio) constraint. Upon creation all containers are assigned the same value (500) for block IO bandwidth (blkio). By modifying the –blkio-weight flag values in individual container values it is possible to change the container’s blkio weight relative to the weighting of all other running containers.
- Constrain Memory Usage. Kernel memory in containers works differently than user memory. While the latter is a flexible allocation that can be automatically swapped to direct performance where needed, kernel memory is a fixed allocation. This means container memory commitments can quickly swell and slow your environment. Correct this by constraining container memory allocation to whatever percentage of maximum available memory you desire. You can find more details here.
Docker makes it easy to optimize these and other container variables so you can fully realize the fast, lightweight potential of container technology.
Container Networking Management
Take masters level control over networking within and between containers by tweaking default settings to better operate in your environment.
By default Docker containers utilize IP addressing to communicate, and each new one is assigned its own number and address. But this can present a problem because containers are ephemeral; they appear, start, stop, and disappear all the time, and the IP address assigned to them can come and go just as quickly. Address this by using these environment variables to pick and choose what containers and ports to expose to internal and external networking:
name_PORTassigns the port a full URL; further define with the following addendums
_num_protocolassigns the correct protocol, usually TCP/IP
_num_protocol_ADDRlets you set the container’s IP address
_num_protocol_PORTdictates the preferred port number to expose to traffic
Docker offers resources for working with the correct command and naming structure to achieve the container environment that meets your unique needs.
Proper Container Management Takes Effort
The true power of container technology lies in its ability to perform complex tasks with minimal resources, which translates to lowered cost of ownership. But properly leveraging all the capabilities of containers requires immersion into the structure and philosophy of the technology behind them. Following the guidelines above while designing a containerization architecture will lay the groundwork for success, however you’ll need to follow through and continually apply container management best practice to truly optimize your Docker environment.