warning
この記事は2年以上前に更新されたものです。情報が古くなっている可能性があります。

前の記事でCloud Native BuildpacksでSpring BootアプリのGraalVM Native Imageを作成する方法を紹介しました。
本記事ではこのNative ImageをKnativeにデプロイし、相性が良いことを確認します。

目次

アプリケーションの作成

この記事と同じです。

curl https://start.spring.io/starter.tgz \
  -s \
  -d javaVersion=11 \
  -d artifactId=hello-world \
  -d bootVersion=2.4.0-M2 \
  -d baseDir=hello-world \
  -d dependencies=webflux,actuator \
  -d packageName=com.example \
  -d applicationName=HelloWorldApplication | tar -xzvf -

cd hello-world
cat <<'EOF' > src/main/java/com/example/HelloWorldApplication.java
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication(proxyBeanMethods = false)
@RestController
public class HelloWorldApplication {
        @GetMapping("/") 
        public String hello() {
                return "Hello World!";
        }
        public static void main(String[] args) {
                SpringApplication.run(HelloWorldApplication.class, args);
        }
}
EOF
sed -i '' -e 's|<artifactId>spring-boot-maven-plugin</artifactId>|<artifactId>spring-boot-maven-plugin</artifactId><configuration><image><builder>paketobuildpacks/builder:tiny</builder><env><BP_BOOT_NATIVE_IMAGE>1</BP_BOOT_NATIVE_IMAGE><BP_BOOT_NATIVE_IMAGE_BUILD_ARGUMENTS>-Dspring.native.remove-yaml-support=true -Dspring.spel.ignore=true -Dspring.xml.ignore=true -Dspring.spel.ignore=true</BP_BOOT_NATIVE_IMAGE_BUILD_ARGUMENTS></env></image></configuration>|' pom.xml
./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=making/hello-world

Note: 自分の環境だと./mvnw spring-boot:build-imageでイメージを作成する時間のうち、初回は5分くらいがgraalvmのダウンロード時間です。

2回目以降はキャッシュされますが、イメージ名を変えると再度ダウンロードが行われます。この時間を省略するには、初めからgraalvmを同梱したbuildpackが必要です。

プロプライエタリなJava Native Image Buildpack for VMware Tanzu(registry.pivotal.io/tanzu-java-native-image-buildpack/java-native-image:3.4.1)はこの用途に使えます。

VMware Tanzu Networkのアカウントを作成して、docker login registry.pivotal.ioすれば、イメージをpullできます。

VMware Tanzu NetworkではBuilderは用意されていないので、このBuildpackを含むBuilderを自分で作成する必要があります。

Builderの作成方法はこちらの記事を参考にしてください。

作成したイメージを確認します。tiny Stackを使用しているので、サイズは小さいです。

$ docker image ls making/hello-world
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
making/hello-world   latest              3f581955b4b7        40 years ago        86.2MB

diveでイメージレイヤーを確認すると、18MBがOS部分で66MBがnative image部分であることがわかります。

image

まずはこのイメージをdocker runで起動します。

$ docker run --rm -p 8080:8080 making/hello-world

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                        

2020-09-21 09:28:20.747  INFO 1 --- [           main] com.example.HelloWorldApplication        : Starting HelloWorldApplication using Java 11.0.8 on cbf8dc7af660 with PID 1 (/workspace/com.example.HelloWorldApplication started by cnb in /workspace)
2020-09-21 09:28:20.747  INFO 1 --- [           main] com.example.HelloWorldApplication        : No active profile set, falling back to default profiles: default
2020-09-21 09:28:20.811  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
2020-09-21 09:28:20.817  WARN 1 --- [           main] i.m.c.i.binder.jvm.JvmGcMetrics          : GC notifications will not be available because MemoryPoolMXBeans are not provided by the JVM
2020-09-21 09:28:20.825  WARN 1 --- [           main] io.netty.channel.DefaultChannelId        : Failed to find the current process ID from ''; using a random value: -401303669
2020-09-21 09:28:20.827  INFO 1 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080
2020-09-21 09:28:20.829  INFO 1 --- [           main] com.example.HelloWorldApplication        : Started HelloWorldApplication in 0.091 seconds (JVM running for 0.095)

8080ポートにアクセスして"Hello World!"が返ることを確認します。

$ curl -s http://localhost:8080
Hello World!

Knativeのインストール

次のこのイメージをKnativeにデプロイします。

ここではKnativeをDocker for Macにデプロイする例を紹介します。Kubernetes 1.16です。

$ kubectl get node -o wide
NAME             STATUS   ROLES    AGE     VERSION          INTERNAL-IP    EXTERNAL-IP   OS-IMAGE         KERNEL-VERSION     CONTAINER-RUNTIME
docker-desktop   Ready    master   2m39s   v1.16.6-beta.0   192.168.65.3   <none>        Docker Desktop   4.19.76-linuxkit   docker://19.3.12

"Reset Kubernetes Cluster"をクリックして一旦環境をクリーンアップします。

image

cert-managerを使わない場合と使う場合、それぞれ紹介します。

Network LayerにはStableなIstioを使用します。

kubectlの他に次のCLIをインストールしてください。

  • istioctl (brew install istioctl)
  • kn (brew install knative/client/kn)
  • vegeta (brew install vegeta)

次のバージョンで動作確認しています。

$ kubectl version --client     
Client Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.5", GitCommit:"e6503f8d8f769ace2f338794c914a96fc335df0f", GitTreeState:"clean", BuildDate:"2020-07-04T15:01:15Z", GoVersion:"go1.14.4", Compiler:"gc", Platform:"darwin/amd64"}

$ istioctl version --remote=false
1.7.2

$ kn version                   
Version:      v0.17.0
Build Date:   2020-08-26 11:08:52
Git Revision: 8fcd25c3
Supported APIs:
* Serving
  - serving.knative.dev/v1 (knative-serving v0.17.0)
* Eventing
  - sources.knative.dev/v1alpha2 (knative-eventing v0.17.0)
  - eventing.knative.dev/v1beta1 (knative-eventing v0.17.0)

$ vegeta -version
Version: 12.8.3
Commit: 
Runtime: go1.14 darwin/amd64
Date: 2020-03-25T14:41:40Z

cert-managerを使わない場合

次のコマンドでKnativeとIstioをインストールします。

kubectl apply -f https://github.com/knative/serving/releases/download/v0.17.0/serving-crds.yaml
kubectl apply -f https://github.com/knative/serving/releases/download/v0.17.0/serving-core.yaml
istioctl install -y
kubectl apply -f https://github.com/knative/net-istio/releases/download/v0.17.0/release.yaml

Pod一覧は次のようになります。

$ kubectl get pod -A 
NAMESPACE         NAME                                     READY   STATUS    RESTARTS   AGE
docker            compose-78f95d4f8c-w4wrz                 1/1     Running   0          58m
docker            compose-api-6ffb89dc58-lr9pk             1/1     Running   0          58m
istio-system      istio-ingressgateway-855cb8cb9c-jjxt5    1/1     Running   0          24s
istio-system      istiod-5f4bd68b45-bm8qn                  1/1     Running   0          28s
knative-serving   activator-68cbc9b5c7-mtkfg               1/1     Running   0          31s
knative-serving   autoscaler-5cf649dbb-zbfkb               1/1     Running   0          31s
knative-serving   controller-bc8d75cbc-ws68r               1/1     Running   0          31s
knative-serving   istio-webhook-6dd89ff45d-247nl           1/1     Running   0          16s
knative-serving   networking-istio-5d68bb7d56-r2t5x        1/1     Running   0          16s
knative-serving   webhook-85758f4589-knj5q                 1/1     Running   0          31s
kube-system       coredns-5644d7b6d9-pxpsd                 1/1     Running   0          59m
kube-system       coredns-5644d7b6d9-wrgr7                 1/1     Running   0          59m
kube-system       etcd-docker-desktop                      1/1     Running   0          58m
kube-system       kube-apiserver-docker-desktop            1/1     Running   0          58m
kube-system       kube-controller-manager-docker-desktop   1/1     Running   0          58m
kube-system       kube-proxy-lpfkf                         1/1     Running   0          59m
kube-system       kube-scheduler-docker-desktop            1/1     Running   0          58m
kube-system       storage-provisioner                      1/1     Running   0          58m
kube-system       vpnkit-controller                        1/1     Running   0          58m

istio-system Namespaceにistio-ingressgatewayという名前のServiceが作成されます。
Docker for Macの場合、type: LoadBalancerなServiceのExternal IPはlocalhostになります。

$ kubectl get -n istio-system service istio-ingressgateway
NAME                   TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                                                      AGE
istio-ingressgateway   LoadBalancer   10.99.131.155   localhost     15021:30391/TCP,80:30649/TCP,443:30572/TCP,15443:32059/TCP   15m

ここではカスタムドメインにlocal.maki.lolを使用します。*.local.maki.lol127.0.0.1に解決されます。

kubectl patch configmap/config-domain  -n knative-serving --type merge --patch '{"data":{"local.maki.lol":""}}'

local.maki.lolではなく、vcap.meも利用可能です。

次にknコマンドで作成したアプリケーションをデプロイします。

kn service create demo --image making/hello-world --env MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE='*'

次のように出力されればOKです。

Creating service 'demo' in namespace 'default':

  0.031s The Configuration is still working to reflect the latest desired specification.
  0.087s The Route is still working to reflect the latest desired specification.
  0.120s Configuration "demo" is waiting for a Revision to become ready.
 15.434s ...
 15.472s Ingress has not yet been reconciled.
 15.591s Waiting for load balancer to be ready
 15.741s Ready to serve.

Service 'demo' created to latest revision 'demo-mdysv-1' is available at URL:
http://demo.default.local.maki.lol

エンドポイントにアクセスして、"Hello World!"が返ることを確認します。

$ curl http://demo.default.local.maki.lol
Hello World!

1分ほどアクセスがなければPodは自動で削除されます。native-imageの場合、0インスタンスから1インスタンスの立ち上がりが速いです。
次のGifアニメでデモを示します。

knative-http

Podが存在しない状態から2-3秒で画面が表示されます。

次にAuto Scalingを試します。Vegetaを使って負荷をかけます。

Podの立ち上がりを次のGifアニメでデモを示します。

auto-scaling-http

$ echo "GET http://demo.default.local.maki.lol" | vegeta attack -duration=10s -rate=200 | tee results.bin | vegeta report

Requests      [total, rate, throughput]         2000, 200.12, 200.04
Duration      [total, attack, wait]             9.998s, 9.994s, 4.083ms
Latencies     [min, mean, 50, 90, 95, 99, max]  2.494ms, 489.215ms, 3.959ms, 2.038s, 2.551s, 2.977s, 3.095s
Bytes In      [total, mean]                     24000, 12.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:2000  
Error Set:

レスポンスは100%成功です。0 -> 1のスケールアウト時のレスポンスに約3秒(Max Latency)かかっていることがわかります。50パーセンタイル(中央値)は3.959msなので、立ち上がってしまえば十分レスポンスが速いことがわかります。

cert-managerを使う場合

cert-managerを使えば、デプロイしたサービスにLet's Encryptの証明書を自動で設定することができます。

cert-managerはこの記事のようにインストールしてください。letsencryptという名前のClusterIssuerオブジェクトができている前提です。

"Reset Kubernetes Cluster"をクリックして一旦環境をクリーンアップした状態で始めます。

次のコマンドでKnativeとIstioをインストールします。

kubectl apply -f https://github.com/knative/serving/releases/download/v0.17.0/serving-crds.yaml
kubectl apply -f https://github.com/knative/serving/releases/download/v0.17.0/serving-core.yaml
istioctl install -y
kubectl apply -f https://github.com/knative/net-istio/releases/download/v0.17.0/release.yaml

Pod一覧は次のようになります。

$ kubectl get pod -A 
NAMESPACE         NAME                                      READY   STATUS    RESTARTS   AGE
cert-manager      cert-manager-58f6cf4b79-xb4nr             1/1     Running   0          2m21s
cert-manager      cert-manager-cainjector-cdb5d697c-6hdsc   1/1     Running   0          2m21s
cert-manager      cert-manager-webhook-565d8c759b-9rtds     1/1     Running   0          2m21s
docker            compose-78f95d4f8c-gcxtt                  1/1     Running   0          3m51s
docker            compose-api-6ffb89dc58-hqlcw              1/1     Running   0          3m51s
istio-system      istio-ingressgateway-855cb8cb9c-4r7tg     1/1     Running   0          81s
istio-system      istiod-5f4bd68b45-gvj9g                   1/1     Running   0          87s
knative-serving   activator-68cbc9b5c7-dttf8                1/1     Running   0          89s
knative-serving   autoscaler-5cf649dbb-lhx7w                1/1     Running   0          89s
knative-serving   controller-bc8d75cbc-z6cd9                1/1     Running   0          89s
knative-serving   istio-webhook-6dd89ff45d-pnmdt            1/1     Running   0          74s
knative-serving   networking-istio-5d68bb7d56-sdgcj         1/1     Running   0          74s
knative-serving   webhook-85758f4589-ztvmd                  1/1     Running   0          89s
kube-system       coredns-5644d7b6d9-2sfgb                  1/1     Running   0          5m8s
kube-system       coredns-5644d7b6d9-nv7qm                  1/1     Running   0          5m8s
kube-system       etcd-docker-desktop                       1/1     Running   0          3m53s
kube-system       kube-apiserver-docker-desktop             1/1     Running   0          4m18s
kube-system       kube-controller-manager-docker-desktop    1/1     Running   0          3m53s
kube-system       kube-proxy-tswjt                          1/1     Running   0          5m8s
kube-system       kube-scheduler-docker-desktop             1/1     Running   0          4m11s
kube-system       storage-provisioner                       1/1     Running   0          3m52s
kube-system       vpnkit-controller                         1/1     Running   0          3m52s

istio-system Namespaceにistio-ingressgatewayという名前のServiceが作成されます。
Docker for Macの場合、type: LoadBalancerなServiceのExternal IPはlocalhostになります。

$ kubectl get -n istio-system service istio-ingressgateway
NAME                   TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                                                      AGE
istio-ingressgateway   LoadBalancer   10.99.131.155   localhost     15021:30391/TCP,80:30649/TCP,443:30572/TCP,15443:32059/TCP   15m

ここではカスタムドメインにlocal.maki.lolを使用します。*.local.maki.lol127.0.0.1に解決されます。
このカスタムドメインでLet's Encryptの証明書を発行できるのは、ドメイン所有者の筆者だけなので、この手順を試す場合は独自ドメインに変更してください。

kubectl patch configmap/config-domain  -n knative-serving --type merge --patch '{"data":{"local.maki.lol":""}}'

cert-manager連携をインストールします。

kubectl apply -f https://github.com/knative/net-certmanager/releases/download/v0.17.0/release.yaml
kubectl apply -f https://github.com/knative/serving/releases/download/v0.17.0/serving-nscert.yaml
kubectl patch configmap/config-certmanager -n knative-serving --type merge --patch '{"data":{"issuerRef":"kind: ClusterIssuer\nname: letsencrypt"}}'
kubectl patch configmap/config-network -n knative-serving --type merge --patch '{"data":{"autoTLS": "Enabled", "httpProtocol": "Redirected"}}'

次にknコマンドで作成したアプリケーションをデプロイします。

kn service create demo --image making/hello-world --env MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE='*'

次のように出力されればOKです。

Creating service 'demo' in namespace 'default':

  0.026s The Configuration is still working to reflect the latest desired specification.
  0.055s The Route is still working to reflect the latest desired specification.
  0.095s Configuration "demo" is waiting for a Revision to become ready.
  9.023s ...
  9.071s Ingress has not yet been reconciled.
  9.161s Certificate default.local.maki.lol is not ready.
 68.206s Ingress has not yet been reconciled.
 68.281s Ready to serve.

Service 'demo' created to latest revision 'demo-xjqks-1' is available at URL:
https://demo.default.local.maki.lol

エンドポイントにアクセスして、"Hello World!"が返ることを確認します。

$ curl https://demo.default.local.maki.lol
Hello World!

0インスタンスから1インスタンスの立ち上がりを次のGifアニメでデモを示します。

knative-https

Podが存在しない状態から2-3秒で画面が表示されます。

次にAuto Scalingを試します。Podの立ち上がりを次のGifアニメでデモを示します。

auto-scaling-https

$ echo "GET https://demo.default.local.maki.lol" | vegeta attack -duration=10s -rate=200 | tee results.bin | vegeta report

Requests      [total, rate, throughput]         2000, 200.10, 200.03
Duration      [total, attack, wait]             9.999s, 9.995s, 3.811ms
Latencies     [min, mean, 50, 90, 95, 99, max]  2.523ms, 610.734ms, 4.93ms, 2.42s, 2.786s, 3.318s, 3.436s
Bytes In      [total, mean]                     24000, 12.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:2000  
Error Set:

レスポンスは100%成功です。0 -> 1のスケールアウト時のレスポンスに約3.5秒(Max Latency)かかっていることがわかります。50パーセンタイル(中央値)は4.93msなので、立ち上がってしまえば十分レスポンスが速いことがわかります。

ExternalDNSが設定されている場合

独自ドメインを使っていて、ExternalDNSがインストールされている場合は、次のアノテーションを設定すればIsto Ingres GatewayへのAレコードを自動でDNSに登録できます。

独自ドメインがkn.maki.lolの場合、

kubectl annotate -n istio-system service istio-ingressgateway "external-dns.alpha.kubernetes.io/hostname=*.kn.maki.lol."
kubectl annotate -n istio-system service istio-ingressgateway "external-dns.alpha.kubernetes.io/ttl=60"

Cloud Native BuildpacksでSpring BootアプリのGraalVM native imageを作成しKnativeにデプロイしました。
Native ImageはKnativeのようなプラットフォームと相性が良いです。

Native Image化の詳細とロードマップはSpringOneの"The Path Towards Spring Boot Native Applications"というセッションで説明されています。こちらも参照してください。

Found a mistake? Update the entry.
Share this article: