Dockerfileはもう古いのかもしれない

この記事はチームラボエンジニアリングアドベントカレンダー 18日目です！

アドベントカレンダー初投稿なので、広い心で見ていただけば、嬉しいです(ついでに、いいね！なんてしてくれると泣いて喜びます)

### はじめに

ある日、こんなツイートが流れてきた

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">なんだこれは。Dockerfile書きたくないでござる <a href="https://t.co/d6cqF0FS7M">https://t.co/d6cqF0FS7M</a></p>&mdash; pospome (@pospome) <a href="https://twitter.com/pospome/status/1200940561995190272?ref_src=twsrc%5Etfw">December 1, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

気になって色々調べたり、Cloud Native Buildpackについての記事で、試してみたら、意外と便利じゃん!!ってなったので、紹介したいなと思います

### Cloud Native Buildpackとは？？

そもそもBuildpackは、PaaS上で、任意の言語やフレームワークを実行できるようになった仕組みです
歴史的背景として、Herokuによって作られ、Heroku内で提供していたが、徐々に、CloudFoundryやGitLabなどでも採用されています

そして、PivovalとHerokuによって開始されたプロジェクトが、CloudNativeBuildPacksです
CloudNativeBuildpacksの登場によって、Buildpacksの仕様が統一され、DockerやKubernetesでも対応されるようになりました

対応言語は、複数あり「Java, .NET, NodeJS, Python, Golang, PHP」に対応しています

> Provide a balance of control that reduces the operational burden on developers and supports enterprise operators who manage apps at scale.
Ensure that apps meet security and compliance requirements without developer intervention.
Provide automated delivery of both OS-level and application-level dependency upgrades, efficiently handling day-2 app operations that are often difficult to manage with Dockerfiles.
Rely on compatibility guarantees to safely apply patches without rebuilding artifacts and without unintentionally changing application behavior.

> Buildpacksはプラグイン可能なモジュラー化されたツールであり、ソースコードをコンテナ対応の成果物へと変換する。しかもDockerfileと比べてより高度な抽象化を実現してくれる。これにより本番投入にかかる時間を最小化するなど開発者のさまざまな手間を省く一方、企業において大規模なアプリケーション展開を行う運用者も支援し、両者を適切なバランスで実現する。

https://buildpacks.io/


### こんな経験無いですか？？

- Dockerfileを書いてたら、半日過ぎてた:sweat_smile:
- Dockerfileを書いてたら、実行順番が効率的でないと指摘を受けた :sob: 
- Dockerfileを書いてたら、怪物イメージが出来上がり、ゴミイメージと呼ばれた:scream:
- Dockerfileのベースイメージに脆弱性があって、ベースイメージ移行で夜があけた:smiling_imp:

辛い。。。。Dockerfile辛い。。。。。:innocent:

そんなToilを削減できるのが、Buildpacksです！！！！

### とりあえず試してみよう

前提:Dockerのインストールとイメージ化したいプロジェクトは用意しておいてください

サンプルのプロジェクトも作ったので、使ってください！！！！
https://github.com/adachishohei/buildpack-java-sample

**Buildpacksのインストール**

- Mac

```cmd
brew tap buildpack/tap
brew install pack
```

- Linux

```cmd
wget https://github.com/buildpack/pack/releases/download/v0.5.0/pack-v0.5.0-linux.tgz
tar xvf pack-v0.5.0-linux.tgz
rm pack-v0.5.0-linux.tgz
./pack --help
```

- Windows

https://github.com/buildpack/pack/releases/download/v0.5.0/pack-v0.5.0-windows.zip


[公式のインストール手順](https://buildpacks.io/docs/install-pack/)


**実行**

```
# Dockerfileが無いことを確認する
$ls
HELP.md                 build.gradle            docker-compose.yml      gradlew.bat             springboot-practice.iml
README.md               config                  gradle                  out                     src
build                   db                      gradlew                 settings.gradle

# buildを実行
$pack build --path . springboot-practice --builder cloudfoundry/cnb:bionic
cflinuxfs3: Pulling from cloudfoundry/cnb
Digest: sha256:8d3e130dac4627022aeb654d601ba8e09f2c7c143f3cc001141340175402722f
Status: Image is up to date for cloudfoundry/cnb:cflinuxfs3
full-cnb: Pulling from cloudfoundry/run
0e1cf81986c5: Pull complete
Digest: sha256:85a5b90046f87cbf1c8d4a75a2a7dac14982000e30ef0d836bc94e4486bcd4e3
Status: Downloaded newer image for cloudfoundry/run:full-cnb
--- 中略 ---
===> CACHING
[cacher] Reusing layer 'org.cloudfoundry.openjdk:2f08c469c9a8adea1b6ee3444ba2a8242a7e99d87976a077faf037a9eb7f884b'
[cacher] Reusing layer 'org.cloudfoundry.openjdk:6dd0c9c8a740e6c19149e98034fba8e368fd9aa16ab417aa636854d40db1a161'
[cacher] Reusing layer 'org.cloudfoundry.openjdk:openjdk-jdk'
[cacher] Caching layer 'org.cloudfoundry.buildsystem:build-system-cache'
[cacher] Reusing layer 'org.cloudfoundry.jvmapplication:executable-jar'
[cacher] Reusing layer 'org.cloudfoundry.springboot:spring-boot'
[cacher] Reusing layer 'org.cloudfoundry.springautoreconfiguration:46ab131165317d91fd4ad3186abf755222744e2d277dc413def06f3ad45ab150'
Successfully built image springboot-practice

# イメージの確認
$docker image -ls
springboot-practice    latest    d5d4aafb5733        2 minutes ago       246MB
``` 

以上で、Docker imageの完成です :raised_hands:

イメージサイズも軽くて、良いですね！


さらに、ベースイメージに脆弱性があった時の乗せ替え作業は、rebaseコマンドで代替え可能です

```
$ pack rebase my-app:my-tag
```


### 実行速度

実際の業務で使うためには、実行速度も一つの観点だと思うので、buildの実行速度も計測してみました

**初回実行**

|項目|時間| 
|:----------------:|------------------:|
| user             |              1.60s |
| system           |            2.09s |
| total             |              4:41.93s |


**2回目以降**

|項目|時間| 
|:----------------:|------------------:|
| user             |              1.68s |
| system           |            2.15s |
| total             |              1:39.87s|


初回の実行でも、少し重いかな？って印象を受けますが、十分に使える速度だと思います
さらに、2回目以降は、キャッシュが聞いてくれるので、かなり早くなりましたね

### CIについて考えてみた

コンテナイメージをbuildするのは、ローカルではなくCI環境のはずなので、CI環境の構築もやってみました

今回は、GithubActionsを使ったCI環境の構築をしてみます！
と言っても、めちゃくちゃ簡単でした

Buildpackを使うために必要なものは、単純に、Linuxのインストール手順を実行するだけです！
今回は、Springbootで試してみました。

以下の手順で、Buildpackのインストールからビルド、ECRへのPushまで実現可能でした
あとは、うまくキャッシュを聞かせてあげれば、ビルド時間の短縮にも繋がるので、より良くなりそうだと思いました！

サンプルのGitHubActionsのテンプレートは、以下にも置いています
https://github.com/adachishohei/buildpack-java-sample/blob/master/.github/workflows/image_build_for_ecr.yml

```yml
on:
  push:
    branches:
      - master
name: buildImageforECR
jobs:
  imageBuild:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@master

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1

      - name: Get Cloud Native Build
        run: wget https://github.com/buildpack/pack/releases/download/v0.5.0/pack-v0.5.0-linux.tgz

      - name: Unzip file
        run: tar xvf ./pack-v0.5.0-linux.tgz

      - name: Remove pack
        run: rm pack-v0.5.0-linux.tgz

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Build, tag, and push image to Amazon ECR
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: ${{ secrets.AWS_ECR_REPO_NAME }}
        run: |
          IMAGE_TAG=$(echo ${{ github.ref }} | sed -e "s#refs/tags/##g")
          ./pack build --path . $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG --builder  cloudfoundry/cnb:bionic
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
```

### 所感

Buildpacksを採用した場合、オリジナルのbuilderを作成することも可能なので、会社として、builderを管理し、それを使ってプロジェクト側でDockerfileを書かずにコンテナイメージを作るのが良いかなと思いました。
そうすることで、脆弱性の対応に関しても、builderのアップデートだけで済むので、プロジェクト単位では、rebaseコマンドを実行するだけで済むので、かなりの運用コストの削減もできます！

あとは、今回Buildpacksの内部の仕組みにまで触れてないのですが、そこに触れるともっと面白そうだなと思ったので、別の機会で記事にできたら良いなと思います

### まとめ

- Buildpacksは、コンテナイメージを作る工程と管理を楽にしてくれる(Toilの削減になる)
- Buildpacksは、Java, .NET, NodeJS, Python, Golang, PHPなど複数の言語に対応してる
- Builderの自作が可能で、脆弱性の対応やイメージの一元管理が可能

**Buildpacksは神**

### 参考文献

- [Dockerfile書きたくないでござる](https://speakerdeck.com/jacopen/dockerfileshu-kitakunaidegozaru)
- [Cloud Native BuildpackでToil減らしていこうという話](https://speakerdeck.com/jacopen/cloud-native-buildpackdetoiljian-rasiteikoutoiuhua)
- [CloudNativeBuildpacks](https://buildpacks.io/)
- [Cloud Native Buildpacks Specification v3](https://github.com/buildpack/spec)
- [Buildpack Samples](https://github.com/buildpack/samples)