This is a transcript of a session I gave at EMEA Red Hat Tech Exchange 2017, a gathering of all Red Hat solution architects and consultants across EMEA. It is about considerations and good practices when creating images that will run on OpenShift. The content is structured in a series of four posts:
- Structuring your images
- Making your images consumable
- Cloud readiness
This is the first part looking at common objectives associated with the usage of container images. These objectives are to be taken into consideration during the design phase of your images.
One of the reasons why containers are seeing so much interest is that they allow packaging an application with all its dependencies in a single deployment unit. This deployment unit, the golden image is then moved from one environment to the next one. You may move it from integration to UAT or from staging to production, for instance. Having all dependencies in a single unit guarantees that what has been validated in a previous stage is also what is being deployed in the next one.
Before the rise of containers, I have seen companies struggling with application promotion. Some of them had written long and detailed installation procedures that had to be manually and carefully followed. Others had invested a large amount of effort in building automation based on puppet, chief, or python scripts. Further, then the initial effort it also required a significant maintenance effort to deal with evolution and changes. Most of them were experiencing issues here and there. What containers bring is a standard and simple approach to this. Application dependencies: operating system, runtime environment (JVM and like), libraries and some of the configurations are part of the container image, which gets simply used for starting container instances in one or the other environment.
Subsequent builds of the image should produce the same result. This is important for having a stable reference that can be used for patches, upgrades and further evolution. This requires that dependencies (libraries or other images) used for creating the container image are uniquely referenced and version.
Agile Standard Operating Environment
There are obvious benefits of a level of homogeneity in the enterprise software landscape:
- Time and cost reduction for maintenance by limiting the number of combinations that may need to be supported.
- Having a known environment when operations are required. The operator knows what to expect, which may be critical for resolving incidents in a timely fashion.
- Compliance is easier with a few different environments.
- It is easier to staff. Employees can more easily move from one domain to another as they rely on the same technologies.
This should be taken into consideration when defining the image landscape. That said strictly enforced and slow evolving SOE has sometimes become an obstacle to business reactivity. Through reusability and easiness to have a central change applied to multiple targets, container images can help with addressing this aspect.
It is important to limit the number of places where components get injected into images like frameworks, application servers, drivers, scripts. These may be used for ground functionality, connectivity, monitoring, asset tracking and management, security, etc. The idea is that the components of the SOE are created once. Layering approaches with inheritance and composition may support this. Considering the global image landscape rather than single image individual is important in this respect.
The central injection point mentioned in the previous chapter also provides a central point for patching and upgrades. These changes, possibly made available as container images by software providers, need to be automatically cascaded so that maintenance is made straightforward.
In the pre-container era, I have seen companies struggling with keeping their Java or application servers up-to-date with patches and security fixes. Container technologies combine the possibility to apply changes in a central place with having them rolled out across the software landscape with little efforts and possibly no downtime. This can have a huge effect in term of security and reliability. Therefore images need to be designed with maintainability in mind.
A minimal consumption of resources: RAM, CPU, storage is an obvious objective when creating applications, hence the images used for their packaging. It means higher density and reduced cost. Containers have the advantage compared to virtual machines that they share the same kernel instead of creating additional instances. This can be pushed further by having containers sharing layers. This is made possible by following some of the objectives already mentioned: SOE and reusability. This plays a bigger role in term of final RAM and storage usage than the image size per se, although the size does impact the time required for pushing and pulling an image.
Besides keeping operating systems, application servers and other libraries patched in a timely fashion with the latest security fixes here are other important aspects in regard to security that need to be taken into consideration during the image creation:
- Reducing the attack surface: This can be done by limiting what is installed inside the image to only what is required for running the container.
- Limiting capabilities: Privileged containers should be exceptions undergoing specific security measures. Other capabilities like mounting the host file system, binding host ports or running with a specific user id, which may match a user on the host, should be granted only when no other option is available and after impact in term of security has been scrutinized and risks mitigated.
- Limited resources: The container image should be built in such a way that it is able to run under limited resources (CPU, RAM, network, storage) so that it may not be a vector for denial of services attacks against the host and other containers running on the same host.
PaaS platforms like Kubernetes and OpenShift, its enterprise distribution by Red Hat provide monitoring and self-healing mechanisms. Readiness and liveness probes ensure that:
- No requests are sent to a container when it is not ready, which in practice means that either it hasn't finished starting, it is terminating or one of its dependencies (database, service) is not available.
- The container is restarted when its liveness check is unsuccessful. Restarting a container brings it to its initial state, similar to a factory reset.
As an image designer, your responsibility is to make sound readiness and liveness probes available.
Another aspect is that when OpenShift wants to terminate a container it first takes the container out of the request processing rotation and sends a SIGTERM signal. It lets time to the application to gracefully shutdown before it is terminated, if the allowed period has expired, by a SIGKILL signal. In this regard the application inside the image should finished processing in-flight requests, release resources and terminate when a SIGTERM signal is received.
Easy to Consume
Reusability has already been mentioned as an objective. This can, however, be achieved at a good level only when images are easy to consume, which includes the following aspects:
- Easiness to extend: By inheriting, composing or making use of extension points. Extension points need however to have been thought of in first place. As an image designer, it is your responsibility to think of how one may want to add capabilities or to customize your image and to make it possible in a simple way.
- Documentation: This includes documenting the image purpose, its usage but also important aspects of running it like its entry point, used ports, directories where the application may write data, etc. Providing a quick start for your image is also a way to help the end user with understanding how the image is supposed to be used.
I hope you found this first part interesting. In the following articles, we will see what techniques and approaches are available for achieving the objectives that have been stated here. Stay tuned!