In this article, we will discuss how to package a microservices-based application for a private cloud.
Join the DZone community and get the full member experience.Join For Free
Microservices architecture is now an established practice to build applications. Microservices are often packaged as container images using container technologies, e.g., Docker and published to an image registry. To deploy these container images on a container orchestration platform like Kubernetes, Helm is by far the most popular choice. Helm charts would refer to the publicly accessible container registry to pull the container images.
However, some companies maintain their private cloud. The public container image registries or even the Internet are not allowed to be accessed from within the private cloud. To deliver an application for such a restricted environment, we need to package all the needed artifacts — container images, Helm charts, documentation, etc. into an archive. In this article, we will discuss how to package a microservices-based application for a private cloud, e.g., on-prem Kubernetes and ways to optimize the package size.
The first step is then to package the Docker images. Tag the Docker images with the release SemVer, e.g., 1.2.3.
Save all the images in a single tarball.
Finally, include Helm Charts and Readme to create a final compressed archive.
To install the package, the customer first untars the package, load the tarballs to Docker images, and if needed they can re-tag it according to the customer-specific repository.
To deploy the application using Helm, create a custom values file,
custom-values.yaml , specific to the customer environment, and run Helm install.
Optimizing Package Size
With the packaging structure as above, the bulk of the package size will come from Docker images. List the contents of the package to confirm it.
To reduce the overall size of the package, let us divide the Docker images into three categories:
- Microservices built using Java
- Microservices built using technologies other than Java, i.e., Python, NodeJS
- Microservices sourced from external sources, i.e., we don't control the containerization process
Containerizing Java Applications
One of the overriding goals while containerizing an application is to strive for the smallest image size. Smaller container size results in
- Faster pod startup time
- Faster autoscaling
- Smaller resource usages
- Better security
To create a Docker image, we need a Dockerfile file that Docker uses to specify the layers of an image. For each microservice in our application, we usually create a fat jar that contains all the dependencies. The actual code may be very small in size. These dependencies are copied again and again into each fat jar leading to a waste of space. We can leverage Docker’s image layering — by putting the dependencies and resources in different layers; we can reuse them and only update the code for each microservice.
Google has an open-source tool called Jib, that has Maven and Gradle plugins to implement this approach. We don’t need Docker daemon to be running to create images using Jib - it builds the image using the same standard output as we get from
docker build but doesn’t use Docker unless specified.
Building Docker Image With Gradle
The first step then is to modify each project's
build.gradle file to add the new plugin:
Add a jib Gradle task with custom specifications to build the Docker image:
To build a tagged Docker image and push it to the Docker registry:
We have used
gcr.io/distroless/java:11 as our base image. "Distroless" images contain only our application and its runtime dependencies. They do not contain package managers, shells, or any other programs one would expect to find in a standard Linux distribution.
Visualize Docker Image
Dive is a useful tool for analyzing docker images and visualizing the impact of fat jars in docker images. The following picture shows the new shiny layer structures that Jib created.
Containerizing and Minifying Non-Java Applications
Our application may have a non-Java application, e.g., Python and NodeJS applications. The Docker images for these applications are created in the traditional way using Dockerfile. To minify the images, we use yet another tool — docker-slim. It does not require us to change anything in our Docker image and minify it.
Minifying Docker Images From Other Teams
Our application may use applications, e.g, Ingress Gateway for which we might use publicly available applications. As we do not control the containerization logic of these microservices, we try to minify the Docker images of these applications using docker-slim.
One disadvantage of minifying Docker images using docker-slim is that it does not preserve the layers. This may result in a small and secure individual image, but it does not help with the overall image size as there is no sharing of layers among different images.
The overall approach was to use Jib to containerize Java applications that we maintain and docker-slim to minify non-Java and images included from other teams.
In this article, we looked at how to package a microservices-based application for installation into a private cloud that may not have access to publicly available repositories. We also looked at different techniques that could be used to reduce the size of the package.
Opinions expressed by DZone contributors are their own.