9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

おうちKubernetesにVue.jsで作ったポートフォリオを乗せる

Last updated at Posted at 2022-12-06

はじめに

フューチャー Advent Calendar 2022の7日目の記事です。
以前、Future Tech Blogの方で「Raspberry Piをかき集めてKubernetesを体感する」を書かせて頂き、おうちKubernetesを実装してみました。
今回はおうちKubernetesにVue.jsとFlaskを乗っけて自作のポートフォリオをデプロイしてみようと思います。

おうちKubernetes

おうちKubernetesは、Raspberry Piを組み合わせたClusterです。
1台をMaster node(Control Plane)とし、残り2台をWorker nodeとして構成しています。

Node Machine IPアドレス
Master Raspberry Pi 4B (4GB) 192.168.1.101
Worker1 Raspberry Pi 4B (8GB) 192.168.1.102
Worker2 Raspberry Pi 4B (8GB) 192.168.1.103

前回からのアップデートとして、

  • Worker nodeのマシンを統一
    • Raspberry Pi 3Bと交換しました。
  • インスタ映えしそうな筐体を導入
    • 大きなファンがついたので、冷却性能が上がりました。
    • ファンが光ります。

の変更を加えました。
Raspberry Pi 4Bは当分買えないかなーと思っていましたが、スイッチサイエンスさんの方で入荷通知登録をしておくことで購入できました!

全体像

今回作成したコードは全てこちらのレポジトリで公開しています。
https://github.com/bigface0202/k8s-flask-vuejs

完成後のおうちKubernetes全体像

完成後のディレクトリツリー

/k8s-flask-vuejs
├── README.md
├── app
│   ├── Dockerfile
│   ├── flask
│   │   ├── main.py
│   │   └── requirements.txt
│   └── vue
│       └── vue関係のファイル・ディレクトリ
└── k8s
    ├── deployment.yaml
    ├── metallb.yaml
    ├── namespace.yaml
    └── service.yaml

ポートフォリオ側の準備

Vue.js側でのポートフォリオの実装は省略し、Vue.jsのHello Worldを利用します。

vue-cliでVue.jsの初期環境を構築する

では早速やっていきましょう。npmやvue-cliのインストールは省略させて頂きます。

terminal
# "vue"というディレクトリ名で作成
vue create vue
# Vue 3を選択してエンター
Vue CLI v5.0.8
? Please pick a preset: (Use arrow keys)
❯ Default ([Vue 3] babel, eslint) 
  Default ([Vue 2] babel, eslint) 
  Manually select features

Vue.jsのDelimterを変更する

Delimiterが初期設定のままだとFlaskのDelimiter('{{', '}}')が被ってしまい、Vue.js上で動的な表示を実装することができません。
vue.config.jsに以下を追記することDelimiterを変更します(ついでにビルド先のディレクトリ構成も変更しています)。

vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
// 以下を追記
  outputDir: 'dist',
  assetsDir: 'static',
  indexPath: 'templates/index.html',
  chainWebpack: config => {
    config.module
      .rule('vue')
      .use('vue-loader')
      .loader('vue-loader')
      .tap(options => {
        options.compilerOptions = { 
          whitespace: 'condense',
          delimiters: ['[[', ']]']
        };
        return options;
      });
  }
})

Delimiterを変更したので、一部書き換えて検証してみましょう。

vue/src/components/HelloWorld.vue
...
  <div class="hello">
-    <h1>{{ msg }} </h1>
+    <h1>[[ msg ]] </h1>
    <p>
...
vue/src/App.vue
...
  <img alt="Vue logo" src="./assets/logo.png">
-  <HelloWorld msg="Welcome to Your Vue.js App"/>
+  <HelloWorld msg="このポートフォリオはおうちK8Sで運営されています。"/>
...
terminal
# 確認する
npm run serve
# 以下が表示される
> vue@0.1.0 serve
> vue-cli-service serve

 INFO  Starting development server...

 DONE  Compiled successfully in 2778ms                                                                                                                                                                                 4:14:31 PM
  App running at:
  - Local:   http://localhost:8080/ 
  - Network: http://172.18.47.103:8080/

  Note that the development build is not optimized.
  To create a production build, run npm run build.

上記の画面が表示されれば完了です。

ビルドしておく

ビルドします。

terminal
// ビルドコマンド
npm run build

 DONE  Compiled successfully in 2968ms                                                                                                                                                                                11:55:22 AM

  File                                        Size                                                                                    Gzipped

  dist/static/js/chunk-vendors.e5344afe.js    72.40 KiB                                                                               27.17 KiB
  dist/static/js/app.006afa24.js              13.05 KiB                                                                               8.40 KiB
  dist/static/css/app.2cf79ad6.css            0.33 KiB                                                                                0.23 KiB

distディレクトリが作成されていればOKです。

Flask

WebフレームワークとしてPythonでFlaskを利用します。
Python3やライブラリ周りの環境構築は省略致します。

main.py
import os
from flask import Flask, render_template

# Specify the directory of Vue.js building files
app = Flask(__name__, static_folder='../vue/dist/static', template_folder='../vue/dist/templates')

# For SPA
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def index(path):
    return render_template('index.html')

if __name__ == "__main__":
    app.run(debug=True, host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))

動作確認してみましょう。

terminal
# Flaskを起動してみる
python3 main.py

 * Serving Flask app 'main'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:8080
 * Running on http://172.18.47.103:8080
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 846-044-410

http://127.0.0.1:8080にアクセスして、先程Vue.jsの検証の画面と同じ画面が表示されればOKです。

Dockerイメージ

Dockerの環境構築は省略致します。
また、Docker Hubを利用するのでアカウントを作成しておいてください。

Kubernetes上でDeploymentを作成するのにDockerイメージが必要となるため、上記で構築した環境をDocker上でも構築できるようにする必要があります。

Dockerfile
# Multi-stage building
# Building Vue.js
FROM node:17-alpine3.14 as vue-build

COPY vue /app
WORKDIR /app
RUN npm install

RUN npm run build

# Building python3 flask environment and copy Vue.js from vue-build
FROM python:3.10-slim

ENV PYTHONUNBUFFERED True

ENV APP_HOME /app
COPY flask $APP_HOME
WORKDIR $APP_HOME
COPY --from=vue-build /app/dist ./html

RUN pip install --no-cache-dir -r requirements.txt

# Execute web server by gunicorn
ENV PORT 8080
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app

Vue.jsとFlaskをマルチステージにてビルドするようにしています。
Dockerfileをビルドする前に、Flask側で検証したコードを一部書き換える必要があります。

main.py
import os
from flask import Flask, render_template

# Specify the directory of Vue.js building files
- app = Flask(__name__, static_folder='../vue/dist/static', template_folder='../vue/dist/templates')
+ app = Flask(__name__, static_folder='html/static', template_folder='html/templates')

...

Flask検証時はローカルにて実行したためstatic_foldertemplate_folderをVue.jsのビルド先を指定しておりましたが、Dockerfile上ではビルドファイルのコピー先ディレクトリを指定する必要があり、そのように書き換えます。

では、ビルドしてみましょう。今回はpush先にDocker Hubを利用するため、自身のDocker Hubとイメージ名をタグとして付けておきます。
※検証の際には自身のWindows/Mac/Linux PCでビルド・動作検証を行えますが、おうちKubernetes上に載せる際はARM製CPUにてビルドする必要があります。
Raspberry Pi 4BなどARM製品でビルドしてください。

terminal
# <Your-Docker-Account>に自身のアカウント名を入力してください
docker build -t <Your-Docker-Account>/flask-vuejs:v1.0 .
# ビルド完了したら、コンテナを作成し検証してみましょう。
docker run -p 8080:8080 d2a8815d10f9                                                                                                                                           
[2022-12-04 03:32:28 +0000] [1] [INFO] Starting gunicorn 20.1.0
[2022-12-04 03:32:28 +0000] [1] [INFO] Listening at: http://0.0.0.0:8080 (1)
[2022-12-04 03:32:28 +0000] [1] [INFO] Using worker: gthread
[2022-12-04 03:32:28 +0000] [7] [INFO] Booting worker with pid: 7

http://0.0.0.0:8080へアクセスしてVue.jsの画面が表示されればOKです。
これでポートフォリオとなるDockerイメージが作成できました。

Kubernetes側の準備

既にマスターノード側へkube-flannelを導入し、ワーカーノードがjoinしている前提で始めます。

terminal
kubectl get nodes

NAME                STATUS   ROLES           AGE   VERSION
mas01.example.com   Ready    control-plane   16h   v1.24.4
work01              Ready    worker          16h   v1.25.0
work02              Ready    worker          16h   v1.25.0

Namespaceの作成

おうちKubernetesでは様々なクラスタが共存する(予定)ため、Namespaceで分けることにします。
myapp-spaceというNamespaceを作成します。

namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: myapp-space
terminal
kubectl get namespace
# 以下が表示される
NAME              STATUS   AGE
default           Active   17h
kube-flannel      Active   16h
kube-node-lease   Active   17h
kube-public       Active   17h
kube-system       Active   17h
myapp-space       Active   6m

Deploymentの作成

おうちKubernetes上(Raspberry Pi上)に乗っける前にARM向けにビルドする必要があるので、Raspberry PiへSSHするなどしてさきほどのDockerfileをビルドしましょう。

terminal(Raspberry Pi)
# ビルド
docker build -t bigface00/flask-vuejs:v1.1 .
# プッシュ
docker push <Your-Docker-Account>/flask-vuejs:v1.1

ビルドしたVue.jsとFlaskのイメージをKubernetes上にデプロイするためのDeploymentを作成していきます。

deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
  namespace: myapp-space
spec:
  selector:
    matchLabels:
      app: myapp
  replicas: 2 # tells deployment to run 2 pods matching the template
  template:
    metadata:
        name: myapp
        namespace: myapp-space
        labels:
          app: myapp
    spec:
      containers:
        - name: myapp
          image: <Your-Docker-Account>/flask-vuejs:v1.1 # Please specify your docker account
          ports:
          - containerPort: 8080
          livenessProbe: # Check live or not of container
            httpGet:
              port: 8080
              path: /
            failureThreshold: 5
            periodSeconds: 5

Namespacemyapp-spacemyapp-deploymentというdeployment作成します。
作成したdeploymentの中にはmyappというコンテナがあり、コンテナイメージは今回pushしたDocker Hubのイメージを指定しています。
サービスを公開するポートに対して、containers.ports.containerPortを設定しています。
また、livenessProveで設定したポートを指定することでコンテナが生きているかどうかを判断します。生きていない場合は、コンテナが再起動されます。

applyしましょう。

terminal
# apply
kubectl apply -f deployment.yaml
# 以下が表示される
deployment.apps/myapp-deployment created
# 確認
kubectl get pods -n myapp-space
# Runningなことを確認する
NAME                               READY   STATUS    RESTARTS   AGE
myapp-deployment-fbc449488-mrqz6   1/1     Running   0          13s
myapp-deployment-fbc449488-vgf67   1/1     Running   0          13s

これでVue.jsとFlaskのコンテナがデプロイされました。
ただ、このままではコンテナへのアクセス先が無いため、デプロイしたアプリを見ることはできません。

MetalLBの導入とServiceの作成

Serviceを作成することで、コンテナを外部に公開するためのエンドポイントを作成します。
外部公開と言ってもインターネット上にさらすことはなく、ローカルネットワーク上からのみアクセス可能となります。
また、GKEなどパブリッククラウドのKubernetesエンジンでは、ServiceにLBを指定するとCloud Load Balancingを使ってくれるなどよしなにやってくれるのですが、おうちKubernetes上ではよしなにやってくれません。
MetalLBを利用することでLoadbalanserTypeのServiceを使えるようにしてくれます。

MetalLB, bare metal load-balancer for Kubernetesを参考にインストールします。

terminal
# MetalLBのマニフェストをapply
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.5/config/manifests/metallb-native.yaml
# シークレットを作成
kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)"

memberlistというシークレットを作成しているのは、MetalLBでmemberlistという機能を利用するため必要な手順となります。

参考:MetalLB入門 ― オンプレKubernetesクラスタでLoadBalancerタイプのServiceを使う

確認します。

terminal
kubectl get pod -n metallb-system
# 以下が表示される
NAME                          READY   STATUS    RESTARTS   AGE
controller-8689779bc5-lnfnb   0/1     Pending   0          70s
speaker-4k4k4                  1/1     Running   0          70s

次にLBとしての設定用マニフェストファイルを作成します。

metallb.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: pool-ips
  namespace: metallb-system
spec:
  addresses:
  - 192.168.1.210-192.168.1.215 # External IP range
  autoAssign: true
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: pool-ips
  namespace: metallb-system
spec:
  ipAddressPools:
  - pool-ips

1つめのIPAddressPoolでは外部IPとして利用するIPプールを作成します。
spec.addressesに記載されているIPレンジ(192.168.1.210-192.168.1.215)の中からオートマチックに外部IPを決定します。
今回のIPレンジはおうちKubernetesで利用しているローカルIPを避けて指定しています。
2つめのL2Advertisementは広報のための設定です。
Podが持つIPアドレスはCluster IPのため、Kubernetes外からでは認識することができません。
そのため、「192.168.1.210の宛先はこのPodですよ~」という感じで周りに広報する必要があります。
MetalLBではその役割も担ってくれます。

最後にServiceを作成します。

service.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp-service-lb # Name of Service
  namespace: myapp-space
  annotations:
    metallb.universe.tf/address-pool: pool-ips # IP pool name of MetalLB
spec:
  type: LoadBalancer
  ports:
    - name: myapp-service-lb
      protocol: TCP
      port: 8080 # Listen port by Service's IP
      nodePort: 30080 # Listen port by node's IP (30000-32767)
      targetPort: 8080 # Listen port number by transfered destination (deployed container)
  selector: # service のselctorは、matchLabels 扱いになる
    app: myapp # Pod's label of transfered destination (deployed container)

外部IPとしてMetalLBのIPプールを利用するため、annotationsにてIPプール名pool-ipsを指定しています。
selector.appはdeploymentで利用するPodのラベル名を指定します。

ここまで作成したマニフェストファイルをapplyしましょう。

terminal
# MetalLBのapply
kubectl apply -f metallb.yaml
# 以下が表示される
ipaddresspool.metallb.io/pool-ips created
l2advertisement.metallb.io/pool-ips created
# Serviceのapply
kubectl apply -f service.yaml 
# 以下が表示される
service/myapp-service-lb created
# applyされた内容を確認しましょう
kubectl get svc -n myapp-space
# 以下が表示され、EXTERNAL-IPが割り振られていればOK
NAME               TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)          AGE
myapp-service-lb   LoadBalancer   10.100.2.129   192.168.1.210   8080:30080/TCP   11s

これでコンテナへアクセスする準備が整いました。192.168.1.210:8080へアクセスしてみましょう。
Vue.jsの画面が表示されたでしょうか?これにてVue.jsのデプロイが完了しました。
残りは良い感じにポートフォリオを作成していきましょう!

まとめ

ここまでで自分のポートフォリオをおうちKubernetes上に乗っけることができました。
ポートフォリオ程度ではKubernetesの恩恵にあやかることはできなさそうですが、これにMySQLなどのDBをデプロイしてアプリ化することでKubernetesのパフォーマンスを発揮できるのではないかと思います。
また、ここから更にインターネット上に公開することで、実際に運用するまで持っていけると面白そうです。

記事には載せていませんが、今回の実装ではコンテナイメージを引っ張る際に、ローカル上から引っ張ろうとしてうまくいかず、Google CloudのArtifact Registryから引っ張ろうとしましたが、途中で403エラーになり原因も解明できずで思わぬところで苦戦してしまいました。結局、Docker Hubに落ち着いてからはスムーズにできました。

次のハードウェアアップデートとしてはPoEハットとPoE対応ハブを買って電源コードを廃止し、よりスリム化したおうちKubernetesにしたいところです。
明日は@orangekame3さんの記事になります!

9
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?