---
title: Cloud Foundryの複数Portに対するRouting
tags: ["Cloud Foundry", "PAS", "Pivotal Cloud Foundry", "Zipkin", "Docker", "Spring Boot", "Spring Boot Actuator"]
categories: ["Dev", "PaaS", "CloudFoundry", "PCF"]
date: 2020-03-22T08:19:49Z
updated: 2020-03-23T00:05:08Z
---

Cloud Foundryはかつては単一ポート(通常`8080`)へのRoutingしかサポートしていませんでしたが、
今では任意のポートに対してHTTPでもTCPでもRouteできますし、複数ポートのサポートされています。

次のドキュメントが参考になります。
* https://docs.cloudfoundry.org/devguide/custom-ports.html#procedure
* https://docs.google.com/presentation/d/1_5px0UB0k7USS71xVnn6XgU1eArZ__Ee_eYmxkj-XcE/edit#slide=id.g5ba1df2c92_0_77

**目次**
<!-- toc -->

### Buildpackを使った普通のRouting
まずは復習として"普通のRouting"を改めて見てみます。

次のコマンドでSpring Bootのアプリケーション雛形を作成します。
```
curl https://start.spring.io/starter.tgz \
       -d artifactId=hello-cf \
       -d baseDir=hello-cf \
       -d dependencies=web,actuator \
       -d packageName=com.example \
       -d applicationName=HelloCfApplication | tar -xzvf -
cd hello-cf
./mvnw clean package -DskipTests=true
```

Routeをつけずに`cf push`します。

```
cf push hello -p target/hello-cf-0.0.1-SNAPSHOT.jar --no-route
```

この後頻繁に使うため、`APP_GUID`を環境変数に設定します。

```
APP_GUID=$(cf app hello --guid)
```

次のコマンドでデプロイしたアプリがサポートしているportを確認できます。

```
$ cf curl "/v2/apps/${APP_GUID}" | jq ".entity.ports"               
[
  8080
]
```

`cf map-route`はこのportに対するRouteのmappingです。

この後、使う環境変数を設定しておきます。

```
APPS_DOMAIN=$(cf curl "/v2/shared_domains" | jq -r ".resources[0].entity.name")
CURRENT_SPACE=$(cat ~/.cf/config.json | jq -r ".SpaceFields.Name")
```

通常は次のコマンドでRoute(この場合は`hello1.${APPS_DOMAIN}`)をmapします。

```
cf map-route hello ${APPS_DOMAIN} --hostname hello1
```

これは次のコマンドと概ね同じです。

``` 
cf create-route ${CURRENT_SPACE} ${APPS_DOMAIN} --hostname hello1
ROUTE_GUID=$(cf curl "/v2/routes?q=host:hello1" | jq -r ".resources[0].metadata.guid")
# 対象のROUTE_GUIDを持つRouteを対象のAPP_GUIDを持つAppの8080番ポートにマッピングする
cf curl /v2/route_mappings -X POST -d "{\"app_guid\": \"${APP_GUID}\", \"route_guid\": \"${ROUTE_GUID}\", \"app_port\": 8080}"
```

これで

```
curl -s https://hello1.${APPS_DOMAIN}/actuator
```

にアクセスできます。

> TCP Routeを使う場合は
> ```
> cf map-route ${APP_NAME} ${TCP_DOMAIN} --port 1024
> ```
> または
> ```
> cf create-route ${CURRENT_SPACE} ${TCP_DOMAIN} --port 1024
> ROUTE_GUID=$(cf curl "/v2/routes?q=port:1024" | jq -r ".resources[0].metadata.guid")
> # あとは同じ
> ```

次のコマンドでhelloのAppとRouteを削除します。

```
cf delete -r -f hello
```

### Docker Imageを使った普通のRouting

次にDocker Imageを`cf push`する場合にどうなるか見てみます。
題材には[Zipkin](https://github.com/openzipkin/zipkin)を使用します。

まずは`openzipkin/zipkin-slim`のDocker Imageをpushします。

```
cf push zipkin --docker-image openzipkin/zipkin-slim:2.20.2 --no-route
```

この後頻繁に使うため、`APP_GUID`を環境変数に設定します。

```
APP_GUID=$(cf app zipkin --guid)
```

次のコマンドでデプロイしたアプリがサポートしているportを確認できます。

```
$ cf curl "/v2/apps/${APP_GUID}" | jq ".entity.ports"               
[
  9411
]
```

Buildpackの場合とは異なり`8080`ではありません。
このPortは`Dockerfile`内の`EXPOSE`に設定されている値です。
https://github.com/openzipkin/zipkin/blob/2.20.2/docker/Dockerfile#L81

HTTPのRouteをmappingはBuildpack同様に次のコマンドでできます。

```
cf map-route zipkin ${APPS_DOMAIN} --hostname zipkin1
```

または

```
cf create-route ${CURRENT_SPACE} ${APPS_DOMAIN} --hostname zipkin1
ROUTE_GUID=$(cf curl "/v2/routes?q=host:zipkin1" | jq -r ".resources[0].metadata.guid")
# 対象のROUTE_GUIDを持つRouteを対象のAPP_GUIDを持つAppの9411番ポートにマッピングする
cf curl /v2/route_mappings -X POST -d "{\"app_guid\": \"${APP_GUID}\", \"route_guid\": \"${ROUTE_GUID}\", \"app_port\": 9411}"
```

あえて`cf push`に`--no-route`をつけましたが、`--no-route`をつけなければRouteのmapも自動で行われます。

これで https://zipkin1.${APPS_DOMAIN} でZipkinにアクセスできます。

次のコマンドでzipkinのAppとRouteを削除します

```
cf delete -r -f zipkin
```

### 複数PortをExposeするDocker Imageを使ったRouting

ここまで単一portのみサポートされているAppを見てきましたが、次は複数portをサポートするAppを見てみます。
同じZipkinでも`openzipkin/zipkin` Docker Imageは`9410`と`9411`をサポートします。

```
cf push zipkin --docker-image openzipkin/zipkin:2.20.2 --no-route --health-check-type process
```

`--health-check-type process`をつけた理由は後述します。

この後頻繁に使うため、`APP_GUID`を環境変数に設定します。

```
APP_GUID=$(cf app zipkin --guid)
```

次のコマンドでデプロイしたアプリがサポートしているportを確認できます。

```
$ cf curl "/v2/apps/${APP_GUID}" | jq ".entity.ports"               
[
  9410,
  9411
]
```

この順番は`Dockerfile`内の`EXPOSE`の順です。
https://github.com/openzipkin/zipkin/blob/2.20.2/docker/Dockerfile#L134

Diegoはこの`ports`の最初のportをhealth checkします。
Zipkinは`9411`が通常使用するHTTPのportであり、`9410`はLegacyなScribe Collectorを使う場合のThriftのportです。
デフォルトでScribe Collectorはdisabledになっているため、このままではport health checkがfailします。そのため、`--health-check-type process`をつけました。
`9410`でport health checkするには環境変数`COLLECTOR_SCRIBE_ENABLED`を`true`にする必要があります。

ここでは`9410`ポートを使わず、`9411`だけをRoute対象にします。次のコマンドで設定を行います。

```
cf curl "/v2/apps/${APP_GUID}" -X PUT -d "{\"ports\": [9411]}"
```

変更内容を確認します。

```
$ cf curl "/v2/apps/${APP_GUID}" | jq ".entity.ports"
[
  9411
]
```

これで`openzipkin/zipkin-slim`を使う場合、すなわち単一portと同じ条件になり、次のコマンドでRouteをmapできます。

```
cf map-route zipkin ${APPS_DOMAIN} --hostname zipkin1
```
または
```
cf create-route ${CURRENT_SPACE} ${APPS_DOMAIN} --hostname zipkin1
ROUTE_GUID=$(cf curl "/v2/routes?q=host:zipkin1" | jq -r ".resources[0].metadata.guid")
cf curl /v2/route_mappings -X POST -d "{\"app_guid\": \"${APP_GUID}\", \"route_guid\": \"${ROUTE_GUID}\", \"app_port\": 9411}"
```

まとめるとZipkinを`openzipkin/zipkin`のDocker Imageでpushして`9411`ポートにHTTP Routingするには

```
cf push zipkin --docker-image openzipkin/zipkin:2.20.2 --no-route --no-start
APP_GUID=$(cf app zipkin --guid)
cf curl "/v2/apps/${APP_GUID}" -X PUT -d "{\"ports\": [9411]}"
cf map-route zipkin ${APPS_DOMAIN} --hostname zipkin1
cf start zipkin
```

Docker Imageが複数のPortをExposeする場合は最初のportが使われるのが要注意点です。
`openzipkin/zipkin`のように最初のportが使われると不都合がある場合は上記のように設定を変更する必要があります。

### 複数PortへのRouting

ここまで紹介したことを組み合わせると、アプリケーションが複数Portを持つ場合に、
それぞれのPortに対してRouteをmapすることができます。

最初に作ったアプリケーションにportを追加します。

Spring BootはActuatorのportを`management.server.port`で変更できます。環境変数の場合は`MANAGEMENT_SERVER_PORT`で変更できます。

https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-customizing-management-server-port

```
cf push hello -p target/hello-cf-0.0.1-SNAPSHOT.jar --no-route --no-start
cf set-env hello MANAGEMENT_SERVER_PORT 8081
```

これで`8080`は通常のHTTPリクエスト、`8081`はActuatorへのHTTPリクエストを処理するようになります。

Appが`8080`と`8081` portをサポートできるように設定を変えます。

```
APP_GUID=$(cf app hello --guid)
cf curl "/v2/apps/${APP_GUID}" -X PUT -d "{\"ports\": [8080, 8081]}"
```

変更内容を確認します。

```
$ cf curl "/v2/apps/${APP_GUID}" | jq ".entity.ports"
[
  8080,
  8081
]
```

`8080`と`8081` portそれぞれにRouteを作成してmapします。

```
# 8080番portへのRoute
cf create-route ${CURRENT_SPACE} ${APPS_DOMAIN} --hostname hello1
ROUTE_GUID1=$(cf curl "/v2/routes?q=host:hello1" | jq -r ".resources[0].metadata.guid")
cf curl /v2/route_mappings -X POST -d "{\"app_guid\": \"${APP_GUID}\", \"route_guid\": \"${ROUTE_GUID1}\", \"app_port\": 8080}"

# 8081番portへのRoute
cf create-route ${CURRENT_SPACE} ${APPS_DOMAIN} --hostname hello2
ROUTE_GUID2=$(cf curl "/v2/routes?q=host:hello2" | jq -r ".resources[0].metadata.guid")
cf curl /v2/route_mappings -X POST -d "{\"app_guid\": \"${APP_GUID}\", \"route_guid\": \"${ROUTE_GUID2}\", \"app_port\": 8081}"
```

Appを起動します。

```
cf start hello
```

これでActuator Endpointが`hello2`でのみ有効になっていることが確認できます。

```
$ curl -s https://hello1.${APPS_DOMAIN}/actuator | jq .
{
  "timestamp": "2020-03-22T08:00:14.398+0000",
  "status": 404,
  "error": "Not Found",
  "message": "No message available",
  "path": "/actuator"
}

$ curl -s https://hello2.${APPS_DOMAIN}/actuator | jq .
{
  "_links": {
    "self": {
      "href": "https://hello2.${APPS_DOMAIN}/actuator",
      "templated": false
    },
    "health": {
      "href": "https://hello2.${APPS_DOMAIN}/actuator/health",
      "templated": false
    },
    "health-path": {
      "href": "https://hello2.${APPS_DOMAIN}/actuator/health/{*path}",
      "templated": true
    },
    "info": {
      "href": "https://hello2.${APPS_DOMAIN}/actuator/info",
      "templated": false
    }
  }
}
```

複数ポートへのRoutingはDocker Imageを使う場合も同様です。`EXPOSE`に記述されていないportも`cf curl "/v2/apps/${APP_GUID}" -X PUT -d "{\"ports\": [port1, port2, ...]}"`でサポート可能です。
また、[Sidecar](https://docs.cloudfoundry.org/devguide/sidecars.html)を使う場合も同じようにRouteをmapできます。

[Blue/Greenデプロイ](https://docs.cloudfoundry.org/devguide/deploy-apps/blue-green.html)を行う場合は上記のコマンドをblueとgreenそれぞれに行う必要があるため注意です。
