--- title: Cloud Foundryのbuildpackを使ってDockerイメージを作成しKubernetesにデプロイ tags: ["Cloud Foundry", "Docker", "Kubernetes"] categories: ["Dev", "CaaS", "Kubernetes"] date: 2017-08-21T05:32:40Z updated: 2017-08-21T05:53:23Z --- **目次** ### BuildpackとDockerfile Cloud Foundryの[buildpack](https://docs.cloudfoundry.org/buildpacks/)は便利で、"Source Code to Container Image"を実現してくれます。 Dockerを用いてアプリケーションをデプロイする場合は、一般的に 1. ソースコード作成/修正 1. `Dockerfile`作成/修正 1. Dockerイメージの作成 1. Dockerイメージのデプロイ 1. Dockerイメージを使ってアプリケーション(コンテナ)をデプロイ ですが、Cloud Foundryではbuildpackを使うことにより 1. ソースコード作成/修正 1. `cf push`コマンドでアプリケーション(コンテナ)をデプロイ で完了します。 Dockerの場合、通常`Dockerfile`を作ることになりますが、これをメンテナンスするということは、アプリケーションコードのメンテナンスの他に * OSイメージ(opensshの脆弱性対応など) * ランタイム/ミドルウェア(Javaの場合JDKやTomcat、PHPの場合Apache HTTPやmod_phpなど) のメンテナンスもしなくてはなりません。`Dockerfile`は自由度が高い反面、ケアすべき点も多いです。 一方、buildpackではrootfsとして[cflinuxfs2](https://github.com/cloudfoundry/cflinuxfs2)というUbuntu14.04の派生のイメージを使用します。定期的にセキュリティパッチがあてられています。 また各言語ごとにランタイム/ミドルウェアがメンテナンスされており、原則としてCVE(Common Vulnerabilities and Exposures)公開後48時間以内にパッチがリリースされます。 これにより、基本的にアプリケーションコードにのみ集中することができます。 個人的には、アプリケーションデプロイの観点では`Dockerfile`の作成はあまりやりたい作業でありません。Buildpack方式のほうが好みです。 ### CF Local Plugin [CF Local Plugin](https://github.com/sclevine/cflocal)を使用することで、前述のbuildpackによるメリットをDockerイメージ作成にも適用できます。 CF LocalはLaptop内で`cf push`をエミュレートするプラグインですが、DockerイメージへのExportに対応しています。 これを使うことにより、Dockerを用いてアプリケーションをデプロイする場合も、 1. ソースコード作成/修正 1. CF LocalでDockerイメージの作成 1. Dockerイメージのデプロイ 1. Dockerイメージを使ってアプリケーション(コンテナ)をデプロイ になり、`Dockerfile`のメンテナンスから解放されます。 これでBuildpackのメリットを活かしつつ、DockerイメージをKubernetesにデプロイするといったことが可能になります。 ### CF Localの利用 では実際にCF Localで"Source Code to Docker Image"を行い、アプリケーションをk8sにデプロイしてみます。 #### Cloud Foundry CLIのインストール まずは`cf`コマンドが必要です。golangの実行可能バイナリなので、インストールは容易です。 CF CLIのインストールは[こちら](https://github.com/cloudfoundry/cli/releases)を参照してください。 Macの場合は、 ``` brew install cloudfoundry/tap/cf-cli ``` でインストールできます。 #### CF Local Pluginのインストール CF Local Pluginのバイナリは[こちら](https://github.com/sclevine/cflocal/releases)からダウンロード可能です。 Macの場合は、 ``` curl -L -J -O https://github.com/sclevine/cflocal/releases/download/v0.13.0/cflocal-0.13.0-macos cf install-plugin cflocal-0.13.0-macos ``` でインストールできます。 #### コンテナイメージの作成 今回は次の簡単なJavaアプリケーション(ただのサーブレット)を題材に使用します。 https://github.com/making/hello-servlet ``` git clone git@github.com:making/hello-servlet.git cd hello-servlet ./mvnw package ``` で`target/ROOT.war`ファイルが作成されます。 その後、`cf local stage`コマンドでステージングを行い、コンテナイメージ(Droplet)を作成します。 ``` cf local stage hello-servlet -p ./target/ROOT.war ``` 次のようなログが出力されます。JDKやTomcatがダウンロードされていることがわかります。またコンテナのメモリサイズ(デフォルトで1GB)に合わせて、JVMのメモリの設定が行われていることもわかります。メモリサイズは`-m`で変更可能です。 ``` Buildpack: will detect [hello-servlet] 2017-08-21T04:32:48.695440393Z -----> Java Buildpack Version: v3.17 | https://github.com/cloudfoundry/java-buildpack.git#87fb619 [hello-servlet] 2017-08-21T04:32:51.033834076Z -----> Downloading Open Jdk JRE 1.8.0_141 from https://java-buildpack.cloudfoundry.org/openjdk/trusty/x86_64/openjdk-1.8.0_141.tar.gz (2.1s) [hello-servlet] 2017-08-21T04:32:51.977072148Z Expanding Open Jdk JRE to .java-buildpack/open_jdk_jre (0.9s) [hello-servlet] 2017-08-21T04:32:52.094011807Z -----> Downloading Open JDK Like Memory Calculator 2.0.2_RELEASE from https://java-buildpack.cloudfoundry.org/memory-calculator/trusty/x86_64/memory-calculator-2.0.2_RELEASE.tar.gz (0.1s) [hello-servlet] 2017-08-21T04:32:52.117972916Z Memory Settings: -Xss349K -Xmx681574K -XX:MaxMetaspaceSize=104857K -Xms681574K -XX:MetaspaceSize=104857K [hello-servlet] 2017-08-21T04:32:52.223085215Z -----> Downloading Container Security Provider 1.8.0_RELEASE from https://java-buildpack.cloudfoundry.org/container-security-provider/container-security-provider-1.8.0_RELEASE.jar (0.1s) [hello-servlet] 2017-08-21T04:32:53.174939975Z -----> Downloading Tomcat Instance 8.0.46 from https://java-buildpack.cloudfoundry.org/tomcat/tomcat-8.0.46.tar.gz (0.9s) [hello-servlet] 2017-08-21T04:32:53.278926558Z Expanding Tomcat Instance to .java-buildpack/tomcat (0.1s) [hello-servlet] 2017-08-21T04:32:53.436115761Z -----> Downloading Tomcat Lifecycle Support 2.5.0_RELEASE from https://java-buildpack.cloudfoundry.org/tomcat-lifecycle-support/tomcat-lifecycle-support-2.5.0_RELEASE.jar (0.1s) [hello-servlet] 2017-08-21T04:32:53.668302543Z -----> Downloading Tomcat Logging Support 2.5.0_RELEASE from https://java-buildpack.cloudfoundry.org/tomcat-logging-support/tomcat-logging-support-2.5.0_RELEASE.jar (0.2s) [hello-servlet] 2017-08-21T04:32:53.747964428Z -----> Downloading Tomcat Access Logging Support 2.5.0_RELEASE from https://java-buildpack.cloudfoundry.org/tomcat-access-logging-support/tomcat-access-logging-support-2.5.0_RELEASE.jar (0.0s) Successfully staged: hello-servlet ``` Gifアニメも貼っておきます。 ![cflocal](https://user-images.githubusercontent.com/106908/29503885-8d602192-8676-11e7-9a10-3b730cfaf689.gif) > Java以外の場合、`-p`は不要で、カレントディレクトリからステージングが行われます。 作成されたコンテナイメージは`cf local run`コマンドで実行できます。 ``` $ cf local run hello-servlet Running hello-servlet on port 54921... [hello-servlet] 2017-08-21T04:51:20.509802947Z [CONTAINER] org.apache.coyote.http11.Http11NioProtocol INFO Initializing ProtocolHandler ["http-nio-8080"] [hello-servlet] 2017-08-21T04:51:20.517556491Z [CONTAINER] org.apache.catalina.startup.Catalina INFO Initialization processed in 371 ms [hello-servlet] 2017-08-21T04:51:20.524758060Z [CONTAINER] org.apache.catalina.core.StandardService INFO Starting service Catalina [hello-servlet] 2017-08-21T04:51:20.525311765Z [CONTAINER] org.apache.catalina.core.StandardEngine INFO Starting Servlet Engine: Apache Tomcat/8.0.46 [hello-servlet] 2017-08-21T04:51:20.539589930Z [CONTAINER] org.apache.catalina.startup.HostConfig INFO Deploying web application directory /home/vcap/app/.java-buildpack/tomcat/webapps/ROOT [hello-servlet] 2017-08-21T04:51:20.815559712Z [CONTAINER] org.apache.jasper.servlet.TldScanner INFO At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time. [hello-servlet] 2017-08-21T04:51:20.869397279Z [CONTAINER] org.apache.catalina.startup.HostConfig INFO Deployment of web application directory /home/vcap/app/.java-buildpack/tomcat/webapps/ROOT has finished in 329 ms [hello-servlet] 2017-08-21T04:51:20.874792454Z [CONTAINER] org.apache.coyote.http11.Http11NioProtocol INFO Starting ProtocolHandler ["http-nio-8080"] [hello-servlet] 2017-08-21T04:51:20.880862674Z [CONTAINER] org.apache.tomcat.util.net.NioSelectorPool INFO Using a shared selector for servlet write/read [hello-servlet] 2017-08-21T04:51:20.889905003Z [CONTAINER] org.apache.catalina.startup.Catalina INFO Server startup in 371 ms ``` この例ではlocalhostの54921ポートにアクセスすると、コンテナ内の8080ポートにフォワードされます。 ``` $ curl localhost:54921 ██╗ █████╗ ███╗ ███╗ ███████╗████████╗██╗██╗ ██╗ █████╗ ████████╗ ██╗ ██╗ █████╗ ██████╗ ██║ ██╔══██╗████╗ ████║ ██╔════╝╚══██╔══╝██║██║ ██║ ██╔══██╗╚══██╔══╝ ██║ ██║██╔══██╗██╔══██╗ ██║ ███████║██╔████╔██║ ███████╗ ██║ ██║██║ ██║ ███████║ ██║ ██║ █╗ ██║███████║██████╔╝ ██║ ██╔══██║██║╚██╔╝██║ ╚════██║ ██║ ██║██║ ██║ ██╔══██║ ██║ ██║███╗██║██╔══██║██╔══██╗ ██║ ██║ ██║██║ ╚═╝ ██║ ███████║ ██║ ██║███████╗███████╗ ██║ ██║ ██║ ╚███╔███╔╝██║ ██║██║ ██║ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ``` #### DockerイメージへExport ここまででCloud FoundryのコンテナイメージフォーマットであるDropletファイルが作成されました。これを`cf local export`コマンドでDockerイメージのフォーマットにエクスポートできます。 ``` cf local export hello-servlet -r making/hello-servlet ``` `making/hello-servlet`という名前のDockerイメージが作成できました。 ``` $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE making/hello-servlet latest 61d311f1bd06 3 seconds ago 951MB 74a5658980c7 About a minute ago 951MB cflocal latest 79e3268df4cf 5 minutes ago 953MB cloudfoundry/cflinuxfs2 latest a7adacf72d2a 3 days ago 893MB ``` もちろん、このイメージは`docker run`で実行可能です。 ``` $ docker run -p 8080:8080 making/hello-servlet [CONTAINER] org.apache.coyote.http11.Http11NioProtocol INFO Initializing ProtocolHandler ["http-nio-8080"] [CONTAINER] org.apache.catalina.startup.Catalina INFO Initialization processed in 416 ms [CONTAINER] org.apache.catalina.core.StandardService INFO Starting service Catalina [CONTAINER] org.apache.catalina.core.StandardEngine INFO Starting Servlet Engine: Apache Tomcat/8.0.46 [CONTAINER] org.apache.catalina.startup.HostConfig INFO Deploying web application directory /home/vcap/app/.java-buildpack/tomcat/webapps/ROOT [CONTAINER] org.apache.jasper.servlet.TldScanner INFO At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time. [CONTAINER] org.apache.catalina.startup.HostConfig INFO Deployment of web application directory /home/vcap/app/.java-buildpack/tomcat/webapps/ROOT has finished in 410 ms [CONTAINER] org.apache.coyote.http11.Http11NioProtocol INFO Starting ProtocolHandler ["http-nio-8080"] [CONTAINER] org.apache.tomcat.util.net.NioSelectorPool INFO Using a shared selector for servlet write/read [CONTAINER] org.apache.catalina.startup.Catalina INFO Server startup in 458 ms ``` ``` $ curl localhost:8080 ██╗ █████╗ ███╗ ███╗ ███████╗████████╗██╗██╗ ██╗ █████╗ ████████╗ ██╗ ██╗ █████╗ ██████╗ ██║ ██╔══██╗████╗ ████║ ██╔════╝╚══██╔══╝██║██║ ██║ ██╔══██╗╚══██╔══╝ ██║ ██║██╔══██╗██╔══██╗ ██║ ███████║██╔████╔██║ ███████╗ ██║ ██║██║ ██║ ███████║ ██║ ██║ █╗ ██║███████║██████╔╝ ██║ ██╔══██║██║╚██╔╝██║ ╚════██║ ██║ ██║██║ ██║ ██╔══██║ ██║ ██║███╗██║██╔══██║██╔══██╗ ██║ ██║ ██║██║ ╚═╝ ██║ ███████║ ██║ ██║███████╗███████╗ ██║ ██║ ██║ ╚███╔███╔╝██║ ██║██║ ██║ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ``` そして、`docker push`でDocker Registryへデプロイします。 ``` docker push making/hello-servlet ``` #### k8sへのデプロイ Docker Registryにデプロイした後は、普通にk8sにデプロイできます。 ``` $ kubectl run hello-servlet --image=making/hello-servlet --port=8080 deployment "hello-servlet" created $ kubectl expose deployment hello-servlet --type=NodePort service "hello-servlet" exposed $ kubectl get service hello-servlet -o wide NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR hello-servlet 10.0.0.61 8080:32271/TCP 20s run=hello-servlet $ curl `minikube ip`:32271 ██╗ █████╗ ███╗ ███╗ ███████╗████████╗██╗██╗ ██╗ █████╗ ████████╗ ██╗ ██╗ █████╗ ██████╗ ██║ ██╔══██╗████╗ ████║ ██╔════╝╚══██╔══╝██║██║ ██║ ██╔══██╗╚══██╔══╝ ██║ ██║██╔══██╗██╔══██╗ ██║ ███████║██╔████╔██║ ███████╗ ██║ ██║██║ ██║ ███████║ ██║ ██║ █╗ ██║███████║██████╔╝ ██║ ██╔══██║██║╚██╔╝██║ ╚════██║ ██║ ██║██║ ██║ ██╔══██║ ██║ ██║███╗██║██╔══██║██╔══██╗ ██║ ██║ ██║██║ ╚═╝ ██║ ███████║ ██║ ██║███████╗███████╗ ██║ ██║ ██║ ╚███╔███╔╝██║ ██║██║ ██║ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ``` #### Buildpackの更新 デフォルトのBuildpackのバージョンはCF Local Pluginに依存するため、Buildpackのバージョンを更新したい場合は、`-b`で明示する必要があります。 Java Buildpackのバージョンを、執筆時点で最新の[4.5](https://github.com/cloudfoundry/java-buildpack/releases/tag/v4.5)にあげてみます。 ``` $ cf local stage hello-servlet -p ./target/ROOT.war -b https://github.com/cloudfoundry/java-buildpack.git#v4.5 Buildpack: https://github.com/cloudfoundry/java-buildpack.git#v4.5 [hello-servlet] 2017-08-21T05:23:44.725841336Z -----> Java Buildpack v4.5 | https://github.com/cloudfoundry/java-buildpack.git#36205c5 [hello-servlet] 2017-08-21T05:23:45.153024223Z -----> Downloading Jvmkill Agent 1.10.0_RELEASE from https://java-buildpack.cloudfoundry.org/jvmkill/trusty/x86_64/jvmkill-1.10.0_RELEASE.so (0.2s) [hello-servlet] 2017-08-21T05:23:45.201490613Z -----> Downloading Open Jdk JRE 1.8.0_141 from https://java-buildpack.cloudfoundry.org/openjdk/trusty/x86_64/openjdk-1.8.0_141.tar.gz (found in cache) [hello-servlet] 2017-08-21T05:23:46.099868077Z Expanding Open Jdk JRE to .java-buildpack/open_jdk_jre (0.8s) [hello-servlet] 2017-08-21T05:23:46.937437172Z -----> Downloading Open JDK Like Memory Calculator 3.9.0_RELEASE from https://java-buildpack.cloudfoundry.org/memory-calculator/trusty/x86_64/memory-calculator-3.9.0_RELEASE.tar.gz (0.8s) [hello-servlet] 2017-08-21T05:23:47.235578582Z Loaded Classes: 9742, Threads: 300 [hello-servlet] 2017-08-21T05:23:47.359764715Z -----> Downloading Client Certificate Mapper 1.2.0_RELEASE from https://java-buildpack.cloudfoundry.org/client-certificate-mapper/client-certificate-mapper-1.2.0_RELEASE.jar (0.1s) [hello-servlet] 2017-08-21T05:23:47.416043793Z -----> Downloading Container Security Provider 1.8.0_RELEASE from https://java-buildpack.cloudfoundry.org/container-security-provider/container-security-provider-1.8.0_RELEASE.jar (found in cache) [hello-servlet] 2017-08-21T05:23:48.042413872Z -----> Downloading Tomcat Instance 8.5.20 from https://java-buildpack.cloudfoundry.org/tomcat/tomcat-8.5.20.tar.gz (0.6s) [hello-servlet] 2017-08-21T05:23:48.153498196Z Expanding Tomcat Instance to .java-buildpack/tomcat (0.1s) [hello-servlet] 2017-08-21T05:23:48.211211569Z -----> Downloading Tomcat Access Logging Support 3.0.0_RELEASE from https://java-buildpack.cloudfoundry.org/tomcat-access-logging-support/tomcat-access-logging-support-3.0.0_RELEASE.jar (found in cache) [hello-servlet] 2017-08-21T05:23:48.253126051Z -----> Downloading Tomcat Lifecycle Support 3.0.0_RELEASE from https://java-buildpack.cloudfoundry.org/tomcat-lifecycle-support/tomcat-lifecycle-support-3.0.0_RELEASE.jar (found in cache) [hello-servlet] 2017-08-21T05:23:48.300813949Z -----> Downloading Tomcat Logging Support 3.0.0_RELEASE from https://java-buildpack.cloudfoundry.org/tomcat-logging-support/tomcat-logging-support-3.0.0_RELEASE.jar (found in cache) Successfully staged: hello-servlet ``` これでソースコードや設定ファイルを変更することなく、イメージが更新され、Tomcatが8.5系に上がりました。また、JVMのMemory Calculatorの仕様も[変わりました](https://www.cloudfoundry.org/just-released-java-buildpack-4-0/)。 メモリ設定をBuildpackにお任せできて楽です。k8sにJavaアプリケーションをデプロイする場合も、Buildpackの恩恵に与ることができます。 ``` $ cf local run hello-servlet Running hello-servlet on port 55383... [hello-servlet] 2017-08-21T05:25:36.151338888Z JVM Memory Configuration: -XX:CompressedClassSpaceSize=15326K -Xss1M -Xmx394118K -XX:MaxDirectMemorySize=10M -XX:MaxMetaspaceSize=75931K -XX:ReservedCodeCacheSize=240M [hello-servlet] 2017-08-21T05:25:36.663399060Z [CONTAINER] org.apache.coyote.http11.Http11NioProtocol INFO Initializing ProtocolHandler ["http-nio-8080"] [hello-servlet] 2017-08-21T05:25:36.674031978Z [CONTAINER] org.apache.catalina.startup.Catalina INFO Initialization processed in 373 ms [hello-servlet] 2017-08-21T05:25:36.681052927Z [CONTAINER] org.apache.catalina.core.StandardService INFO Starting service [Catalina] [hello-servlet] 2017-08-21T05:25:36.681435114Z [CONTAINER] org.apache.catalina.core.StandardEngine INFO Starting Servlet Engine: Apache Tomcat/8.5.20 [hello-servlet] 2017-08-21T05:25:36.709458734Z [CONTAINER] org.apache.catalina.startup.HostConfig INFO Deploying web application directory [/home/vcap/app/.java-buildpack/tomcat/webapps/ROOT] [hello-servlet] 2017-08-21T05:25:37.030274533Z [CONTAINER] org.apache.jasper.servlet.TldScanner INFO At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time. [hello-servlet] 2017-08-21T05:25:37.097099849Z [CONTAINER] org.apache.catalina.startup.HostConfig INFO Deployment of web application directory [/home/vcap/app/.java-buildpack/tomcat/webapps/ROOT] has finished in [387] ms [hello-servlet] 2017-08-21T05:25:37.101279358Z [CONTAINER] org.apache.coyote.http11.Http11NioProtocol INFO Starting ProtocolHandler ["http-nio-8080"] [hello-servlet] 2017-08-21T05:25:37.110166410Z [CONTAINER] org.apache.tomcat.util.net.NioSelectorPool INFO Using a shared selector for servlet write/read [hello-servlet] 2017-08-21T05:25:37.126321558Z [CONTAINER] org.apache.catalina.startup.Catalina INFO Server startup in 451 ms ``` --- CF Local PluginはもともとはCloud Foundryを使った開発において、ローカル環境でのイテレーションを円滑に行うためのプラグインですが、 これを使って、`Dockerfile`を作ることなくBuildpackからDockerイメージを作成し、k8sにデプロイできることを確認しました。 CF Local Pluginはまだ、[Stephen Levine](https://github.com/sclevine)さんの個人プロジェクト扱いですが、Cloud Foundry Summit 2017のKeynoteで紹介されていたので、 Cloud Foundry公式プロジェクトになることを期待しています。