※現在進行中※【移行・設計】almalinuxへのkubernetes導入Tips（k3d編③） アプリ実装その１

### 目的
* 「QOL向上」を目的として旧LAMPサーバを構築してきました。
* 旧LAMPサーバでは、収支管理、健康管理目的で使用しておりますが、新機能を今後追加していくため、機能が膨大になり管理できなくなる懸念があるため、今までの機能も見直しを進めていきます。
    * 見直し対象：https://qiita.com/t_kyn/items/9a4cbd0ea54f68ca37a9
* また、旧LAMPサーバは、定期的なセキュリティバージョンアップとモジュール管理ができてないため、下記も検討しています。
    * コンテナイメージを作成・定期的なバージョンアップをする。
    * k3dベースへのアーキテクチャシフトする
    * モジュール管理として、Github（プライベートリポジトリ）で構成管理する
* 旧LAMPサーバでは、BIツールでJavaVMのメモリを多く使用し、OSがフリーズする事象が発生していました。
* 対策として、BIツールをCPU負荷をメインとしたpythonベースの自作アプリケーションに変更します。（JavaよりもPythonのほうがデータ分析やAI、機械学習と幅広く使われている点と、googlecolaboratoryでの実行も可能なため、学習範囲が広げやすい点より、新機能をより円滑にエンハンスするという狙いもあります。）
* PCのディスク使用量削減のため、[新LAMPサーバ](https://qiita.com/t_kyn/items/18d299b17c2b3d6283ca)と[旧Kubernetesサーバ](https://qiita.com/t_kyn/items/715982eb347c715078b9)は廃止し、[k3dサーバを搭載したサーバ](https://qiita.com/t_kyn/items/07ca9a456915a914f4d4)に統合します。(下図イメージ）
```mermaid
graph TD;
subgraph 'PC After'
direction TB
新Kubernetesサーバ\nVM
end
subgraph 'PC Before'
direction TB
新LAMPサーバ\nVM
旧Kubernetesサーバ\nVM
end
```
* アーキテクチャ変更により、詳細な設計・実装を行います

### 前提
* 本来、要件整理後、アーキテクチャ構築を進めるべきですが、動作検証を優先するため、アーキテクチャ構築を先にしています。
* k3dクラスタベースの下記アーキテクチャを構築しております。
    * ロードバランサ（k3dベース）
    * Nginx（Deployment）
    * Python（Deployment）
    * MySQL（Deployment）
* 上記アーキテクチャを構築した前提で、アプリケーションの実装まで検討します。

### システム化の方針
* 要件定義以前に、そもそも何を実現していくか
* まず、今までの構築したLAMPサーバとして、何が必要か不要かを判断し、システム化の方針を立てます

#### 誰に対しての開発か
* 自分のみならず、妻、家族

#### 目的の再確認
* 目的は「QOL向上」ですが、もう少し分類します。
    * スキルや趣味の開発（個人スキル向上）
    * 健康への配慮（睡眠時間、メンタルケア）
    * 家族のコミュニケーション向上（アルバム作成）
    * 時間の管理（優先順位付けの向上）

### システム化の方針の整理
* 具体的にすると以下の4つで、個人開発としてどういう技術でアプローチしていくかを考えます
* 整理した結果、下記3つは必要なので、
    * スキルや趣味の開発（個人スキル向上）
    * 健康への配慮（睡眠時間、メンタルケア）
    * 家族のコミュニケーション向上（アルバム作成）

#### スキルや趣味の開発（重要度：★★★）
* 目標：個人のスキルアップ
* 現状：個人開発での、技術要件定義スキル強化が乏しい
* 課題：マネジメント業務が重きとなっており、業務でのモダンな技術経験が希薄
* 解決策：個人でのアプリ開発向上、データ分析、運用スキルの向上
* 期待値①：アプリ開発・運用管理での上記機能が使いこなせること。
* 期待値②：新規機能追加（写真管理のアルバム管理＋旅行・レシピなどの提案企画支援）
* 使用技術：既に利用している下記を利用
    * Nginx（html、css、javascript）
    * python（Flask、FastAPI、SQLAlchemy、Pandas）
    * MySQL
    * k3d
    * Github（構成管理、スケジュール管理）

#### 健康への配慮（重要度：★★☆）
* 目標：メンタルヘルスのケア、リラックスする時間を確保
* 現状：勤怠管理から勤務時間を割り出したり、iPhoneの行動記録やジムでのトレーニング実績の分析は可能
* 課題：個人および妻のメンタルケアが必要不可欠。
* 解決策：メンタルヘルスの情報サイト一覧作成、睡眠・トレーニング記録管理
* 期待値①：ジム通いを週2～3は維持できること。
* 期待値②：個人開発の時間を減らし、睡眠・趣味の時間に充てられること。
* 使用技術：既に利用している下記を利用
    * Nginx（html、css、javascript）
    * python（Flask、FastAPI、SQLAlchemy、Pandas、Plotly）
    * MySQL
    * k3d
    * Github（スケジュール管理）

#### 家族のコミュニケーション向上（重要度：★★★）
* 目標：家族との団欒を増やす
* 現状：家族とのコミュニケーションを維持できてない
* 課題：現状の通り
* 解決策：維持できるためのツール導入（アルバムとチャット機能作成）
* 期待値：家族とのコミュニケーションを支援するコンテンツ作成
* 役割：アルバム、データ分析などを通じたつながりの維持支援
* 使用技術：既に利用している下記を利用
    * Nginx（html、css、javascript）
    * python（Flask、FastAPI、SQLAlchemy、Pandas）
    * MySQL
    * k3d
    * Github（スケジュール管理）

#### 時間の管理（重要度：対象外）
時間管理に関しては、Github（ProjectによるBacklog利用）で事足りるため、
対象から外すことにしました。
* 目標：時間を有効活用する
* 現状：メモとかで管理している
* 課題：プライベートでの時間管理ができてない
* 解決策：行動履歴と優先順位を立てるとともに、タスクに対して時間記録と分析をする
* 期待値①：タスク優先順位設定の判断が早まること
* 期待値②：タスクの時間記録

---

### システム化の方針から機能要件・非機能要件整理 :construction: 
#### 機能要件
* 下記を1つのサーバで行います
    * スキルや趣味の開発（個人スキル向上）
    * 健康への配慮（睡眠時間、メンタルケア）
    * 家族のコミュニケーション向上（アルバム作成）
* システム化の方針から機能要件を大枠レベルで整理します。
    * 画面設計（画面一覧、画面遷移、画面レイアウト）
        * ログイン機能 :construction: 
        * 画面遷移（ログイン→Top画面→各メニューの画面一覧）
        * 収支計算、健康管理の画面
        * アルバムコンテンツ作成 :construction: 
        * URL一覧 :construction: 
    * API設計（URL一覧、処理内容）
        * 収支計算、健康管理の表、グラフURL
        * アルバムコンテンツのURL :construction: 
        * URL一覧 :construction: 
    * 外部インターフェース設計（外部サイトとのやりとり、通信フロー）
        * アルバムコンテンツの保管先サーバへの通信 :construction: 
        * URL一覧 :construction: 
    * DB設計（テーブル定義）
        * 収支計算、健康管理のテーブル定義
    * 処理パターン（オンライン、バッチ）
        * オンライン：スマホからの打鍵
        * バッチ：各種メンテナンス
            * ログバックアップ・削除・圧縮
            * 統計情報収集（k3d、OS、コンテナ）
            * k3d起動・停止、k3dクラスタ再作成
            * :construction: イメージアップデート（難易度高め）
            * データベースエクスポート・インポート

---
#### 非機能要件
* AWSのWell Archtected Flameworkの考え方を参考にしていますが、セキュリティ、可用性、性能、メンテナンス、コストの5観点に加え、移植性についての要件になります。
    * セキュリティ要件（アクセス制御、データの暗号化など）
        * ログイン、FlaskAPI、TFA
        * Nginxでのアクセス制御
    * システムの信頼性や可用性（障害時の復旧能力、システムの稼働時間など）
        * k3dの導入（Kubernetesアーキテクチャ環境）
        * ファイルバックアップ
        * 制約：データベースが単一障害点となるため、その考慮点と対策
            * データベースバックアップ・リストア・エクスポート・インポート機能
    * パフォーマンス（応答時間、処理能力など）
        * FlaskAPIとFastAPIベースのPythonフレームワークを利用
    * 運用（メンテナンス）
        * 汎用化するため、Blueprint、オブジェクト指向プログラミングを活用する
        * ソースコードを<b>Github</b>で管理する
        * k3dの起動停止スクリプトを開発する
        * k3dはバグだらけなので、バグを受容しつつ、回避策用のスクリプトを開発する
    * コスト
        * <b>無料で使うため、OSSを利用する。</b>
    * 移植性
        * 異なるOSに移行することを考慮し、マニフェストファイルを管理する
        * データエクスポートはmysqldumpコマンド、インポートはmysqlコマンドを利用
* 使用する技術やプラットフォームの制約
（ハードウェアの仕様、ソフトウェアのバージョンなど）
    * 仮想マシンは、VMWareを引き続き利用。OSはAlmaLinux8.9を採用する。
* システムの利用環境に関する要件（言語、地域、ユーザーの文化的背景など）
    * 特にないが、日本語ベースとする。地域も日本。

#### 考慮すること
* 実テーブルを複数使用する場合、内部結合したビューを作成し、管理します。
* データベース破損に備え、データベースをファイルエクスポートし、Slaveデータベースへインポートする処理を定期実行します。
* PythonのCPU、メモリ負荷を減らすため、loggerによるログ出力は必要最小限とします。
* ブラウザからの接続は秘匿情報の盗聴を防ぐため、https接続のみ許可します。

---

### 詳細設計（実装方針・イメージ）
* pythonでの実装方法を検討します。
#### モジュール
* flask,flasksecurity,pandas,sqlalchemy,plotlyが必要そうです
* plotlyとはデータからグラフ描画・可視化をする機能です。
* Matplotlibとは違い、plotlyではマウス操作での拡大縮小も可能なのが特徴です。

| ライブラリ   | 用途                                                                                                                                                                      |
|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Flask        | ウェブアプリケーションやAPIの開発                                                                                                                                 |
| SQLAlchemy   | SQLデータベースとの対話、オブジェクトリレーショナルマッピング（ORM）                                                                                     |
| plotly   | グラフの描画や可視化                                                                                                                                                   |
| Pandas       | 表形式のデータ操作や解析                                                                                                                                               |
| NumPy        | 数値計算や配列操作、科学技術計算、データ分析                                                                                                                         |
| openai        | 各種提案※外部APIの利用                                                                                                                         |

#### ディレクトリ構成
* 下記役割を分けるようディレクトリ作成し、pythonスクリプトを作成し、配置ています。
    ```
    pyscripts
      ├testapp.py（メインスクリプト）
      └content
        ├t_kyn_connection（データベース接続）
        ├t_kyn_models（データモデル定義）
        ├t_kyn_operations（データ操作）
        ├t_kyn_routes（URLとAPI操作とのマッピング）
        └t_kyn_views（表示テンプレート定義）
    ```
* データモデル定義
    * pythonのSQLAlchemyのmodelsで定義します(以下は一例）
        ```bash
        from sqlalchemy import Column, Integer, String, DateTime, BigInteger, Numeric, Float, Date
        from sqlalchemy.ext.declarative import declarative_base
        
        Base = declarative_base()
        
        class ActPlanHouserepaircostsTotalView(Base):
            __tablename__ = 'act_plan_houserepaircosts_total_view'
        
            #id = Column(Integer, primary_key=True)
            aphc_year = Column(Integer, primary_key=True)
            aphc_month = Column(Integer, primary_key=True)
            aphc_payment = Column(Numeric)
        ```

* データ操作
    * 操作単位で定義します。例えば、データベースのデータをSELECTする場合、下記pandasを利用します(以下は一例）
        ```bash
        import pandas as pd
        from t_kyn_connection.create_session import get_session
        import os
        import logging
        import matplotlib.pyplot as plt
        
        # ログ設定
        logging.basicConfig(level=logging.DEBUG)
        logger = logging.getLogger(__name__)
        
        class DataViewer:
            def __init__(self):
                # セッションを取得
                self.session = get_session()
        
            def view_all_data_as_html(self, table_class):
                try:
                    # 全データを検索
                    logger.debug("データベースからデータを取得します")
                    results = self.session.query(table_class).all()
                    logger.debug(f"取得したデータの数: {len(results)}")
        
                    if results:
                        # 検索結果をデータフレームに変換
                        logger.debug("データフレームに変換します")
                        columns = table_class.__table__.columns.keys()
                        data = [tuple(getattr(result, column) for column in columns) for result in results]
                        df = pd.DataFrame(data, columns=columns)
                        logger.debug("データフレームの内容:\n%s", df)
        
                        # データフレームをHTML形式で表示
                        html_table = df.to_html(index=False)
                        logger.debug("HTML形式のデータ:\n%s", html_table)
        
                        return html_table
                    else:
                        logger.debug("No data found in the table.")
                        return "No data found in the table."
                except Exception as e:
                    logger.error("Error occurred while viewing data: %s", e)
                    return f"Error occurred while viewing data: {e}"
                finally:
                    # セッションをクローズ
                    self.session.close()
        ```

* URLとAPI操作とのマッピング・データ表示
    * FlaskのBlueprint, render_templateを用いて、テンプレート用のhtmlファイルを利用し、動的ページを作成します。(以下は一例）
        ```bash
        import sys
        from flask import Blueprint, render_template
        
        # Create a Blueprint
        act_plan_houserepaircosts_total_view_bp = Blueprint('act_plan_houserepaircosts_total_view_bp', __name__)
        act_plan_houserepaircosts_total_view_graph_bp = Blueprint('act_plan_houserepaircosts_total_view_graph_bp', __name__)
        
        sys.path.append('/app')
        @act_plan_houserepaircosts_total_view_bp.get('/')
        def execute_sample_task():
            sys.path.append('/app/content')
        
            from t_kyn_operations.select_args_10desc import DataViewer
            from t_kyn_models.models_t_kyn_act_plan_houserepaircosts_total_view import ActPlanHouserepaircostsTotalView
        
            data_viewer = DataViewer()
        
            html_table = data_viewer.view_all_data_as_html(ActPlanHouserepaircostsTotalView)

            return render_template('data.html', table=html_table)
        ```
* 表示テンプレート定義
    * data.htmlを定義します。これにより、各テーブルごとにhtmlファイルを用意する必要がなくなります(以下は一例）
        ```
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Data Table</title>
            <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
        </head>
        <body>
            <div class="container mt-5">
                <h1>Data Table</h1>
                <div class="table-responsive">
                    {{ table|safe }}
                </div>
                <div class="mt-3">
                    <button class="btn btn-primary" onclick="goBack()">戻る</button>
                </div>
            </div>
        
            <script>
                // 戻るボタンの関数
                function goBack() {
                    window.history.back();
                }
            </script>
        
        </body>
        </html>
        
        ```
* グラフ表示
    * plotlyを用いて、グラフ作成します(以下は一例）
        ```
        @act_plan_houserepaircosts_total_view_graph_bp.route('/')
        def execute_sample_plotgraphtask():
            sys.path.append('/app/content')
        
            from t_kyn_operations.select_args_10desc import DataViewer
            from t_kyn_models.models_t_kyn_act_plan_houserepaircosts_total_view import ActPlanHouserepaircostsTotalView
        
            data_viewer = DataViewer()
        
            # データを取得してデータフレームに変換
            df = data_viewer.get_data_as_dataframe(ActPlanHouserepaircostsTotalView)
            graphtitle = ActPlanHouserepaircostsTotalView.__tablename__
        
            if df is not None:
                # グラフをプロット
                data_viewer.plot_data(df, 'aphc_month', 'aphc_payment', graphtitle)
        
            return 'Sample graph plot task executed successfully.'
        ```

* データアップロードダウンロード
    * 実装方法を確認中 :construction: 

* データエクスポート、インポート
MySQLのPodからデータベースをエクスポートし、MasterデータベースではないMYSQLのPodへデータインポートを行います。
    * MySQLダンプファイルをエクスポートするbashスクリプト
    * MySQLダンプファイルからインポートするbashスクリプト

* ログイン機能
    * 実装方法を確認中 :construction: 

#### 改善したい機能
* 住宅ローンのシミュレーション
    * 1シミュレーション1テーブルとしており、複数のシミュレーション結果をグラフにする際、複数テーブルを結合してました。しかし、結合処理は応答時間が長くなるため、非効率です。
    * 単一テーブルとし、シミュレーション毎に計算で出すようカラムを分けるよう変更します。


### 今後
* 画面遷移、データモデルをもう少し詳細化します
* 2024/9より、ローカルPC上のVMに構築ではリソースが足りないことが判明しました。ディスクサイズは問題ないとしても、CPUとメモリは共用で利用されることから、PC上から、クラウド（github codespaceかgoogle colabにデプロイ）する方針に梶をとります
* 2024/10 運用機能をいくつか追加するため、以下を対応しています。詳細事項は後続で記載予定です。
  * ログ集約化（fluentdによる agentpod→serverpod ）
  * 監視要件として統計用スクリプトの追加
  * デプロイ機能スクリプト追加
  * codespaceによる接続確認（localhostでの接続確認）

# 参考
* flask-security

https://zenn.dev/saiki_toshiki/articles/946e4a3c2eb4c5

* セキュリティ

https://pythonjp.ikitai.net/entry/2024/02/08/060000

* パフォーマンス

https://pythonjp.ikitai.net/entry/2024/02/08/060000