Dockerコンテナの作成

Estimated reading time: 2 minutes

Dockerはコンテナプラットフォームです。 それを使うことで任意のオペレーティングシステムで独立した形で動くようソフトウェアのパッケージングをすることができます。

KtorアプリケーションをDockerで配信することは非常に簡単で、数ステップでできます。

  • Dockerのインストール
  • JARパッケージングツール

このページではDockerイメージの作成とイメージへのアプリケーションの設置方法について説明します。

目次:

Gradleを使ったアプリケーションのパッケージング

このチュートリアルでは、Gradle shadow pluginを使います。 それを使うことで、全Outputクラス・resource・必要な依存ライブラリを1つのJarファイルにパッケージングでき、 またmainメソッドを含むエントリーポイントmainクラスがどれかをJavaに知らせるマニフェストファイルも添付することができます。

初めに、shadow pluginへの依存を、build.gradleファイルに追加する必要があります。

buildscript {
    ...
    repositories {
        ...
        maven { url "https://plugins.gradle.org/m2/" }
    }
    dependencies {
        ...
        classpath "com.github.jengelman.gradle.plugins:shadow:2.0.1"
    }
}

その後、applicationプラグインと一緒にapplyする必要があります。

apply plugin: "com.github.johnrengelman.shadow"
apply plugin: 'application'

Docker内部でJavaのJARが起動する際にどれを起動すればよいのか伝えるため、mainクラスを指定します。

mainClassName = 'org.sample.ApplicationKt'

設定する値はmain関数を含むクラスの完全修飾名です。 main関数がファイルのトップレベル関数だったときは、クラス名はファイル名にKtサフィックスを付与したものになります。 上の例だと、main関数はorg.sampleパッケージのApplication.ktファイルになります。

最後に、shadow pluginを設定する必要があります。

shadowJar {
    baseName = 'my-application'
    classifier = null
    version = null
}

今、./gradlew buildを実行することで、アプリケーションをビルド・パッケージングすることができます。 build/libsフォルダにmy-application.jarができるかと思います。

さらなる情報としては、プラグインの設定方法のドキュメントを御覧ください。

完全なbuild.gradleは例えば以下のようになります。

build.gradle
buildscript {
    ext.kotlin_version = '1.3.70'
    ext.ktor_version = '1.3.2'
    ext.logback_version = '1.2.3'
    ext.slf4j_version = '1.7.25'
    repositories {
        jcenter()
        maven { url "https://plugins.gradle.org/m2/" }
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "com.github.jengelman.gradle.plugins:shadow:2.0.1"
    }
}

apply plugin: 'kotlin'
apply plugin: "com.github.johnrengelman.shadow"
apply plugin: 'application'

mainClassName = "io.ktor.server.netty.EngineMain"

sourceSets {
    main.kotlin.srcDirs = [ 'src' ]
    main.resources.srcDirs = [ 'resources' ]
}

repositories {
    jcenter()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    compile "io.ktor:ktor-server-netty:$ktor_version"
    compile "io.ktor:ktor-html-builder:$ktor_version"
    compile "ch.qos.logback:logback-classic:$logback_version"
}

kotlin.experimental.coroutines = 'enable'

shadowJar {
    baseName = 'my-application'
    classifier = null
    version = null
}
resources/application.conf
ktor {
    deployment {
        port = 8080
    }

    application {
        modules = [ io.ktor.samples.hello.HelloApplicationKt.main ]
    }
}
src/HelloApplication.kt
package io.ktor.samples.hello

import io.ktor.application.*
import io.ktor.features.*
import io.ktor.html.*
import io.ktor.routing.*
import kotlinx.html.*

fun Application.main() {
    install(DefaultHeaders)
    install(CallLogging)
    routing {
        get("/") {
            call.respondHtml {
                head {
                    title { +"Ktor: jetty" }
                }
                body {
                    p {
                        +"Hello from Ktor Jetty engine sample application"
                    }
                }
            }
        }
    }
}

動作例をktor-samplesリポジトリで確認することもできます。

Dockerイメージの準備

プロジェクトのrootフォルダ内で、Dockerfileという名前のファイルを以下の内容で作成してください。

Dockerfile
FROM openjdk:8-jre-alpine

ENV APPLICATION_USER ktor
RUN adduser -D -g '' $APPLICATION_USER

RUN mkdir /app
RUN chown -R $APPLICATION_USER /app

USER $APPLICATION_USER

COPY ./build/libs/my-application.jar /app/my-application.jar
WORKDIR /app

CMD ["java", "-server", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap", "-XX:InitialRAMFraction=2", "-XX:MinRAMFraction=2", "-XX:MaxRAMFraction=2", "-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", "-XX:+UseStringDeduplication", "-jar", "my-application.jar"]

これが何なのか見てみましょう。

FROM openjdk:8-jre-alpine

この行はDockerに対し、pre-builtイメージであるAlpine Linuxをベースにすることを伝えています。 もちろん他のイメージをOpenJDK registryから利用することもできます。 Alpine Linuxのメリットはイメージがとても小さいことです。

また、イメージ内のコードをコンパイルする必要がなくコンパイル済みクラスの実行のみしか行わない場合、JREのみのイメージを選択するといったこともできます。

RUN mkdir /app
COPY ./build/libs/my-application.jar /app/my-application.jar
WORKDIR /app

これらの行はパッケージされたアプリケーションをDockerイメージ内にコピーし、コピー先ディレクトリをWorkingディレクトリとして指定しています。

CMD ["java", "-server", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap", "-XX:InitialRAMFraction=2", "-XX:MinRAMFraction=2", "-XX:MaxRAMFraction=2", "-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", "-XX:+UseStringDeduplication", "-jar", "my-application.jar"]

最終行は、Dockerにjavaをオプション・パッケージしたアプリケーション指定で起動するように指定しています。

Dockerイメージのビルド・起動方法

アプリケーションパッケージのビルド:

./gradlew build

ビルドとイメージへのタグ付け:

docker build -t my-application .

イメージの起動:

docker run -m512M --cpus 2 -it -p 8080:8080 --rm my-application

このコマンドでDockerをforegroundモードで実行できます。 サーバーが終了するのを待ち、またCtrl+Cを押すことでも終了することができるモードです。 -itオプションを使うことでDockerにターミナル(tty)を割り当て、stdoutをパイプし、キー割り込みに反応するようにすることができます。

サーバーが独立したコンテナ内で今動いているため、Dockerに実際にサーバーにアクセスするためのportを開けるように指定する必要があります。 パラメーター-p 8080:8080はDockerにコンテナ内部のport番号8080を、ローカルマシンのport番号8080番として利用可能にするよう指定します。 それゆえにlocalhost:8080にブラウザ経由でアクセスした際、最初にDockerに到達した上でアプリケーションの8080番ポートにブリッジされます。

-m512Mオプションでメモリを、--cpus 2オプションでCPU数を割当てできます。

デフォルトでは、コンテナのファイルシステムはコンテナが終了後も残るようになっていますが、--rmオプションをつけることでゴミファイルが残ることを防げます。

Dockerイメージの起動についてのさらなる情報はdocker runに関するドキュメントをご覧ください。

DockerイメージのPush

アプリケーションがローカルで無事起動したならば、次はそれをデプロイしてみましょう。

docker tag my-application hub.example.com/docker/registry/tag
docker push hub.example.com/docker/registry/tag

これらのコマンドはアプリケーションにタグ付けと、ImageのPushをしています。 もちろん、hub.example.com/docker/registry/tagをあなたのレジストリの実際のURLと置き換えることもできます。

ここでは詳細に触れませんが、認証や特定のオプションや特定のツールに関する設定が必要とされることもあります。 あなたの組織やクラウドプラットフォームの情報を探してみたり、docker pushに関するドキュメントを確認してみてください。

サンプル

動作サンプルはktor-samplesリポジトリで確認できます。