前回のあらすじ

  • 本番環境を構築した
  • Dockerfileを作ってそこからDockerImageを作った

この記事ですること

  • k8sマニフェストを作る
  • 動かす

k8sマニフェスト作成

前回の記事は準備でここからが本題。
k8sを動かすにはmanifestという物を作って渡してあげる必要がある。
正確にはなくても長いコマンドでパラメータ渡せば行けるんだが、みんなDockerではDocker-compose -f使うよね?ってことだ。
設定はyamlに残すべき。

さて書いていこう。
まずは分かりやすいMongoDBの設定から。

mongo-statefulset.yaml

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mongo-sts
spec:
  selector:
    matchLabels:
      app: mongo-pod
  serviceName: mongo-svc
  replicas: 3
  template:
    metadata:
      labels:
        app: mongo-pod
    spec:
      terminationGracePeriodSeconds: 10
      containers:
        - name: mongo-container
          image: mongo:4.4
          command:
            - mongod
            - "--replSet"
            - rs0
            - "--bind_ip"
            - 0.0.0.0
          ports:
            - containerPort: 27017
          volumeMounts:
            - name: mongo-pvc
              mountPath: /data/db
  volumeClaimTemplates:
    - metadata:
        name: mongo-pvc
      spec:
        accessModes:
          - "ReadWriteOnce"
        resources:
          requests:
            storage: 10Gi

…???
やっていることを説明していく。
まずは先頭のkind:statefulsetって何だろうか。
これはステートフルなアプリケーションを動かす際に指定する。
…全く解説になってないのでとりあえずDBにはこれ使う。
理由としてはアプリケーションの接続するDBをコンテナ名で指定したいのと、DB1つ1つに永続ボリュームを割り当てたい為だ。
https://kubernetes.io/ja/docs/concepts/workloads/controllers/statefulset/

安定した一意のネットワーク識別子
安定した永続ストレージ

ってところが欲しい感じ。
逆にこの辺が要らないStrapiサーバーは違うの使ってるわけだがそれは後述。
ネットワーク識別子って所だが、k8s内なら大抵Pod名で名前解決できるのでPod名となる。
Podってのは1つ以上のコンテナの集まり。今回はPod1つにコンテナ1つなのでコンテナと同じと見てもそこまで支障はない。
で、これを起動した時のPod名になるのがmetadata:nameだ。
今回はmongo-stsなので、mongo-sts-0とかで立ち上がる予定。

次にspec。
ここから設定項目になる。
selectorというのが監視するPodがどれかというのを設定する場所。
今回は app: mongo-pod というラベルが付いたPodを監視する。
監視するにしても何を基準に見守るのか?
そこで replicas: 3 である。
要はPod3つ作って維持してねという指示を出しているわけだ。
これでこのマニフェストを実行したらmongo-podってのが3つ出来ることは分かった。
じゃあPodの中身ってどんなんよ?ということがtemplete:以下にかかれている。

まずはlabelが app: mongo-pod 。監視対象である。
こちらもspec以下が設定項目。
名前はmongo-containerでDockerImageにmongo:4.4を使っている。
そしてmongodコマンドで起動するように設定を書いている。
portsの内容含め、Dockerに近いのでまだ読めるほうではないだろうか。

その次に volumeMounts というのがあるが、これは名前の通りストレージをマウントしている。
コンテナの中の/data/dbmongo-pvcっていうところから分捕ってきたストレージをマウントしてね。っていう指示。
mongo-pvcは何っていうのは下のvolumeClaimTemplatesに書いてある。
英語まんまでボリュームを請求するテンプレートである。
ここでは10Giちょうだいといっている。
誰が出すのってなるがそのあたりはMinikubeやDocker for mac/windowsがよしなにやってくれるので気にしなくていい。
気になる人はPersistentVolumeとDynamic provisioningあたりで調べればいいと思う。
https://kubernetes.io/ja/docs/concepts/storage/dynamic-provisioning/
https://minikube.sigs.k8s.io/docs/handbook/persistent_volumes/

設定説明ここまで。
どうせ一発では理解できないし、とりあえずローカルで動かしてみよう。
Docker for mac/windows環境なら設定一つ変えるだけでkubectlコマンドが使えるのでテスト最適。

kubectl apply -f mongo-statefulset.yml

created的な文字が出てくるはずだ。

kubectl get pods

NAME                                 READY   STATUS      RESTARTS   AGE
mongo-sts-0                          1/1     Running     0          11d
mongo-sts-1                          1/1     Running     0          11d
mongo-sts-2                          1/1     Running     0          11d

mongo-sts-0などがrunningになっていれば正常起動している。
AGEは起動時間なんで1mとか短い値になってるはず。
一応Dockerよろしくコンテナの中に入ることも出来る。

sudo kubectl exec -it mongo-sts-0 -- /bin/bash

これで中に入れるんで、mongoコマンドでも打ってちゃんと動いているか確認しておこう。

次にStrapiの立ち上げ。

strapi-deployment.yaml

今度はdeploymentである。何これ。

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: strapi-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: strapi-deployment
  labels:
    app: strapi-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: strapi-pod
  template:
    metadata:
      labels:
        app: strapi-pod
    spec:
      containers:
      - name: strapi-container
        image: tim0401/dreamer-strapi:latest
        ports:
        - containerPort: 1337
        env:
        - name: PORT
          value: "1337"
        - name: ADMIN_JWT_SECRET
          valueFrom:
            secretKeyRef:
              name: secret
              key: admin-jwt-secret
        - name: DATABASE_HOST
          value: "mongo-sts-0.mongo-svc.default.svc.cluster.local:27017,mongo-sts-1.mongo-svc.default.svc.cluster.local:27017,mongo-sts-2.mongo-svc.default.svc.cluster.local:27017/?replicaSet=rs0"
        - name: DATABASE_PORT
          value: "27017"
        - name: DATABASE_NAME
          value: "dreamer-strapi"
        volumeMounts:
        - name: strapi-data
          mountPath: "/dreamer-strapi/app/public/uploads"

      volumes:
      - name: strapi-data
        persistentVolumeClaim:
          claimName: strapi-pvc

PersistentVolumeClaim…ボリュームくれくれ君。
今回も10Gi要求。
Deploymentが何なのかというとReplicaSetを管理するものである。
ReplicaSetが何なのかというとPodを管理するものである。
StatefulSetとの違いは順番に作られなかったり、コンテナ名が指定できないので特定のコンテナにネットワークアクセス出来なかったり、ボリューム共有だったりする。
要はステートがないものに使えってことらしい。
全部StatefulSetじゃ駄目なのとか思うけど管理が楽とかなんか理由があるんでしょうね。 あとReplicaSetって意識して使うことあるんかな…?

さて説明だが、途中まではmongoと被るのでカット。
要はPod3つ作って。以上。
その後もdocker-composeで見たこれって内容。
まあ自分で作ったイメージだしそりゃ同じ内容のenvいるわな。
ADMIN_JWT_SECRETがsecretってなってるが、secret.yamlを後で紹介するのでそっちの値を参照していると思ってもらえればいい。
パスワードとかはそっちに書いて読み込ませる方式。
DATABASE_HOSTがすごいことになってるが、mongoのreplicasetはこんな感じでアクセスできるらしい…3つのPodのアドレスを連ねて書いている。
一応動くんだけど、これ本当に正しいのか…。
残りはボリュームマウントしてるだけ。
書き方の決まりはあるけどやってることはそこまで難解ではない。

mongo-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: mongo-svc
  labels:
    name: mongo-svc
spec:
  type: ClusterIP
  ports:
    - port: 27017
      targetPort: 27017
  clusterIP: None
  selector:
    app: mongo-pod

お次はサービス。
サービスっていうのはPodを束ねて内部や外部からアクセス出来るポイントを作るもの。
今回の場合、mongo-svcっていうサービスを通してmongo-podのアドレス解決が出来るようになって内部からアクセスできるようになる。
上の設定でmongo-sts-0.mongo-svc.default.svc.cluster.local:27017ってのがあったけどmongo-svcの文字が見えると思う。
これがあるからアクセス出来るわけだ。
やってることはそれだけだけど重要な役目。
ポートはmongo-podに合わせて27017。

strapi-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: strapi-svc
spec:
  selector:
    app: strapi-pod
  ports:
  - port: 1337
    targetPort: 1337
    nodePort: 30000
  type: NodePort

今度はサービスでもNodePortって種類のもの。
ちなみに上のはClusterIP。多分クラスタ内だけのIPって意味。
NodePortは外部にPodを公開できる。
ports:nodePortが30000に設定されているのでlocalhost:30000で公開される。
ポートは指定しなければ30000~32767がランダムで割り当てられるけどそれじゃ使えないので固定。
https://kubernetes.io/ja/docs/concepts/services-networking/service/
一応上記の範囲で指定したほうがいいと思う。
他も出来るかもしれないけどやったことない。
これでstrapi-podにlocalhost:30000でアクセスできるようになる。

secret.yaml

apiVersion: v1
kind: Secret
metadata:
  name: secret
type: Opaque
stringData:
  admin-jwt-secret: "<your-secret>"

シンプル。
strapi-deploymentで使ったadmin-jwt-secretはここで指定している。
シークレットは生成していれといてください。

立ち上げ

ここまでお疲れ様でした。
書いてて疲れるので数ヶ月経った私が読んでも相当疲れることだろう。

全ての準備が整った(はず)なので、立ち上げてみよう。 環境はUbuntuの本番でするのでもいいけど、ローカルの環境でとりあえず試した方が上手く動かなかった時に修正しやすいと思う。
立ち上げるのはコマンド一発でサクッと終わる。

kubectl apply -f ./

カレントのマニフェスト全適用である。
ちなみに、これだけだとStrapiがコケる。
MongoDBの中身に対してレプリケーション設定をしないといけない。
これをマニフェストに書かないのは初回のみすることだから、マニフェスト変更とかでPodの再生成がかかった際にやってほしくないため。

立ち上げ後のMongoDBレプリケーション設定

kubectl exec -it mongo-sts-0 -- /bin/bash
mongo mongo-sts-0.mongo-svc
config = {
   _id : "rs0",
   members: [
      { _id: 0, host: "mongo-sts-0.mongo-svc:27017" },
      { _id: 1, host: "mongo-sts-1.mongo-svc:27017" },
      { _id: 2, host: "mongo-sts-2.mongo-svc:27017" },
   ]
}

rs.initiate(config)
rs.status()

コンテナに入ってmongoのreplicasetを3台で構成する設定。
これで3台によるレプリケーションをしてくれる。
rs.status()でreplicasetの状態を長々と出してくれたら成功。

確認

$ sudo kubectl get all
NAME                                     READY   STATUS      RESTARTS   AGE
pod/mongo-sts-0                          1/1     Running     0          11d
pod/mongo-sts-1                          1/1     Running     0          11d
pod/mongo-sts-2                          1/1     Running     0          11d
pod/strapi-deployment-754494fd88-74bd5   2/2     Running     0          4d19h
pod/strapi-deployment-754494fd88-dcft2   2/2     Running     0          4d19h
pod/strapi-deployment-754494fd88-smf2f   2/2     Running     0          4d19h

NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
service/kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP          13d
service/mongo-svc    ClusterIP   None             <none>        27017/TCP        13d
service/strapi-svc   NodePort    10.102.209.245   <none>        1337:30000/TCP   12d

NAME                                READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/strapi-deployment   3/3     3            3           11d

NAME                                           DESIRED   CURRENT   READY   AGE
replicaset.apps/strapi-deployment-547449485f   0         0         0       8d
replicaset.apps/strapi-deployment-6bd7bcd9d7   0         0         0       11d
replicaset.apps/strapi-deployment-6dc6597556   0         0         0       8d
replicaset.apps/strapi-deployment-6df77bdbf6   0         0         0       9d
replicaset.apps/strapi-deployment-754494fd88   3         3         3       4d19h

NAME                         READY   AGE
statefulset.apps/mongo-sts   3/3     11d

綺麗に立ち上がるとこんな形でちょっと達成感がある。
苦戦したところが多すぎて苦労に見合ってるかは微妙かもしれない。

長くなったがこれでLAN内からStrapiにアクセス出来るようになったはずだ。
LAN内の他のPCから構築したPCのIPアドレス:30000にアクセスしてみよう。
Strapiの見慣れた画面が出てくるはずだ。
localhostからは行けるのにLAN内の他PCからは見れない場合、Firewallが邪魔をしている可能性があるので一時的にポート開放をしておくといい。

次は外部からアクセスするためにNginxによるリバースプロキシを作っていく。
とはいえ、やらなくてもアクセス出来たりするし今回の比じゃないくらいさくっと終わるので書くのが楽そうだ。

今回貼ったマニフェストはGithubに置いてるので最新の物が見たかったらどうぞ。
https://github.com/Tim0401/dreamer-strapi/tree/master/manifest

次の記事: Gatsby+Strapiでブログを構築した話(9) リバースプロキシの構築と外部からのアクセス