In the current software development environment, microservices architecture has emerged as a prevalent method for creating applications. Generally, microservices are packaged as container images using containerization tools like Docker, which are then uploaded to an image registry. For deploying these container images on a container orchestration platform such as Kubernetes, Helm stands out as the most widely used option. Helm charts refer to publicly available container registries to retrieve the container images.
However, some companies operate their own private cloud systems. In these settings, public container image registries or even the Internet may not be accessible. To deploy an application in these limited environments, it is crucial to bundle all necessary components—container images, Helm charts, documentation, and more—into a single archive. This article will examine how to package a microservices-based application for a private cloud, like on-premises Kubernetes, and will also cover strategies to reduce the package size.
Packaging Your Application
The first step is to package the Docker images. Assign a release SemVer tag to the Docker images, for example, 1.2.3.
gt; docker tag lt;current repogt;/image-name:tagname lt;appnamegt;/image-name:tagnamegt; docker tag myrepo:5000/myimage1:1.2.3 myapp/myimage1:1.2.3gt; docker tag myrepo:5000/myimage2:1.2.3 myapp/myimage2:1.2.3
Next, save all the images into a single tarball.
gt; docker save --output lt;App Namegt;-images-1.2.3.tar lt;docker-images with tagsgt;gt; docker save --output myapp-images-1.2.3.tar myrepo:5000/myimage1:1.2.3 myrepo:5000/myimage2:1.2.3 lt;more imagesgt;
Finally, add Helm Charts and a Readme to create a complete compressed archive.
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.
gt; docker load --input /root/myapp-images-1.2.3.targt; docker tag myapp/myimage:1.2.3 lt;customer repogt;/myimage:1.2.3gt; docker push lt;customer repogt;/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.
gt; 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.
gt; tar -ztvf myapp-package-1.2.3.tgz
To reduce the overall size of the package, let’s classify Docker images into three categories:
- Microservices crafted with Java
- Microservices constructed using alternative technologies, such as Python and NodeJS
- Microservices obtained from external sources, where we lack oversight of the containerization procedure
We will explore technologies like distroless images, Jib, docker-slim, dive, and others to decrease image sizes and visualize our containerization journey.
Containerizing Java Applications
A key aim when containerizing an application is to achieve the smallest image size feasible. A reduced container size results in
- Quicker pod initialization times
- Faster autoscaling
- Lower resource consumption
- Improved security
To generate a Docker image, we need a Dockerfile that Docker employs to define the layers of an image. For each microservice in our application, we generally create a fat jar that includes all dependencies. The actual code may be relatively compact. These dependencies are redundantly included and copied into each fat jar, leading to unnecessary space usage. We can take advantage of Docker’s image layering—by placing dependencies and resources in distinct layers, we can reuse them and only modify the code for each microservice.
Google provides an open-source tool named Jib, which offers Maven and Gradle plugins to facilitate this method. We do not require a running Docker daemon to create images with Jib—it constructs the image using the same standard output as we receive from docker build
but does not engage Docker unless explicitly stated.
Creating Docker Image With Gradle
The first step, therefore, is to adjust each project’s build.gradle
file to incorporate the new plugin:
plugins { id 'com.google.cloud.tools.jib' version '2.2.0'}
Incorporate a tailored jib Gradle task to create the Docker image:
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'}
Create a labeled Docker image and push it to the Docker registry:
gt; gradle build jib gt; Task :compileJavaNote: Some input files utilize or override a deprecated API.Note: Recompile with -Xlint:deprecation for specifics.Note: Some input files employ unchecked or unsafe operations.Note: Recompile with -Xlint:unchecked for specifics. gt; Task :jib Containerizing application to myrepo:5000/myimage, myrepo:5000/myimage:staging-620672...Base image 'gcr.io/distroless/java:11' does not specify a particular image digest - build may not be reproducibleUtilizing credentials from Docker config (/root/.docker/config.json) for myrepo:5000/myimageUnable to verify server at https://myrepo:5000/v2/. Retrying without TLS verification.Failed to connect to https://myrepo:5000/v2/ over HTTPS. Retrying with HTTP.Using base image with digest: sha256:c94feda039172152495b5cd60a350a03162fce4f8986b560ea555de4d276ce19 Container entrypoint established as [java, -XX:GCPauseIntervalMillis=750, -XX:MaxGCPauseMillis=100, -XX:-OmitStackTraceInFastThrow, -cp, /app/resources:/app/classes:/app/libs/*, com.mycompany.myapp.MyServiceApplication] Constructed and pushed image as myrepo:5000/myimage, myrepo:5000/myimage:staging-620
Foundation Image
We have selected gcr.io/distroless/java:11
as our foundational image. “Distroless” images encompass solely our application along with its runtime necessities. They lack package managers, shells, or any other utilities typically found in a conventional Linux distribution.
Illustrate Docker Image
Dive serves as an invaluable resource for scrutinizing docker images and illustrating the effects of bulky jars within docker images. The subsequent illustration displays the newly crafted layer structures that Jib has generated.
gt; dive myrepo:5000/myimage:1.2.3
Encapsulating and Enhancing Non-Java Applications
Within our application, we might incorporate a non-Java element, such as Python or NodeJS applications. The Docker images for these elements are constructed using the traditional approach with a Dockerfile. To refine the images, we employ an additional utility – docker-slim. This tool necessitates no alterations to our Docker image and optimizes it effortlessly.
docker-slim build myapp --http-probe=false...docker-slim[build]: state=building message='building optimized image'docker-slim[build]: state=completeddocker-slim[build]: info=results status='OPTIMIZED BY 18.82X [417722590 (418 MB) = 22199995 (22 MB)]'docker-slim[build]: info=results image.name=myapp.slim image.size='22 MB' data=true...
Feel free to reach out if you require any additional support!
Enhancing Docker Images from External Origins
In our software, we frequently depend on external components, such as Ingress Gateway, which are accessible to the public. As we lack authority over the containerization of these microservices, we employ docker-slim to streamline their Docker images.
A notable drawback of utilizing docker-slim for image reduction is that it fails to preserve the original layering configuration. Although this method produces compact and secure individual images, it does not aid in diminishing the total image size, as there is no layer sharing between various images.
Holistic Approach
Our comprehensive approach entails using Jib for containerizing our proprietary Java applications and docker-slim for minimizing non-Java images, along with those obtained from other teams.
Final Thoughts
This article has delved into the methodology of packaging applications based on microservices for deployment in private clouds with restricted access to public repositories. We have also investigated various strategies for minimizing package size, ensuring effective deployment and management.