VPSに乗せていたappを自宅のオンプレに移す試み(アプリを乗せるところ)

スポンサーリンク
個人開発

はじめに

VPSで自作のサービスを動かしていましたが、もうお金を払うのに限界を感じてきました。

その理由はnoteのこちらの記事に詳細が記載されています。

ということで、おうちk8sに自作サービスを移行して、自前構築した環境でサービスを動かしていく事を目指そうと思いました。

ちなみにおうちk8sの最初の構築は以下の記事に記載しています。

今回はアプリをおうちk8sに乗せるところまで記載しようと思います。

構成は以下のようにする予定でして、今回は以下の図の赤の領域の実装を試みました。

以前のポイント

おうちk8sに自作アプリを乗せるのは以下で記載しました。

こちらで記載した乗せる上でのポイントは

  • docker imageはDocker Hubにpushしておく
  • フロントエンド部分
    • node_modulesを名前付きボリュームで別管理する
    • EnptyDir(無名ボリューム)を設定して、pod内でviteを入れる
    • npm installの際は--include=devを設定する
  • バックエンド部分
    • DjangoのALLOW_HOSTSでの制限をpodの値を動的に充てられるようにする

です。

これらに気を付けてk8s上に乗せていきます。

構築

それでは構築していきます。

自作Helm Chart

まずはHelm Chartを作っていきます。

今回の場合はfrontend、backend、dbの3つですね。

以前、自作のHelm Chartはwordpressで作成したこともあるので、そちらの記事も参考にしてもらえればと思います。

$ helm create change_view_frontend
$ helm create change_view_backend
$ helm create change_view_db

この時点では

chart用のディレクトリが3つできている状態です。

$ tree -L 1
.
├── change_view_backend
├── change_view_db
└── change_view_frontend

4 directories, 0 files

今回は前よりも運用しやすいように工夫します。

それはテンプレートを使用することです。

前回は初めてという事もあり、テンプレートは使用しませんでした。

今回は更なる改善の意味も込めてテンプレートを使用した実装をします。

テンプレートを使うメリットとしては以下のような点が挙げられるかと思います。

  • 再利用性
    • 異なる環境(dev, staging, prod)で values.yaml だけ切り替えれば同じテンプレートを再利用できる
  • 柔軟性
    • 環境ごとにタグやリソース制限、ポート番号などを簡単に変更できる
  • 保守性
    • 設定の変更が values.yaml だけで完結するので、テンプレート本体は基本的に書き換え不要
  • {{ if }}, {{ range }} などのロジックで複雑な構成も扱える
    • include を使って命名規則やラベル構造を共通化・一元管理できる

今回のコードはどうなったか参考のために記載しておきます。

以下がfrontendのファイル群になります。

.
├── Chart.yaml
├── charts
├── templates
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── pv.yaml
│   ├── pvc.yaml
│   ├── service.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml

これらのファイルの内、編集していくのは

  • deployment.yaml
  • pv.yaml
  • pvc.yaml
  • service.yaml
  • values.yaml

です。

まずはvalues.yamlを見てみます。

namespace: change-view

frontend:
  appName: change-view-frontend
  replicaCount: 2
  strategy: Recreate
  containerName: change-view-frontend-container
  image:
    repository: appare99/change-view-frontend
    tag: latest
    pullPolicy: IfNotPresent

  containerPort: 8080
  portName: vite

  command: "npm install --include=dev && npm run dev"

  env:
    NODE_ENV: production

  imagePullSecret: regcred

  pvc:
    app: change-view-frontend-pvc-app

#### service
service:
  name: change-view-frontend-svc
  labels:
    app: change-view-frontend
  port: 8080
  targetPort: 8080
  protocol: TCP
  selector:
    app: change-view-frontend

#### pvc
persistentVolumeClaims:
  - name: change-view-frontend-pvc-app
    labels:
      app: change-view
    accessModes:
      - ReadWriteOnce
    storage: 1Gi
    selectorLabels:
      app: change-view-frontend-pv-app

  - name: change-view-frontend-pvc-nodemodules
    labels:
      app: change-view
    accessModes:
      - ReadWriteOnce
    storage: 1Gi
    selectorLabels:
      app: change-view-frontend-pv-nodemodules

### pv
persistentVolumes:
  - name: change-view-frontend-pv-app
    labels:
      app: change-view-frontend-pv-app
    capacity: 1Gi
    accessModes:
      - ReadWriteOnce
    reclaimPolicy: Retain
    localPath: /mnt/change_view_frontend/change-view
    nodeAffinity:
      key: kubernetes.io/hostname
      operator: In
      values:
        - k8s-worker2

  - name: change-view-frontend-pv-nodemodules
    labels:
      app: change-view-frontend-pv-nodemodules
    capacity: 1Gi
    accessModes:
      - ReadWriteOnce
    reclaimPolicy: Retain
    localPath: /mnt/change_view_frontend/change-view/node_modules
    nodeAffinity:
      key: kubernetes.io/hostname
      operator: In
      values:
        - k8s-worker2

values.yamlの値を各ファイルで読み込んでいきます。

それでは残りのdeployment.yaml、pv.yaml、pvc.yaml、service.yamlも記載していきます。

deployment.yamlは以下のようになっています。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Values.frontend.appName }}
  namespace: {{ .Values.namespace }} #
  labels:
    app: {{ .Values.frontend.appName }}
spec:
  replicas: {{ .Values.frontend.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Values.frontend.appName }}
  strategy:
    type: {{ .Values.frontend.strategy | default "Recreate" }}
  template:
    metadata:
      labels:
        app: {{ .Values.frontend.appName }}
    spec:
      containers:
        - name: {{ .Values.frontend.containerName }}
          image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}"
          ports:
            - containerPort: {{ .Values.frontend.containerPort }}
              name: {{ .Values.frontend.portName }}
          volumeMounts:
            - name: app
              mountPath: /app
            - name: nodemodules
              mountPath: /app/node_modules
          workingDir: /app
          command: [ "sh", "-c", "npm install --include=dev && npm run dev -- --host" ]
          env:
            - name: NODE_ENV
              value: {{ .Values.frontend.env.NODE_ENV | quote }}
      imagePullSecrets:
        - name: {{ .Values.frontend.imagePullSecret }}
      volumes:
        - name: app
          persistentVolumeClaim:
            claimName: {{ .Values.frontend.pvc.app }}
        - name: nodemodules
          emptyDir: {}

.Values~~のところで、values.yamlから変数を読み込んでいます。

pv.yamlは以下のようになります。

{{- range .Values.persistentVolumes }}
apiVersion: v1
kind: PersistentVolume
metadata:
  name: {{ .name }}
  namespace: {{ $.Release.Namespace }}
  labels:
    {{- toYaml .labels | nindent 4 }}
spec:
  capacity:
    storage: {{ .capacity }}
  accessModes:
    {{- toYaml .accessModes | nindent 4 }}
  persistentVolumeReclaimPolicy: {{ .reclaimPolicy }}
  local:
    path: {{ .localPath }}
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: {{ .nodeAffinity.key }}
              operator: {{ .nodeAffinity.operator }}
              values:
                {{- toYaml .nodeAffinity.values | nindent 16 }}
---
{{- end }}

.Releaseのところではリリース情報から値を取得しています。

{{- toYaml .accessModes | nindent 4 }}では、

.accessModesがValues ファイルから渡されたリスト(例: ["ReadWriteOnce"])で、toYaml はそのリストを YAML 形式に変換します。nindent 4 は、インデント(スペース4個)付きで出力します。

次はpvc.yamlです。

{{- range .Values.persistentVolumeClaims }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: {{ .name }}
  namespace: {{ $.Release.Namespace }}
  labels:
    {{- toYaml .labels | nindent 4 }}
spec:
  accessModes:
    {{- toYaml .accessModes | nindent 4 }}
  resources:
    requests:
      storage: {{ .storage }}
  selector:
    matchLabels:
      {{- toYaml .selectorLabels | nindent 6 }}
---
{{- end }}

最後にservice.yamlです。

apiVersion: v1
kind: Service
metadata:
  name: {{ .Values.service.name }}
  namespace: {{ .Release.Namespace }}
  labels:
    {{- toYaml .Values.service.labels | nindent 4 }}
spec:
  selector:
    {{- toYaml .Values.service.selector | nindent 4 }}
  ports:
    - name: vite
      protocol: {{ .Values.service.protocol }}
      port: {{ .Values.service.port }}
      targetPort: {{ .Values.service.targetPort }}

同様にbackendもDBも設定していきます。

次にクラスタ内で使用するdocker imageはhelm構築の前にDocker Hubにpushしておきます。

$ sudo docker build -t appare99/change-view-frontend .

$ sudo docker push appare99/change-view-frontend

そして、helm installを実施すると以下のようにpodが立ち上がります。

上記の図で赤い枠のnamespace内の構築ができました。

$ kubectl get pod -n change-view
NAME                                    READY   STATUS    RESTARTS   AGE
change-view-backend-55885b7d6d-g47nw    1/1     Running   0          63s
change-view-backend-55885b7d6d-sd9rw    1/1     Running   0          63s
change-view-db-54585b6bf-sxqg2          1/1     Running   0          69s
change-view-frontend-5768dbb4bf-5sfw4   1/1     Running   0          57s
change-view-frontend-5768dbb4bf-6djxc   1/1     Running   0          57s

ちなみにもう一つのnamespaceは以下のように構築ができています。

$ kubectl get pod -n go-slim
NAME                                READY   STATUS    RESTARTS   AGE
go-slim-backend-69899c5d9-828gd     1/1     Running   0          25s
go-slim-backend-69899c5d9-9lgbj     1/1     Running   0          25s
go-slim-db-6c7c47964f-8w9fq         1/1     Running   0          32s
go-slim-frontend-6fcf4d48d7-mksmd   1/1     Running   0          19s
go-slim-frontend-6fcf4d48d7-rlgxj   1/1     Running   0          19s

その他、helm installとuninstall用のスクリプトを用意しておくと良いということ

すぐ構築できるように「helm install」と「helm uninstall」を実行するスクリプトを用意しておくと良いと思います。

以下のようなスクリプトです。

#!/bin/bash

SCRIPT_DIR=$(dirname "$0")
CHART_DIR="${SCRIPT_DIR}/../change_view"

helm install change-view-db ${CHART_DIR}/change_view_db --namespace change-view
sleep 5
helm install change-view-backend ${CHART_DIR}/change_view_backend --namespace change-view
sleep 5
helm install change-view-frontend ${CHART_DIR}/change_view_frontend --namespace change-view

このスクリプトでは各チャートに対してhelm installコマンドを実施しています。

このようなスクリプトを幾つか用意しておけば(helm uninstallなど)、作業効率が上がります。

最後に

月額のランニングコストを抑えるためにもなるはやでおうちk8sにサービスを乗せて運用したいところです。

タイトルとURLをコピーしました