cf-operator と KubeCF を使って Kubernetes 上で Cloud Foundry Application Runtime を実行する

# はじめに

この記事は [Kubernetes3 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/kubernetes3) 23日目の記事です。

~~[Project Quarks](https://www.cloudfoundry.org/project-quarks/) の [cf-operator v1.0.0](https://github.com/cloudfoundry-incubator/cf-operator/releases/tag/v1.0.0) と [KubeCF v0.1.0](https://github.com/SUSE/kubecf/releases/tag/v0.1.0) がリリースされたので動かしてみていたら、ちょうど Advent Calendar に空きができたようなので、せっかくの機会と思い投稿させて頂きました。~~

**【2020/03/23 更新】[KubeCF v1.0.0](https://github.com/cloudfoundry-incubator/kubecf/releases/tag/v1.0.0) がリリースされ、また [Cloud Foundry Foundation の Incubating Project に移管された](https://www.cloudfoundry.org/blog/kubecf-now-an-incubating-project-of-cloud-foundry-foundation/)ので、最新情報を記事に反映しました。**

Project Quarks は、[Cloud Foundry Application Runtime](https://www.cloudfoundry.org/application-runtime/) (以降、Cloud Foundry と略します) を Kubernetes のワークロードとして実行できるようにするための試みです。
従来、Cloud Foundry をインストールするためには、[BOSH](bosh.io) という管理ツールを扱う必要があった (※１) のですが、`helm install` を実行するだけでインストールができる (※２) ようになりました。

* ※１ (余談) BOSH 自体は IaaS レイヤを抽象化して管理・運用するためのツールとして非常に優れており、例えば Managed Kubernetes を使わず、自前で Kubernetes クラスタを本番運用する場合などに有用なツールです。また、BOSH も Kubernetes もルーツは Google の Borg なので、思想が似通っています。
* ※２ デフォルト設定でインストールする場合。一部の設定については `values.yaml` でカスタマイズ可能ですが、それ以外の設定をカスタマイズするためには (現時点では) BOSH の知識が依然として必要になります。将来的には [CF-for-K8s](https://github.com/cloudfoundry/cf-for-k8s) プロジェクトにて開発中の Kubernetes native なコンポーネントに置き換えられていくことで、BOSH への依存性は段階的に取り除かれていく見込みのようです ([詳細後述](#おわりに))。

Project Quarks の狙いやより技術的な詳細については、[Proposal 原文](https://docs.google.com/document/d/1_IvFf-cCR4_Hxg-L7Z_R51EKhZfBqlprrs5NgC2iO2w)が参考になるかと思います。
なお、Kubernetes 上で Cloud Foundry を実行する取り組みは Project Quarks 以前からあり、その取り組みで生まれた技術が Project Quarks でも活かされています。
経緯を知るにあたっては、以下の記事を参照させて頂きました。

* https://qiita.com/jyoshise/items/88a57c9b46760a839c87
* http://jyoshise.hatenablog.com/entry/2017/12/22/234527

本記事で紹介する手順は、基本的には [KubeCF Documentation の Getting Started](https://kubecf.suse.dev/docs/getting-started/) の記載に従っていますが、一部の手順はアレンジしています。

また、手順の途中でところどころ、BOSH 上で実現していたことをどのように Kubernetes で扱っているのかの説明を (主に自身の理解の整理のために) 挟んでいますが、BOSH に詳しくない方や、とりあえず動かしてみたいという方は読み飛ばして頂いて問題ありません。

## お約束

Project Quarks は、本記事執筆時点においてはインキュベータプロジェクトであり、今後実装が大きく変わる可能性もあります。
したがって、本記事に記載した手順を実行した結果について、筆者は一切の責任を負いかねます。ただし、誤りの指摘等のフィードバックにつきましては歓迎致しますのでよろしくお願いします。

# 検証環境

[Azure Kubernetes Service (AKS)](https://azure.microsoft.com/ja-jp/services/kubernetes-service/) 上で検証しました。

API サーバのバージョンは以下です。

```zsh
❯ kubectl version
Client Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.10", GitCommit:"150f36044fe31bee3891b5a3fae69c17237e022c", GitTreeState:"clean", BuildDate:"2020-02-21T10:02:50Z", GoVersion:"go1.12.12", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.10", GitCommit:"150f36044fe31bee3891b5a3fae69c17237e022c", GitTreeState:"clean", BuildDate:"2020-02-21T10:02:50Z", GoVersion:"go1.12.12", Compiler:"gc", Platform:"linux/amd64"}
```

ノードリソースについてですが、cf-oprator と KubeCF 合わせて 20 個ほどの Pod (1 Pod に複数コンテナが入った Pod が多く、コンテナ数にすると 100 個ほど) が起動するので、十分に大きめのリソースを確保しておく必要があります。
具体的にどの程度必要なのかは調べられていないのですが、[Standard_D2_v3 インスタンス](https://docs.microsoft.com/ja-jp/azure/virtual-machines/linux/sizes-general#dv3-series-sup1sup) (2 vCPU / 8GiB RAM) を 5 つ用意して検証したところ、問題なく稼働することは確認できました。

```zsh
❯ kubectl get node -o wide
NAME                       STATUS   ROLES   AGE   VERSION    INTERNAL-IP   EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
aks-agentpool-31661883-0   Ready    agent   20m   v1.15.10   10.240.0.7    <none>        Ubuntu 16.04.6 LTS   4.15.0-1071-azure   docker://3.0.10+azure
aks-agentpool-31661883-1   Ready    agent   19m   v1.15.10   10.240.0.5    <none>        Ubuntu 16.04.6 LTS   4.15.0-1071-azure   docker://3.0.10+azure
aks-agentpool-31661883-2   Ready    agent   19m   v1.15.10   10.240.0.6    <none>        Ubuntu 16.04.6 LTS   4.15.0-1071-azure   docker://3.0.10+azure
aks-agentpool-31661883-3   Ready    agent   19m   v1.15.10   10.240.0.4    <none>        Ubuntu 16.04.6 LTS   4.15.0-1071-azure   docker://3.0.10+azure
aks-agentpool-31661883-4   Ready    agent   19m   v1.15.10   10.240.0.8    <none>        Ubuntu 16.04.6 LTS   4.15.0-1071-azure   docker://3.0.10+azure
```

# (準備) Helm のインストール

KubeCF v0.1.0 の時点では Helm v3 に対応していませんでしたが、現在は v3 に対応しているので Tiller のインストールは不要です。

```zsh
❯ helm version
version.BuildInfo{Version:"v3.1.2", GitCommit:"d878d4d45863e42fd5cff6743294a11d28a9abce", GitTreeState:"clean", GoVersion:"go1.13.8"}
```

# cf-operator のインストール

cf-operator は、BOSH の概念を [Custom Resource Definition (CRD)](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) として定義して Kubernetes 上で扱えるようにするための [Kubernetes Operator](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) です。

KubeCF をインストールする namespace を決め、`global.operator.watchNamespace` パラメータの値に指定して `helm install` を実行します。

その他の設定は基本的に[デフォルト](https://github.com/cloudfoundry-incubator/cf-operator/blob/v3.3.0/deploy/helm/cf-operator/README.md#configuration)のままで問題ありません。

```zsh
❯ kubectl create namespace cf-operator
❯ helm install cf-operator \
  --namespace cf-operator \
  --set "global.operator.watchNamespace=kubecf" \
  https://s3.amazonaws.com/cf-operators/release/helm-charts/cf-operator-3.3.0%2B0.gf32b521e.tgz
NAME: cf-operator
LAST DEPLOYED: Mon Mar 23 11:57:12 2020
NAMESPACE: cf-operator
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Running the operator will install the following CRD´s:

- boshdeployments.quarks.cloudfoundry.org
- quarksjobs.quarks.cloudfoundry.org
- quarksecrets.quarks.cloudfoundry.org
- quarkstatefulsets.quarks.cloudfoundry.org

You can always verify if the CRD´s are installed, by running:
 $ kubectl get crds

Interacting with the cf-operator pod

1. Check the cf-operator pod status
  kubectl -n cf-operator get pods

2. Tail the cf-operator pod logs
  export OPERATOR_POD=$(kubectl get pods -l name=cf-operator --namespace cf-operator --output name)
  kubectl -n cf-operator logs $OPERATOR_POD -f

3. Apply one of the BOSH deployment manifest examples
  kubectl -n kubecf apply -f docs/examples/bosh-deployment/boshdeployment-with-custom-variable.yaml

4. See the cf-operator in action!
  watch -c "kubectl -n kubecf get pods"
```

インストールが完了すると、確かに以下の CRD が作成されていることが確認できます。

```zsh
❯ kubectl get crds
NAME                                         CREATED AT
boshdeployments.quarks.cloudfoundry.org      2020-03-23T02:57:37Z
quarksjobs.quarks.cloudfoundry.org           2020-03-23T02:57:36Z
quarkssecrets.quarks.cloudfoundry.org        2020-03-23T02:57:37Z
quarksstatefulsets.quarks.cloudfoundry.org   2020-03-23T02:57:37Z
```

各 CRD と BOSH の概念のマッピングは、以下のとおりです。

* [boshdeployments.quarks.cloudfoundry.org](https://github.com/cloudfoundry-incubator/cf-operator/blob/v1.0.0/docs/controllers/bosh_deployment.md) : [BOSH Deployment](https://bosh.io/docs/deployment/)
* [quarksjobs.quarks.cloudfoundry.org](https://github.com/cloudfoundry-incubator/quarks-job/blob/master/docs/quarksjob.md) : [BOSH Errands](https://bosh.io/docs/errands/)
* [quarkssecrets.quarks.cloudfoundry.org](https://github.com/cloudfoundry-incubator/cf-operator/blob/v1.0.0/docs/controllers/quarks_secret.md) : [BOSH Variables](https://bosh.io/docs/terminology/#variable)
* [quarksstatefulsets.quarks.cloudfoundry.org](https://github.com/cloudfoundry-incubator/cf-operator/blob/v1.0.0/docs/controllers/quarks_statefulset.md) : [BOSH Instance Groups](https://bosh.io/docs/terminology/#instance-group)

cf-operator の利用者がメインで意識する必要があるリソースは boshdeployments.quarks.cloudfoundry.org で、これは Kubernetes の ConfigMap と Secret として定義した、BOSH manifest と [Ops File](https://bosh.io/docs/cli-ops-files/) をまとめて一つの BOSH Deployment として管理するためのリソースです。

# KubeCF のインストール

KubeCF は、Project Quarks 仕様にビルドされた [cf-deployment](https://github.com/cloudfoundry/cf-deployment) 用の BOSH Releases とマニフェスト群を Helm Chart にパッケージングしたものです。

Cloud Foundry が使うシステムドメインを values.yaml に記載します。
(`your domain` は実際に所持しているドメインを記載します。)

```yaml:values.yaml
system_domain: kubecf.<your domain>
```

上記設定は `helm install` を実行するために最低限必要な設定です。

その他に `values.yaml` にて定義可能な設定は、以下から参照できます。

* https://github.com/cloudfoundry-incubator/kubecf/blob/v1.0.1/deploy/helm/kubecf/values.yaml

`values.yaml` を記載したら `helm install` で KubeCF をインストールします。

```zsh
❯ helm install kubecf \
  --namespace kubecf \
  --values values.yaml \
  https://github.com/cloudfoundry-incubator/kubecf/releases/download/v1.0.1/kubecf-v1.0.1.tgz
NAME: kubecf
LAST DEPLOYED: Mon Mar 23 12:13:33 2020
NAMESPACE: kubecf
STATUS: deployed
REVISION: 1
TEST SUITE: None
```

インストールすると、ほどなくして `type: LoadBalancer` の Service に EXTERNAL-IP が割り当たるので、この IP を指し示すように、所持しているドメインの DNS レコードに A レコードを登録しておきます。

```zsh
❯ kubectl -n kubecf get services
NAME                       TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)                                                                                                                                                        AGE
kubecf-database            ClusterIP      10.0.205.218   <none>           3306/TCP                                                                                                                                                       2m43s
kubecf-database-repl       ClusterIP      None           <none>           4567/TCP,4568/TCP,4444/TCP                                                                                                                                     2m43s
kubecf-router-public       LoadBalancer   10.0.89.230    XX.YY.ZZ.228    80:30145/TCP,443:30657/TCP                                                                                                                                     2m43s
kubecf-ssh-proxy-public    LoadBalancer   10.0.125.82    XX.YY.ZZ.227    2222:30584/TCP                                                                                                                                                 2m43s
kubecf-tcp-router-public   LoadBalancer   10.0.134.85    XX.YY.ZZ.142   80:31190/TCP,20000:30919/TCP,20001:30658/TCP,20002:31593/TCP,20003:32107/TCP,20004:30992/TCP,20005:30670/TCP,20006:30967/TCP,20007:31352/TCP,20008:30825/TCP   2m43s
```

* `kubecf.<your domain>` -> `kubecf-router-public` の EXTERNAL-IP (上記の例だと `XX.YY.ZZ.228`)
* `*.kubecf.<your domain>` -> `kubecf-router-public` の EXTERNAL-IP (上記の例だと `XX.YY.ZZ.228`)
* `ssh.kubecf.<your domain>` -> `kubecf-ssh-proxy-public` の EXTERNAL-IP (上記の例だと `XX.YY.ZZ.227`)
* `tcp.kubecf.<your domain>` -> `kubecf-tcp-router-public` の EXTERNAL-IP (上記の例だと `XX.YY.ZZ.142`)

インストールが完了するまで少し時間がかかるので、待ち時間でどのようなリソースが構成されたのか見てみましょう。

cf-operator の説明で少し触れたように boshdeployments CRD にて、KubeCF の BOSH Deployment が定義されていることが確認できます。

```zsh
❯ kubectl -n kubecf get boshdeployments.quarks.cloudfoundry.org kubecf -o yaml
apiVersion: quarks.cloudfoundry.org/v1alpha1
kind: BOSHDeployment
metadata:
  creationTimestamp: "2020-03-23T03:13:36Z"
  generation: 1
  labels:
    app.kubernetes.io/instance: kubecf
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: kubecf
    app.kubernetes.io/version: v1.0.1
    helm.sh/chart: kubecf-v1.0.1
  name: kubecf
  namespace: kubecf
  resourceVersion: "9557"
  selfLink: /apis/quarks.cloudfoundry.org/v1alpha1/namespaces/kubecf/boshdeployments/kubecf
  uid: 3dd148d3-a537-4211-a643-5712429571bb
spec:
  manifest:
    name: kubecf-cf-deployment
    type: configmap
  ops:
  - name: kubecf-ops-move-auctioneer
    type: configmap
  - name: kubecf-ops-move-routing-api
    type: configmap
  - name: kubecf-ops-set-suse-buildpacks
    type: configmap
  - name: kubecf-ops-acceptance-tests
    type: configmap
  - name: kubecf-ops-adapter
    type: configmap
  - name: kubecf-ops-api
    type: configmap
  - name: kubecf-ops-app-autoscaler
    type: configmap
  - name: kubecf-ops-auctioneer
    type: configmap
  - name: kubecf-ops-bits
    type: configmap
  - name: kubecf-ops-brain-tests
    type: configmap
  - name: kubecf-ops-cc-worker
    type: configmap
  - name: kubecf-ops-credhub
    type: configmap
  - name: kubecf-ops-database
    type: configmap
  - name: kubecf-ops-diego-api
    type: configmap
  - name: kubecf-ops-diego-cell
    type: configmap
  - name: kubecf-ops-doppler
    type: configmap
  - name: kubecf-ops-eirini
    type: configmap
  - name: kubecf-ops-log-api
    type: configmap
  - name: kubecf-ops-nats
    type: configmap
  - name: kubecf-ops-router
    type: configmap
  - name: kubecf-ops-routing-api
    type: configmap
  - name: kubecf-ops-scheduler
    type: configmap
  - name: kubecf-ops-singleton-blobstore
    type: configmap
  - name: kubecf-ops-smoke-tests
    type: configmap
  - name: kubecf-ops-sync-integration-tests
    type: configmap
  - name: kubecf-ops-tcp-router
    type: configmap
  - name: kubecf-ops-uaa
    type: configmap
  - name: kubecf-ops-sizing
    type: configmap
  - name: kubecf-ops-azs
    type: configmap
  - name: kubecf-ops-addons
    type: configmap
  - name: kubecf-ops-releases
    type: configmap
  - name: kubecf-ops-set-deployment-name
    type: configmap
  - name: kubecf-ops-set-opensuse-stemcells
    type: configmap
  - name: kubecf-user-provided-properties
    type: configmap
status:
  lastReconcile: "2020-03-23T03:14:23Z"
```

ConfigMap で定義されている BOSH manifest を確認してみます。

```zsh
❯ kubectl -n kubecf get configmaps kubecf-cf-deployment -o yaml | head
apiVersion: v1
data:
  manifest: |-
    ---
    name: cf
    manifest_version: v12.33.0
    update:
      canaries: 1
      canary_watch_time: 30000-1200000
      max_in_flight: 1
```

ConfigMap の data の中に BOSH manifest の YAML 定義がそのまま格納されていることが確認できます。

`helm install` 実行後、1 時間程度で、すべての Pod が Running になりました。

```zsh
❯ kubectl -n kubecf get pod
NAME                               READY   STATUS    RESTARTS   AGE
kubecf-adapter-0                   4/4     Running   0          47m
kubecf-api-0                       15/15   Running   1          46m
kubecf-auctioneer-0                4/4     Running   2          46m
kubecf-bosh-dns-86784b95f7-q7wg4   1/1     Running   0          47m
kubecf-bosh-dns-86784b95f7-v6spg   1/1     Running   0          47m
kubecf-cc-worker-0                 4/4     Running   0          46m
kubecf-credhub-0                   6/6     Running   0          46m
kubecf-database-0                  2/2     Running   0          66m
kubecf-diego-api-0                 6/6     Running   4          46m
kubecf-diego-cell-0                7/7     Running   2          46m
kubecf-doppler-0                   9/9     Running   0          46m
kubecf-log-api-0                   7/7     Running   0          46m
kubecf-nats-0                      4/4     Running   0          47m
kubecf-router-0                    5/5     Running   4          46m
kubecf-routing-api-0               4/4     Running   1          46m
kubecf-scheduler-0                 10/10   Running   5          46m
kubecf-singleton-blobstore-0       6/6     Running   0          46m
kubecf-tcp-router-0                5/5     Running   0          46m
kubecf-uaa-0                       7/7     Running   5          46m
```

`helm install` を 2 回実行するだけで Cloud Foundry が立ち上がりました！

各 Pod は BOSH Instance Group を表しているようです。
quarksstatefulsets の状態に従って、StatefulSet が生成され、StatefulSet で定義された Pod が上記になっています。
多数のコンテナで構成される Pod も見受けられますが、これは各コンテナが [BOSH の Job](https://bosh.io/docs/jobs/) の単位で立ち上がっているためのようです。

試しに一つのコンテナの中に入って確認してみます。

```zsh
❯ kubectl -n kubecf exec -it kubecf-api-0 -c cloud-controller-ng-cloud-controller-ng /bin/bash
/:/var/vcap/jobs/cloud_controller_ng# ps -alx
F   UID     PID    PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND
4     0       1       0  20   0   4172   700 sigtim Ss   ?          0:00 /usr/bin/dumb-init -- /var/vcap/all-releases/container-run/container-run --post-start-name /var/vcap/jobs/cl
0     0       6       1  20   0 106444  8508 futex_ Ssl  ?          0:00 /var/vcap/all-releases/container-run/container-run --post-start-name /var/vcap/jobs/cloud_controller_ng/bin/
4     0      11       6  20   0 1312760 158988 poll_s Sl ?          0:19 ruby /var/vcap/packages/cloud_controller_ng/cloud_controller_ng/bin/cloud_controller -c /var/vcap/jobs/cloud
4     0    1465       0  20   0  18328  6180 wait   Ss   pts/0      0:00 /bin/bash
0     0    1520    1465  20   0  36544  1740 -      R+   pts/0      0:00 ps -alx
```

コンテナ内では単一の Job プロセスだけが稼働しているようです。

ただし、Instance Group の単位で Pod を構成しているため、ファイルシステムとしては各 Job の設定やログなどは見られるようです。

```zsh
/:/var/vcap/jobs/cloud_controller_ng# ls /var/vcap/jobs/
binary-buildpack       go-buildpack	      nginx-buildpack	      python-buildpack	    statsd_injector		suse-nginx-buildpack   suse-staticfile-buildpack
cc_uploader	       java-buildpack	      nodejs-buildpack	      r-buildpack	    suse-binary-buildpack	suse-nodejs-buildpack
cloud_controller_ng    loggregator_agent      php-buildpack	      route_registrar	    suse-dotnet-core-buildpack	suse-php-buildpack
dotnet-core-buildpack  loggr-forwarder-agent  policy-server	      ruby-buildpack	    suse-go-buildpack		suse-python-buildpack
file_server	       loggr-udp-forwarder    policy-server-internal  staticfile-buildpack  suse-java-buildpack		suse-ruby-buildpack
/:/var/vcap/jobs/cloud_controller_ng# tail -n1 /var/vcap/sys/log/cloud_controller_ng/cloud_controller_ng.log
{"timestamp":1584937427.0345204,"message":"Completed 200 vcap-request-id: aa9c2b94-8c66-4c2e-9ac2-2abe60479dbc","log_level":"info","source":"cc.api","data":{"request_guid":"aa9c2b94-8c66-4c2e-9ac2-2abe60479dbc"},"thread_id":47375851013980,"fiber_id":47375849848360,"process_id":11,"file":"/var/vcap/packages/cloud_controller_ng/cloud_controller_ng/middleware/request_logs.rb","lineno":24,"method":"call"}
```

# 動作確認

[cf CLI](https://github.com/cloudfoundry/cli) を使って、Cloud Foundry にログインしてみます。

```zsh
❯ cf api --skip-ssl-validation "https://api.kubecf.<your domain>"
API エンドポイントを https://api.kubecf.<your domain> に設定しています...
OK

API エンドポイント:   https://api.kubecf.<your domain>
api version:          2.146.0
ログインしていません。 'cf login' を使用してログインしてください。

❯ admin_pass=$(kubectl get secret \
        --namespace kubecf kubecf.var-cf-admin-password \
        -o jsonpath='{.data.password}' \
        | base64 --decode)

❯ cf auth admin "${admin_pass}"
API エンドポイント: https://api.kubecf.<your domain>
認証中です...
OK
ターゲットの組織とスペースを表示または設定するには 'cf target' を使用してください。
```

[スペース](https://docs.cloudfoundry.org/concepts/roles.html#spaces)を作成し、サンプルアプリケーションをデプロイしてみます。

```zsh
❯ cf create-space -o system kubecf
❯ cf target -o "system" -s "kubecf"
❯ git clone https://github.com/cloudfoundry-samples/test-app.git
❯ cd test-app
❯ cf push test-app

(snip...)

要求された状態: started
インスタンス: 1/1
使用: 256M x 1 インスタンス
URL: test-app-thioacetic-nonvariance.kubecf.<your domain>
最終アップロード日時: Mon Mar 23 04:26:21 UTC 2020
スタック: cflinuxfs3
ビルドパック: go

     状態   開始日時                 CPU    メモリー            ディスク          詳細
#0   実行   2020-03-23 01:32:03 PM   6.5%   256M の中の 15.5M   1G の中の 11.7M
```

普通の Cloud Foundry と変わりなくアプリケーションが起動しました。

アプリケーションにアクセスしてみます。

```zsh
❯ curl -k https://test-app-thioacetic-nonvariance.kubecf.<your domain>/env

(snip...)

<body class="">
<div class="envs"><dl><dt>CF_INSTANCE_ADDR</dt><dd>10.244.3.9:61001</dd><dt>LANG</dt><dd>en_US.UTF-8</dd><dt>OLDPWD</dt><dd>/home/vcap</dd><dt>CF_INSTANCE_PORT</dt><dd>61001</dd><dt>VCAP_APPLICATION</dt><dd>{"application_id":"d9461e75-f4a3-4007-abe2-d687adccf893","application_name":"test-app","application_uris":["test-app-thioacetic-nonvariance.kubecf.<your domain>"],"application_version":"cc6339c5-e27b-4010-b1f1-53b56e3b78f8","cf_api":"https://api.kubecf.<your domain>","host":"0.0.0.0","instance_id":"8d40f86c-eca2-4f53-41e3-0cdd","instance_index":0,"limits":{"disk":1024,"fds":16384,"mem":256},"name":"test-app","organization_id":"5998979e-4f00-4bfc-9f62-75fde744ff80","organization_name":"system","port":8080,"process_id":"d9461e75-f4a3-4007-abe2-d687adccf893","process_type":"web","space_id":"5acc1fc7-94f0-4b33-8fb7-ff6306c6907b","space_name":"kubecf","uris":["test-app-thioacetic-nonvariance.kubecf.<your domain>"],"version":"cc6339c5-e27b-4010-b1f1-53b56e3b78f8"}</dd><dt>MEMORY_LIMIT</dt><dd>256m</dd><dt>USER</dt><dd>vcap</dd><dt>CF_INSTANCE_INTERNAL_IP</dt><dd>10.38.0.2</dd><dt>VCAP_APP_PORT</dt><dd>8080</dd><dt>PWD</dt><dd>/home/vcap/app</dd><dt>HOME</dt><dd>/home/vcap/app</dd><dt>CF_INSTANCE_KEY</dt><dd>/etc/cf-instance-credentials/instance.key</dd><dt>PORT</dt><dd>8080</dd><dt>TMPDIR</dt><dd>/home/vcap/tmp</dd><dt>DEPS_DIR</dt><dd>/home/vcap/deps</dd><dt>CF_INSTANCE_GUID</dt><dd>8d40f86c-eca2-4f53-41e3-0cdd</dd><dt>CF_INSTANCE_PORTS</dt><dd>[{"external":61001,"internal":8080,"external_tls_proxy":61003,"internal_tls_proxy":61001},{"external":61002,"internal":2222,"external_tls_proxy":61004,"internal_tls_proxy":61002}]</dd><dt>CF_SYSTEM_CERT_PATH</dt><dd>/etc/cf-system-certificates</dd><dt>CF_INSTANCE_IP</dt><dd>10.244.3.9</dd><dt>INSTANCE_INDEX</dt><dd>0</dd><dt>CF_INSTANCE_INDEX</dt><dd>0</dd><dt>SHLVL</dt><dd>1</dd><dt>INSTANCE_GUID</dt><dd>8d40f86c-eca2-4f53-41e3-0cdd</dd><dt>VCAP_SERVICES</dt><dd>{}</dd><dt>VCAP_APP_HOST</dt><dd>0.0.0.0</dd><dt>PATH</dt><dd>/usr/local/bin:/usr/bin:/bin:/home/vcap/app/bin</dd><dt>CF_INSTANCE_CERT</dt><dd>/etc/cf-instance-credentials/instance.crt</dd><dt>_</dt><dd>/home/vcap/app/bin/test-app</dd></dl></div>
</body>
</html>
```

ログの取得や SSH アクセスも問題なさそうです。

```zsh
❯ cf logs --recent test-app
Retrieving logs for app test-app in org system / space kubecf as admin...

(snip...)

   2020-03-23T13:34:16.25+0900 [APP/PROC/WEB/0] OUT test-app. Says Hello. on index: 0

❯ cf ssh test-app
vcap@8d40f86c-eca2-4f53-41e3-0cdd:~$ ls
app  deps  logs  profile.d  staging_info.yml  tmp
vcap@8d40f86c-eca2-4f53-41e3-0cdd:~$ cd app/
vcap@8d40f86c-eca2-4f53-41e3-0cdd:~/app$ ls
bin  build.sh  Dockerfile  go.mod  go.sum  handlers  helpers  LICENSE  main.go  Procfile  README.md  routes  vendor
```

# Eirini を有効にしてみる

デフォルト設定で KubeCF をインストールした場合、Cloud Foundry にデプロイしたアプリケーションは、Cloud Foundry 独自のコンテナオーケストレーターである [Diego](https://docs.cloudfoundry.org/concepts/diego/diego-architecture.html) によってスケジューリングされます。
この場合、Diego のワーカーノードに相当する Diego Cell というコンポーネントが　Pod として稼働しており、デプロイしたアプリケーションはさらにその上で稼働するという状態になります。

このようなオーバヘッドをなくし、Cloud Foundry のワークロードを直接 Kubernetes のリソースとして実行できるようにする機構が [Eirini](https://www.cloudfoundry.org/project-eirini/) です。

KubeCF v1.0.1 では Eirini v1.0 を取り込んでいるそうなので、試してみます。

現行の Eirini なしの構成からありの構成にスイッチできるのかわからなかったので、確実のために一旦 KubeCF をアンインストールしてから、新しい構成でインストールし直します。

```zsh
❯ helm -n kubecf delete kubecf
```

先ほどの `values.yaml` に `features.eirini.enabled=true` の設定を追加します。

```yaml:values.yaml
system_domain: kubecf.<your domain>
features:
  eirini:
    enabled: true
```

再度 `helm install` で KubeCF をインストールします。

先ほどとリソース構成が少し変わっていることが確認できます。

```zsh
❯ helm install kubecf \
  --namespace kubecf \
  --values values.yaml \
  https://github.com/cloudfoundry-incubator/kubecf/releases/download/v1.0.1/kubecf-v1.0.1.tgz

NAME: kubecf
LAST DEPLOYED: Mon Mar 23 13:51:14 2020
NAMESPACE: kubecf
STATUS: deployed
REVISION: 1
TEST SUITE: None
```

今度は 30 分程度ですべての Pod が Running になりました。

```zsh
❯ kubectl -n kubecf get pod
NAME                               READY   STATUS    RESTARTS   AGE
kubecf-adapter-0                   4/4     Running   0          17m
kubecf-api-0                       15/15   Running   1          16m
kubecf-bits-0                      6/6     Running   0          16m
kubecf-bosh-dns-86784b95f7-tcx29   1/1     Running   0          17m
kubecf-bosh-dns-86784b95f7-zbkmz   1/1     Running   0          17m
kubecf-cc-worker-0                 4/4     Running   0          16m
kubecf-credhub-0                   6/6     Running   0          16m
kubecf-database-0                  2/2     Running   0          25m
kubecf-diego-api-0                 6/6     Running   2          17m
kubecf-doppler-0                   9/9     Running   0          16m
kubecf-eirini-0                    9/9     Running   0          16m
kubecf-log-api-0                   7/7     Running   0          16m
kubecf-nats-0                      4/4     Running   0          17m
kubecf-router-0                    5/5     Running   2          16m
kubecf-routing-api-0               4/4     Running   0          16m
kubecf-scheduler-0                 8/8     Running   0          16m
kubecf-singleton-blobstore-0       6/6     Running   0          17m
kubecf-tcp-router-0                5/5     Running   0          16m
kubecf-uaa-0                       7/7     Running   2          16m
```

前回と同様に一般的な Cloud Foundry の操作が実行できるか確認します。

```zsh
❯ cf api --skip-ssl-validation "https://api.kubecf.<your domain>"
❯ admin_pass=$(kubectl get secret \
        --namespace kubecf kubecf.var-cf-admin-password \
        -o jsonpath='{.data.password}' \
        | base64 --decode)
❯ cf auth admin "${admin_pass}"
❯ cf create-space -o system kubecf
❯ cf target -o "system" -s "kubecf"
❯ git clone https://github.com/cloudfoundry-samples/test-app.git
❯ cd test-app
❯ cf push test-app
❯ curl -k https://test-app-unnauseating-underzealot.kubecf.<your domain>/env
❯ cf logs --recent test-app
```

上記までは問題なく実行できましたが、リリースノートにある通り、`cf ssh` は機能しませんでした。

```zsh
❯ cf ssh test-app
失敗
エラー: SSH session allocation failed: EOF
```

Eirini を有効にしたので、デプロイしたアプリケーションは Kubernetes のリソースとして稼働していることが確認できます。

デプロイしたアプリケーションは、Cloud Foundry のシステムコンポーネントとは別の Namespace で稼働するようです。

```zsh
❯ kubectl -n kubecf-eirini get all
NAME                               READY   STATUS    RESTARTS   AGE
pod/test-app-kubecf-5ae5449c68-0   1/1     Running   0          9m19s

NAME                                          READY   AGE
statefulset.apps/test-app-kubecf-5ae5449c68   1/1     9m19s
```

StatefulSet として定義されているアプリケーションの状態を見てみると、Cloud Foundry のアプリケーションとして振る舞うための情報がぎっしり詰め込まれていることが確認できます。

```zsh
❯ kubectl -n kubecf-eirini get statefulsets.apps test-app-kubecf-5ae5449c68 -o yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  annotations:
    cloudfoundry.org/application_id: 1c559be1-5534-4bc2-a11e-357a462edf7b
    cloudfoundry.org/application_name: test-app
    cloudfoundry.org/application_uris: '[{"hostname":"test-app-dubitable-flesh.kubecf.<your domain>","port":8080}]'
    cloudfoundry.org/last_updated: "1584946309.0"
    cloudfoundry.org/original_request: '{"guid":"1c559be1-5534-4bc2-a11e-357a462edf7b","version":"b06cbcf3-595e-43f2-857a-a2dd6ba6a78d","process_guid":"1c559be1-5534-4bc2-a11e-357a462edf7b-b06cbcf3-595e-43f2-857a-a2dd6ba6a78d","process_type":"web","app_guid":"1c559be1-5534-4bc2-a11e-357a462edf7b","app_name":"test-app","space_guid":"e1afaffc-3381-4c7c-8c8e-957102aa2406","space_name":"kubecf","organization_guid":"79c9de03-3dbb-4a1b-a7d2-de7c999cc0a5","organization_name":"system","environment":{"VCAP_APPLICATION":"{\"cf_api\":\"https://api.kubecf.<your domain>\",\"limits\":{\"fds\":16384,\"mem\":256,\"disk\":1024},\"application_name\":\"test-app\",\"application_uris\":[\"test-app-dubitable-flesh.kubecf.<your domain>\"],\"name\":\"test-app\",\"space_name\":\"kubecf\",\"space_id\":\"e1afaffc-3381-4c7c-8c8e-957102aa2406\",\"organization_id\":\"79c9de03-3dbb-4a1b-a7d2-de7c999cc0a5\",\"organization_name\":\"system\",\"uris\":[\"test-app-dubitable-flesh.kubecf.<your domain>\"],\"process_id\":\"1c559be1-5534-4bc2-a11e-357a462edf7b\",\"process_type\":\"web\",\"application_id\":\"1c559be1-5534-4bc2-a11e-357a462edf7b\",\"version\":\"b06cbcf3-595e-43f2-857a-a2dd6ba6a78d\",\"application_version\":\"b06cbcf3-595e-43f2-857a-a2dd6ba6a78d\"}","MEMORY_LIMIT":"256m","VCAP_SERVICES":"{}","PORT":"8080","VCAP_APP_PORT":"8080","VCAP_APP_HOST":"0.0.0.0"},"egress_rules":[{"protocol":"all","destinations":["0.0.0.0-9.255.255.255"],"annotations":["security_group_id:94bc433e-54f6-4e06-91ce-b5819eeec621"]},{"protocol":"all","destinations":["11.0.0.0-169.253.255.255"],"annotations":["security_group_id:94bc433e-54f6-4e06-91ce-b5819eeec621"]},{"protocol":"all","destinations":["169.255.0.0-172.15.255.255"],"annotations":["security_group_id:94bc433e-54f6-4e06-91ce-b5819eeec621"]},{"protocol":"all","destinations":["172.32.0.0-192.167.255.255"],"annotations":["security_group_id:94bc433e-54f6-4e06-91ce-b5819eeec621"]},{"protocol":"all","destinations":["192.169.0.0-255.255.255.255"],"annotations":["security_group_id:94bc433e-54f6-4e06-91ce-b5819eeec621"]},{"protocol":"tcp","destinations":["0.0.0.0/0"],"ports":[53],"annotations":["security_group_id:5ad109f0-96c9-48a3-9f9a-3c97d8ac0f84"]},{"protocol":"udp","destinations":["0.0.0.0/0"],"ports":[53],"annotations":["security_group_id:5ad109f0-96c9-48a3-9f9a-3c97d8ac0f84"]},{"protocol":"tcp","destinations":["10.244.4.27/32"],"ports":[8443],"annotations":["security_group_id:193773f1-315d-4d70-988b-4604c4d34a24"]},{"protocol":"tcp","destinations":["10.244.1.15/32"],"ports":[8844],"annotations":["security_group_id:f2463f0c-4db6-4220-9889-9aaef8ac13b1"]}],"placement_tags":[],"instances":1,"memory_mb":256,"disk_mb":1024,"cpu_weight":3,"health_check_type":"port","health_check_http_endpoint":null,"health_check_timeout_ms":0,"start_timeout_ms":60000,"last_updated":"1584946309.0","volume_mounts":[],"ports":[8080],"routes":{"cf-router":[{"hostname":"test-app-dubitable-flesh.kubecf.<your domain>","port":8080}]},"lifecycle":{"buildpack_lifecycle":{"start_command":"test-app","droplet_hash":"fe97c9c416f0ead6fa59fe3581bb94d22ab03d24","droplet_guid":"14ee9069-03b9-4cc2-ad5e-903f57ba43c7"}},"user_defined_annotations":{}}'
    cloudfoundry.org/process_guid: 1c559be1-5534-4bc2-a11e-357a462edf7b-b06cbcf3-595e-43f2-857a-a2dd6ba6a78d
    cloudfoundry.org/routes: '[{"hostname":"test-app-dubitable-flesh.kubecf.<your domain>","port":8080}]'
    cloudfoundry.org/space_name: kubecf
    cloudfoundry.org/version: b06cbcf3-595e-43f2-857a-a2dd6ba6a78d
  creationTimestamp: "2020-03-23T06:51:49Z"
  generation: 1
  labels:
    cloudfoundry.org/app_guid: 1c559be1-5534-4bc2-a11e-357a462edf7b
    cloudfoundry.org/guid: 1c559be1-5534-4bc2-a11e-357a462edf7b
    cloudfoundry.org/process_type: web
    cloudfoundry.org/rootfs-version: ""
    cloudfoundry.org/source_type: APP
    cloudfoundry.org/version: b06cbcf3-595e-43f2-857a-a2dd6ba6a78d
  name: test-app-kubecf-5ae5449c68
  namespace: kubecf-eirini
  resourceVersion: "43813"
  selfLink: /apis/apps/v1/namespaces/kubecf-eirini/statefulsets/test-app-kubecf-5ae5449c68
  uid: df890530-43f9-4284-b6db-54ba2c75907a
spec:
  podManagementPolicy: Parallel
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      cloudfoundry.org/guid: 1c559be1-5534-4bc2-a11e-357a462edf7b
      cloudfoundry.org/source_type: APP
      cloudfoundry.org/version: b06cbcf3-595e-43f2-857a-a2dd6ba6a78d
  serviceName: ""
  template:
    metadata:
      annotations:
        cloudfoundry.org/application_id: 1c559be1-5534-4bc2-a11e-357a462edf7b
        cloudfoundry.org/process_guid: 1c559be1-5534-4bc2-a11e-357a462edf7b-b06cbcf3-595e-43f2-857a-a2dd6ba6a78d
        seccomp.security.alpha.kubernetes.io/pod: runtime/default
      creationTimestamp: null
      labels:
        cloudfoundry.org/app_guid: 1c559be1-5534-4bc2-a11e-357a462edf7b
        cloudfoundry.org/guid: 1c559be1-5534-4bc2-a11e-357a462edf7b
        cloudfoundry.org/process_type: web
        cloudfoundry.org/rootfs-version: ""
        cloudfoundry.org/source_type: APP
        cloudfoundry.org/version: b06cbcf3-595e-43f2-857a-a2dd6ba6a78d
    spec:
      automountServiceAccountToken: false
      containers:
      - command:
        - dumb-init
        - --
        - /lifecycle/launch
        env:
        - name: PORT
          value: "8080"
        - name: VCAP_APP_PORT
          value: "8080"
        - name: VCAP_APPLICATION
          value: '{"cf_api":"https://api.kubecf.<your domain>","limits":{"fds":16384,"mem":256,"disk":1024},"application_name":"test-app","application_uris":["test-app-dubitable-flesh.kubecf.<your domain>"],"name":"test-app","space_name":"kubecf","space_id":"e1afaffc-3381-4c7c-8c8e-957102aa2406","organization_id":"79c9de03-3dbb-4a1b-a7d2-de7c999cc0a5","organization_name":"system","uris":["test-app-dubitable-flesh.kubecf.<your domain>"],"process_id":"1c559be1-5534-4bc2-a11e-357a462edf7b","process_type":"web","application_id":"1c559be1-5534-4bc2-a11e-357a462edf7b","version":"b06cbcf3-595e-43f2-857a-a2dd6ba6a78d","application_version":"b06cbcf3-595e-43f2-857a-a2dd6ba6a78d"}'
        - name: PATH
          value: /usr/local/bin:/usr/bin:/bin
        - name: TMPDIR
          value: /home/vcap/tmp
        - name: MEMORY_LIMIT
          value: 256m
        - name: VCAP_APP_HOST
          value: 0.0.0.0
        - name: CF_INSTANCE_PORT
          value: "8080"
        - name: START_COMMAND
          value: test-app
        - name: HOME
          value: /home/vcap/app
        - name: CF_INSTANCE_ADDR
          value: 0.0.0.0:8080
        - name: VCAP_SERVICES
          value: '{}'
        - name: CF_INSTANCE_PORTS
          value: '[{"external":8080,"internal":8080}]'
        - name: LANG
          value: en_US.UTF-8
        - name: USER
          value: vcap
        - name: POD_NAME
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: metadata.name
        - name: CF_INSTANCE_IP
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: status.podIP
        - name: CF_INSTANCE_INTERNAL_IP
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: status.podIP
        image: 127.0.0.1:32123/cloudfoundry/14ee9069-03b9-4cc2-ad5e-903f57ba43c7:fe97c9c416f0ead6fa59fe3581bb94d22ab03d24
        imagePullPolicy: Always
        livenessProbe:
          failureThreshold: 4
          periodSeconds: 10
          successThreshold: 1
          tcpSocket:
            port: 8080
          timeoutSeconds: 1
        name: opi
        ports:
        - containerPort: 8080
          protocol: TCP
        readinessProbe:
          failureThreshold: 1
          periodSeconds: 10
          successThreshold: 1
          tcpSocket:
            port: 8080
          timeoutSeconds: 1
        resources:
          limits:
            ephemeral-storage: 1024M
            memory: 256M
          requests:
            cpu: 30m
            memory: 256M
        securityContext:
          allowPrivilegeEscalation: false
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      imagePullSecrets:
      - name: bits-service-registry-secret
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
  updateStrategy:
    rollingUpdate:
      partition: 0
    type: RollingUpdate
status:
  collisionCount: 0
  currentReplicas: 1
  currentRevision: test-app-kubecf-5ae5449c68-6f56989b98
  observedGeneration: 1
  readyReplicas: 1
  replicas: 1
  updateRevision: test-app-kubecf-5ae5449c68-6f56989b98
  updatedReplicas: 1
```

`cf ssh` コマンドが使えない代わりに `kubectl exec` を使うことでアプリケーション内部に入ることができます。

ただし、`cf ssh` よりも強い権限を持った状態でコンテナにログインできてしまうので、セキュリティ上好ましくはありません。

```zsh
❯ kubectl -n kubecf-eirini exec -it test-app-kubecf-5ae5449c68-0 /bin/bash
vcap@test-app-kubecf-5ae5449c68-0:/$ ls /app
bin  build.sh  Dockerfile  go.mod  go.sum  handlers  helpers  LICENSE  main.go	Procfile  README.md  routes  vendor
```

# 後片付け

検証が終わったら `helm delete` でリソースを削除します。

（削除があっという間に終わることも地味に嬉しいポイントです。）

```zsh
❯ helm -n kubecf delete kubecf
❯ helm -n cf-operator delete cf-operator
```

# おわりに

従来の BOSH を使った Cloud Foundry のインストール手順に比べて、あまりにも簡単にインストールができてしまったので、従来の方式を知る者としては大変感動しました。

ただし、今回検証した KubeCF の構成はあくまでデフォルト構成であり、構成定義自体は ConfigMap に BOSH manifest がそのまま書いてあるだけなので、
構成をカスタマイズしようと思うと従来どおり Ops File をゴリゴリ書いていく必要が (少なくとも現状では) ありそうです。

**【2020/03/23 追記】Project Quarks は、互換性維持の観点から既存の BOSH Releases を (cf-operator によって) そのまま利用する方式を採用していますが、これとは別の動きとして、BOSH 非依存で Kubernetes のネイティブ機能のみで実行可能な Cloud Foundry リリースを開発する [CF-for-K8s](https://github.com/cloudfoundry/cf-for-k8s) というプロジェクトが立ち上がっています。これらの 2 プロジェクトは競合関係にはなく、KubeCF のコンポーネントを CF-for-K8s のコンポーネントに段階的に置き換えていくことによって、既存ユーザーにスムーズな移行パスを提供することを図っていくようです。~~近いうちに [CF-for-K8s のアルファ版がリリースされる](https://github.com/cloudfoundry/cf-for-k8s/tree/bd7b4f6efba37e5eb418430cd270b06fcf8725fd#-whats-next)ようなので、リリースされた際にはそちらも動作検証してみようと思います。~~**

**【2020/04/18 追記】CF-for-K8s のアルファ版がリリースされたので検証しました。**

* **[Cloud Foundry for Kubernetes を使って Kubernetes native な Cloud Foundry Application Runtime を構築する](https://qiita.com/a09-capf/items/ec6a32bb2b3eed6b0ce8)**

Kubernetes の上に PaaS のレイヤを提供しようという取り組みは、Project Quarks 以外にも [Knative](https://github.com/knative) など多数存在しており、今後の展望を見通すことは難しいですが、少なくとも Cloud Foundry の「ソースコードをアップロードしたらすぐ動く」という、Developer Experience を重要視する思想は大変素晴らしいと思っているので、この思想がうまく Kubernetes の世界にも統合されていくことを願っております。

それでは、良い新年をお迎えください！