Cloud Foundryの複数Portに対するRouting
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
目次
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を使用します。
まずは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で変更できます。
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を使う場合も同じようにRouteをmapできます。
Blue/Greenデプロイを行う場合は上記のコマンドをblueとgreenそれぞれに行う必要があるため注意です。