Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Provide task to stage output in a format sufficient to build Docker images #236

Merged
merged 4 commits into from
May 14, 2014
Merged

Conversation

fiadliel
Copy link
Contributor

This is a WIP to generate Docker images for applications which are packaged using sbt-native-packager.

It offers a docker:stage task which creates a directory sufficient to create images with
"docker build .", based on extending the layout from Universal.

It does not offer any deployment options, as that generally seems out of scope for this package.

I was wondering if you had any comments, or interest in merging functionality like this? Otherwise, it could also go into a separate plugin, and then might be able to include functionality to publish to Docker repositories.

…mages.

This creates a new docker config, and updates the stage task to create
a Dockerfile which can create an image capable of running an application.
It works with basic Java applications, and basic Play 2.2 applications.
@muuki88
Copy link
Contributor

muuki88 commented Apr 24, 2014

Thanks. This looks very interesting. Can you provide a small description what this extension should offer for docker packaging? I just learned about docker and think this could fit nicely in an archetype.

Wonder what the rest things, @jsuereth , @kardapoltsev , @aparkinson ?

}
))

private[this] def stageFiles(config: String)(cacheDirectory: File, to: File, mappings: Seq[(File, String)]): Unit = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably share this somewhere if you plan to use it over here too.

@jsuereth
Copy link
Member

This looks like a really great start!!! Would love to see full docker support. I made a few notes on general architecture of native-packager and some adjustments I think should be made. Let me know what you think, if you have any questions.

@fiadliel
Copy link
Contributor Author

For the publishing side of things, either here or elsewhere:

There are two ways I think one could build a Docker image:
One is to run "docker build ". This requires an existing Docker binary, and you can set the DOCKER_HOST environment variable to build to a remote server. Needing these tools is a bit... ugh... but you do give the responsibility for keeping up to date with an API to another tool.

Another is to use the Docker remote API, which is what I'd prefer: http://docs.docker.io/reference/api/docker_remote_api_v1.10/

POST /build HTTP/1.1
{{ STREAM }}

The stream must be a tar archive compressed with one of the following algorithms: identity (no compression), gzip, bzip2, xz. The archive must include a file called Dockerfile.

I would expect that publishing would allow the following settings:

  • image name
  • image tag
  • server to publish to
  • authentication details

@fiadliel
Copy link
Contributor Author

I'm not convinced this should be an archetype, as I can imagine somebody wanting to create both a Debian package and a Docker image for a Play application... that isn't possible with archetypes? This seems to have more in common with the Universal config.

@kardapoltsev
Copy link
Member

Yes, it's possible. Since you set up Rpm and Debian setting you could run

debian:PackageBin
rpm:PackageBin

It'll be nice to run docker:PackageBin and get Docker image.

@fiadliel
Copy link
Contributor Author

@kardapoltsev I'd like packageBin too, but I'm not sure of its utility. None of the native command-line tools have any interest in a tgz file or anything like that (unfortunately). It might be useful for sharing an image, except that a Docker registry seems to be a more standard method of transportation.

@jsuereth
Copy link
Member

I'm not suggesting that it should be an archetype. However, if you look at Debian/Rpm setups, they all have the hooks I described.

  1. Raw generate docker image settings
  2. Mappings from Universal into the specific package

Then, the archetypes have mappings into both the Universal setting and The raw settings.

@muuki88
Copy link
Contributor

muuki88 commented Apr 24, 2014

The archetype was just an idea, so you can mixin your additional docker settings with one simple line archetypes.docker_image. However I need more insight on how docker works to really advocate this :D and this is the last thing to do package everything up user-friendly.

@jsuereth
Copy link
Member

@muuki88 I think docker is more like an "RPM", "DEB" thing. We actually want the inverse: Server/App archetypes should appropriately fill out docker info as needed.

@garycoady can you give us more info into a docker registry, including how to publish to one? Also, to package for docker, would this require building on a docker instance, or can you do it from a different machine?

Adds dockerPackageMappings which contains mappings specific to Docker build - defaults
to reading from sourceDirectory/docker (overrideable).
Builds mappings from dockerPackageMappings and universal:mappings (overrideable).
Modifies "docker:stage" command to build a Docker context and Dockerfile from
docker:mappings.
@fiadliel
Copy link
Contributor Author

@jsuereth Updated code based on your initial comments.
I now have a dockerPackageMappings which defaults to the contents of docker:sourceDirectory (overrideable).
docker:mappings takes the contents of dockerPackageMappings and universal:mappings (set in mapGenericFilesToDocker).

@fiadliel
Copy link
Contributor Author

Docker registry: it should be possible to publish using the remote API I mentioned earlier, using a POST request to a /build endpoint on a HTTP server. The body should be a compressed tarball. You then should not need anything related to Docker to be present locally.

I think this should also be possible without adding any new dependencies (dispatch is already required by sbt-native-packager), I'll try a proof of concept for this next.

@jsuereth
Copy link
Member

jsuereth commented May 9, 2014

@garycoady What you have now actually looks good (and ready to merge if you want).

As for publishing, you could try to do so through Ivy or just push w/ dispatch. I'm thinking whatever you do, it'd be fun if it could abide by the other publish command conventions. We already do this for pushing (PUT) to Bintray, so if you need any help there, let me know.

ALso, sorry for the LONG delay in review.

@muuki88
Copy link
Contributor

muuki88 commented May 9, 2014

Fine by me. Should I merge? Future work will be another pull request. Thanks a lot @garycoady

@jsuereth
Copy link
Member

jsuereth commented May 9, 2014

Yep. Merge is good

@fiadliel
Copy link
Contributor Author

fiadliel commented May 9, 2014

I want to de-scope docker* settings to make them simpler to set, and re-use defaultLinuxInstallLocation instead of using defaultDockerInstallLocation.

If you can wait until tomorrow, I'll push an update then.

Other changes (but can wait for a different commit):

  • add execArgs as extra args when running a binary (e.g. -mem 200 might be nice to add as an overridable parameter set).
  • also add execArgs to /etc/default for other server targets
  • publish functionality

@muuki88
Copy link
Contributor

muuki88 commented May 9, 2014

Sure, no problem :-)

- Trait DockerPlugin should not extend DockerKeys
- publishArtifact in Docker is false - do not generate POM/etc.
- Tasks for stage do not depend on each other.
- Change the CWD in the Docker container to install location.
- Adding Docker-specific mappings happens outside mapGenericFilesToDocker
- Use defaultLinuxInstallLocation instead of defaultDockerInstallLocation
- dockerBaseImage is set outside Docker config, easier to override
@fiadliel
Copy link
Contributor Author

@jsuereth:

  • cacheDirectory is a deprecated value already in use in sbt-native-packager, but I decided to continue an existing pattern that obviously worked (and a cleanup commit can fix all usages at the same time).

In general about publishing, I'm still thinking of the best option. I don't like pulling dispatch/async-http-client/etc. into people's builds, it isn't a problem with bintray as it is only used when building this plugin. I would consider just using the docker binary, but I'd still like to manage credentials with SBT.

Also: I can squash and/or rebase the commits here if people want?

@jsuereth
Copy link
Member

squashing is fine, doesn't matter.

As far as publishing/credentials I think you could use the existing publish/ivy support we have, just with some "cleverness". If you need details, let's chat about that.

I just restarted the build to see if we can get past the timeout issue. This LGTM as it is.

@muuki88
Copy link
Contributor

muuki88 commented May 14, 2014

Should we merge this?

@jsuereth
Copy link
Member

Yep! Tests now pass :)

muuki88 added a commit that referenced this pull request May 14, 2014
WIP: Provide task to stage output in a format sufficient to build Docker images
@muuki88 muuki88 merged commit 7fe7eb2 into sbt:master May 14, 2014
@muuki88
Copy link
Contributor

muuki88 commented May 14, 2014

Feels good :)

@ahjohannessen
Copy link

@garycoady this is really useful for many of us! :) great contribution

@ahjohannessen
Copy link

@garycoady I wonder if it makes sense to allow for a hook-in in makeDockerContent with respect to EXPOSE or VOLUME and similar?

@fiadliel
Copy link
Contributor Author

@ahjohannessen It probably would, but I'm going to concentrate on publish support first. It seems like an sbt.RawRepository enclosing an sbt.ResolverAdapter (implementing the appropriate Docker calls) should allow some kind of meaningful behaviour for publish and publishLocal.

@mhamrah
Copy link
Contributor

mhamrah commented Jun 20, 2014

For those interested, I have this sample which uses sbt-native-packager with sbt-docker to build docker containers from sbt.

  • Uses sbt-native-packager to stage files
  • Uses sbt-docker (which depends on the stage task) to build and customize the docker container.

Sbt-docker is a good plugin, albeit young. I agree Docker is not an archetype- it's a package, and I think "pushing" to a registry should be handled as a separate task, a-la sbt-release or something similar.

On the plus side, I like how sbt-docker asks you to build a dockerfile. This allows docker-specific customizations to occur independently of other build types, like exposing ports, setting environment variables, and running commands.

@muuki88
Copy link
Contributor

muuki88 commented Jun 20, 2014

Thanks for sharing. The sbt-docker plugin looks quite interesting. The new Dockerfile API looks promising. @garycoady will you provide further extensions to this plugin?

@fiadliel
Copy link
Contributor Author

I like the idea that docker is configured similarly to other targets; so the start script is the same in all cases; if I want to customize that start script, bashScriptExtraDefines will work similarly for all cases (the behaviour is consistent between RPM/Debian/Docker).

So where you have a start script with:
CLUSTER_IP=``/sbin/ifconfig eth0 | grep 'inet addr:' | cut -d: -f2 | awk '{ print $1}'``bin/$1 ${*:2}

I might suggest

NativePackagerKeys.bashScriptExtraDefines ++= Seq(
  "CLUSTER_IP=`/sbin/ifconfig eth0 | grep 'inet addr:' | cut -d: -f2 | awk '{ print $1}'",
  "addResidual $CLUSTER_IP"
)

There is an advantage in this, in that the setup works across all packaged types.

So while you are looking at how to make things different across build types, I was more considering how to keep them the same :-)

But I agree that the Dockerfile API could perhaps be improved here. I had a more complex API initially, but it was unclear how to support the Dockerfile without adding complexity for complexity's sake. In the end, I went with a minimal wrapper that supported the EXEC and shell formats for execution.

@muuki88
Copy link
Contributor

muuki88 commented Jun 20, 2014

You're right, keeping things consistent should be the main goal.

I'm not that familiar with docker, but would it be possible to build a debian/rpm package and package it within the docker file and install it with a command like dpkg -i package.deb or rpm -Uvh package.rpm? I like idea of being independent from a packaging system, but this will result in more duplicated code.

@fiadliel
Copy link
Contributor Author

For publishing, I have some code that creates an Ivy resolver, but I have to spend a bit more time on it to see how to generate an appropriate artifact URL. Something like this for publish:

def publish(artifact: Artifact, file: java.io.File, force: Boolean): Unit = {
  val tag = artifact.getUrl.getPath
  val cmd = Seq("docker", "build", "-t", tag, ".")
  val cwd = file.getParentFile

  // Also push the image with "docker push"

  Process(cmd, cwd).!
}

I gave up on doing it without the binary files and server; not only do you need a HTTP client, but to support all protocols, you also need Unix sockets, so it adds a nasty set of dependencies. Perhaps working without local build tools/server could be added by an external plugin...

For authentication; the docker client doesn't currently seem to support using credentials without adding them to the local docker configuration; I think this violates people's expectations about leaking of credentials from an SBT build, so I would currently assume that the user has already used the client to authenticate against the server they want to publish to (while providing an appropriate error message if this has not happened).

@fiadliel
Copy link
Contributor Author

@muuki88 It's certainly possible, but it then places a dependency on having a Debian or RedHat base image to install against. It would be nice to be able to have a very very minimal install: my current assumptions are:

  • existence of chown/etc.
  • existence of user "daemon"
  • java binary discoverable by the start script
  • appropriate libraries to allow java to run

@muuki88
Copy link
Contributor

muuki88 commented Jun 20, 2014

I think this is a good approach. My described option would then only be a matter of configuration (removing the chown commands, setting task dependencies, etc.).

Building docker locally is fine, too for the start. In 4 weeks I will start adding the option to build debian packages with JDeb. One of our main goals is to support the native-platform. So for debian build you need dpkg installed. Maybe an additional plugin is a good option as you mentioned, which does the nasty http/socket stuff.

@mhamrah
Copy link
Contributor

mhamrah commented Jun 20, 2014

@garycoady thank you for the bash suggestion- much better approach.

@fiadliel
Copy link
Contributor Author

I've made some progress; with this (play 2.2.3) build.sbt:

import com.typesafe.sbt.SbtNativePackager._
import NativePackagerKeys._

name := "play-docker-test"

play.Project.playScalaSettings

dockerRepository := Some("garycoady")

maintainer := "Gary Coady <[email protected]>"

Docker now does a publishLocal (to the local server), followed by publish (to the remote repository).

[play-docker-test] $ docker:publish
[info] Wrote /home/gary/play-docker-test/target/scala-2.10/play-docker-test_2.10-0.1-SNAPSHOT.pom
default#play-docker-test;0.1-SNAPSHOT!garycoady/play-docker-test:0.1-SNAPSHOT.(docker)
Going to run docker build -t garycoady/play-docker-test:0.1-SNAPSHOT . in directory /Users/gcoady/Code/web/play-docker-test/target/docker
Step 0 : FROM dockerfile/java
 ---> 8a1dc97315d1
Step 1 : MAINTAINER Gary Coady <[email protected]>
 ---> Using cache
 ---> 9da3be062d6b
Step 2 : ADD files /
 ---> f2f33d10e525
Removing intermediate container 0224d899484c
Step 3 : WORKDIR /opt/docker
 ---> Running in c790f7172e91
 ---> 3e80493d4a9b
Removing intermediate container c790f7172e91
Step 4 : RUN ["chown", "-R", "daemon", "."]
 ---> Running in 82aea60af743
 ---> 6b853aa62fa0
Removing intermediate container 82aea60af743
Step 5 : USER daemon
 ---> Running in 90479aee0bee
 ---> a4b8fb8817db
Removing intermediate container 90479aee0bee
Step 6 : ENTRYPOINT ["bin/play-docker-test"]
 ---> Running in bf5c3bcf66b5
 ---> cd5075b20105
Removing intermediate container bf5c3bcf66b5
Step 7 : CMD []
 ---> Running in 0010ba2f2911
 ---> d26cbffef885
Removing intermediate container 0010ba2f2911
Successfully built d26cbffef885

default#play-docker-test;0.1-SNAPSHOT!garycoady/play-docker-test:0.1-SNAPSHOT.(docker)
Going to run docker push garycoady/play-docker-test:0.1-SNAPSHOT
The push refers to a repository [garycoady/play-docker-test] (len: 1)
Sending image list
Pushing repository garycoady/play-docker-test (1 tags)

Pushing tag for rev [d26cbffef885] on {https://registry-1.docker.io/v1/repositories/garycoady/play-docker-test/tags/0.1-SNAPSHOT}

[success] Total time: 401 s, completed 20-Jun-2014 20:28:41

And the result is:

$ docker run -i garycoady/play-docker-test:0.1-SNAPSHOT -mem 200 -v -J-Dhttps.port=9443
# Executing command line:
java
-Xms200m
-Xmx200m
-XX:MaxPermSize=256m
-XX:ReservedCodeCacheSize=128m
-Duser.dir=/opt/docker
-Dhttps.port=9443
-cp
/opt/docker/lib/default.play-docker-test-0.1-SNAPSHOT.jar:/opt/docker/lib/newrelic.jar:/opt/docker/lib/org.scala-lang.scala-library-2.10.3.jar:/opt/docker/lib/com.typesafe.play.play_2.10-2.2.3.jar:/opt/docker/lib/com.typesafe.play.sbt-link-2.2.3.jar:/opt/docker/lib/org.javassist.javassist-3.18.0-GA.jar:/opt/docker/lib/com.typesafe.play.play-exceptions-2.2.3.jar:/opt/docker/lib/com.typesafe.play.templates_2.10-2.2.3.jar:/opt/docker/lib/com.github.scala-incubator.io.scala-io-file_2.10-0.4.2.jar:/opt/docker/lib/com.github.scala-incubator.io.scala-io-core_2.10-0.4.2.jar:/opt/docker/lib/com.jsuereth.scala-arm_2.10-1.3.jar:/opt/docker/lib/com.typesafe.play.play-iteratees_2.10-2.2.3.jar:/opt/docker/lib/org.scala-stm.scala-stm_2.10-0.7.jar:/opt/docker/lib/com.typesafe.config-1.0.2.jar:/opt/docker/lib/com.typesafe.play.play-json_2.10-2.2.3.jar:/opt/docker/lib/com.typesafe.play.play-functional_2.10-2.2.3.jar:/opt/docker/lib/com.typesafe.play.play-datacommons_2.10-2.2.3.jar:/opt/docker/lib/joda-time.joda-time-2.2.jar:/opt/docker/lib/org.joda.joda-convert-1.3.1.jar:/opt/docker/lib/com.fasterxml.jackson.core.jackson-annotations-2.2.2.jar:/opt/docker/lib/com.fasterxml.jackson.core.jackson-core-2.2.2.jar:/opt/docker/lib/com.fasterxml.jackson.core.jackson-databind-2.2.2.jar:/opt/docker/lib/org.scala-lang.scala-reflect-2.10.3.jar:/opt/docker/lib/io.netty.netty-3.7.1.Final.jar:/opt/docker/lib/com.typesafe.netty.netty-http-pipelining-1.1.2.jar:/opt/docker/lib/org.slf4j.slf4j-api-1.7.5.jar:/opt/docker/lib/org.slf4j.jul-to-slf4j-1.7.5.jar:/opt/docker/lib/org.slf4j.jcl-over-slf4j-1.7.5.jar:/opt/docker/lib/ch.qos.logback.logback-core-1.0.13.jar:/opt/docker/lib/ch.qos.logback.logback-classic-1.0.13.jar:/opt/docker/lib/com.typesafe.akka.akka-actor_2.10-2.2.0.jar:/opt/docker/lib/com.typesafe.akka.akka-slf4j_2.10-2.2.0.jar:/opt/docker/lib/org.apache.commons.commons-lang3-3.1.jar:/opt/docker/lib/com.ning.async-http-client-1.7.18.jar:/opt/docker/lib/oauth.signpost.signpost-core-1.2.1.2.jar:/opt/docker/lib/commons-codec.commons-codec-1.3.jar:/opt/docker/lib/oauth.signpost.signpost-commonshttp4-1.2.1.2.jar:/opt/docker/lib/org.apache.httpcomponents.httpcore-4.0.1.jar:/opt/docker/lib/org.apache.httpcomponents.httpclient-4.0.1.jar:/opt/docker/lib/commons-logging.commons-logging-1.1.1.jar:/opt/docker/lib/xerces.xercesImpl-2.11.0.jar:/opt/docker/lib/xml-apis.xml-apis-1.4.01.jar:/opt/docker/lib/javax.transaction.jta-1.1.jar
play.core.server.NettyServer

Play server process ID is 1
[info] play - Application started (Prod)
[info] play - Listening for HTTP on /0:0:0:0:0:0:0:0:9000
[info] play - Listening for HTTPS on port /0:0:0:0:0:0:0:0:9443

I'll try and clean up the code, do error checking, etc., before opening a pull request for it.

@fiadliel
Copy link
Contributor Author

@ahjohannessen Support for EXPOSE and VOLUME:
#278
#280

@ahjohannessen
Copy link

@garycoady 👍

@muuki88
Copy link
Contributor

muuki88 commented Jun 24, 2014

We will wait for some feedback on the 0.7.2-RC2. If there are no bug reports,
I will push a 0.7.2 release in the upcoming weeks. Really excited about this :)

@fiadliel
Copy link
Contributor Author

@muuki88 Have you tried it? So far, my users are... me. If you have any concerns about the current functionality, or think there's something extra that would be useful to add before release, let me know.

And it's my opinion that a Docker wrapper should just execute the appropriate script, not try and stick runit/daemontools/etc. inside the container. Restarting a failed service can be left to docker -d or geard/systemd, or whatever other service manager people want to use.

@muuki88
Copy link
Contributor

muuki88 commented Jun 24, 2014

Yeah, that's the feeling I got to when reading and experimenting with docker. IMHO the less we do in the beginning the better. No magic is preferable ;)

There will be a docker meetup in a few days here in munich and I will talk to the people there hoping for feedback. I hope to give a lightning talk on sbt-native-packager and docker on next meetup in a month or so and share the slides here.

@jsuereth mentioned he had some requests, too. So I think there is a potential userbase out there, but we will need to promote this feature when it's packaged in a release.

@ahjohannessen
Copy link

"So I think there is a potential userbase out there..."

Absolutely.

@ahjohannessen
Copy link

@garycoady

"So far, my users are... me."

Me too! 😄 It works great.

"And it's my opinion that a Docker wrapper should just execute the appropriate script, not try and stick runit/daemontools/etc. inside the container. Restarting a failed service can be left to docker -d or geard/systemd, or whatever other service manager people want to use."

That is the general consensus in the docker community, as well.

@softprops
Copy link
Member

Just catching up on this issue which someone pointed out to me. I thought you guys should know about sbt-docker. It just generates your Dockerfile and creates the docker image. It provides a nice set of examples

@fiadliel
Copy link
Contributor Author

It was pointed out a few days ago.

I think the aim of sbt-docker looks different to what this code does. This does not try to write any possible Docker image; it creates a Docker image which runs the program configured with sbt-native-packager. sbt-docker looks a lot more like an unopinionated tool that can create any kind of image.

So if you look at the differences in how the settings/tasks are defined, sbt-docker exports a Dockerfile representation, where this code (currently) keeps that hidden, and instead exports possible features (exported ports, volumes, ...), allows extra files to be added to the image, etc.

Perhaps in the area of Dockerfile representation, and how to build/push images, some code could be shared. But I'm fairly happy with the the way the features are represented, in the context of this plugin.

I did check that both plugins can be added at the same time, in case some keys conflicted.

@muuki88
Copy link
Contributor

muuki88 commented Jun 24, 2014

I contacted @marcuslonnberg to take a look at our project as sbt-docker was pointed out a few days ago. He read all of our discussion at is willing to help if we decide to reveal some of the underlying API to create the dockerfile.

@fiadliel
Copy link
Contributor Author

Revealing it could be useful, and the internal representation in sbt-docker is more complete. But I don't think it's a blocker for using Docker here.

@marcus-drake
Copy link

As @garycoady points out the plugins currently have different aims. Perhaps is it not necessary for this plugin to allow full configuration of the Dockerfile. I think that in a lot of cases (when using this plugin) the predefined Dockerfile with configurable ports, volumes etc are sufficient. In those other cases one could use sbt-docker with the output of this plugin in a Dockerfile, as in the example from above https://github.com/mhamrah/akka-docker-cluster-example.

Another thing that you mentioned was to communicate with the docker server directly without using a local docker client. I started working on this for sbt-docker a while ago but other things got in the way, but I hope to pick that up soon.

@softprops
Copy link
Member

Another thing that you mentioned was to communicate with the docker server directly without using a local docker client. I started working on this for sbt-docker a while ago but other things got in the way, but I hope to pick that up soon

@marcuslonnberg I'd like to help if I can. I was all excited to write a docker plugin but the got just as excited when I learned that someone already wrote one ( multiple ones 😄 ). I'm still pretty motivated so let me know if I can help out on either side. I'm just getting into docker, once you get into docker. There's no getting out!

@marcus-drake
Copy link

@softprops Great to hear you want to help. Lets discuss that issue here: marcus-drake/sbt-docker#7
I aim to cleanup my current code for that (which is not much) this weekend and then push it.

@softprops
Copy link
Member

@marcuslonnberg cool. I may contribute an example for dockerized unfiltered in the /examples dir which I guess doesn't need to be help up by any release schedule but would be nice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants