はじめに
VPSで自作のサービスを動かしていましたが、もうお金を払うのに限界を感じてきました。
その理由はnoteのこちらの記事に詳細が記載されています。
ということで、おうちk8sに自作サービスを移行して、自前構築した環境でサービスを動かしていく事を目指そうと思いました。
ちなみにおうちk8sの最初の構築は以下の記事に記載しています。
今回はアプリをおうちk8sに乗せるところまで記載しようと思います。
構成は以下のようにする予定でして、今回は以下の図の赤の領域の実装を試みました。

以前のポイント
おうちk8sに自作アプリを乗せるのは以下で記載しました。
- 「おうちk8sに自作アプリを乗せる①(案の定k8sが上手くいかない #11)」
- 「おうちk8sに自作アプリを乗せる②(案の定k8sが上手くいかない #12)」
- 「おうちk8sに自作アプリを乗せる③(案の定k8sが上手くいかない #13)」
こちらで記載した乗せる上でのポイントは
- 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
だけ切り替えれば同じテンプレートを再利用できる
- 異なる環境(dev, staging, prod)で
- 柔軟性
- 環境ごとにタグやリソース制限、ポート番号などを簡単に変更できる
- 保守性
- 設定の変更が
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にサービスを乗せて運用したいところです。