Packaging Microservices
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 FreeMicroservices 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.
Creating Package
The first step is then to package the Docker images. Tag the Docker images with the release SemVer, e.g., 1.2.3.
x
> docker tag <current repo>/image-name:tagname <appname>/image-name:tagname
> docker tag myrepo:5000/myimage1:1.2.3 myapp/myimage1:1.2.3
> docker tag myrepo:5000/myimage2:1.2.3 myapp/myimage2:1.2.3
Save all the images in a single tarball.
x
> docker save --output <App Name>-images-1.2.3.tar <docker-images with tags>
> docker save --output myapp-images-1.2.3.tar myrepo:5000/myimage1:1.2.3 myrepo:5000/myimage2:1.2.3 <more images>
Finally, include Helm Charts and Readme to create a final compressed archive.
x
myapp-package-1.2.3.tgz
|_ _ _ _ _ _ myapp-1.2.3.tgz (helm chart)
|_ _ _ _ _ _ myapp-images-1.2.3.tar (docker images)
|_ _ _ _ _ _ Readme.txt (Contains checksums and installation instructions)
Installing Package
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.
x
> docker load --input /root/myapp-images-1.2.3.tar
> docker tag myapp/myimage:1.2.3 <customer repo>/myimage:1.2.3
> docker push <customer repo>/myimage:1.2.3
To deploy the application using Helm, create a custom values file, custom-values.yaml
, specific to the customer environment, and run Helm install.
xxxxxxxxxx
> helm install -f custom-values.yaml myapp myapp-1.2.3.tgz
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.
x
> tar -ztvf myapp-package-1.2.3.tgz
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
We'll explore technologies like distroless images, Jib, docker-slim, dive, etc. to reduce image sizes and visualize our 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:
x
plugins {
id 'com.google.cloud.tools.jib' version '2.2.0'
}
Add a jib Gradle task with custom specifications to build the Docker image:
xxxxxxxxxx
jib {
from {
image = 'gcr.io/distroless/java:11'
}
to {
image = System.getenv('DOCKER_REGISTRY_ADDR') + "/myimage"
tags = [branchName + "-" + System.getenv('CI_PIPELINE_ID'), 'latest']
}
container {
mainClass = 'com.mycompany.myapp.MyServiceApplication'
jvmFlags = ['-XX:GCPauseIntervalMillis=750', '-XX:MaxGCPauseMillis=100',
'-XX:-OmitStackTraceInFastThrow']
ports = ['8080', '9443', '5801']
user = '5000:5000'
}
allowInsecureRegistries = 'true'
}
To build a tagged Docker image and push it to the Docker registry:
xxxxxxxxxx
> gradle build jib
> Task :compileJava
Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: Some input files use unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
> Task :jib
Containerizing application to myrepo:5000/myimage, myrepo:5000/myimage:staging-620672...
Base image 'gcr.io/distroless/java:11' does not use a specific image digest - build may not be reproducible
Using credentials from Docker config (/root/.docker/config.json) for myrepo:5000/myimage
Cannot verify server at https://myrepo:5000/v2/. Attempting again with no TLS verification.
Failed to connect to https://myrepo:5000/v2/ over HTTPS. Attempting again with HTTP.
Using base image with digest: sha256:c94feda039172152495b5cd60a350a03162fce4f8986b560ea555de4d276ce19
Container entrypoint set to [java, -XX:GCPauseIntervalMillis=750, -XX:MaxGCPauseMillis=100, -XX:-OmitStackTraceInFastThrow, -cp, /app/resources:/app/classes:/app/libs/*, com.mycompany.myapp.MyServiceApplication]
Built and pushed image as myrepo:5000/myimage, myrepo:5000/myimage:staging-620672
Executing tasks:
[==============================] 100.0% complete
BUILD SUCCESSFUL in 1m 2s
Base Image
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.
xxxxxxxxxx
> dive myrepo:5000/myimage:1.2.3
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.
> docker-slim build myapp --http-probe=false
...
docker-slim[build]: state=building message='building optimized image'
docker-slim[build]: state=completed
docker-slim[build]: info=results status='MINIFIED BY 18.82X [417722590 (418 MB) => 22199995 (22 MB)]'
docker-slim[build]: info=results image.name=myapp.slim image.size='22 MB' data=true
...
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.
Overall Approach
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.
Conclusion
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.
Comments