fabric8io / docker-maven-plugin

Maven plugin for running and creating Docker images
https://dmp.fabric8.io
Apache License 2.0
1.87k stars 640 forks source link

new goal: mvn docker:run-build to speed up trying out a new build inside docker on a developer's laptop #119

Open jstrachan opened 9 years ago

jstrachan commented 9 years ago

Every time we make a change, running a maven build then doing mvn docker:build then running the image can take a little bit of time. Particularly docker image creation. It slows down the feedback loop as a developer. However trying code inside docker is increasingly important.

Since the target/docker/build/maven folder is a copy of whats put into the docker image; we could have a maven goal that runs the last image created but mounting the local target/docker/build/maven folder as the /maven volume inside the docker container. i.e. as a quick way of putting the current build in a docker image. This would allow folks to quickly run the docker image without doing a full rebuild of the image via docker.

I've tried this locally via docker and it works well. The only trick is that the project must be inside the /Users folder on OS X without symlinks as that seems visible in boot2docker. Also paths must be absolute. So this worked for me for a project in my ~/dockerBuilds/jstrachan-example-camel-cdi project:

docker run -it -p 9778:8778 -v /Users/jstrachan/dockerBuilds/jstrachan-example-camel-cdi/target/docker/build/maven:/maven:ro fabric8/example-camel-cdi:1.0-SNAPSHOT

BTW am not sure "docker:run-build" is the right maven goal name here; figured we could have "docker:run" to run the current image built by 'docker:build" then "docker:run-build" overrides this with the current local build volume mounting to /maven? Maybe "docker:run-with-volume" or something is more descriptive?

When using application servers like tomcat, jetty, karaf, wildfly that can watch /maven for updated files then there'd be no need to restart the docker container whenever we do a maven build of the artefact; it could just keep running & whenever the jar/war is updated and copied to target/docker/build/maven then the docker container would reload the code. (Folks could try using JRebel for cases where folks are using micro services when they are not using an app server).

Then we'd just need a maven goal to build the current artefact and copy it into target/docker/build/maven so that the docker container's app server would be able to detect the new artefact and reload on the fly - which would be great!

(FWIW I tried using a symlink on a mac to see if that'd avoid us having to do the copy of target/*.jar into target/docker/build/maven); then a regular "mvn install" would update the jar in the /maven inside the docker container - but that doesn't seem to work - not sure if its a boot2docker limitation or something?)

rhuss commented 9 years ago

Awesome idea in order to boost the developer experience. Give me a night to think about it.

rhuss commented 9 years ago

I think the story has two parts: One to prepare the artifacts and extra files in target/docker/.../maven and one to mount this directory into containers at /maven (or whatever has been specified as basedir when building the image).

Preparing target/maven

This is probably the easier part. I suggest to introduce a new configuration parameter prepareOnly which, if switched on, will simply prepare the appropriate target/maven directories for all configured images with a <build> section. Then, with hooking docker:build into the package phase everything will be fresh after a mvn package in the Docker mounted /maven dirs:

It's a bit of an overhead to copy things around, but I think it's more robust than doing symlinking which is heavily OS dependent.

Starting container with target/maven mounted as volumes

Instead of introducing an extra goal, I would prefer to switch this on with a configuration parameter like mountDatadir (or localMount, liveMount, ...). If this is given on the command line (or preferable via an Maven profile) for docker:start, then target/maven dir will be mounted as a volume (but only if a the image to start also has a <build> section, otherwise it doesn't make much sense).


It's still to check, whether this works out for every case (linked data images, merged data images, all those different setups available). Also, it's not clear to me, what happens if a mvn clean is called while the target/maven directory is mounted. Will it fail ? Will the container stop ? Will it be remounted when available again ?

rhuss commented 9 years ago

Please note, that this sort of live update via mounts only works, when the build machine where mvn is running is also the docker host. For setups, where the docker host is running in a separate VM (boot2docker, vagrant), doing this via with volumes mounts only won't work. One would have to mount a directory from the docker host on the local machine, too.

This is a bit related to #133 so I wonder whether we should add this second story.

Does anybody has a clever idea how to easily remote-mount maven build directories into the running container ?

rhuss commented 9 years ago

Mounts will work for boot2docker for certain directories, too.

rhuss commented 9 years ago

The current idea is, that for setup with data images we could provide a specially crafted base image (instead of busybox), which can communicate with the plugin via TCP (http probably), so that during build artefacts can be pushed to the data container transparently.

The only missing piece then is that the appserver picks up the artefact and does a redeployment. Most appserver already know how to do this (tomcat, jboss, ..), so this shouldn't be a problem. This is however not the task of the plugin and should hence be done by the appserver specific image.

Even when not a separate data container is used in production (but only a combiner artefact/appserver image), this could be still useful for development, since one could setup different profiles with different plugin configuration, so that the data container approach can be used for development (of course, integration tests should use the final image).

I will prepare such a 'maven-data` base image with a nginx or so for file upload and the corresponding part within the plugin.

jgangemi commented 9 years ago

just to play devil's advocate for a moment...

why go through all that work if you could just expose a mount point where the app server for artifacts, eg /usr/local/tomcat/webapps, start the container, and then just run your regular mvn clean package goals w/ a ;cp target/artifact.war /whatever/your/mnt/path/is?

what other real benefit am i getting here by being able to upload a file as opposed to copying it to a certain location?

rhuss commented 9 years ago

My point is, that mounting doesn't work for all setups. It works for Linux with the Docker daemon on the same machine, it works for boot2docker via Virtual Box Extensions (but only when you project directory is below /Users), it probably won't work for Vagrant based setups, ...

Since our plugin use the remote Rest API it shouldn't assume that it has local filesystem access (as long as the mount doesn't work via the HTTP Rest API which probably will be never happen).

Of course we could offer both, since mounting is indeed much more practicable (but not universal and I love self-contained builds which work without tweaks on every platform ;-)

jgangemi commented 9 years ago

well, technically mounting support already exists it just requires the user to do the cp themselves. :)

never thought this was a bad idea, just seemed a bit more of an edge case. will be interesting to see it put into practice.

rhuss commented 9 years ago

What do you mean with technically mounting support already exists ? In the generic case, isn't it only possible to mount a local volume from the Docker host itself ? For VM based solutions this means directories within the VM (e.g. boot2docker's TinyLinux VM), not dirs from the client machine where Maven is running (which is OS X)

But I probably I missed something here ....

jgangemi commented 9 years ago

no, i meant from the client machine itself. boot2docker will let you share directories from the host machine provided they live under a certain folder.

rhuss commented 9 years ago

Good news: There will be a way to copy files into a running container via the Docker-API --> docker/docker#13171

I think we should wait until this bubbles up and then we use that to do reployments easily. Until then I leave the issue open.

jstrachan commented 9 years ago

@rhuss oh thats so awesome! Then you'll be able to just restart the docker container after the copy! (for when folks are not using some kind of jrebel class reloading thingy)