こんにちは。KOUKIです。k8s学習中のWebエンジニアです。
前回は、Networkingを学ぶため、3つのAPIのDeploymentやServiceの作成を行い、Pod間通信を確認しました。
今回は、JavaScriiptのライブラリである「React」を導入して、UI画面を作成し、Nginxを介してAPIにアクセスしたいと思います。
<目次>
React環境の構築
Reactの環境をまず整えてください。必要なことは、次の記事に記載しています。
MacでHomebrewをインストールしている場合は、以下のコマンドで一発です。
1 |
brew install nodejs |
ReactAppの生成
Reactの開発環境が整ったら、terminal上で「create-react-app」を実行してください。
1 2 3 4 5 6 |
// rootで実行 npx create-react-app frontend cd frontend # React起動 yarn start |

ファイル準備
アプリ作成に必要なファイルを準備します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# frontendフォルダ直下 mkdir conf touch conf/nginx.conf touch Dockerfile mkdir src/components touch src/components/NewTask.js touch src/components/NewTask.css touch src/components/TaskList.css touch src/components/TaskList.js rm src/App.test.js rm src/logo.svg $ tree -L 3 -I node_modules frontend ├── Dockerfile ├── README.md ├── conf │ └── nginx.conf ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.css │ ├── App.js │ ├── components │ │ ├── NewTask.css │ │ ├── NewTask.js │ │ ├── TaskList.css │ │ └── TaskList.js │ ├── index.css │ ├── index.js │ ├── reportWebVitals.js │ └── setupTests.js └── yarn.lock |
package.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
{ "name": "frontend", "version": "0.1.0", "private": true, "dependencies": { "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", "react": "^16.13.1", "react-dom": "^16.13.1", "react-scripts": "3.4.3" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": "react-app" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } } |
nginx.conf
これは、nginxの設定ファイルです。
1 2 3 4 5 6 7 8 9 10 11 |
server { listen 80; location / { root /usr/share/nginx/html; index index.html index.htm; try_files $uri $uri/ /index.html =404; } include /etc/nginx/extra-conf.d/*.conf; } |
Dockerfile
Dockerファイルも作成しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
FROM node:14-alpine as builder WORKDIR /app COPY package.json . RUN npm install COPY . . RUN npm run build FROM nginx:1.19-alpine COPY --from=builder /app/build /usr/share/nginx/html COPY conf/nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD [ "nginx", "-g", "daemon off;" ] |
「npm run build」を実行するとfrontendフォルダ配下に「build」フォルダが生成されます。そこにはReactアプリをビルドしたファイルが保存されているので、nginxの公開ディレクトリ(/usr/share/nginx/html)に渡すことで、Nginxからページを表示できるようになります。
これは、勉強になりますね。
App.css
1 2 3 4 5 6 7 8 |
section { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26); padding: 1rem; margin: 3rem auto; max-width: 40rem; background-color: white; border-radius: 15px; } |
App.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
import React, { useState, useEffect, useCallback } from 'react'; import './App.css'; import TaskList from './components/TaskList'; import NewTask from './components/NewTask'; function App() { const [tasks, setTasks] = useState([]); const fetchTasks = useCallback(function () { // minikube service tasks-serviceで生成したアドレスに変える fetch('http://192.168.99.100:32287/tasks', { headers: { 'Authorization': 'Bearer abc' } }) .then(function (response) { return response.json(); }) .then(function (jsonData) { setTasks(jsonData.tasks); }); }, []); useEffect( function () { fetchTasks(); }, [fetchTasks] ); function addTaskHandler(task) { // minikube service tasks-serviceで生成したアドレスに変える fetch('http://192.168.99.100:32287/tasks', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer abc', }, body: JSON.stringify(task), }) .then(function (response) { console.log(response); return response.json(); }) .then(function (resData) { console.log(resData); }); } return ( <div className='App'> <section> <NewTask onAddTask={addTaskHandler} /> </section> <section> <button onClick={fetchTasks}>Fetch Tasks</button> <TaskList tasks={tasks} /> </section> </div> ); } export default App; |
ここには、一つ注意点があります。fetchのURLには、「minikube service tasks-service」を実行して得たアドレスを入力してください。
1 2 3 4 5 |
$ minikube service tasks-service |-----------|---------------|-------------|-----------------------------| | NAMESPACE | NAME | TARGET PORT | URL | |-----------|---------------|-------------|-----------------------------| | default | tasks-service | 8000 | http://192.168.99.100:32287 | |
index.js
1 2 3 4 5 6 7 |
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; ReactDOM.render(<App />, document.getElementById('root')); |
index.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
* { box-sizing: border-box; } html { font-family: sans-serif; } body { margin: 0; background-color: #3b3b3b; } button { background-color: #3d003d; border: 1px solid #3d003d; color: white; padding: 0.5rem 1.5rem; font: inherit; cursor: pointer; } button:hover, button:active { background-color: #861586; border-color: #861586; } |
NewTask.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
import React, { useState } from 'react'; import './NewTask.css'; function NewTask(props) { const [enteredTitle, setEnteredTitle] = useState(''); const [enteredText, setEnteredText] = useState(''); function submitForm(event) { event.preventDefault(); if ( enteredTitle && enteredTitle.trim().length > 0 && enteredText && enteredText.trim().length > 0 ) { props.onAddTask({ title: enteredTitle, text: enteredText }); setEnteredTitle(''); setEnteredText(''); } } return ( <form onSubmit={submitForm}> <div className='form-control'> <label htmlFor='title'>Title</label> <input type='text' id='title' onChange={(event) => setEnteredTitle(event.target.value)} value={enteredTitle} ></input> </div> <div className='form-control'> <label htmlFor='text'>Text</label> <input type='text' id='text' onChange={(event) => setEnteredText(event.target.value)} value={enteredText} ></input> </div> <button type='submit'>Add Task</button> </form> ); } export default NewTask; |
NetTask.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
.form-control { margin: 1rem 0; } .form-control label { font-weight: bold; display: block; } .form-control input { display: block; width: 100%; border: 1px solid #ccc; font: inherit; padding: 0.15rem; } .form-control input:focus { outline: none; border-color: #3d003d; } |
TaskList.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import React from 'react'; import './TaskList.css'; function TaskList(props) { return ( <ul> {props.tasks.map((task) => ( <li key={task.title}> <h2>{task.title}</h2> <p>{task.text}</p> </li> ))} </ul> ); } export default TaskList; |
TaskList.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
ul { list-style: none; margin: 0; padding: 0; } li { margin: 1rem 0; color: white; padding: 1rem; border: 1px solid #3d003d; } li h2 { margin: 0; margin-bottom: 0.5rem; color: #3d003d; } li p { margin: 0; color: #313131; } |
アプリ表示
ここまで実装すると以下のようになります。

ようやく下準備が完了しました。
Pod化
先ほど作成したfrontendアプリをPod化していきます。
Docker Hubのレポジトリ作成
以下の名前でレポジトリを作成してください。
- <Docker Hubのアカウント名>/kub-demo-frontend
Docker HubへPush
minikube上でk8sを動かしているので、ローカルからDocker ImageをPullすることができません。そのため、Docker HubにDocker ImageをPushする必要があります。
1 2 3 4 5 6 7 8 9 |
# インストールに失敗しそうなモジュールを削除しておく rm -rf node_modules rm yarn.lock # ビルド docker build -t <Docker Hubのアカウント名>/kub-demo-frontend . # Docker HubへPush docker push <Docker Hubのアカウント名>/kub-demo-frontend |
Deploymentの作成
kubernetesフォルダ配下に「frontend-deplyment.yml」を作成しましょう。
1 2 3 4 |
# ディレクトリを一階層上へ cd .. # ファイル作成 touch kubernetes/frontend-deployment.yml |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# frontend-deployment.yml apiVersion: apps/v1 kind: Deployment metadata: name: frontend-deployment spec: replicas: 1 selector: matchLabels: app: frontend template: metadata: labels: app: frontend spec: containers: - name: frontend image: <Docker Hubのアカウント名>/kub-demo-frontend:latest |
Serviceの作成
続いて、Serviceを作成しましょう。
1 |
touch kubernetes/frontend-service.yml |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# frontend-service.yml apiVersion: v1 kind: Service metadata: name: frontend-service spec: selector: app: frontend # deploymentのfrontendを指定 type: LoadBalancer ports: - protocol: TCP port: 80 targetPort: 80 |
DeploymentとServiceの作成
次のコマンドで、DeploymentとServiceを作成します。
1 2 3 |
$ kubectl apply -f=kubernetes/frontend-service.yml -f=kubernetes/frontend-deployment.yml service/frontend-service created deployment.apps/frontend-deployment created |
動作確認
次のコマンドで、frontendアプリを起動します。
1 2 3 4 5 6 7 |
$ minikube service frontend-service |-----------|------------------|-------------|-----------------------------| | NAMESPACE | NAME | TARGET PORT | URL | |-----------|------------------|-------------|-----------------------------| | default | frontend-service | 80 | http://192.168.99.100:32387 | |-----------|------------------|-------------|-----------------------------| 🎉 Opening service default/frontend-service in default browser... |
データを送信してみましょう。
OKですね。
Nginxのリバースプロキシ
Nginxのリバースプロキシ設定も使ってみましょう。nginx.confに/apiロケーションを追加します。
1 2 3 4 5 6 7 8 9 10 11 |
# nginx.conf server { listen 80; # /apiにリクエストを送ればtaskサービスへ転送する location /api/ { proxy_pass http://tasks-service.default:8000/; } ... } |
上記は、/apiへアクセスしたら「tasksのドメイン」にリクエストされる設定です。
前回やりましたが、k8sは自動的にドメインを作成してくれます。「サービス名.ネームスペース」で取得できます。
また、App.jsのfetchに指定していたURLを以下のように変更します。
1 2 3 4 5 6 7 8 9 10 11 |
const fetchTasks = useCallback(function () { // nginxのproxy_passを指定 fetch('/api/tasks', { ... }) function addTaskHandler(task) { // nginxのproxy_passを指定 fetch('/api/tasks', { ... }} |
ビルドをしなおします。
1 2 3 4 5 6 |
cd frontend docker build -t <Docker Hubのアカウント名>/kub-demo-frontend . docker push <Docker Hubのアカウント名>/kub-demo-frontend cd .. kubectl delete -f=kubernetes/frontend-deployment.yml kubectl apply -f=kubernetes/frontend-deployment.yml |
これで通信ができるはずです。

おわりに
Networking編はこれで完了です。お疲れ様でした^^
コメントを残す
コメントを投稿するにはログインしてください。