はじめに
フューチャー Advent Calendar 2022の7日目の記事です。
以前、Future Tech Blogの方で「Raspberry Piをかき集めてKubernetesを体感する」を書かせて頂き、おうちKubernetesを実装してみました。
今回はおうちKubernetesにVue.jsとFlaskを乗っけて自作のポートフォリオをデプロイしてみようと思います。
おうちKubernetes
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F271414%2F81ca35db-92dc-51b4-d276-ad53d6918827.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=877da1688db91587b7fe7ac855179eea)
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F271414%2Ff2dd6306-b9ac-a237-a924-50055eecffea.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=a7df0858ecd33f48242854f070e5e36d)
おうち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全体像
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F271414%2F9f6bcd85-a0f9-669b-f355-85c37261310a.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=f26a1f3198662d9cc66324b53cfb770f)
完成後のディレクトリツリー
/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のインストールは省略させて頂きます。
# "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を変更します(ついでにビルド先のディレクトリ構成も変更しています)。
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を変更したので、一部書き換えて検証してみましょう。
...
<div class="hello">
- <h1>{{ msg }} </h1>
+ <h1>[[ msg ]] </h1>
<p>
...
...
<img alt="Vue logo" src="./assets/logo.png">
- <HelloWorld msg="Welcome to Your Vue.js App"/>
+ <HelloWorld msg="このポートフォリオはおうちK8Sで運営されています。"/>
...
# 確認する
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.
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F271414%2F237c1648-3426-bfe0-9a0d-52e7d4b32be1.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=9e49f8f087812b013d8a95bfa6b67b12)
上記の画面が表示されれば完了です。
ビルドしておく
ビルドします。
// ビルドコマンド
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やライブラリ周りの環境構築は省略致します。
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)))
動作確認してみましょう。
# 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上でも構築できるようにする必要があります。
# 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側で検証したコードを一部書き換える必要があります。
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_folder
やtemplate_folder
をVue.jsのビルド先を指定しておりましたが、Dockerfile上ではビルドファイルのコピー先ディレクトリを指定する必要があり、そのように書き換えます。
では、ビルドしてみましょう。今回はpush先にDocker Hubを利用するため、自身のDocker Hubとイメージ名をタグとして付けておきます。
※検証の際には自身のWindows/Mac/Linux PCでビルド・動作検証を行えますが、おうちKubernetes上に載せる際はARM製CPUにてビルドする必要があります。
Raspberry Pi 4BなどARM製品でビルドしてください。
# <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している前提で始めます。
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を作成します。
apiVersion: v1
kind: Namespace
metadata:
name: myapp-space
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をビルドしましょう。
# ビルド
docker build -t bigface00/flask-vuejs:v1.1 .
# プッシュ
docker push <Your-Docker-Account>/flask-vuejs:v1.1
ビルドしたVue.jsとFlaskのイメージをKubernetes上にデプロイするためのDeploymentを作成していきます。
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-space
にmyapp-deployment
というdeployment作成します。
作成したdeploymentの中にはmyapp
というコンテナがあり、コンテナイメージは今回pushしたDocker Hubのイメージを指定しています。
サービスを公開するポートに対して、containers.ports.containerPort
を設定しています。
また、livenessProve
で設定したポートを指定することでコンテナが生きているかどうかを判断します。生きていない場合は、コンテナが再起動されます。
applyしましょう。
# 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を参考にインストールします。
# 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を使う
確認します。
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としての設定用マニフェストファイルを作成します。
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を作成します。
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しましょう。
# 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のデプロイが完了しました。
残りは良い感じにポートフォリオを作成していきましょう!
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F271414%2Ffe995b04-b770-fa83-4ee9-f1e90571a2c3.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=8fd85372f5aff578a448f6e58d015465)
まとめ
ここまでで自分のポートフォリオをおうちKubernetes上に乗っけることができました。
ポートフォリオ程度ではKubernetesの恩恵にあやかることはできなさそうですが、これにMySQLなどのDBをデプロイしてアプリ化することでKubernetesのパフォーマンスを発揮できるのではないかと思います。
また、ここから更にインターネット上に公開することで、実際に運用するまで持っていけると面白そうです。
記事には載せていませんが、今回の実装ではコンテナイメージを引っ張る際に、ローカル上から引っ張ろうとしてうまくいかず、Google CloudのArtifact Registryから引っ張ろうとしましたが、途中で403エラーになり原因も解明できずで思わぬところで苦戦してしまいました。結局、Docker Hubに落ち着いてからはスムーズにできました。
次のハードウェアアップデートとしてはPoEハットとPoE対応ハブを買って電源コードを廃止し、よりスリム化したおうちKubernetesにしたいところです。
明日は@orangekame3さんの記事になります!