uni-3 log

    Search by

    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 間の名前解決について詳しい

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