読者です 読者をやめる 読者になる 読者になる

冥冥乃志

ソフトウェア開発会社でチームマネージャをしているエンジニアの雑記。アウトプットは少なめです。

follow us in feedly

ScalaプロジェクトのDockerイメージを作ってDockerHubにpushする

今作ってるアプリケーションはコマンドラインツールとして使う予定なのですが、graphvizとpandocがインストールされている環境に依存しています。

github.com

インストールしてから使ってね、でもいいのですが、どうせなのでgraphvizとpandocがインストール済みのDockerイメージを作って配布できるようにできるとかっこいいよね、と思って試してみました。

sbt-dockerプラグインの導入

まずはプロジェクトからdockerfileを出力するためのプラグインを導入します。

github.com

project/plugins.sbt に以下を追加します。

addSbtPlugin("se.marcuslonnberg" % "sbt-docker" % "1.2.0")

また、 build.sbt に以下を追加してください。

enablePlugins(DockerPlugin)

Dockerfileの生成ルール作成

sbt-dockerプラグインdockerdockerPushdockerBuildAndPush というタスクを追加します。これらのタスクで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の設定名と一致しているので、問題ないかと思います。詳しくは以下を参照頂ければ。

velvia.github.io

runentryPoint などの引数は可変長です。上記リンクには載っていないですが、ちゃんとcmdもある模様。

今回はアプリケーションの内部で graphvizpandoc を使うため、 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できただけで満足していて、まだイメージの動作確認とかしてませんので。

*1:Dockerのイメージ名のルールは ユーザ名/リポジトリ名:タグ