jibでDockerfileを作らずにコンテナをビルドする
背景
Dockerのようなコンテナは、"プロセスの仮想化"であって、VMの代わりではありません。インフラの管理や、リソースの管理という次元の話ではないのです。 "常駐プロセス"を仮想的に動かすもの、という制限すらありません。
だからこそ、コンテナは、DevOpsの文脈において、コードを書くアプリ開発者に大きく関わる事なのです。 アプリ開発者は作ったコードをコンテナに入れて、すぐに使えるようにしておくのです。 成果物はそのコンテナ自身も含まれ、毎週の打ち合わせでは、すぐに動く成果物でレビューが出来るのです。
課題
ただ、動く成果物を用意すると言っても、Javaデベロッパーにとってどうでもいい(あまり時間を使いたくない)話がつきまといます。
- Docker daemonの管理
- Dockerfileの作成・管理
- イメージサイズを減らすための検討をする
- 適切なベースイメージを選択する
.dockerignore
ファイルを作成する- Mavenプラグインでコンテナビルドするように修正する
今のDockerでは、Javaデベロッパーがこれらをやらなければなりません。 とても面倒です。 Javaデベロッパーは、このようなことは気にせず、極上のアプリを作る事に専念すべきです。
Jib
そこでGoogleがJibというものを開発しました。 Jibが何か、内部でどんな動きがされているかは、Googleのブログを見た方が正確でしょう。
より詳しく背景や内容を見たい場合は、発表資料が良いと思います。
ここでは、具体的に使い方を見ていきましょう。
準備
まず、以下のものはインストールします。
Gradleを利用した日本語での解説ページが見つからなかったので、Gradleでやってみましょう。
あと、これから以降で入力するコマンドは、全てsuper user状態で入力しています。 その理由は、Dockerコンテナをビルドするときに、Docker daemonを利用する必要があり、そこでsuper user権限が必要なためです。
使い方
ローカルにDockerイメージを作成して保存してみましょう。
まず、サンプル用に、Spring bootのひな形プロジェクトを持ってきます。 Spring Initializrを利用しましょう。 Spring Initializrの説明は、GitHubのREADMEに詳しく記載されています。
# curl https://start.spring.io/starter.tgz -d dependencies=web,actuator \ -d language=java -d type=gradle-project -d baseDir=jib-sample-app | tar -xzvf - # cd jib-sample-app # vim build.gradle
恐らく、build.gradle
には、このように書かれています。
buildscript { ext { springBootVersion = '2.0.4.RELEASE' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile('org.springframework.boot:spring-boot-starter-actuator') compile('org.springframework.boot:spring-boot-starter-web') testCompile('org.springframework.boot:spring-boot-starter-test')
これに、binary pluginを追加します。
buildscript { ext { springBootVersion = '2.0.4.RELEASE' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } plugins { id 'com.google.cloud.tools.jib' version '0.9.0' } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile('org.springframework.boot:spring-boot-starter-actuator') compile('org.springframework.boot:spring-boot-starter-web') testCompile('org.springframework.boot:spring-boot-starter-test')
あとは、gradleコマンドで、追加したプラグインを利用して、Dockerイメージをビルドしましょう。
# gradle jibDockerBuild
ビルドが成功したら、docker images
で、Docker daemonに保存されたDockerイメージを参照してみましょう。
REPOSITORY TAG IMAGE ID CREATED SIZE demo 0.0.1-SNAPSHOT c88b80900e92 292 years ago 136MB
保存されていました。REPOSITORYは、demoになっています。
demoというワードがbuild.gradle
には存在していなかったので、どこから引っ張られたのか調べてみました。
# grep -r demo . ./settings.gradle:rootProject.name = 'demo' ./src/main/java/com/example/demo/DemoApplication.java:package com.example.demo; ./src/test/java/com/example/demo/DemoApplicationTests.java:package com.example.demo; Binary file ./.gradle/4.9/taskHistory/taskHistory.bin matches Binary file ./build/classes/java/main/com/example/demo/DemoApplication.class matches
settings.gradle:rootProject.name
で指定されたディレクトリ名が、REPOSITORY名になります。
TAGは簡単で、build.gradle
に記述されたversionの値です。
CREATEDが、謎ですね笑
作成したコンテナの利用
Googleのブログなどにも、上までの使い方しか書かれていませんが、作ったアプリは使ってなんぼです。
# docker run -p 8080:8080 --name jib_demo demo:0.0.1-SNAPSHOT . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.0.4.RELEASE) 2018-08-26 05:43:06.634 INFO 1 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication on dd72c49cdd03 with PID 1 (/app/classes started by root in /) 2018-08-26 05:43:06.639 INFO 1 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default 2018-08-26 05:43:06.809 INFO 1 --- [ main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@568bf312: startup date [Sun Aug 26 05:43:06 GMT 2018]; root of context hierarchy 2018-08-26 05:43:10.362 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2018-08-26 05:43:10.429 INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2018-08-26 05:43:10.434 INFO 1 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.32 2018-08-26 05:43:10.468 INFO 1 --- [ost-startStop-1] o.a.catalina.core.AprLifecycleListener : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/usr/java/packages/lib/amd64:/usr/lib/x86_64-linux-gnu/jni:/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu:/usr/lib/jni:/lib:/usr/lib] 2018-08-26 05:43:10.698 INFO 1 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2018-08-26 05:43:10.699 INFO 1 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 3898 ms 2018-08-26 05:43:11.873 INFO 1 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*] 2018-08-26 05:43:11.878 INFO 1 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'webMvcMetricsFilter' to: [/*] 2018-08-26 05:43:11.878 INFO 1 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*] 2018-08-26 05:43:11.878 INFO 1 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*] 2018-08-26 05:43:11.878 INFO 1 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*] 2018-08-26 05:43:11.878 INFO 1 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpTraceFilter' to: [/*] 2018-08-26 05:43:11.878 INFO 1 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Servlet dispatcherServlet mapped to [/] 2018-08-26 05:43:12.108 INFO 1 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2018-08-26 05:43:12.440 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@568bf312: startup date [Sun Aug 26 05:43:06 GMT 2018]; root of context hierarchy 2018-08-26 05:43:12.595 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) 2018-08-26 05:43:12.597 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest) 2018-08-26 05:43:12.665 INFO 1 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2018-08-26 05:43:12.666 INFO 1 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2018-08-26 05:43:13.176 INFO 1 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 2 endpoint(s) beneath base path '/actuator' 2018-08-26 05:43:13.192 INFO 1 --- [ main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator/health],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>) 2018-08-26 05:43:13.198 INFO 1 --- [ main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator/info],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>) 2018-08-26 05:43:13.199 INFO 1 --- [ main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto protected java.util.Map<java.lang.String, java.util.Map<java.lang.String, org.springframework.boot.actuate.endpoint.web.Link>> org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping.links(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) 2018-08-26 05:43:13.312 INFO 1 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup 2018-08-26 05:43:13.420 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2018-08-26 05:43:13.429 INFO 1 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 7.652 seconds (JVM running for 8.559) 2018-08-26 05:44:36.546 INFO 1 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet' 2018-08-26 05:44:36.546 INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started 2018-08-26 05:44:36.578 INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 31 ms
では、ブラウザから、http://(ホスト名):8080
にアクセスしてみましょう。
恐らく、Spring Bootのエラーページが出力されているものと思われます。
自動的に、Dockerfileに記載すべきENTRYPOINTの設定まで行われているのでしょう。