React アプリを Kubernetes で動かしてみた
はじめに
勉強がてら React アプリを Kubernetes 上にデプロイしてみたので備忘録としてまとめておきます。
この記事のゴール
ローカルの Kubernetes 上に React アプリを実行して動作を確認できるところまでを目指します。
前提
環境については macOS を使用していて、Home Brew を導入済みという前提で進めます。使用するツールについては後述します。
% sw_vers
ProductName: macOS
ProductVersion: 12.0.1
BuildVersion: 21A559
使用するツールとそのセットアップ
- Docker
- Minikube
- ローカルで簡単に Kubernetes を実行できるツールです。試しに動かしてみたい場合に使えるので重宝しています。詳しくは公式ドキュメントをご覧ください。
- kubectl
- Kubernetes のコマンドラインツールです。本記事は macOS ユーザ向けなので、そうではない方はこちらの公式ドキュメントの kubectlのインストールおよびセットアップ をご覧ください。
それぞれ Home Brew 経由でインストールしていきます。
# Minikube
% brew install minikube
% minikube version
minikube version: v1.23.0
commit: 5931455374810b1bbeb222a9713ae2c756daee10
# kubectl
% brew install kubectl
% kubectl version
Client Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.1", GitCommit:"632ed300f2c34f6d6d15ca4cef3d3c7073412212", GitTreeState:"clean", BuildDate:"2021-08-19T15:38:26Z", GoVersion:"go1.16.6", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.1", GitCommit:"632ed300f2c34f6d6d15ca4cef3d3c7073412212", GitTreeState:"clean", BuildDate:"2021-08-19T15:39:34Z", GoVersion:"go1.16.7", Compiler:"gc", Platform:"linux/amd64"}
サクッと試してみたい方
これから手順に沿って解説していくのですが、そんなものはいらないからとりあえず動かしてみたいという方は下記にソースコードを公開しているのでそちらをご覧ください。
React プロジェクトの作成
まずは、シンプルな React アプリケーションを作成します。
# 適当なディレクトリに移動
% cd path/you/like
# プロジェクトを作成(テンプレートとして TS を指定)
% npx create-react-app --template tyepscript react-k8s-sample
これで React プロジェクトの雛形が作成されました。試しに立ち上げてみましょう。
% cd react-k8s-sample
# 依存パッケージをインストール
% yarn
% yarn start
start スクリプトでブラウザが立ち上がり localhost:3000 を開き下記のように表示されたら OK です。
確認できたら ^C で一旦停止しておきましょう。
コンテナ化
続いて、プロジェクト全体をコンテナ化していきます。
Dockerfile の作成
まずは、コンテナ化していくにあたりコンテナのベースとなるイメージを作成していきます。プロジェクト直下に Dockerfile というファイルを作成し、下記のように記述します。
FROM node:16.13.0-alpine AS builder
WORKDIR /usr/local/app
COPY . .
RUN yarn --frozen-lockfile && \
yarn build
FROM nginx:1.20-alpine
COPY /usr/local/app/build /usr/share/nginx/html
CMD [ "nginx", "-g", "daemon off;" ]
そこまで凝ったことはしていませんが、簡単に解説します。
1 つ目の FROM 命令でビルド用の node をベースイメージとして指定しています。そして React をビルドし、2 つ目の FROM 命令以下でビルドによって生成されたファイル ./build
以下を nginx イメージにコピーしています。
このようにマルチステージビルドを使用することで、Dockerfile の保守性を向上させ、最終的な成果物としてのイメージサイズを抑えることができます。可搬性が高いというコンテナの性質を活かし、デプロイを速くするためにもイメージサイズには常に気にすることですよね。
イメージのビルド、コンテナの起動
記述した Dockerfile を元にコンテナで正常にプロジェクトが起動できるか簡単にチェックしておきましょう。
# イメージをビルド
% docker build --tag <YOUR_NAME>/react-k8s-sample:latest \
--no-cache .
# 生成されたイメージを確認
% docker images --filter "dangling=false" \
--format "table {{ .Repository }}:{{ .Tag }} {{ .Size }}" | \
head -n 2
REPOSITORY:TAG SIZE
<YOUR_NAME>/react-k8s-sample:latest 23.7MB
# イメージからコンテナを起動
% docker run --name tmp-react-k8s-sample \
--rm --publish 3000:80 \
<YOUR_NAME>/react-k8s-sample:latest
ターミナルでログが出力されて、コンテナが起動できたら再度 localhost:3000 を開いてみましょう。先ほどと同様に React のデフォルト画面が表示されることを確認できたら OK です。また ^C でターミナルのプロセスを終了させておきます。
イメージのプッシュ
作成した Docker イメージをレジストリにプッシュします。今回は、イメージレジストリには Docker Hub を使用します。
% docker push <YOUR_NAME>/react-k8s-sample:latest
マニフェストファイルの作成
これからようやく Kubernetes です。
Kubernetes ではリソースやそれらの設定をマニフェストファイルと呼ばれる YAML に定義します。
今回は Deployment と Service を作成していきます。
apiVersion: apps/v1
kind: Deployment
metadata:
name: react-k8s-sample
spec:
replicas: 1
selector:
matchLabels:
app: react-k8s-sample
template:
metadata:
labels:
app: react-k8s-sample
spec:
containers:
- name: react
image: <YOUR_NAME>/react-k8s-sample # Edit
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: react-k8s-sample
spec:
type: NodePort
selector:
app: react-k8s-sample
ports:
- port: 3000
targetPort: 80
protocol: TCP
name: react-k8s-sample
Deployment では、先ほど作ったコンテナを起動する Pod と、レプリカセットを(ここでは単に 1 つ)定義しています。spec.template.spec.containers[0].image
の部分だけご自身の Docker Hub のユーザ名に書き換えてください。
Service では、Deployment で作成した Pod に対してクラスタ外部から通信するための設定を書いていきます。Service の種類の中でも静的なポートで公開する NodePort を使用します。
また、余談にはなりますが、マニフェストファイルは ---
で分けることによって複数のリソースを定義できます。別ファイルとしての管理も可能ですが、今回はこの量なので 1 ファイルにまとめました。
Kubernetes クラスタ上で動かす
Minikube を起動
# Minikube を起動
% minikube start
..
🏄 完了しました! kubectl が「"minikube"」クラスタと「"default"」ネームスペースを使用するよう構成されました
# 確認
% minikube status
minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured
上記のように Running になっていたら OK です。
デプロイ
次に、Minikube 上の Kubernetes クラスタにマニフェストファイルに書いたリソースをデプロイしていきます。
# デプロイ
% kubectl apply --filename ./deployment-service.yaml
deployment.apps/react-k8s-sample created
service/react-k8s-sample created
# 実行結果を確認
% kubectl get all | grep -v kubernetes
NAME READY STATUS RESTARTS AGE
pod/react-k8s-sample-6c5dc7dd67-wjvcj 1/1 Running 0 10m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/react-k8s-sample NodePort 10.111.180.101 <none> 3000:30669/TCP 40m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/react-k8s-sample 1/1 1 1 40m
NAME DESIRED CURRENT READY AGE
replicaset.apps/react-k8s-sample-6544bfd9d6 1 1 1 4m19s
Pod や Deployment などの READY が 0/1 と表示されている場合は、数秒待って再度実行すると上記のように 1/1 となり、ステータスも Running となります。
Service の公開
% kubectl get service | grep -v kubernetes
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
react-k8s-sample NodePort 10.111.180.101 <none> 3000:30669/TCP 4m
Service の情報を見てみると、EXTERNAL-IP が none になっています。このままだとクラスタ外からはアクセスできないので、NodePort 経由で Service を公開します。
% minikube service react-k8s-sample
上記のコマンドを実行すると、自動的にブラウザが開かれて下記のように表示が確認できます。
これで Minikube を使ってローカルの Kubernetes 上に React アプリケーションをデプロイできました。
クリーンナップ
作業が終わったらリソースを停止、削除します。
# Deployment, Service を削除
% kubectl delete --filename ./deployment-service.yaml
deployment.apps "react-k8s-sample" deleted
service "react-k8s-sample" deleted
# クラスターの停止
% minikube stop
✋ ノード "minikube" を停止しています...
🛑 SSH 経由で「minikube」の電源をオフにしています...
🛑 1台のノードが停止しました
# クラスターの削除
% minikube delete
🔥 docker の「minikube」を削除しています...
🔥 コンテナ "minikube" を削除しています...
🔥 /Users/<YOUR_NAME>/.minikube/machines/minikube を削除しています...
💀 クラスタ "minikube" の全てのトレースを削除しました。
まとめ
今回は Minikube を使ってローカルの Kubernetes 上に React プロジェクトを構築してみました。かなり長々となってしまいましたが、kubectl コマンドの使い方やマニフェストファイルの YAML の書き方など非常に勉強になることが多ったです。
Kubernetes は、ローカルでの学習が大変ですが、このように少しずつユースケースベースで触って試すのがなんだかんだで近道な気がしています。
ソースコードも公開しているので、読んでくださっている方の参考になれたら幸いです。
参考にさせていただいたサイト
Discussion