Tanzu Build Service を触ってみる

最終更新日

はじめに

VMware は去年、モダンアプリケーションにフォーカスを当てた Tanzu というビジョンを掲げた。Tanzu はBuild, Run, Manage と細分化され、例えばRun であればアプリケーションの実行を支援するソフトウェアスタック、vSphere with Kubernetes やTanzu Kubernetes Grid が該当する。

インフラエンジニアとしては、アプリケーションなんて知ったこっちゃない、というのが正直なところだが、最近はインフラ側とアプリ側の境目がKubernetes で曖昧になってきて、インフラエンジニアだとしてもアプリのキャッチアップをすることが重要となってきている。

本記事では、まだ記事執筆時点ではBeta ではあるが、Tanzu のBuild に該当するTanzu Build Service v0.1 を実際に触ってみたので、その紹介をする。アプリのビルドを支援するとはどういうことか、手を動かして確認してみよう。

Tanzu Build Service の概要

略してTBS。どこぞのテレビ局みたいな名前だが、名前の通りTBS を使うとアプリのビルドが楽になる。アプリのビルドというと、ソースコードを書いて、コンパイルし、その結果をリンクして実行可能形式にすることを基本的に指すわけだが、コンテナの世界ではコンテナイメージを作らなくてはならず、そのために作成した実行ファイルをコンテナイメージに内包するか、もしくはコンテナの中でビルドしてイメージを作り上げる作業が入る。最後に、作成したイメージをコンテナレジストリにプッシュすることで、チームなどで共有する。

このソースコードを書いてからコンテナイメージを作り上げてレジストリにプッシュする一連の流れを、TBS は自動化することができる。開発者が実行することはソースコードを書くだけであり、コードを書く以外の面倒な部分を考えずに済むというメリットがある。面倒なDockerfile の作成も必要ない。

Tanzu Build Service のインストール

実際に動かしてみよう。基本的なインストールの流れは下記を参考してほしい。

https://tanzu.vmware.com/content/practitioners-blog/getting-started-with-vmware-tanzu-build-service-for-local-development

ちなみに、公式のドキュメントは下記となる。

https://docs.pivotal.io/build-service/0-1-0/installing.html

繰り返しになるが、現時点ではBeta であり、インストール方法は変わる可能性がある。実際Duffle を使ったインストールは結構手間だったりする。

その前提の下で、Docker の入ったクライアントマシンから、用意したKubernetes 環境に対してTBS 用のNamespace やPod などを作成していくことになる。なお、Kubernetes のクラスタは、Katacoda ではなく自前で用意することを推奨する。理由はAPI token などが記載されたプライベートな設定ファイルを配置することになるためだ。また、永続ボリュームを作成できることが必須なのと、どうもWorker Node が2 台以上ないとビルドがうまくいかないことがあるようで、そのあたりも注意する。

事前準備として、テスト用に事前にDocker Hub やgithub のアカウントを用意しておき、上記ブログの指示に従いkubectl / pivnet / duffle の実行ファイルをダウンロードしPATH を通しておく。chmod x でのパーミッション変更を忘れずに。

kubectl : https://kubernetes.io/docs/tasks/tools/install-kubectl/

pivnet : https://github.com/pivotal-cf/pivnet-cli

duffle : https://github.com/cnabio/duffle

指示通り設定ファイルをダウンロードし、pivnet コマンドでTBS のバイナリをダウンロードする。この時、pivnet login –api-token xxx でログインしておく。

api-token の取得だが、 https://network.pivotal.io/ から行う。旧Pivotal のサイトであり、My VMware とは統合されていないのが面倒なのだが、とりあえず登録してAPI token をEdit Profile から取得しよう。

ついでに、github およびDocker Hub のAPI token も取得しておく。

API token の取得とpivnet のlogin が済んだら、TBS のバイナリをダウンロードする。

pivnet download-product-files --product-slug='build-service' --release-version='0.1.0' --product-file-id=648378

ダウンロード後のクレデンシャル変更について、微妙にわかりづらいので補足しておく。source はクライアントのkubeconfig ファイルのディレクトリを指す。 destination はduffle でのインストール時にクライアント上で一時的に実行されるコンテナ上に配置されるkubeconfig のディレクトリを指す。つまり、destination は変更しなくてよいし、source とdestination は下記のように同じ場合もあることに注意。また、TBS インストール時にk8s クラスタに接続できない場合、クライアント-k8s ではなく、クライアント上のコンテナ-k8s の疎通問題を疑う必要がある。

name: build-service-credentials
credentials:
- name: kube_config
  source:
    path: "/root/.kube/config"
  destination:
    path: "/root/.kube/config"

その後、ブログの指示に従い duffle relocate コマンドでpivotal リポジトリから自身のDocker Hub にイメージ群を移す。その後、duffle install コマンドでTBS をインストールする。

duffle install tbs-deploy-demo -c ./tbs-creds/credentials.yml  \
--set kubernetes_env=docker-desktop \
--set docker_registry=index.docker.io \
--set docker_repository=“index.docker.io/$USER/” \
--set registry_username=“yyyyyyyyyyyyyyyyyyyyyyyyyyy” \
--set registry_password=“$DOCKERHUB_API_TOKEN” \
 --set custom_builder_image=“index.docker.io/$USER/cf-build-service-dev-219913-invocation-image-d370867b6d9f765a72bc7ad10367ca5f:0.1.0-rc.14” \
-f ./build-service-0.1.0.tgz \
-m ./relocated.json

なお、インストールに失敗し、アンインストールする場合は下記コマンドになる。

duffle uninstall tbs-deploy-demo -c ./tbs-creds/credentials.yml -m ./relocated.json

このコマンドで失敗した場合、直接k8s で展開されたTBS のNamsepace ごとオブジェクトを削除し、~/.duffle/claims にあるjson フォーマットの設定ファイルを削除する。

pivnet コマンドでpb cli をインストールし、下記のような出力を得られたら、インストールは完了したことになる。

pb builder list
Cluster Builders
----------------
default

Tanzu Build Service を使ってみよう

今回は、イメージの更新をわかりやすくするために、ブログのnode-todo アプリではなく、私がテキトーに作ったgo のhelloworld アプリを使うことにする。

https://github.com/vkbaba/go-sampleapp

まずは、下準備としてプロジェクトを作成し設定ファイルをapply する。

[root@localhost tbs-deploy]# pb project create go-sampleapp
Successfully created project 'go-sampleapp'
[root@localhost tbs-deploy]# pb project target go-sampleapp
Successfully set 'go-sampleapp' as target. Subsequent commands will assume you are targeting 'go-sampleapp'.
[root@localhost tbs-deploy]# pb secrets git apply -f secrets/github-config.yml
Successfully created git secret for 'github.com/vkbaba/' in project 'go-sampleapp'
[root@localhost tbs-deploy]# pb secrets registry apply -f secrets/dockerhub-config.yml
Successfully created registry secret for 'https://index.docker.io/v1/' in project 'go-sampleapp'
[root@localhost tbs-deploy]# pb project list
Projects
--------
go-sampleapp

dockerhub-config.yml :

project: go-sampleapp
registry: https://index.docker.io/v1/
username: vkbaba
password: xxx

github-config.yml:

project: go-sampleapp
repository: github.com/vkbaba/
username: vkbaba
password: yyy

その次にイメージをbuild するわけだが、その前に永続ボリュームの設定をする。この設定、地味にブログにもドキュメントにも書いておらず、実施しないとbuild 用のPod が立ち上がらないのだが、k8s クラスタにStorage Class オブジェクトを作成し、それをDefault Storage Class として設定する。vSpphere CSI を使っている場合は、事前にストレージクラスをタグベースで作成しておき、それと紐づけるようStorage Class マニフェストを作成する。

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: fast
provisioner: csi.vsphere.vmware.com
parameters:
    storagePolicyName: tkg-policy

kubectl apply 後、コチラを参考に、Default Storage Class の設定をする。ちなみに、vSphere with Kubernetes のTanzu Kubernetes Cluster で実行しようとすると、現在のissue のためか、デフォルトを示すアノテーションが自動的に削除されてしまう。回避方法は不明。

kubectl patch storageclass fast -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
[root@localhost tbs-deploy]# kubectl get sc
NAME             PROVISIONER              RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
fast (default)   csi.vsphere.vmware.com   Delete          Immediate           false                  10d

準備ができたら、いよいよイメージを作成する。

pb image apply -f images/go-sampleapp

イメージを作成している間、裏側でk8s 上にビルド用のPod が立ち上がる。完了すると、次のような出力を得ることができる。また、Docker Hub にもTBS によって作成されたイメージが自動的にpush されている。

[root@localhost tbs-deploy]# pb image builds index.docker.io/vkbaba/go-sampleapp
Build    Status      Started Time           Finished Time          Reason    Digest
-----    ------      ------------           -------------          ------    ------
    1    SUCCESS     2020-06-06 20:34:27    2020-06-06 20:35:58    CONFIG    77a7c6248a6a283978c9724ddb653571963d42dff532ebb905606bc83c7c6afd

この後、ソースコードに変更を加える。 “Hello World! v2” などと変更し、commit するとソースコードの変更を検知し、自動的にビルドが走る。

[root@localhost tbs-deploy]# pb image builds index.docker.io/vkbaba/go-sampleapp
Build    Status      Started Time           Finished Time          Reason    Digest
-----    ------      ------------           -------------          ------    ------
    1    SUCCESS     2020-06-06 20:34:27    2020-06-06 20:35:58    CONFIG    77a7c6248a6a283978c9724ddb653571963d42dff532ebb905606bc83c7c6afd
    2    BUILDING    2020-06-06 20:36:58    --                     COMMIT    --


[root@localhost tbs-deploy]# pb image builds index.docker.io/vkbaba/go-sampleapp
Build    Status     Started Time           Finished Time          Reason    Digest
-----    ------     ------------           -------------          ------    ------
    1    SUCCESS    2020-06-06 20:34:27    2020-06-06 20:35:58    CONFIG    77a7c6248a6a283978c9724ddb653571963d42dff532ebb905606bc83c7c6afd
    2    SUCCESS    2020-06-06 20:36:58    2020-06-06 20:38:01    COMMIT    58e3d84cf88cffd142d361247c35bedd9cff7abf22a639fe306fb7bd15d68155

とまあこんな感じで、自動的にビルドしてくれるのだが、重要な点として、今回はgo build も実行していないし、Dokerfile を作成していない。それなのにイメージができているのが不思議なのであるが、それは言語を自動判定する仕組みとCloud Native Buildpacks というイメージ作成のためのOSS のおかげなのである。

イメージを深堀してみよう

さて、ここまで実行すると色々疑問がわいてくる。例えばDockerfile なしでどのようにイメージを作成しているのか?そもそもどのようにgo と判断してビルドしているのか?など。自分の場合は、とりわけどのようにイメージが構成されているかは気になった。

そこで、今回は docker history よりも詳細にイメージのレイヤ構造を調べることができる dive というツールを使う。

https://github.com/wagoodman/dive

先ほど作製したイメージをさっそく解析してみよう。

イメージは5 つのレイヤで構成される。1 つ目のレイヤはベースイメージで、buildpack ではどうやらubuntu を使うようだ。1

[root@localhost Workspace]# docker run --rm -it vkbaba/go-sampleapp sh
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.4 LTS"

レイヤ3 を作るときにビルドされた実行ファイルが格納されるようだ。注目するのは最後のレイヤで配置される5 のmetadata.toml ファイルである。

$ cat /layers/config/metadata.toml
[[processes]]
  type = "web"
  command = "/layers/org.cloudfoundry.go-mod/app-binary/bin/go-sampleapp"
  direct = false

[[buildpacks]]
  id = "org.cloudfoundry.go-compiler"
  version = "0.0.99"

[[buildpacks]]
  id = "org.cloudfoundry.go-mod"
  version = "0.0.86"

[[bom]]
  name = "go"
  version = "1.13.8"
  [bom.metadata]
    licenses = []
    name = "Go"
    sha256 = "40558d2781509e3619d9cbf37f0d9430ce1fa50684196f66612cc3a48cbe87b6"
    stacks = ["org.cloudfoundry.stacks.cflinuxfs3"]
    uri = "https://buildpacks.cloudfoundry.org/dependencies/go/go1.13.8.linux-amd64-cflinuxfs3-40558d27.tgz"
  [bom.buildpack]
    id = "org.cloudfoundry.go-compiler"
    version = "0.0.99"

[[bom]]
  name = ""
  version = ""
  [bom.buildpack]
    id = "org.cloudfoundry.go-compiler"
    version = "0.0.99"

[[bom]]
  name = ""
  version = ""
  [bom.buildpack]
    id = "org.cloudfoundry.go-mod"
    version = "0.0.86"

注目するのは stacks = ["org.cloudfoundry.stacks.cflinuxfs3"] の部分で、buildpack におけるstack とはアプリケーションのビルドと実行環境を提供するベースのイメージであり2、ここでは org.cloudfoundry.stacks.cflinuxfs3 を使っており、詳細はこちらに記載されている。

https://hub.docker.com/r/cloudfoundry/cnb

要するに、TBS(というかBuildpack)でイメージを作成するときは、Builder と呼ばれるアプリケーションのビルドに必要な環境がベースイメージとして使用され、そこでビルドしたアプリケーションの実行ファイルを格納し、いくつかのメタデータを付け加えられるということになる。つまりキモはBuilder であり、Builder イメージが含むBuildpack だ。

今回のgo のアプリケーションの場合、Buildpack は下記にある。

https://github.com/cloudfoundry/go-buildpack/releases/tag/v1.9.13

そして「どのようにソースコードをgo と判断しているか」の答えは下記にある。

https://github.com/cloudfoundry/go-buildpack/blob/master/bin/detect

スクリプトを使って、go っぽいリポジトリ構成ならgo と判断しているというなんとも単純なものだった。

まとめ

Tanzu Build Service を利用することで、Docker Image を作り上げるまでの面倒なプロセスの多くを自動化することができ、開発者はソースコードを書くことに集中できる。個人的には、この仕組みはBuildpack がすべてなので、Buildpack を作ることに負担がかかると元も子もないと感じた。標準化の仕組みを整えているようだが、このようなDockerfile を使わないビルドの仕組みが市民権を得られるかどうかは注目したい。

  1. マルチステージビルドで軽量なイメージを作り上げる、ということはできないようだ
  2. https://buildpacks.io/docs/concepts/components/stack/
アバター

vkbaba

仮想化関連のSE をしています。 投稿内容は私個人の意見であり、所属企業・部門見解を代表するものではありません。