Cloud Native Buildpacksをちょっとだけ触る

はじめに

Dockerfileを書くことなく、ソースコードからDockerイメージを作ることができると言うCloud Native Buildpacksという存在を知りました。

buildpacks.io

Dockerfileを色々と調べながら書くのはあんまり好きじゃなく、できれば楽できたらなと思ったところに知ったものなので、早速試してみました。

packコマンドのインストール

まず、packコマンドのインストールを行います。やり方は公式サイトにドキュメントがあるので、それを参考に。

buildpacks.io

私が試した時には、0.26.0というバージョンでした。

azureuser@myVM:~$ pack --version
0.26.0
azureuser@myVM:~$

また、Docker環境も整えておきます。

docs.docker.com

Javaのサンプルアプリを動かす

チュートリアルが用意されているので、それに従ってやってみます。

buildpacks.io

サンプルリポジトリをcloneして、packコマンドを実行します。

azureuser@myVM:~/work/samples/apps/java-maven$ pack build myapp --builder cnbs/sample-builder:bionic
bionic: Pulling from cnbs/sample-builder
(省略)
===> ANALYZING
===> DETECTING
[detector] samples/java-maven 0.0.1
===> RESTORING
[restorer] Restoring metadata for "samples/java-maven:jdk" from app image
[restorer] Restoring metadata for "samples/java-maven:maven_m2" from cache
[restorer] Restoring data for "samples/java-maven:jdk" from cache
[restorer] Restoring data for "samples/java-maven:maven_m2" from cache
===> BUILDING
[builder] ---> Java buildpack
[builder] ---> Installing JDK
[builder] ---> Running Maven Wrapper
[builder] [INFO] Scanning for projects...
[builder] [INFO]
[builder] [INFO] --------------------< io.buildpacks.example:sample >--------------------
[builder] [INFO] Building sample 0.0.1-SNAPSHOT
[builder] [INFO] --------------------------------[ jar ]---------------------------------
[builder] [INFO]
(省略)
[builder] [INFO] ------------------------------------------------------------------------
[builder] [INFO] BUILD SUCCESS
[builder] [INFO] ------------------------------------------------------------------------
[builder] [INFO] Total time:  2.827 s
[builder] [INFO] Finished at: 2022-05-29T08:05:21Z
[builder] [INFO] ------------------------------------------------------------------------
===> EXPORTING
[exporter] Reusing layer 'samples/java-maven:jdk'
[exporter] Adding 1/1 app layer(s)
[exporter] Reusing layer 'launcher'
[exporter] Reusing layer 'config'
[exporter] Reusing layer 'process-types'
[exporter] Adding label 'io.buildpacks.lifecycle.metadata'
[exporter] Adding label 'io.buildpacks.build.metadata'
[exporter] Adding label 'io.buildpacks.project.metadata'
[exporter] Setting default process type 'web'
[exporter] Saving myapp...
[exporter] *** Images (d8a7e88bd822):
[exporter]       myapp
[exporter] Reusing cache layer 'samples/java-maven:jdk'
[exporter] Adding cache layer 'samples/java-maven:maven_m2'
Successfully built image myapp
azureuser@myVM:~/work/samples/apps/java-maven$

Mavenによるビルドが実行され、Dockerイメージが作成されました。

azureuser@myVM:~/work/samples/apps/java-maven$ docker images
REPOSITORY               TAG       IMAGE ID       CREATED        SIZE
heroku/pack              20        187818685cd8   8 hours ago    602MB
cnbs/sample-stack-run    bionic    0aa0fb9222a5   5 months ago   70.2MB
hello-world              latest    feb5d9fea6a5   8 months ago   13.3kB
buildpacksio/lifecycle   0.13.1    76412e6be4e1   42 years ago   16.4MB
heroku/buildpacks        20        51904c87109f   42 years ago   1.56GB
cnbs/sample-builder      bionic    83509780fa67   42 years ago   181MB
myapp                    latest    d8a7e88bd822   42 years ago   300MB  ## ← 作られたDockerイメージ
azureuser@myVM:~/work/samples/apps/java-maven$

アプリを動かしてみます。

azureuser@myVM:~/work/samples/apps/java-maven$ docker run --rm -p 8080:8080 myapp
    |'-_ _-'|       ____          _  _      _                      _             _
    |   |   |      |  _ \        (_)| |    | |                    | |           (_)
     '-_|_-'       | |_) | _   _  _ | |  __| | _ __    __ _   ___ | | __ ___     _   ___
|'-_ _-'|'-_ _-'|  |  _ < | | | || || | / _` || '_ \  / _` | / __|| |/ // __|   | | / _ \
|   |   |   |   |  | |_) || |_| || || || (_| || |_) || (_| || (__ |   < \__ \ _ | || (_) |
 '-_|_-' '-_|_-'   |____/  \__,_||_||_| \__,_|| .__/  \__,_| \___||_|\_\|___/(_)|_| \___/
                                              | |
                                              |_|

:: Built with Spring Boot :: 2.1.18.RELEASE

2022-05-29 08:09:28.225  INFO 1 --- [           main] i.b.example.sample.SampleApplication     : Starting SampleApplication v0.0.1-SNAPSHOT on c6e7f160ad2c with PID 1 (/workspace/target/sample-0.0.1-SNAPSHOT.jar started by cnb in /workspace)
2022-05-29 08:09:28.229  INFO 1 --- [           main] i.b.example.sample.SampleApplication     : No active profile set, falling back to default profiles: default
2022-05-29 08:09:29.912  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2022-05-29 08:09:29.975  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-05-29 08:09:29.976  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.39]
2022-05-29 08:09:30.096  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-05-29 08:09:30.097  INFO 1 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1766 ms
2022-05-29 08:09:30.377  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2022-05-29 08:09:30.463  INFO 1 --- [           main] o.s.b.a.w.s.WelcomePageHandlerMapping    : Adding welcome page template: index
2022-05-29 08:09:30.635  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2022-05-29 08:09:30.639  INFO 1 --- [           main] i.b.example.sample.SampleApplication     : Started SampleApplication in 3.089 seconds (JVM running for 3.708)

ブラウザでアクセスしてみると、以下のような画面が出てきました。

Rubyのサンプルアプリを試す

サンプルリポジトリにはruby-bundlerというSinatraで作られたアプリがあるので、これを試してみます。

Javaの時と同じようにpackコマンドを使ってDockerイメージを作成します。

azureuser@myVM:~/work/samples/apps/ruby-bundler$ pack build myapp-ruby --builder cnbs/sample-builder:bionic
bionic: Pulling from cnbs/sample-builder
Digest: sha256:f9aa933450900df3a91a5f405c4a55ed3bb9decc9ee503b2dd85ff728db75e22
Status: Image is up to date for cnbs/sample-builder:bionic
bionic: Pulling from cnbs/sample-stack-run
Digest: sha256:7b0946aa031f1ddd30bda8963eb33431458a25a576208646348ce89536fef59a
Status: Image is up to date for cnbs/sample-stack-run:bionic
0.13.1: Pulling from buildpacksio/lifecycle
Digest: sha256:f2cb410c8e204e6d2c6d86b1a171862595d8dccefc35197804eff09ab9221219
Status: Image is up to date for buildpacksio/lifecycle:0.13.1
===> ANALYZING
===> DETECTING
[detector] samples/ruby-bundler 0.0.1
===> RESTORING
[restorer] Restoring metadata for "samples/ruby-bundler:bundler" from app image
[restorer] Restoring metadata for "samples/ruby-bundler:ruby" from app image
[restorer] Restoring data for "samples/ruby-bundler:bundler" from cache
===> BUILDING
[builder] ---> Ruby Buildpack
[builder] ---> Downloading and extracting Ruby 2.5.0
[builder] ---> Installing bundler
[builder] Successfully installed bundler-2.3.14
[builder] 1 gem installed
[builder] ---> Reusing gems
[builder] Your RubyGems version (2.7.6.2) has a bug that prevents `required_ruby_version` from working for Bundler. Any scripts that use `gem install bundler` will break as soon as Bundler drops support for your Ruby version. Please upgrade RubyGems to avoid future breakage and silence this warning by running `gem update --system 3.2.3`
[builder] Your RubyGems version (2.7.6.2) has a bug that prevents `required_ruby_version` from working for Bundler. Any scripts that use `gem install bundler` will break as soon as Bundler drops support for your Ruby version. Please upgrade RubyGems to avoid future breakage and silence this warning by running `gem update --system 3.2.3`
===> EXPORTING
[exporter] Reusing layer 'samples/ruby-bundler:bundler'
[exporter] Reusing layer 'samples/ruby-bundler:ruby'
[exporter] Adding 1/1 app layer(s)
[exporter] Reusing layer 'launcher'
[exporter] Reusing layer 'config'
[exporter] Reusing layer 'process-types'
[exporter] Adding label 'io.buildpacks.lifecycle.metadata'
[exporter] Adding label 'io.buildpacks.build.metadata'
[exporter] Adding label 'io.buildpacks.project.metadata'
[exporter] Setting default process type 'web'
[exporter] Saving myapp-ruby...
[exporter] *** Images (360bd2e06828):
[exporter]       myapp-ruby
[exporter] Reusing cache layer 'samples/ruby-bundler:bundler'
Successfully built image myapp-ruby
azureuser@myVM:~/work/samples/apps/ruby-bundler$

docker runコマンドを使って動かします。

azureuser@myVM:~/work/samples/apps/ruby-bundler$ docker run --rm -p 8080:8080 myapp-ruby
Hello from .profile
Your RubyGems version (2.7.6.2) has a bug that prevents `required_ruby_version` from working for Bundler. Any scripts that use `gem install bundler` will break as soon as Bundler drops support for your Ruby version. Please upgrade RubyGems to avoid future breakage and silence this warning by running `gem update --system 3.2.3`
[2022-05-29 08:18:51] INFO  WEBrick 1.4.2
[2022-05-29 08:18:51] INFO  ruby 2.5.0 (2017-12-25) [x86_64-linux]
== Sinatra (v2.0.7) has taken the stage on 8080 for development with backup from WEBrick
[2022-05-29 08:18:51] INFO  WEBrick::HTTPServer#start: pid=1 port=8080

ブラウザでアクセスしてみます。大変簡素な画面ですが、無事動きました。

Rubyサンプルアプリのバージョンアップを行う

サンプルアプリのRubyのバージョンは2.5だったので最新バージョンである3.1.2で動かすことにします。

最初は.ruby-versionを変更すれば良いだけでしょと思っていたのですが、それだけではうまく動きませんでした。

まずはローカルで動かす

まずはローカルで動かしてみます。ruby app.rbを実行すると、以下のようなメッセージが出てうまく動きません。

azureuser@myVM:~/work/samples/apps/ruby-builder3$ ruby app.rb
/home/azureuser/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-2.2.3.1/lib/rack/handler.rb:45:in `pick': Couldn't find handler for: thin, puma, reel, HTTP, webrick. (LoadError)
    from /home/azureuser/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/sinatra-2.2.0/lib/sinatra/base.rb:1503:in `run!'
    from /home/azureuser/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/sinatra-2.2.0/lib/sinatra/main.rb:45:in `block in <module:Sinatra>'
azureuser@myVM:~/work/samples/apps/ruby-builder3$

これはwebrickが最近のRubyでは標準ではインストールされないために起こる問題です。gem "webrick"をGemfileに追加してbundle installします。また、bundle update --bundlerbundle updateを実行して、大量に出てくるdeprecatedを消しておきます。これでローカルで動かすことができます。

azureuser@myVM:~/work/samples/apps/ruby-builder3$ ruby app.rb
[2022-05-29 08:37:43] INFO  WEBrick 1.7.0
[2022-05-29 08:37:43] INFO  ruby 3.1.2 (2022-04-12) [x86_64-linux]
== Sinatra (v2.2.0) has taken the stage on 8080 for development with backup from WEBrick
[2022-05-29 08:37:43] INFO  WEBrick::HTTPServer#start: pid=34563 port=8080

Dockerイメージを作成する

続いてpackコマンドでDockerイメージを作成してみます。これまでと同じようにイメージ名だけ変えて動かしてみたら以下のメッセージが出てDockerイメージは作られませんでした。

azureuser@myVM:~/work/samples/apps/ruby-builder3$ pack build myapp-ruby3 --builder cnbs/sample-builder:bionic
bionic: Pulling from cnbs/sample-builder
(省略)
Status: Image is up to date for buildpacksio/lifecycle:0.13.1
===> ANALYZING
[analyzer] Previous image with name "myapp-ruby3" not found
===> DETECTING
[detector] samples/ruby-bundler 0.0.1
===> RESTORING
===> BUILDING
[builder] ---> Ruby Buildpack
[builder] ---> Downloading and extracting Ruby 3.1.2
[builder] ---> Installing bundler
[builder] <internal:/layers/samples_ruby-bundler/ruby/lib/ruby/3.1.0/rubygems/core_ext/kernel_require.rb>:85:in `require': libyaml-0.so.2: cannot open shared object file: No such file or directory - /layers/samples_ruby-bundler/ruby/lib/ruby/3.1.0/x86_64-linux/psych.so (LoadError)
(省略)
[builder] ERROR: failed to build: exit status 1
ERROR: failed to build: executing lifecycle. This may be the result of using an untrusted builder: failed with status code: 51
azureuser@myVM:~/work/samples/apps/ruby-builder3$

これを解決するには--builderで指定した値を変えます。どの値を指定していいかわかりませんでしたが、以下のマニュアルにあるようにpack builder suggestコマンドで提示されたものを指定してみます。

buildpacks.io

今回は、heroku/buildpacks:20を指定してみることにします。

azureuser@myVM:~/work/samples/apps/ruby-builder3$ pack build myapp-ruby3 --builder heroku/buildpacks:20
20: Pulling from heroku/buildpacks
(省略)
Status: Image is up to date for heroku/pack:20
===> ANALYZING
Previous image with name "myapp-ruby3" not found
===> DETECTING
1 of 2 buildpacks participating
heroku/ruby 0.1.3
===> RESTORING
===> BUILDING
-----> Installing bundler 2.2.21
-----> Removing BUNDLED WITH version in the Gemfile.lock
-----> Compiling Ruby/Rack
-----> Using Ruby version: ruby-2.7.4
-----> Loading Bundler Cache
-----> Installing dependencies using bundler 2.2.21
       Running: BUNDLE_WITHOUT='development:test' BUNDLE_PATH=/layers/heroku_ruby/gems/vendor/bundle BUNDLE_BIN=vendor/bundle/bin BUNDLE_DEPLOYMENT=1 bundle install -j4
       Fetching gem metadata from https://rubygems.org/
       Fetching gem metadata from https://rubygems.org/....
(省略)
-----> Detecting rake tasks

###### WARNING:

       You have not declared a Ruby version in your Gemfile.

       To declare a Ruby version add this line to your Gemfile:

       ```
       ruby "2.7.4"
       ```

       For more information see:
         https://devcenter.heroku.com/articles/ruby-versions



===> EXPORTING
(省略)
Saving myapp-ruby3...
*** Images (163a27f2364c):
      myapp-ruby3
Adding cache layer 'heroku/ruby:gems'
Successfully built image myapp-ruby3

今度はうまくDockerイメージが作られましたが、Rubyのバージョンが2.7.4になっています。ログの警告にあるようにGemfileRubyのバージョンが書かれていないのが原因のようです。このため、Gemfile内にruby "3.1.2"と書いておきます。bundle updateもして再度pack buildしてみます。

azureuser@myVM:~/work/samples/apps/ruby-builder3$ pack build myapp-ruby3 --builder heroku/buildpacks:20
20: Pulling from heroku/buildpacks
(省略)
===> BUILDING
-----> Installing bundler 2.2.21
-----> Removing BUNDLED WITH version in the Gemfile.lock
-----> Compiling Ruby/Rack
-----> Using Ruby version: ruby-3.1.2
-----> Loading Bundler Cache
-----> Installing dependencies using bundler 2.2.21
       Running: BUNDLE_WITHOUT='development:test' BUNDLE_PATH=/layers/heroku_ruby/gems/vendor/bundle BUNDLE_BIN=vendor/bundle/bin BUNDLE_DEPLOYMENT=1 bundle install -j4
(省略)
Successfully built image myapp-ruby3
azureuser@myVM:~/work/samples/apps/ruby-builder3$

無事イメージが作られました。

あとはdocker runすればいいだけと思っていたのですが、まだ動きません。

azureuser@myVM:~/work/samples/apps/ruby-builder3$ docker run --rm -p 8080:8080 myapp-ruby3
Hello from .profile
configuration /workspace/config.ru not found
azureuser@myVM:~/work/samples/apps/ruby-builder3$

動かすためには、Procfileを作成します。Procfileについては以下を参照。

devcenter.heroku.com

今回は次のような内容にします。

web: ruby app.rb

Procfileを作成後、再度pack buildを実行し、docker runすると無事動かすことができました。

azureuser@myVM:~/work/samples/apps/ruby-builder3$ docker run --rm -p 8080:8080 myapp-ruby3
Hello from .profile
[2022-05-29 08:57:06] INFO  WEBrick 1.7.0
[2022-05-29 08:57:06] INFO  ruby 3.1.2 (2022-04-12) [x86_64-linux]
== Sinatra (v2.2.0) has taken the stage on 8080 for production with backup from WEBrick
[2022-05-29 08:57:06] INFO  WEBrick::HTTPServer#start: pid=1 port=8080