なになれ

IT系のことを記録していきます。

MEAN StackをKubernetesで動かす(その1)

前回はMEAN StackをDocker Composeで動かしてみました。
hi1280.hatenablog.com

今回はMEAN StackをKubernetesで動かします。
Dockerに対応したので、Kubernetesでも動くようになっているはずです。試してみます。

なお、今回のKubernetesの環境はGKE(Google Kubernetes Engine)を使用します。
手軽にKubernetes環境が用意できるので、試しに使ってみるには良いと思います。
無料のトライアル期間もあります。

MEAN Stackのプログラム一式はこちら
github.com

Docker Composeの時と似たような構成でAngularとExpressとMongoDBを動かします。
実運用に耐えうることを想定して、以下の内容を含めます。

  • MongoDBは可用性を高めるためにReplica Setで構成する
  • 外部公開を考慮して、HTTPS対応を行う

HTTPS対応の話は次回に行います。今回はHTTPでアクセスするところまでです。

事前準備

GKEを利用するために、GCP(Google Cloud Platform)用のコマンドラインツール(gcloud)をインストールします。
Quickstarts  |  Cloud SDK  |  Google Cloud

kubectlコマンドをインストールします。
Quickstart  |  Kubernetes Engine Documentation  |  Google Cloud

gcloudコマンドでKubernetesクラスタを構築します。

$ gcloud container clusters create mean-example

MongoDBのセットアップ

MongoDBを作成する前にデータを保存するディスクを準備する必要があります。

ディスクの設定

何のディスクを利用するのかを決めるためにStorageClassを作成します。
ここでは、GCE(Google Compute Engine)のディスクを利用するように設定します。

gce-storageclass.yml

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: slow
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-standard
$ kubectl apply -f gce-storageclass.yml

MongoDBの作成

Replica Setで認証を行うためにkeyfileを用意します。
KubernetesのSecretに登録します。

$ TMPFILE=$(mktemp)
$ /usr/bin/openssl rand -base64 741 > $TMPFILE
$ kubectl create secret generic shared-bootstrap-data --from-file=internal-auth-mongodb-keyfile=$TMPFILE
$ rm $TMPFILE

Replica Setにおける認証の参考情報
Internal Authentication — MongoDB Manual

MongoDB用のStatefulSetとServiceを作成します。

mongodb-service.yml

apiVersion: v1
kind: Service
metadata:
  name: mongodb-service
  labels:
    name: mongo
spec:
  ports:
  - port: 27017
    targetPort: 27017
  clusterIP: None
  selector:
    role: mongo
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mongod
spec:
  selector:
    matchLabels:
      role: mongo
  serviceName: mongodb-service
  replicas: 3
  template:
    metadata:
      labels:
        role: mongo
        environment: test
        replicaset: MainRepSet
    spec:
      terminationGracePeriodSeconds: 10
      volumes:
        - name: secrets-volume
          secret:
            secretName: shared-bootstrap-data
            defaultMode: 256
      containers:
        - name: mongod-container
          image: mongo:3.4.16-jessie
          command:
            - "mongod"
            - "--bind_ip"
            - "0.0.0.0"
            - "--replSet"
            - "MainRepSet"
            - "--auth"
            - "--clusterAuthMode"
            - "keyFile"
            - "--keyFile"
            - "/etc/secrets-volume/internal-auth-mongodb-keyfile"
            - "--setParameter"
            - "authenticationMechanisms=SCRAM-SHA-1"
          ports:
            - containerPort: 27017
          volumeMounts:
            - name: secrets-volume
              readOnly: true
              mountPath: /etc/secrets-volume
            - name: mongodb-persistent-storage-claim
              mountPath: /data/db
  volumeClaimTemplates:
  - metadata:
      name: mongodb-persistent-storage-claim
      annotations:
        volume.beta.kubernetes.io/storage-class: "slow"
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 10Gi

ServiceはHeadless Serviceで作成します。
この後にReplica Setの設定を行う際に、PodのIPアドレスを知る必要があるためです。

StatefulSetではMongoDBコンテナが起動する3つのPodを作成します。
3つのPodがReplica Setで利用されます。
mongodコマンドで、事前に作成したSecretを利用して認証を有効にします。 volumeClaimTemplatesで先ほどのStorageClassを指定しています。
Pod毎に10Gのディスクが設定されます。

$ kubectl apply -f mongodb-service.yml

MongoDBの設定

PrimaryになるMongoDBに接続します。

$ kubectl exec -it mongod-0 -c mongod-container bash
$ mongo

Replica Setを設定します。

> rs.initiate({_id: "MainRepSet", version: 1, members: [
       { _id: 0, host : "mongod-0.mongodb-service:27017" },
       { _id: 1, host : "mongod-1.mongodb-service:27017" },
       { _id: 2, host : "mongod-2.mongodb-service:27017" }
  ]})

ユーザを作成します。

> db.getSiblingDB("admin").createUser({
      user : "xxx",
      pwd  : "xxx",
      roles: [ { role: "root", db: "admin" } ]
  })
> use admin
> db.auth("xxx","xxx")
> db.getSiblingDB("my-heroes").createUser({
      user : "xxx",
      pwd : "xxx",
      roles: [{role:"readWrite", db: "my-heroes"}]
  })

ユーザ、パスワードは適宜設定する。

Angular+Expressの作成

Expressを作成します。

webapp-backend-service.yml

apiVersion: v1
kind: Service
metadata:
  name: webapp-backend-service
  labels:
    name: webapp-backend
spec:
  type: ClusterIP
  ports:
    - protocol: 'TCP'
      port: 3000
      targetPort: 3000
  selector:
    app: webapp-backend
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp-backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: webapp-backend
  template:
    metadata:
      labels:
        app: webapp-backend
    spec:
      containers:
      - name: webapp-backend
        image: asia.gcr.io/arched-photon-204013/mean-example_backend:latest
        ports:
        - containerPort: 3000
        env:
        - name: PORT
          value: '3000'
        - name: MONGODB_URI
          value: 'mongodb://xxx:xxx@mongod-0.mongodb-service:27017,mongod-1.mongodb-service:27017,mongod-2.mongodb-service:27017/my-heroes?replicaSet=MainRepSet'

コンテナレジストリにはGCR(Google Container Registry)を利用しています。
前回のExpress実行用のDockerfileから作成したイメージです。

Replica SetのMongoDBに接続するため、3つのMongoDBをホスト名で指定しています。

$ kubectl apply -f webapp-backend-service.yml

Angularを作成します。

webapp-frontend-service.yml

apiVersion: v1
kind: Service
metadata:
  name: webapp-frontend-service
  labels:
    app: webapp-frontend
spec:
  type: NodePort
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 80
      targetPort: 80
  selector:
    app: webapp-frontend
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp-frontend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: webapp-frontend
  template:
    metadata:
      labels:
        app: webapp-frontend
    spec:
      containers:
      - name: webapp-frontend
        image: asia.gcr.io/arched-photon-204013/mean-example_frontend:latest
        ports:
        - containerPort: 80
        env:
        - name: APP_HOST
          value: 'webapp-backend-service'
        - name: APP_PORT
          value: '3000'
        command: ['/bin/sh']
        args: ['-c', "envsubst '$$APP_HOST$$APP_PORT' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'"]

HTTPSに対応するためにIngressを利用することになります。Ingressを利用するためには、事前にNodePortのServiceを作成しておく必要があります。

コンテナイメージに関しては、前回のAngularアプリ実行用のDockerfileから作成したイメージです。

$ kubectl apply -f webapp-frontend-service.yml

外部からの疎通が可能なネットワークの設定

Ingressを作成します。

ingress.yml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: webapp-service
  labels:
    app: webapp
spec:
  backend:
    serviceName: webapp-frontend-service
    servicePort: 80

backendでは先ほど作成したNodePortのServiceを指定しています。

$ kubectl apply -f ingress.yml

Ingressを作成することで、GCLB(Google Cloud Load Balancer)が設定されます。

$ kubectl get ingress

NAME             HOSTS     ADDRESS           PORTS     AGE
webapp-service   *         xxx.xxx.xxx.xxx   80        5m

5分ほどで静的IPアドレスが割り当てられて、アクセスできるようになります。

HTTPS対応の話は次回に。

参考にした資料

Kubernetesの基礎情報 thinkit.co.jp

MongoDBのKubernetes対応 pauldone.blogspot.com