10 ms·
Introducing Jib — Build Java Docker images better
- mattlondon 8y agoMaybe someone can help me out on a question I've not got a clear answer on: is it better to build your artefact (jar file in this case) and put that in the docker image, or is it better to put everything you need in the image (source files, dependencies etc) then build the artefact from within the container? Seems to me like the first approach (compile outside then put artefact in container) is the most sane/sensible approach and the one used here in Jib, but I have seen people advocating the build-in-container approach as better (self-contained, repeatable and so on). Any benefits to the build-in-container approach that are compelling?
- organsnyder 8y agoWe run our builds within containers (using the Jenkins k8s plugin) for repeatability, but the generated artifacts (jars and Docker images) are pushed to our artifact repository (Artifactory) as part of the build process. Our final deployable artifact is our base image with a single layer added to include our app.jar.
- paulddraper 8y agoBuilding in a container gives you the portability benefit of having tools installed in a container. But not building in your productiom container makes it smaller. Dockerfiles can actually do both, via "mutli-stage builds". You can build in one container and in the same Dockerfiles copy files from that build container to your final container. Some take a really extreme stance where the production container has nothing superfluous, not even a shell. https://github.com/GoogleContainerTools/distroless https://github.com/GoogleContainerTools/distroless
- bzajax 8y agoDocker recently has support for multi stage builds. So you can use one Dockerfile to define how to build your image (produce your jar and necessary other resources) and then copy them into a fresh container running off a more lightweight image that might not contain all the intermediates and your build tools. https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds https://docs.docker.com/develop/develop-images/multistage-bu...
- zokier 8y agoMulti-stage builds is definitely my preferred method of building stuff, but the problem I've found is that caching dependencies is bit of a pain, either you need to do some tricks with volumes or your builds will be very slow.
- kinghajj 8y agoAt work, I setup the build system to use Apache Archiva to cache Java dependencies, along with other services like apt-cacher, devpi, and squid, for Debian, Python, and general HTTP caching, respectively. We build all of our images atop the "bitnami/minideb" base, and split the Dockerfiles into "partials" that source a dynamic parent image via a build argument. For example, the partial that sets up Debian package and HTTP caching: ARG base FROM ${base} ARG apt_cacher_host ARG http_proxy ARG https_proxy ARG no_proxy ENV HTTP_PROXY="${http_proxy}" ENV HTTPS_PROXY="${https_proxy}" ENV NO_PROXY="${no_proxy}" ENV http_proxy="${http_proxy}" ENV https_proxy="${https_proxy}" ENV no_proxy="${no_proxy}" RUN set -ex; \ if [ ! -z "${apt_cacher_host}" ]; then \ echo "Acquire::http::Proxy \"${apt_cacher_host}\";Acquire::https::Proxy \"false\";" >/etc/apt/apt.conf.d/01proxy; \ fi; \ For Java, another partial creates a Maven settings file with the Apache Archiva URL: ARG base FROM ${base} ARG maven_mirror COPY devops/image-partials/java-artifacts/settings.xml /tmp/settings.xml RUN set -ex; \ env; \ if [ ! -z "${maven_mirror}" ]; then \ sed -r "s!%%MAVEN_MIRROR%%!${maven_mirror}!g;" /tmp/settings.xml >/usr/share/maven/conf/settings.xml; \ fi; The important part of settings.xml is this section: <mirrors> <mirror> <id>archiva.default</id> <url>%%MAVEN_MIRROR%%</url> <mirrorOf>external:*</mirrorOf> </mirror> </mirrors> And voila, Maven will go through Archiva for all its dependencies, which are transparently cached so that subsequent builds don't need to re-download everything again.
- jacques_chester 8y ago> Docker recently has support for multi stage builds. I view these as a future antipattern. They bring what is properly CI's work inside the Docker daemon, which makes it a larger attack surface (many folks run PRs sight-unseen) and obscures it from outside tooling. They only exist because of the limits of the Dockerfile format. With something like Jib, that disappears.
- humbleMouse 8y agoEverything your jar contains can be stored in a git-hub repo with the project. Then you build a fat jar and put it in the image.
- zokier 8y agoI would advice against using fat jars with docker. They do not layer nicely, so your layer sizes become much bigger than necessary, slowing everything down. If you have multiple applications then with fat jars you lose the ability to share the common bits in the applications, which could also have impact on maintainability.
- humbleMouse 8y agoI would argue that fat jars make everything simple and portable. Especially if you do performance testing frequently on hardware that isn't running docker. You can compare performance baremetal vs. VM's this way. Furthermore, as log as you use gradle/maven with artifactory then maintainability of fat jar artifacts is pretty simple.
- peterwwillis 8y agoI would do both. Make a build container that pulls in the Git hash that you want to build/test against. You can keep your dependencies, build process, unit and func tests in the container. Your dev team can use the same container image to build and test in the same way that you do, pulling in the latest code from Git and running a docker build. Now nobody needs to set up their environment because it's all just in the Docker image in the same way. You don't need all those build and test files in production, so you can build a new "deploy container" with just the artifacts from your build process. I would also record the versions of your dependencies, as well as the SHA hash of the build/test container you used, and the SHA hash of the Git repos you pulled from. You can also set things like flyway migrations to run each time the container runs. Now you can deploy this second container somewhere. You can then use the same build/test container you used before to run func tests against the deployed container, to double check it works in your deployed environment.
- hinkley 8y agoThe former can be handily taken care of in your CI toolchain. You need a build agent with all of the CLI tools you need for your build. Many support Docker these days. Some can even spin up images on demand.
- peterwwillis 8y agoThis is (for me) the best part of keeping your build process in a container: reducing the complexity of the build agent. The only software dependencies on your build agent are Docker and Git. Your CI/CD job pulls a Git repo with a Dockerfile, and runs 'docker build', and that pulls every dependency it needs.
- hinkley 8y agoAgreed, and I can’t wait to work someplace like that again. We have a build agent with 9 versions of Node in it. It was 6 when I started here. Once a month I get to help someone figure out weird errors because one task has the wrong value in a drop down. It’s such busy work I can’t even. When I run the CI system, shit gets done and people are expected to be able to recreate build failures by themselves. Give a man a tool, make it reliable, then expect him to use it.
- rifung 8y ago> Any benefits to the build-in-container approach that are compelling? The build in container approach makes it so that in theory you have reproducible builds too from any Docker supported machine. I personally like this approach because it makes it so that you don't run into the case where "It works on my machine" when testing locally.
- adrianmsmith 8y agoI think Java has such good build tooling that you don’t need to build in a container for reproducible builds. Other languages might be different. Install the JDK, install Maven, check out the project, “mvn package” and it’ll download what it needs and produce the JAR to be copied into the Docker image by the Dockerfile.
- joshschreuder 8y agoLooking forward to Microsoft investing some more in this area with Core. Creating a new Core project offers to add Docker support which is pretty decent out of the box but it’s pretty much hands off after that point and you have to do everything yourself from there.
- CSDude 8y agoSomeone asked me if I used Google's Skaffold (save+push to kubernetes), and I giggled and told we mainly use Java and Spring even boot takes more than 10 seconds and building also takes around 10 seconds, so if you do not have .m2 caches, it would take much more time. Dependency caches and already built components, in all languages (Go, Java, Nodejs) is always a problem for us in Docker in our CI/CD, glad to see such solutions coming up
- akvadrako 8y agoOften you can just mount a persistent volume where the cache should go.
- CSDude 8y agoWhat do you do when they are shared and remote?
- elFarto 8y agoI had this problem with Gitlab until I worked out how to use it's built in cache mechanism to save the .m2 directory, the bit I was missing was to force maven to use a different location for that directory.
- rckrd 8y agoI work on skaffold and with the jib folks at Google. Why not have the best of both worlds? :) The code that lives here will use jib for the build and skaffold for the deploy, so you get those really fast (and native) workflows. https://github.com/GoogleContainerTools/skaffold-tools-for-java https://github.com/GoogleContainerTools/skaffold-tools-for-j...
- pillfill 8y agoIt'll be really interesting to see where this goes. We currently use Fabric8's Docker Maven Plugin (https://dmp.fabric8.io/ https://dmp.fabric8.io/) for our Java-based containers. It's a little verbose but works really well- it allows us to run our integration tests directly against the final container images (+ any container dependencies) as part of the standard Maven build.
- organsnyder 8y agoThe Fabric8 Maven plugin is awesome. It has some rough edges, but streamlines a ton of things.
- blahblahblogger 8y agoI wish fabric8 had Gradle support. This ticket implies that it isn't "build tool agnostic" (https://github.com/fabric8io/fabric8-maven-plugin/issues/609#issuecomment-297363332 https://github.com/fabric8io/fabric8-maven-plugin/issues/609...) and I found a few github repos that are "gradle plugins" for Fabric8 but they only had a few stars or were out of date.
- chvid 8y agoMaybe I am old school. But I kinda like a WAR-file, run/deployment via a maven plugin and a simple servlet container (like Tomcat). If you run a pure Java stack why not keep to this stuff that has been around for ages?
- paulddraper 8y agoContainers give control over the application's JRE version (without making a self-extracting executable).
- chvid 8y agoSure but in practice that would change very rarely. For what I am working on a deployment takes a couple of seconds including controlled shutdown and startup of the application. I don't know how fast docker/jib would be but I am imagining this would be a somewhat heavier setup?
- mateuszf 8y agoIt depends. The build / environment base images can be cached, and then deployment could be just pushing and additional layer with the jar. This could be pretty pretty fast if setup correctly.
- ryanianian 8y agoNot having to install and manage a tomcat server is pretty nice. There is "embedded" tomcat so you can just run `java -jar my-app.jar` but then you still have to worry about installing and managing the JVM plus whatever file-system resources like logging locations etc. > [JRE version] would change very rarely. It's optimizing for setting up new machines and having everything about how to run an application contained within that application itself rather than relying on sysadmins to `yum install` things or whatever. Plus with docker you always run the same version in prod that you do in dev (and on your local machine) unless you intentionally do something else. > I don't know how fast docker/jib would be but I am imagining this would be a somewhat heavier setup? Not really - a layered docker image may have tomcat as a base layer and then the app jars etc layered atop. Deployment is just downloading a new layer which wouldn't really be any overhead atop of just the jars.
- skrebbel 8y agoI'm a Docker noob and I haven't shipped Java in ten years, and I find that this article leaves the main question unanswered: what happened to just building a JAR? When I read that sentence, I assumed that it must be something about controlling the JVM parameters and version, or the ability to include certain system tools or imagemagick type of CLI thingos, but then as the article goes on, I see none of that. None of the things that, to my uninitiated mind, give Docker an edge over building a plain old JAR. So why? Why is it necessary / a good idea to Dockerize a Java app? Can anyone help me out?
- koalaman 8y agoThere are really a host of reasons why it's a good idea. Off the top of my head here are three that I think justify the weight: 1. A Jar includes your app, but not your runtime or your applications native deps. Docker containers let you specify a whole lot more about your image that goes beyond the language specific application package. 2. You can use the same tooling and infrastructure for managing your executable binary artifacts across all the languages used in your org. 3. You can take advantage of cluster schedulers like K8s.
- pjmlp 8y agoPoint 1 is taken care by fat jars and jlink.
- rbjorklin 8y agoWhile technically correct the maven-jlink-plugin is in alpha and I’ve not yet seen any application packaged this way. If you’re aware of any open-source project doing this I would be very interested :)
- pjmlp 8y agoNot yet. But I would argue that it would still be more mature than Jib, given that Java 9 is almost one year old.
- 8y ago
- merb 8y agoHere is my "initial" sbt implementation: https://github.com/schmitch/sbt-jib https://github.com/schmitch/sbt-jib (Should be easier to do after: https://github.com/GoogleContainerTools/jib/issues/337 https://github.com/GoogleContainerTools/jib/issues/337) It also still misses some important things like that not all resources will be put into the resources layer and also that the resources should be deduplicated, if any. Edit: thanks google for this awesome library! With this library you prolly loose some money!
- rad_gruchalski 8y agoHow does this compare to sbt-native-packager docker integration?
- maccam94 8y agosbt-native-packager doesn't do dependencies as separate layers, so the image has a big fat layer with all your jars.
- djsumdog 8y agoSeems like a good idea for new project. The article says "You don't have to create a Dockerfile" but honestly, who does? There are a ton of Gradle/SBT/Maven plugins that build your container for you. Most call your Docker daemon API directly, copy in all your jars and write a startup script. Some generate a Dockerfile, but they stick it in your build directly and you never touch it directly. The advantage is not needing a Docker daemon. That is pretty nice. However I already need a daemon in our Jenkins pipeline and a lot of my unit tests use the Scala testcontainers framework (so I can run tests against a real MySQL or Postgres DB, which is way nicer than using mocks or H2). So it is nice, but chances are, we're already running container daemons everywhere already. Plus I'm sure for this to be really useful, we'll need ECR plugins as well (which will probably need to be 3rd party because I don't see Google supporting AWS officially). I wouldn't move to this if I was on something that worked. That previous article about the team going back to maven after a gradle migration is kinda in that same view: with a new project you can make it clean, but your current project is probably so customized around your workflow that unless you can make a case for a really bad pain point it will solve, it's probably not worth it. A good pain point I think this would solve is if your CI doesn't have access to a Docker daemon at all (for security or you're using a hosted solution or whatever) and you want to be able to build containers and publish them in your CI.
- jacques_chester 8y agoThere's two things here. The first is layering. A goal is smarter layering without a Dockerfile. A dependency-management tool gives fairly good insight into how to arrange layers to improve cacheability and reduce layer churn. The second is security. Going without a daemon socket is more than "pretty nice", it is a massive security win. Docker daemons have a lot of privileged access and a wide API. Having that entire massive attack surface lying around to write some tarballs and a JSON file on your behalf doesn't make a great deal of sense. These both become more valuable at large scales, which is why it makes sense for Google folks to have developed it.
- kronin 8y agoAgreed re: both of these wins. On the security front, would be ideal if these resulting containers ran as non-root. Hopefully that will come soon.
- politician 8y agoIf you really want to explore why running a JVM in a Docker container makes a lot of sense, try running a modded Minecraft server with and without a container for a few weeks. Another commenter in the thread mentioned that "Docker eliminates wishful thinking." Getting that first modded Minecraft server operational will drive that home.
- djhworld 8y agoThis seems pretty neat if you have the infrastructure already, removes a lot of the busy work around creating docker images etc, and from the looks of the instructions it looks like you don't even need Docker installed to create the image - which is great for CI!
- phanboy4 8y agoJava isn't an ideal fit for containerization or horizontal scaling by nature, but if you're stuck with it and really want to avoid using anything other than Java ecosystem tooling, this seems like a timesaver.
- preordained 8y agoWhy would Java not be an ideal fit for containerization (why would any runtime not be)? Also, given you don't have to pack everything into one VM (can have many nodes, VMs, load balanced, whatever)...what makes Java particularly bad for horizontal scaling?
- opmac 8y agoYea I don't understand the original comment... however it is true Java isn't (or at least wasn't until recently) ideal for containerization, since the JVM would not obey things like cgroup limits for memory and CPU. This was fixed in Java 9 and backported to the Java 8 (update 131) however there are still potential issues regarding CPU isolation that are not trivially solved without some explicit configuration. For a good overview, see: https://mesosphere.com/blog/java-container/ https://mesosphere.com/blog/java-container/
- benatkin 8y agoI started to make something similar with Node, since I wanted to use Node to make containers rather than shell scripts (I don't think learning shell scripts should be a prereq for NoOps). Unfortunately I got distracted, before I eliminated the need for a Dockerfile (it had a wrapper Dockerfile that ran npm scripts) https://github.com/diodejs/dev/blob/master/Dockerfile https://github.com/diodejs/dev/blob/master/Dockerfile It's based on a plain alpine container, with the idea that you can run `apk remove node` when you're done to clean it up if you don't need node once the container is built. A lot of people are missing that this is really about replacing shell scripts with Maven or Gradle. You can still build a fat JAR.
- jacques_chester 8y agoGoogle have also done work on the more general idea of "read dependencies to layout containers", under the title FTL. I would give you a link but it is a maddeningly ungoogleable name.
- Perseids 8y agoYay! Good steps (in the Java ecosystem at least) to bring containers closer to their potential. Docker is the best example I know of how "worse is better" can lead to absurdly bad local maxima. Docker solves the most shallow and most obvious problems of software deployment: How to package all dependencies in one big ball of mud. Instead of declaring dependencies explicitly (with acceptable version ranges like maven, bundler, even nvm can do for ages) we get the equivalent of a bash script (Dockerfile) that is impossible to parse automatically and encourages volatile external dependencies (`curl http://mirror.com/current_version.sh http://mirror.com/current_version.sh | bash`) Instead of slim, reproducible build descriptions that could reference single files or packages via hash sums (ala `packages.lockfile`) we get fat filesystems with the caching granularity of snapshots. But why care, developers have multi terabyte SSDs and gigabit network connections by now, right? Instead of being able to propagate security updates down to each package (/container), we have a filesystem snapshot caching, where cache invalidation is for all intends and purposes impossible. But cache invalidation is hard, so how could you blame docker? And I get why Dockerfiles are so seductive: They are damn easy. You can build your first docker image in minutes. But also docker doesn't help you to solve any of the deeper problems for you. E.g. you better figure out how to invalidate your cached image layers yourself when packages get security updates. Or just rebuild everything all the time, but there goes your performance and maybe some of those pesky external dependencies are no longer available. Want to internalize those? Build that yourself, too, docker can't help you there, because it is too damn simple. So, Horray to Jib! All the work that has already gone into Maven to solve the deeper problems (e.g. internalizing external dependencies with Nexus) can be repurposed to build container images. Let's hope we get more of that "more complex but better" sanity.
- jcolella 8y agoI ended up leveraging multi-stage building with Docker and have a gradle image as my builder image, build the jar, and copy that to the image that is going to get deployed.
- dogtail 8y agoU can help with dockerizing Java application and running it in nomad cluster
- he0001 8y agoDocker works wonders if you don’t have a functioning app server or something equivalent. If you don’t have the possibility of deploying to an already functioning cluster, use docker. If you have an environment where you can’t “run everywhere” use docker. However docker is just something to handle environments. Not applications.