{{announcement.body}}
{{announcement.title}}

Packaging Microservices

DZone 's Guide to

Packaging Microservices

In this article, we will discuss how to package a microservices-based application for a private cloud.

· Microservices Zone ·
Free Resource

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. 

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.

Shell
 




x


 
1
> docker tag <current repo>/image-name:tagname <appname>/image-name:tagname
2
 
          
3
> docker tag myrepo:5000/myimage1:1.2.3 myapp/myimage1:1.2.3
4
> docker tag myrepo:5000/myimage2:1.2.3 myapp/myimage2:1.2.3 



Save all the images in a single tarball.

Shell
 




x


 
1
> docker save --output <App Name>-images-1.2.3.tar <docker-images with tags>
2
 
          
3
> docker save --output myapp-images-1.2.3.tar myrepo:5000/myimage1:1.2.3 myrepo:5000/myimage2:1.2.3 <more images>
4
 
          



Finally, include Helm Charts and Readme to create a final compressed archive.

Plain Text
 




x


1
myapp-package-1.2.3.tgz
2
           |_ _ _ _ _ _ myapp-1.2.3.tgz (helm chart)
3
           |_ _ _ _ _ _ myapp-images-1.2.3.tar (docker images)
4
           |_ _ _ _ _ _ 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. 

Shell
 




x


 
1
> docker load --input /root/myapp-images-1.2.3.tar
2
> docker tag myapp/myimage:1.2.3 <customer repo>/myimage:1.2.3
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.

Shell
 




xxxxxxxxxx
1


1
> 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.

Shell
 




x


 
1
> 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:

  1. Microservices built using Java
  2. Microservices built using technologies other than Java, i.e., Python, NodeJS
  3. 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:

Shell
 




x


 
1
plugins {
2
  id 'com.google.cloud.tools.jib' version '2.2.0'
3
}



Add a jib Gradle task with custom specifications to build the Docker image:

Shell
 




xxxxxxxxxx
1
18


 
1
jib {
2
  from {
3
    image = 'gcr.io/distroless/java:11'
4
  }
5
  to {
6
    image = System.getenv('DOCKER_REGISTRY_ADDR') + "/myimage"
7
    tags = [branchName + "-" + System.getenv('CI_PIPELINE_ID'), 'latest']
8
  }
9
  container {
10
    mainClass = 'com.mycompany.myapp.MyServiceApplication'
11
    jvmFlags = ['-XX:GCPauseIntervalMillis=750', '-XX:MaxGCPauseMillis=100',
12
                '-XX:-OmitStackTraceInFastThrow']
13
    ports = ['8080', '9443', '5801']
14
    user = '5000:5000'
15
  }
16
 
17
  allowInsecureRegistries = 'true'
18
}



To build a tagged Docker image and push it to the Docker registry:

Shell
 




xxxxxxxxxx
1
24


 
1
> gradle build jib
2
 
3
> Task :compileJava
4
Note: Some input files use or override a deprecated API.
5
Note: Recompile with -Xlint:deprecation for details.
6
Note: Some input files use unchecked or unsafe operations.
7
Note: Recompile with -Xlint:unchecked for details.
8
 
9
> Task :jib
10
 
11
Containerizing application to myrepo:5000/myimage, myrepo:5000/myimage:staging-620672...
12
Base image 'gcr.io/distroless/java:11' does not use a specific image digest - build may not be reproducible
13
Using credentials from Docker config (/root/.docker/config.json) for myrepo:5000/myimage
14
Cannot verify server at https://myrepo:5000/v2/. Attempting again with no TLS verification.
15
Failed to connect to https://myrepo:5000/v2/ over HTTPS. Attempting again with HTTP.
16
Using base image with digest: sha256:c94feda039172152495b5cd60a350a03162fce4f8986b560ea555de4d276ce19
17
 
18
Container entrypoint set to [java, -XX:GCPauseIntervalMillis=750, -XX:MaxGCPauseMillis=100, -XX:-OmitStackTraceInFastThrow, -cp, /app/resources:/app/classes:/app/libs/*, com.mycompany.myapp.MyServiceApplication]
19
 
20
Built and pushed image as myrepo:5000/myimage, myrepo:5000/myimage:staging-620672
21
Executing tasks:
22
[==============================] 100.0% complete
23
 
24
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. 

Shell
 




xxxxxxxxxx
1


1
> dive myrepo:5000/myimage:1.2.3



default

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. 

Shell
 




x


1
> docker-slim build myapp --http-probe=false
2
...
3
docker-slim[build]: state=building message='building optimized image'
4
docker-slim[build]: state=completed
5
docker-slim[build]: info=results status='MINIFIED BY 18.82X [417722590 (418 MB) => 22199995 (22 MB)]'
6
docker-slim[build]: info=results  image.name=myapp.slim image.size='22 MB' data=true
7
...



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.

Tar archive

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.

Topics:
containers, dive, docker, helm chart, java, kubernetes, microservices, packaging, private cloud, tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}