4. マニフェストの作成とWebモジュールの実行

# はじめに
本記事は以下3の続きです。
1. [Google Kubernetes Engine（GKE）でJavaのWebモジュールをサッと動かしてみる](https://qiita.com/Turtle-child-No2/items/a5260ed94f01ccd5cfec)
2. [Dockerイメージの作成とRegistry登録](https://qiita.com/Turtle-child-No2/items/b8c307eb652e42420f7a)
3. [Webモジュールからアクセスするデータベースを作成](https://qiita.com/Turtle-child-No2/items/0fd09117beb74e848146)
4. [マニフェストの作成とWebモジュールの実行](https://qiita.com/Turtle-child-No2/items/23982059d188e44618df)
## 4-1. マニフェストの作成とWebモジュールの実行
### 手順4-1.1
GKEにアプリ（今回はWebモジュール）を配置するために必要なマニフェストファイルを作成します。
viを起動し、以下の内容でsample-app_dev.yamlを作成します。
※2箇所ある[project_id]は各自の環境に合わせて忘れず書き換えてください。

```
[userid]@cloudshell:~ ([project_id])$ vi sample-app_dev.yaml
```

```
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  # Deploymentの名前。namespace内ではユニークである必要があります
  name: sample-app
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sample-app
  # PodがReady状態になってから使用可能な状態になるまでの待ち時間を指定する。
  # デフォルトは0秒です。なので指定をしないと即座に使用可能な状態として扱われる。
  minReadySeconds: 5
  
  # Deploymentの処理の最大処理時間を指定する。
  # Deploymentの処理時間がこの時間を超えた場合
  # 処理を失敗させProgressDeadlineExceededをstatusにセットし、自動でロールバックされます。
  # デフォルトは600秒です。
  progressDeadlineSeconds: 600
  
  strategy:
    # ローリングアップデートを実行
    type: RollingUpdate
    
    rollingUpdate:
      # 現在起動しているPod数に対してアップデート時にどの程度までの減少（スケールイン）を許すか。
      # 例えば現在2台のPodが起動しており、maxUnavailable=50%(もしくは1)を設定した場合
      # アップデートに伴い2台中1台のPodは利用不可となることを許可します。
      maxUnavailable: 50%
    
      # アップデート時に現在起動しているPod数に対してどの程度までの増加（スケールアウト）を許すか。
      # 例えば現在2台のPodが起動しており、maxSurge=50%(もしくは1)を設定した場合、アップデートに伴いPod1台までのスケールアウトは許可します。
      # つまりアップデート中は旧バージョン含め合計3台までであればPodの起動を許可することになります。
      maxSurge: 50%
  
  # この部分がPodのTemlate。ここの記述内容に変更があったときローリングアップデートが起こる
  template:
    metadata:
      labels:
        # ラベル指定は必須 spec.selector.matchLabelsと合わせる必要がある
        app: sample-app
    spec:
      containers:
        - name: sample-app-container
          image: gcr.io/[project_id]/sample-app:latest
          
          ports:
            - containerPort: 8080
              name: api-port
              protocol: TCP
          
          # 正常に起動しているかの確認。ヘルスチェック失敗時にはPodを再起動します。
          # initialDelaySecondsはアプリを起動してから何秒後にヘルスチェックを始めるかを示している。
          # 例えば、起動するまで時間かかるようなアプリケーションだとこの設定を指定すると便利。
          # timeoutSecondsはヘルスチェックのレスポンスを何秒待つかを示す。livenessProbeの場合はこれをできるだけ短くしたほうが早く検知するので復活が速い。
          # でも注意すべき点があって負荷がかかっている状態でも適切なタイムアウトを設定しないと
          # 一番忙しい時なのにアプリが再起動されてしまったり、パフォーマンスに影響がでる。なので、適切なtimeoutSecondsを指定するのが大事。
          # アプリケーションのlivenessProbeに接続できなかったり、HTTPエラーコードが返ってきた場合
          # periodSecondsは定期実行のスケジュール。
          # failureThresholdは指定値の失敗でコンテナ再起動を行う。
          livenessProbe:
            httpGet:
              scheme: HTTP
              path: /sample-app/views/NewFile.jsp
              port: api-port
            
            initialDelaySeconds: 10
            timeoutSeconds: 5
            periodSeconds: 30
            failureThreshold: 20
          
          # Podがサービスイン出来ているかの確認。ヘルスチェック失敗時にはトラフィックを流さない(Podの再起動はしない）。
          # readinessProbeが失敗した場合、そのPodがサービスのエンドポイントから外される。
          # 例えばローリングアップデートの時や、スケールアップした時、新しいPodが機能されていたんだけど
          # まだトラフィック受けられないタイミングでリクエストが来たら、困るのでreadinessProbeでそういうことを防ぐ。
          readinessProbe:
            httpGet:
              scheme: HTTP
              path: /sample-app/servlet/MyServlet
              port: api-port
            
            initialDelaySeconds: 10
            timeoutSeconds: 5
            periodSeconds: 60
            failureThreshold: 5
          
          resources:
            # Podをデプロイする時に必要とするリソース(CPU/メモリ)を指定する。
            # ただしPodは指定したresource requests以上のリソースを使うことができる。(リソース使用量の制限はlimitを使う)
            # ポイントとしてはPodをデプロイするときに、nodeのリソース使用量は見ないでresource requestsをみてデプロイが行われる。
            # cpu 200m は200 millicoresの略で1000milli coreで1cpu。
            # 200 mill coreで 1/5 CPU使うということ。(ちなみにcpu: 1と指定したらcpu: 1000mと解釈される)
            # 500Miは 500mebibytes。
            requests:
              memory: "500Mi"
            
            # Podが使用するリソース(CPU/メモリ)に制限を設けることができる。
            # たとえnodeにリソースの空きがあっても指定されたresource limits以上のリソースは使用することができなくなる。
            # resource limitsを指定しない場合、無制限にリソースを使用できる。
            # resource requestsを指定していなかったら、limit値と同様の値がresource requestsに設定される。
            limits:
              memory: "600Mi"
          
          # 環境変数にデータベースホストとPostgreSQLインスタンスに
          # 接続するためのユーザーとパスワードを設定します。
          # cloud sql proxy経由でデータベースに接続するのでデータベースホストにはループバックアドレスを指定している。
          env:
            - name: POSTGRES_DB_HOST
              value: 127.0.0.1:5432
            # [START cloudsql_secrets]
            # 記事3で作成したsecretのusernameとpasswordを使用する
            - name: POSTGRES_DB_USER
              valueFrom:
                secretKeyRef:
                  name: cloudsql-db-credentials
                  key: username
            - name: POSTGRES_DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: cloudsql-db-credentials
                  key: password
            # [END cloudsql_secrets]
        
        # [START proxy_container]
        - name: cloudsql-proxy
          image: gcr.io/cloudsql-docker/gce-proxy:1.13
          # 記事3で作成したsecretのサービスアカウントでデータベースとのプロキシを開始する
          command: ["/cloud_sql_proxy",
                    "-instances=[project_id]:us-central1:sample-app-ist=tcp:5432",
                    "-credential_file=/secrets/cloudsql/credentials.json"]
          # [START cloudsql_security_context]
          securityContext:
            runAsUser: 2  # non-root user
            allowPrivilegeEscalation: false
          # [END cloudsql_security_context]
          # 記事3で作成したsecretのサービスアカウントをマウントする
          volumeMounts:
            - name: cloudsql-instance-credentials
              mountPath: /secrets/cloudsql
              readOnly: true
        # [END proxy_container]
      
      # Podのシャットダウンプロセスが始まってからSIGKILLが送られるまでの時間です。
      # preStopの設定を入れている場合は、preStop処理が終了後にSIGTERMが送られ
      # シャットダウンプロセスが始まってからterminationGracePeriodSecondsで指定した秒数が経過してもプロセスが完了していない場合にはSIGLKILLが送られます。
      # デフォルトは30秒となっているはずです。
      terminationGracePeriodSeconds: 30
      
      # [START volumes]
      volumes:
        - name: cloudsql-instance-credentials
          secret:
            secretName: cloudsql-instance-credentials
      # [END volumes]
```

### 手順4-1.2
作成したマニフェストファイルを実行します。
無事、createされました。

```
[userid]@cloudshell:~ ([project_id])$ kubectl apply -f sample-app_dev.yaml --record
deployment.apps "sample-app" created
```

### 手順4-1.3
しばらくしたら以下のコマンドで正常にPodが起動しているか確認します。
READYが2/2になっていれば正常に2つのコンテナが起動しています。

```
[userid]@cloudshell:~ ([project_id])$ kubectl get pods -o wide
NAME                       READY     STATUS    RESTARTS   AGE       IP           NODE
[pod_name]                 2/2       Running   0          40s       10.40.1.10   [node_name]
```

### 手順4-1.4
正常に起動していない場合は以下のコマンドでコンテナのログを確認してください。以下はsample-app-containerとcloudsql-proxyコンテナが正常に起動している場合のログです。

```
[userid]@cloudshell:~ ([project_id])$ kubectl logs [pod_name] -c sample-app-container
                 ・
                 ・
                 ・
12-Mar-2019 12:02:50.933 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
12-Mar-2019 12:02:50.935 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet engine: [Apache Tomcat/9.0.16]
12-Mar-2019 12:02:50.960 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/opt/apache-tomcat-9.0.16/webapps/sample-app]
12-Mar-2019 12:02:52.609 INFO [main] org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
12-Mar-2019 12:02:52.705 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/opt/apache-tomcat-9.0.16/webapps/sample-app] has finished in [1,744] ms
12-Mar-2019 12:02:52.714 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
12-Mar-2019 12:02:52.786 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-nio-8009"]
12-Mar-2019 12:02:52.817 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [2,069] milliseconds
```

```
[userid]@cloudshell:~ ([project_id])$ kubectl logs [pod_name] -c cloudsql-proxy
2019/03/12 11:55:17 current FDs rlimit set to 1048576, wanted limit is 8500. Nothing to do here.
2019/03/12 11:55:17 using credential file for authentication; email=sample-app-db-client@[project_id].iam.gserviceaccount.com
2019/03/12 11:55:17 Listening on 127.0.0.1:5432 for [project_id]:us-central1:sample-app-ist
2019/03/12 11:55:17 Ready for new connections
```

### 手順4-1.5
インターネット側から先ほど起動したPodにアクセスできるようServiceを定義したマニフェストを作成します。

```
[userid]@cloudshell:~ ([project_id])$ vi sample-app_svc.yaml
```

```
apiVersion: v1
kind: Service
metadata:
  name: sample-app-svc
  namespace: default
spec:
  selector:
    app: sample-app
  type:
    LoadBalancer
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
```

### 手順4-1.6
早速Serviceを定義したマニフェストを実行します。

```
[userid]@cloudshell:~ ([project_id])$ kubectl apply -f sample-app_svc.yaml
service "sample-app-svc" created
```

### 手順4-1.7
しばらくしたら以下のコマンドでsample-app-svcのEXTERNAL-IPを確認します。

```
[userid]@cloudshell:~ ([project_id])$ kubectl get service
NAME             TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes       ClusterIP      xxx.xxx.xxx.xxx     <none>        443/TCP        3d
sample-app-svc   LoadBalancer   yyy.yyy.yyy.yyy   zzz.zzz.zzz.zzz   80:31004/TCP   3m
```

### 手順4-1.8
別のブラウザを起動して先ほどのEXTERNAL-IPで以下のURLを表示させてみましょう。現在日時やテーブルの内容が表示されます。

http://zzz.zzz.zzz.zzz/sample-app/views/NewFile.jsp
![44.png](https://qiita-image-store.s3.amazonaws.com/0/263269/f75d4ec6-17be-739b-0dea-1cacefe716b3.png)

http://zzz.zzz.zzz.zzz/sample-app/servlet/MyServlet
![45.png](https://qiita-image-store.s3.amazonaws.com/0/263269/3a03c44c-f2eb-2dea-642f-ee4a266df6b2.png)

# おわりに
参考文献は以下です。
Kubernetes完全ガイド - 青山 真也 (著)
Docker/Kubernetes 実践コンテナ開発入門 - 山田 明憲 (著)
以上