konchangakita

KPSを一番楽しんでいたブログ 会社の看板を背負いません 転載はご自由にどうぞ

【Nutanix ログほいほい】バックエンド(Elasticsearchも)のベースコンテナ作る

フロントエンドはJava ScriptフレームワークNext.jsをベースに作りました
バックエンド側は FlaskAPIゲートウェイ的にたてて、Pythonでデータのやりとりの中心として作っていきます

開発環境全体像



Githubリポジトリ

GitHub - konchangakita/blog-loghoi: Nutanix log hoihoi

今日のブログ分は最終形態は blog/0821配下で
Docker Compose で起動してやると挙動を確認できます

# docker-compose -f "docker-compose.yml" up -d --build


バックエンドコンテナをつくる

Dockerhub の Python Official image をベースに必要なモジュール追加していきます

blackpython用コードフォーマッター
elastic:Elasticsearch用
flask:FlaskでAPIゲートウェイとする
jupyter:ちょっとしたPythonコード確認用
paramiko:CVM へ ssh する用
requests:Prism REST API する用

こんな感じの requirements.txt を準備

black==23.7.0
elastic-transport==8.4.0
elasticsearch==8.9.0
Flask==2.3.2
Flask-Cors==4.0.0
Flask-SocketIO==5.3.5
jupyter-events==0.7.0
jupyter-lsp==2.2.0
jupyter_client==8.3.0
jupyter_core==5.3.1
jupyter_server==2.7.1
jupyter_server_terminals==0.4.4
jupyterlab==4.0.5
jupyterlab-pygments==0.2.2
jupyterlab_server==2.24.0
paramiko==3.3.1
requests==2.31.0

開発中のコンテナはこんな感じで

FROM python:3.11.4-slim

RUN apt-get update -y
RUN apt-get install -y curl
RUN apt-get install -y ssh-client
RUN pip install -U pip

WORKDIR /tmp
COPY ./requirements.txt .
RUN pip install -r requirements.txt

#CMD ["tail", "-f", "/dev/null"]

#WORKDIR /usr/src/flaskr
#CMD ["python", "app.py"]


今回もリアルタイムでコードを書きながら開発していきたいので、docker-compose.ymlファイルで、ローカルファイルシステムをマウントしておきます

version: "3.8"
services:
  backend:
    build:
      context: ./backend
      dockerfile: dockerfile
    container_name: loghoi-backend
    ports:
      - 7776:7776
    volumes:
      - ./backend/flaskr:/usr/src/flaskr:z
    command:
      bash -c "cd /usr/src/flaskr
      && tail -f /dev/null"
    working_dir: /usr/src/flaskr
    networks:
      - local

  frontend:
    build:
      context: ./frontend
      dockerfile: dockerfile
    container_name: loghoi-frontend
    ports:
      - 7777:7777
    volumes:
      - ./frontend/next-app:/usr/src/next-app:z
    command:
      bash -c "cd /usr/src/next-app/loghoi
      && yarn
      && tail -f /dev/null"
    working_dir: /usr/src/next-app/loghoi
    networks:
      - local

networks:
  local:
    driver: bridge


Flaskで初期設定

最小構成はこれだけでよいはず

./flaskr
    ├── app.py
    ├── static
    │   └── style.css
    └── templates
        └── index.html


まずは動作確認の為、シンプルに Webサーバたててみます
適当に style.cssを作って、 index.html を呼び出すだけの簡単な app.py を作ります

app.py

from flask import Flask
from flask import render_template

app = Flask(__name__)

# Just for GUI
@app.route("/")
def index():
    return render_template("index.html")

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=7776, debug=True)


Flask の起動

# python app.py
 * Serving Flask app 'app'
 * 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:7776
 Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 963-107-412



ブラウザにてhttp://開発サーバIP:7776/へアクセス


Flask で REST APIする

フロントエンドからのHTTPリクエストに応じてデータを返す REST API サーバを作ります
URI と メソッド(GET, POST など)の組み合わせで、Python内の関数を呼び出します

GET用, POST用の関数を作る

GET /get/testPOST /post/testそれぞれでテキストを返す簡単な関数です

from flask import Flask
from flask import render_template
from flask import request
from flask import make_response, jsonify

app = Flask(__name__)

# Just for GUI
@app.route("/")
def index():
    return render_template("index.html")

# Rest API Test GET
@app.route("/get/test", methods=["GET"])
def get_test():
    data = 'GET saremashita'
    print(">>>>> response: ", data)
    return make_response(jsonify(data))

# Rest API Test POST
@app.route("/post/test", methods=["POST"])
def post_test():
    data = request.json
    print(">>>>> POST sareta: ", data)
    return make_response(jsonify(data))


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=7776, debug=True)


REST APIのテストには、Postmanを使います
ブラウザ版を使うには、「Postman Agent」をインストールしておきましょう
Postman Agent: For Mac, Windows, & Linux

GET する

Postman上で GETリクエストを「開発用VMのIP:7776/get/test」

「GET Saremashita」と返ってきます

POST する

Header に 「Content-Type」「application/json」を追加

Bodyは Rawを選択し、json形式でメッセージを入力します

POSTで送ったメッセージがそのまま表示されればOK


Elasticsearchとの連携

docker-compose.ymlに elasticsearch と kibana コンテナを追記します
(ゼロから作る場合は、「./elastic/es-data/node/.gitkeep」 を作っておきましょう)

version: "3.8"
services:
  elasticsearch:
    image: elasticsearch:7.17.9
    container_name: elasticsearch
    environment:
      - discovery.type=single-node
    ports:
      - 9200:9200
      - 9300:9300
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - ./elastic/es-data:/usr/share/elasticsearch/data:z
    networks:
      - local

  kibana:
    image: kibana:7.17.9
    container_name: kibana
    ports:
      - 5601:5601
    ulimits:
      memlock:
        soft: -1
        hard: -1
    networks:
      - local

  backend:
    build:
      context: ./backend
      dockerfile: dockerfile
    container_name: loghoi-backend
    ports:
      - 7776:7776
    volumes:
      - ./backend/flaskr:/usr/src/flaskr:z
    command:
      bash -c "cd /usr/src/flaskr
      && tail -f /dev/null"
    working_dir: /usr/src/flaskr
    networks:
      - local

  frontend:
    build:
      context: ./frontend
      dockerfile: dockerfile
    container_name: loghoi-frontend
    ports:
      - 7777:7777
    volumes:
      - ./frontend/next-app:/usr/src/next-app:z
    command:
      bash -c "cd /usr/src/next-app/loghoi
      && yarn
      && tail -f /dev/null"
    working_dir: /usr/src/next-app/loghoi
    networks:
      - local

networks:
  local:
    driver: bridge

REST API して Elasticsearch とやりとり

GET で Elasticsearch の インデックス一覧を返し
POST で インデックス名を送ると存在しなければ、インデックスを作る
というのを作ってみます

app.py

from flask import Flask
from flask import render_template
from flask import request
from flask import make_response, jsonify

from elasticsearch import Elasticsearch

ELASTIC_SERVER = "http://elasticsearch:9200"
es = Elasticsearch(ELASTIC_SERVER)

app = Flask(__name__)

# Just for GUI
@app.route("/")
def index():
    return render_template("index.html")

# Rest API Test
@app.route("/get/test", methods=["GET"])
def get_test():
    data = es.cat.indices(index="*", h="index").splitlines()

    print(">>>>> response: ", data)
    return make_response(jsonify(data))

# Rest API Test
@app.route("/post/test", methods=["POST"])
def post_test():
    data = request.json
    index_name = data['index']
    indices = es.cat.indices(index="*", h="index").splitlines()
    if index_name not in indices:
        es.indices.create(index=index_name)
        res = index_name + " created"
        return make_response(res)

    print(">>>>> POST sareta: ", data)
    res = index_name + " is already exists"  
    return make_response(res)


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=7776, debug=True)

これを実行してみます

python app.py


GET でインデックス一覧取得

POST でインデックス作成

new_index というインデックス名を POSTしてみます

インデックス名を GET すると「new_index」というのが作られています


まとめ

ここまでで開発に使うコンテナたちのベースが作られました
フロントエンド:Next.jsをベースに JavaScriptGUIを作る
バックエンド:Flaskをベースに、フロントエンドからREST APIを受けて、Nutanixクラスタ、Elasticsearchからデータ入出力を行う
Elasticsearch:外部データベース、サーチエンジンとして利用

次回からいよいよ各ページを作っていきます
まずは入口のページからです