case-kの備忘録

日々の備忘録です。データ分析とクラウドに興味があります。

Google Kubernetes Engineにアプリをデプロイする方法

Google Kubernetes Engine上にコンテナ化した株価アプリをデプロイしてみました。

アプリの構成

アプリの構成は次のようになっています。

  • React

 役割:フロントエンド

  • Express

 役割:バックエンド

  • Nginx

 役割:プロキシサーバ(ReactとExpressのプロキシサーバ)

DeploymentはPod単位で行うのでフロントエンドとバックエンドそれぞれのマニフェストを定義します。フロントエンドはReactとNginxでPodで定義し、バックエンドはExpressとNginxでPodで定義します。Serviceは1つは外部公開できるように種類としては「LoadBalancer Service」を選び、ReactからExpressへのリクエストにはIPはクラスタ内部でトラフィックを流せれば良いので、デフォルト値である「ClusterIP Service」を選びます。

Kubernetsのリソースについて
www.case-k.jp

セットアップ

デプロイする前にクラスタレジストリへのイメージの登録、マニフェストファイルを定義します。

GKEのクラスタを作成

次の記事の通り事前にGKEのクラスタを作成します。
www.case-k.jp

$ gcloud container clusters create casek --machine-type=n1-standard-1 --num-nodes=3

DockerfileをGoogle Cloud Registoryにpushする

次の記事の通り事前にDockerfileをGoogle Cloud Registoryにpushしておきます。
www.case-k.jp

マニフェストファイルの作成

マニフェストファイルを定義していきます。

Deploymentを定義(React+Nginx)ーreact-app-deployment.yaml

ReactとプロキシサーバであるNginxコンテナのPodを定義します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: react-app
  labels:
    name: react-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: react-app
  template:
    metadata:
      labels:
        app: react-app
    spec:
      containers:
      - name: nginx
        image: gcr.io/[project id]/nginx-app:tag2
        imagePullPolicy: Always
        ports:
         - containerPort: 80
        env:
        - name: WORKER_PROCESSES
          value: "2"
        - name: WORKER_CONNECTIONS
          value: "1024"
        - name: KEEPALIVE_TIMEOUT
          value: "65"
        - name: GZIP
          value: "on"
        - name: BACKEND_HOST
          value: "localhost:3000"
        - name: BACKEND_MAX_FAILS
          value: "3"
        - name: BACKEND_FAIL_TIMEOUT
          value: "10s"
        - name: SERVER_PORT
          value: "80"
        - name: SERVER_NAME
          value: "localhost"
        - name: LOG_STDOUT
          value: "true"

      - name: react-app
        image: gcr.io/[project id]/react-app:tag2
        imagePullPolicy: Always
        ports:
         - containerPort: 3000

アプリのpackage.jsonにはリクエスト先であるExpressとNginxのサービス名を定義しておきます。こちらも環境変数で制御した方が良いため、本番・ステージング・開発環境を分けてデプロイする際に変更します。

 "proxy": "http://express-app",
Serviceを定義(React+Nginx)- react-app-service.yaml

Serviceを定義:Serviceの種類としては「LoadBalancer Service」を選択します。「LoadBalancer Service」はGCPAWSなどのクラウドプラットフォームで提供されているロードバランサーと連携するためのものです。Ingressなしで外部公開が可能となっています。

apiVersion: v1
# リースの種類を定義
kind: Service
metadata:
  name: react-app
  labels:
    app: react-app
# リソースを定義
spec:
  # ServiceのターゲットとしたいPodがもつラベルを指定
  selector:
    app: react-app
  ports: 
    - name: http
      port: 80
  type: LoadBalancer
Deploymentを定義(Express+Nginx)ーexpress-app-deployment.yaml

Deploymentを定義:ExpressとプロキシサーバであるNginxコンテナのPodを定義します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: express-app
  labels:
    name: express-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: express-app
  template:
    metadata:
      labels:
        app: express-app
    spec:
      containers:
      - name: nginx
        image: gcr.io/[project id]/nginx-app:tag2
        imagePullPolicy: Always
        ports:
         - containerPort: 80
        env:
        - name: WORKER_PROCESSES
          value: "2"
        - name: WORKER_CONNECTIONS
          value: "1024"
        - name:  KEEPALIVE_TIMEOUT
          value: "65"
        - name: GZIP
          value: "on"
        - name: BACKEND_HOST
          value: "localhost:3001"
        - name: BACKEND_MAX_FAILS
          value: "3"
        - name: BACKEND_FAIL_TIMEOUT
          value: "10s"
        - name: SERVER_PORT
          value: "80"
        - name: SERVER_NAME
          value: "localhost"
        - name: LOG_STDOUT
          value: "true"

      - name: express-app
        image: gcr.io/[project id]/express-app:tag2
        imagePullPolicy: Always
        ports:
         - containerPort: 3001
Serviceを定義(Express+Nginx)- express-app-service.yaml

Serviceを定義:Serviceの種類としてはデフォルト値である「ClusterIP Service」を選択します。「ClusterIP Service」ではKubernetesクラスタ内部IPアドレスにServiceを公開できます。今回は同一クラスタの別のPod(React+ Nginx)からリクエストを受け取るために使います。

apiVersion: v1
# リースの種類を定義
kind: Service
metadata:
  name: express-app
  labels: 
    app: express-app
# リソースを定義
spec:
  # spec.selector:ServiceのターゲットとしたいPodがもつラベルを設定
  selector:
    app: express-app
  ports: 
    - name: http
      port: 80

デプロイ

マニフェストファイルができたので、アプリのイメージをクラスタにデプロイしてみます。

$ kubectl apply -f express-app-deployment.yaml
deployment.apps/express-app created

※ docker-compose時と異なり、環境変数は「""」で囲まないと上のエラーが出てしまいます。

$ kubectl apply -f express-app-deployment.yaml
Error from server (BadRequest): error when creating "express-app-deployment.yaml": Deployment in version "v1" cannot be handled as a Deployment: v1.Deployment.Spec: v1.DeploymentSpec.Template: v1.PodTemplateSpec.Spec: v1.PodSpec.Containers: []v1.Container: v1.Container.Env: []v1.EnvVar: v1.EnvVar.v1.EnvVar.Value: ReadString: expects " or n, but found 2, error found in #10 byte of ...|,"value":2},{"name":|..., bigger context ...|ers":[{"env":[{"name":"WORKER_PROCESSES","value":2},{"name":"WORKER_CONNECTIONS","value":1024},{"nam|...

デプロイが完了したら各リソースの状態を確認してみます。Serviceの種類として「LoadBalancer」を選んだので外部IPアドレスが確認できます。

$ kubectl get pod,node,svc
NAME                              READY   STATUS    RESTARTS   AGE
pod/express-app-8c57cddff-79549   2/2     Running   0          70m
pod/express-app-8c57cddff-kpcxv   2/2     Running   0          69m
pod/react-app-5654c6d5c6-h7wp9    2/2     Running   0          69m
pod/react-app-5654c6d5c6-tp72m    2/2     Running   0          69m

NAME                                        STATUS   ROLES    AGE     VERSION
node/gke-casek-default-pool-2440cdf7-5rkw   Ready    <none>   5h46m   v1.14.10-gke.17
node/gke-casek-default-pool-2440cdf7-99f8   Ready    <none>   5h46m   v1.14.10-gke.17
node/gke-casek-default-pool-2440cdf7-ww5v   Ready    <none>   5h46m   v1.14.10-gke.17

NAME                    TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/express-app     ClusterIP      10.51.246.54    <none>        80/TCP         69m
service/kubernetes      ClusterIP      10.51.240.1     <none>        443/TCP        5h47m
service/nginx-express   ClusterIP      10.51.251.211   <none>        80/TCP         125m
service/react-app       LoadBalancer   10.51.247.154   34.85.51.78   80:31221/TCP   121m

f:id:casekblog:20200303171544p:plain


大丈夫そうです。

※ エラーがあった場合はPod内のコンテナのログを確認します。

$ kubectl logs express-app-7668659dd5-dtkn2 -c nginx
2020/03/03 03:38:39 [emerg] 1#1: host not found in upstream "express-app:3001" in /etc/nginx/conf.d/upstream.conf:2
nginx: [emerg] host not found in upstream "express-app:3001" in /etc/nginx/conf.d/upstream.conf:2

※ Podに入りたい場合は次のようにして入ることができます。

$ kubectl exec -it [Pod name] bash

リソースの削除

デプロイできたのでリソースを削除しておきます。

$ kubectl delete -f express-app-deployment.yaml
$ kubectl delete -f express-app-service.yaml
$ kubectl delete -f react-app-deployment.yaml
$ kubectl delete -f react-app-service.yaml

Google Kubernetes Engineに株価アプリのデプロまでできたので、次はCloud Soure Repository と Cloud Buildを使ってビルドを自動化していければと思います。