ScalaプロジェクトのDockerイメージを作ってDockerHubにpushする
今作ってるアプリケーションはコマンドラインツールとして使う予定なのですが、graphvizとpandocがインストールされている環境に依存しています。
インストールしてから使ってね、でもいいのですが、どうせなのでgraphvizとpandocがインストール済みのDockerイメージを作って配布できるようにできるとかっこいいよね、と思って試してみました。
sbt-dockerプラグインの導入
まずはプロジェクトからdockerfileを出力するためのプラグインを導入します。
project/plugins.sbt
に以下を追加します。
addSbtPlugin("se.marcuslonnberg" % "sbt-docker" % "1.2.0")
また、 build.sbt
に以下を追加してください。
enablePlugins(DockerPlugin)
Dockerfileの生成ルール作成
sbt-dockerプラグインは docker
、 dockerPush
、dockerBuildAndPush
というタスクを追加します。これらのタスクでdockerイメージのビルドやpushなどを行うわけですが、イメージをビルドするためにはDockerfileを作らなければなりません。Dockerfileの生成ルールを build.sbt
に追記していきます。
まず、プロジェクトの package
タスクなどが生成するファイル(jarファイルなど)をDockerfileの対象にしたい場合は、 docker
タスクをそのタスクと関連づけて依存するように指定します。下記は package
タスクに依存させた例で、 docker
タスクを実行すると package
タスクが合わせて実行されるようにします。
docker <<= docker.dependsOn(sbt.Keys.`package`.in(Compile, packageBin))
ちなみにこの部分、githubのREADMEだと sbt.Keys
がないんですが、これがないとIntelliJのプロジェクトリフレッシュでエラーになります。サンプルには sbt.Keys
が書いてあるので、そっちを参考にしましょう。
さて、サンプルを参考にしてDockerfileの出力ルールを書いていきます。イメージ内の /app
以下を作業ディレクトリとし、そこにアプリケーションのjarファイルや依存関係のあるライブラリなどを配置します。また、実行時にイメージ内で参照するクラスパスなどを生成します。以下が実際の設定です。
dockerfile in docker := { // 各種設定値生成 val jarFile = artifactPath.in(Compile, packageBin).value val classpath = (managedClasspath in Compile).value val mainclass = mainClass.in(Compile, packageBin).value.getOrElse(sys.error("Expected exactly one main class")) val jarTarget = s"/app/${jarFile.getName}" val classpathString = classpath.files.map("/app" + _.getName).mkString(":") + ":" + jarTarget // Dockerファイル生成 new Dockerfile { from("java") add(classpath.files, "/app/") add(jarFile, jarTarget) run(“apt-get”, “update") run("apt-get", “-y”, "install", "graphviz") run("apt-get", “-y”, "install", "pandoc") entryPoint("java", "-cp", classpathString, mainclass) } }
前半部分は各種設定値を作っている箇所で、後半の Dockerfile
クラスで実際に生成しています。それぞれの設定値の意味合いは以下の通りです。
jarFile
: packageタスクで生成されるアプリケーションのjarファイルclasspath
: 他に依存関係のあるライブラリなどのクラスパスmainclass
:package
タスクで作られたjarファイルに格納されているmainクラスjarTarget
: 実行イメージのクラスパスに追加するアプリケーションjarファイルの配置先classpathString
: 実行イメージでjava -jar
を実行するときに指定するクラスパス
Dockerfile
クラスのAPIはだいたいDockerfileの設定名と一致しているので、問題ないかと思います。詳しくは以下を参照頂ければ。
run
や entryPoint
などの引数は可変長です。上記リンクには載っていないですが、ちゃんとcmdもある模様。
今回はアプリケーションの内部で graphviz
と pandoc
を使うため、 run
でそれらのインストールを指定しています。普通に apt-get
を走らせるとインストール確認のところでyes/noが判断できなくてこけるので、 -y
オプションを追加しています。
イメージをビルドする
まずはdokcerを立ち上げます。今回は新しいdocker-machineを作って、そこでビルドしてみることにしました。docker-machineはこんな感じで作っては壊しできるのでいいですね。
$ docker-machine create -d virtualbox scala-apps $ eval "$(docker-machine env scala-apps)"
で、おもむろに sbt docker
を叩きます。気になっていた部分の標準出力を抜粋します。
[info] Step 1 : ADD 0/scala-library-2.11.7.jar 1/plantuml-8031.jar 2/ammonite-ops_2.11-0.4.8.jar 3/pprint_2.11-0.3.4.jar 4/derive_2.11-0.3.4.jar 5/scala-parser-combinators_2.11-1.0.4.jar /app/ [info] ---> Using cache [info] ---> 895f23d21b27 [info] Step 2 : ADD 6/thoth_2.11-1.0.jar /app/thoth_2.11-1.0.jar [info] ---> Using cache [info] ---> 5c7fdf674ae2
依存関係のあるライブラリもちゃんとクラスパスに入れてくれています。
出来上がったイメージの情報を見てみましょう。
$ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE default/thoth latest 86029ed88cf2 59 seconds ago 895.7 MB java latest 7547e52aac4b 3 weeks ago 817.5 MB
できてますできてます。
DockerHubにpushする
まず、DockerHubにpushするには名前のルールが違うので *1 イメージ名をカスタマイズします。
imageNames in docker := Seq( ImageName(s"shinsukeabe/thoth:latest"), ImageName(namespace = Some("shinsukeabe"), repository = "thoth", tag = Some(version.value)) )
dockerPush
タスクで最新の状態と、バージョンごとにイメージがpushされるようにします。設定値はサンプルのパクリです。イメージ名を二つ指定してますが、このようにpushしておけば latest
タグを指定している限りは必ず最新に、特定のバージョンが欲しい場合はそのタグを指定するという流れにできるからでしょう。
というわけで dockerPush
タスクを実行します。
$ sbt dockerPush [info] Loading project definition from /Users/shinsuke-abe/IdeaProjects/Thoth/project [info] Set current project to Thoth (in build file:/Users/shinsuke-abe/IdeaProjects/Thoth/) [info] Pushing docker image with name: 'shinsukeabe/thoth:latest' [info] The push refers to a repository [docker.io/shinsukeabe/thoth] (len: 1) [info] 86029ed88cf2: Buffering to Disk [info] unauthorized: access to the requested resource is not authorized
こけましたね。DockerHubへのアクセス情報をどこにも指定してないのでそうだろうとは思ってましたけど。 docker login
してからやってみるとうまくいきました。CircleCIとかで仕掛ける場合は、プロジェクトの環境変数で指定してする前提でcircle.yml書くのが良さそうですね。CIからイメージpushまでのおおまかな流れができてきた気がします。
DockerHubのリポジトリです。ご査収ください。
https://hub.docker.com/r/shinsukeabe/thoth/
何か、ローカルで見た時の VIRTUAL SIZE
の値とDockerHub上の SIZE
の値が違うんですが、どういうことなんでしょうかねえ。。。
まとめ
ちなみに、まだ作成途中のツールなので利用方法も用意していませんし、未実装の機能とかもあります。使うなとは言いませんが、今は何の責任も負えないので悪しからず。というか、pushできただけで満足していて、まだイメージの動作確認とかしてませんので。