uni farm

kubernetesでmicro-service(React, Flask, PostgreSQL)のローカル開発環境を構築

kubernetes を使ってローカルで web アプリを開発できる環境を整えてみた

React, Flask, PostgreSQL の構成で作成した

構成

マイクロサービスといわれるらしい

利用イメージとしては

react(計算結果の描画) <==> flask(計算、dbの値操作) <==> postgresql(計算データや計算結果を保存)

こんな感じだろうか 各サービスはレプリカ数など独立して変更できるよう 各々 deployment として作成し、service で相互通信を行う

ディレクトリ構成はこんな感じ

tree -L 1
.
├── micro-database
├── kube-deploy.yml
├── kube-start.sh
├── micro-api
└── micro-app

deployment 作成

起動用スクリプト

任意の名前でdocker buildして、kubernetes 起動コマンドを実行する

kube-start.sh
#/bin/sh
docker build -t api -f ./micro-api/Dockerfile ./micro-api
docker build -t app -f ./micro-app/Dockerfile.dev ./micro-app

kubectl apply -f kube-deploy.yml

kubernetes コンフィグ

Namespace と、各 Deployment、Service を作成

kube-deploy.yml
apiVersion: v1
kind: Namespace
metadata:
  name: micro

---
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: micro-app
  namespace: micro
spec:
  replicas: 1
  selector:
    matchLabels:
      name: micro-app
  template:
    metadata:
      labels:
        name: micro-app
    spec:
      containers:
      - name: app
        image: app
        ports:
        - containerPort: 3000
        command: ["npm", "run", "start"]
        imagePullPolicy: IfNotPresent
        env:
        - name: REACT_APP_API_URL
          value: "http://127.0.0.1:5000"
        - name: CHOKIDAR_USEPOLLING
          value: "true"
        volumeMounts:
        - mountPath: /app/src
          name: app-src
      volumes:
        - name: app-src
          hostPath:
            path: "絶対パス"/micro-app/src
            type: DirectoryOrCreate

---
kind: Service
apiVersion: v1
metadata:
  labels:
    name: micro-app
  name: micro-app-svc
  namespace: micro
spec:
  type: LoadBalancer
  ports:
    - protocol: TCP
      port: 3000
      targetPort: 3000
  selector:
    name: micro-app

---
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: micro-api
  namespace: micro
spec:
  replicas: 1
  selector:
    matchLabels:
      name: micro-api
  template:
    metadata:
      labels:
        name: micro-api
    spec:
      containers:
      - name: api
        image: api
        ports:
        - containerPort: 5000
        command: ["sh", "/app/src/server_config/start_uwsgi.sh"]
        imagePullPolicy: IfNotPresent
        env:
        - name: POSTGRES_URL
          value: "micro-db-svc:5432"
        - name: POSTGRES_USER
          value: "postgres"
        - name: POSTGRES_PW
          value: "postgres"
        - name: POSTGRES_DB
          value: "postgres"
        volumeMounts:
        - mountPath: /app/src
          name: api-src
      volumes:
        - name: api-src
          hostPath:
            path: "絶対パス"/micro-api
            type: DirectoryOrCreate

---
kind: Service
apiVersion: v1
metadata:
  labels:
    name: micro-api
  name: micro-api-svc
  namespace: micro
spec:
  type: LoadBalancer
  ports:
    - protocol: TCP
      port: 5000
      targetPort: 5000
  selector:
    name: micro-api

---
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: micro-db
  namespace: micro
spec:
  replicas: 1
  selector:
    matchLabels:
      name: micro-db
  template:
    metadata:
      labels:
        name: micro-db
    spec:
      containers:
      - name: db
        image: postgres:10
        ports:
          - containerPort: 5432
        imagePullPolicy: IfNotPresent
        env:
        - name: POSTGRES_USER
          value: "postgres"
        - name: POSTGRES_PASSWORD
          value: "postgres"
        - name: POSTGRES_DB
          value: "postgres"
        volumeMounts:
          - mountPath: /var/lib/postgresql/data
            name: db-data
          - mountPath: /docker-entrypoint-initdb.d
            name: db-init
      volumes:
        - name: db-data
          hostPath:
            path: "絶対パス"/micro-database/data
            type: DirectoryOrCreate

        - name: db-init
          hostPath:
            path: "絶対パス"/micro-database/init
            type: DirectoryOrCreate

---
kind: Service
apiVersion: v1
metadata:
  labels:
    name: micro-db
  name: micro-db-svc
  namespace: micro
spec:
  type: LoadBalancer
  ports:
    - protocol: TCP
      port: 5432
      targetPort: 5432
  selector:
    name: micro-db
sh ./kube-start.sh
...
Sending build context to Docker daemon  2.189GB
...
service "micro-db-svc" created

pod 間通信

ネットワーク(service)の設定が難しかったので、わかったことを残しておく

  • 同じネームスペースであれば、service の名前(micro-db-svc)で名前解決ができる

  • 異なるネームスペースであれば、service の名前+namespace(micro)の名前で名前解決できる

また、ホスト PC から localhost で繋げるようにするには type をLoadBalancerで service を作成する必要がある

接続確認

app->apiの接続確認を行う (db の例は長くなるので割愛します。api から db につなげるのは確認してます)

api

flask api には uwsgi を入れて起動する(UWSGI

./server_config/uwsgi.ini
[uwsgi]
master = true
base = /app/src
chdir = /app/src
# filename
module = server
callable = app
http-socket = :5000
processes = 1
threads = 2
uid = uwsgiusr
gid = uwsgiusr
logto = ./log/uwsgi.log
pidfile = ./log/uwsgi.pid

pcre = true
vacuum = true
die-on-term = true

# auto reload
py-autoreload = 1

wsgi-disable-file-wrapper = true
./server_config/start_uwsgi.sh
uwsgi --ini /app/src/server_config/uwsgi.ini &
tail -f /app/src/log/uwsgi.log
Dockerfile
FROM python:3.6

RUN mkdir -p /app/src
WORKDIR /app/src

COPY requirements.txt /app/src
RUN pip3 install -r requirements.txt

## set uwsgi
RUN apt-get install -y libpcre3 libpcre3-dev
RUN useradd -r -s /bin/fslse uwsgiusr
USER uwsgiusr

COPY --chown=uwsgiusr:uwsgiusr . /app/src

EXPOSE 5000
CMD sh ./server_config/start_uwsgi.sh
requirements.txt
flask
flask-cors
Flask-SQLAlchemy
uwsgi
server.py
from __future__ import print_function
from __future__ import unicode_literals

from flask import Flask, jsonify
from flask_cors import CORS
app = Flask(__name__)

# cors対策
cors = CORS(app)

def main():
    data = [
      {"name":"山田",
      "age":30}
    ]
    return jsonify({
             'status': 200,
             'data': data
           })


@app.route('/test', methods=['GET'])
def index():
    return main()

if __name__ == '__main__':
    print('url map', app.url_map)
    app.run(host='0.0.0.0', port=5000, debug=True)

app

react の構築方法は割愛する

app 側では、api へリクエストを投げて返ってきた値を render する リクエストには axios を用いる

Dockerfile.dev
FROM node:8
ENV NODE_ENV=development

RUN mkdir /app
WORKDIR /app
ADD . /app

RUN npm install

EXPOSE 3000
CMD ["npm", "run", "start"]
App.js
import React, { Component } from 'react'
import axiosBase from 'axios'

export default class Home extends Component {

  state = {
    result: null
  }

  componentDidMount() {
    this.getFromApi()
  }

  getFromApi() {
    const getTest = (text) => {
      // ブラウザからアクセスするので
      // 127.0.0.1:5000
      let url = process.env.REACT_APP_API_URL

      let ax = axiosBase.create({
        baseURL: url,
        responseType: 'json'
      })

      ax.get('/test').then((res) => {
        this.setState({
          result: res.data.data
        })
      }).catch((err) => {
        return err
      })
    }
    getTest()
  }

  renderResult() {
    let result = this.state.result
    if (result === null) {
      return
    }

    return (
      <div>
        <p>name: {result[0].name}</p>
        <p>age: {result[0].age}</p>
      </div>
    )
  }

  render() {
    return (
      <div>
        {this.renderResult()}
      </div>
    )

  }
}

api から取得した値が表示される

response from api in browser

削除

kubectl delete -f kube-deploy.yml

感想

kubernetes でローカル開発環境を整えるには、docker build を行う手間が加わるためそこが面倒に感じた ローカルで使うにはdocker-composeの方が手軽だと思われる

kubernetes 発環境〜本番環境まで一貫してできるといいかなと思ったが、本番環境向けにするための設定をもう少し考えないといけない。もうちょっと楽な方法があれば調べていきたい

困ったところ

hostpath は絶対パスでないといけない

kubectl describe pods 'POD_NAME'

...
"~/" includes invalid characters for a local volume name, only "[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed. If you intended to pass a host directory, use absolute path

相対パスや、変数を設定してコンフィグファイル内で使いたいが、方法がわからず。

参考

pod 間の名前解決について詳しい

2022, Built with Gatsby. This site uses Google Analytics.