NKPアプリケーションカタログ 削除方法
NKPのアプリカタログ実装するときにハマったことメモ
NKPカタログ一覧に表示されない場合の確認と削除方法
カタログ登録に失敗した後、間違って登録された情報を削除しないと
修正して正しい設定をいれてもカタログに表示されない。。。
<原因>
・metadata.yamlやhelmrelease.yamlの内容が間違っている
<対処方法>
NKPマネージメントクラスタ上のocirepositoryを手動で削除する
WorkspaceのNamespaceを確認し(ややこしい)
この場合、”hoihoi-workspace-97m66-v6hv9” の方
$ nkp get ws NAME NAMESPACE default-workspace kommander-default-workspace hoihoi-workspace-97m66 hoihoi-workspace-97m66-v6hv9 kommander-workspace kommander
ocirepocitoryのアプリカタログを確認し、
$ kubectl get ocirepository -n hoihoi-workspace-97m66-v6hv9 nkp-catalog-loghoihoi NAME URL READY STATUS AGE nkp-catalog-loghoihoi oci://ghcr.io/konchangakita/nkp-catalog/loghoihoi True stored artifact for digest '0.0.1@sha256:3784397b0b2baec9d44d1e26b89ac1954daaf1589e75a6659a93df8b998a8659' 19h
deleteする
$ kubectl delete ocirepository -n hoihoi-workspace-97m66-v6hv9 nkp-catalog-loghoihoi ocirepository.source.toolkit.fluxcd.io "nkp-catalog-loghoihoi" deleted
ログほいほいを NKPアプリケーションカタログしてみる

この記事は、Nutanix Advent Calendar 2025の11日目として書きました。
ログほいほいHelmチャート化したので、これを活用してNKPアプリケーションカタログに登録してみます
NKPがなんのかというのは、ここでは説明は飛ばして
とにかくどうやって自作WebアプリをNKPのカタログ化させるのかという方法を紹介です
https://github.com/konchangakita/loghoihoi
Helm化したソースはココです
- 前提知識
- 準備
- NKPカタログ用のファイル作成
- NKPアプリカタログの実装
- NKP Workspace の Applications にカタログを登録
- カタログから ログほいほいを実装
- おまけ:カタログから削除するには
- さいごに
準備
Helm パッケージを作成
$ helm package ./helm/loghoihoi Successfully packaged chart and saved it to: /home/nutanix/konchangakita/loghoihoi/loghoihoi-0.1.0.tgz
Chart.yamlの Versionに合わせて、"loghoihoi-0.1.0.tgz" な感じのファイルが作成されます
NKPカタログ用のファイル作成
Catalog Repository(Git)を準備
NKPカタログには Git 管理されたカタログリポジトリを必要とします
NKPカタログ用のディレクトリを作って、nkpコマンドでテンプレート一式を作成します
nkp generate catalog-repository --apps <アプリ名>=<バージョン> --repo-dir ./
アプリ名を"loghoihoi"(Helmパッケージと同じ名前)、"0.0.1"で作成してみます
$ mkdir nkp-catalog $ cd nkp-catalog $ nkp generate catalog-repository --apps loghoihoi=0.0.1 --repo-dir ./ Successfully initialized application layout for loghoihoi-0.0.1 Catalog layout generated at /home/nutanix/konchangakita/nkp-catalog
こんな感じの構造で一式が作成されます
.
├── .bloodhound.yml
└── applications
└── loghoihoi
└── 0.0.1
├── helmrelease
│ ├── cm.yaml
│ ├── helmrelease.yaml
│ └── kustomization.yaml
├── helmrelease.yaml
├── kustomization.yaml
└── metadata.yaml
設定ファイルたち
アイコンの準備
※アイコンはなくても実装可能です
NKPアプリカタログ上で表示したいアイコンの設定できます
svg形式のファイルを用意して base64 でエンコードします
$ cat hoihoi.svg | base64 -w0 PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcm ~~~~~~~~~超長い~~~~~~~~~~~~~
metadata.yaml
metadata.yamlでは、NKPアプリカタログで表示される項目設定できます
descriptionとicon(エンコードした文字列)、supportLinkを追記しました、その他はそのまま
nkp-catalog/applications/loghoihoi/0.0.1/metadata.yaml
schema: catalog.nkp.nutanix.com/v1/application-metadata allowMultipleInstances: true category: - general description: "Nutanix LogHoihoi is a tool that helps you manage your logs dayo." displayName: loghoihoi icon: "PHN2ZyB4bWxucz0iaH~~~さっきのやつ~~~~~^" licensing: - Pro - Ultimate overview: "" scope: - project supportLink: "https://x.com/konchangakita" ---
※descriptionやiconはなくてもよいけど、supportLinkは何か入れとかないとエラー出ます
helmrelease.yaml
helmrelease.yamlでは、OCIレジストリにプッシュしたOCI アーティファクトの URL のみ指定
"url: oci://ghcr.io/konchangakita/loghoihoi" と "tag: 0.1.0"を追記
nkp-catalog/applications/loghoihoi/0.0.1/helmrelease/helmrelease.yaml
apiVersion: source.toolkit.fluxcd.io/v1 kind: OCIRepository metadata: name: ${releaseName}-chart-source namespace: ${releaseNamespace} spec: interval: 6h0m0s ref: tag: 0.1.0 url: oci://ghcr.io/konchangakita/loghoihoi --- apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: name: loghoihoi namespace: ${releaseNamespace} spec: chartRef: kind: OCIRepository name: ${releaseName}-chart-source namespace: ${releaseNamespace} install: crds: CreateReplace createNamespace: true remediation: retries: 30 interval: 15s targetNamespace: ${releaseNamespace} upgrade: crds: CreateReplace remediation: retries: 30 valuesFrom: - kind: ConfigMap name: ${releaseName}-config-defaults ---
その他のファイルはデフォルトのまま
NKPアプリカタログの実装
Catalog Bundle の生成
nkp create catalog-bundle --repo-dir <ディレクトリ指定>
$ nkp create catalog-bundle --repo-dir . Bundling 1 application(s) (airgapped : false) ✓ Validating metadata.yaml for loghoihoi/0.0.1 ✓ Building OCI artifact nkp-catalog/loghoihoi:0.0.1 Processing application loghoihoi/0.0.1 ✓ K8s v1.33.0: parsing resources ✓ K8s v1.33.0: validating ✓ Pulling requested images [====================================>1/1] (time elapsed 00s) ✓ Saving application bundle to /home/nutanix/konchangakita/nkp-catalog/loghoihoi-0.0.1.tar Run the following to push the artifact to your registry: nkp push bundle --bundle /home/nutanix/konchangakita/nkp-catalog/loghoihoi-0.0.1.tar --to-registry <your-registry-url> Run the following command to create catalog artifact(s) after pushing them: nkp create catalog-application --url oci://<registry-url>/nkp-catalog/loghoihoi --tag 0.0.1 --workspace kommander-workspace
Bundle を GHCR へ Push
nkp push bundle \ --bundle <バンドルファイル> \ --to-registry <OCIレジストリ>
$ nkp push bundle \ --bundle loghoihoi-0.0.1.tar \ --to-registry oci://ghcr.io/konchangakita ✓ Creating temporary directory ✓ Extracting bundle configs from "loghoihoi-0.0.1.tar" ✓ Parsing image bundle config ✓ Starting temporary Docker registry ✓ Pushing bundled images [====================================>1/1] (time elapsed 02s)
Github上のパッケージ確認
パッケージをpublicにして、認証無しでダウンロード可能に
github上のパッケージ名を確認、自動的にディレクトリ名の"nkp-catalog"が付与されている

先程と同じくパッケージをpublicにして、認証無しでダウンロード可能にしておく


NKP Workspace の Applications にカタログを登録
NKP の Workspace Name を確認
NKP Managementクラスタに対して、コマンドを実行
実行例)
$ nkp get ws NAME NAMESPACE default-workspace kommander-default-workspace hoihoi-workspace-xbjfn hoihoi-workspace-xbjfn-k9xgv kommander-workspace kommander
カタログ登録
先程プッシュした Catalog Bundle を指定し、カタログ登録したい workspace name "hoihoi-workspace-xbjfn" を指定
nkp create catalog-application \ --url <OCIリポジトリ> \ --tag <バージョン> \ --workspace <ワークスペース名> \ --skip-oci-registry-patches
実行例)
$ nkp create catalog-application \ --url oci://ghcr.io/konchangakita/nkp-catalog/loghoihoi \ --tag 0.0.1 \ --workspace hoihoi-workspace-xbjfn \ --skip-oci-registry-patches Catalog application nkp-catalog-loghoihoi created. Use 'nkp edit ocirepository -n testest-workspace-mpjsh-2jwlt nkp-catalog-loghoihoi' to change its configuration if needed. Note that the OCIRepository is not patched by NKP with credentials due to custom configuration. Make sure to configure the url and credentials according to your cluster networking capabilities.
NKP上でWorkspaceを選択して、Applicationをみると追加されている!

カタログから ログほいほいを実装
特にオプションなど必要ないのでEnableするだけ



loghoihoiのネームスペースが自動的に作成されて、アプリケーションが実装されているのが分かります
$ kubectl get all -n loghoihoi NAME READY STATUS RESTARTS AGE pod/elasticsearch-546d769b69-b7sxr 1/1 Running 0 9h pod/kibana-6d6778c97b-gtwj8 1/1 Running 0 9h pod/loghoi-backend-74c797cd79-nvhgm 1/1 Running 0 9h pod/loghoi-frontend-6d57c96cfd-7nbg5 1/1 Running 0 9h pod/loghoi-syslog-555bff66d9-27vcq 1/1 Running 0 9h NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/elasticsearch-service ClusterIP 10.106.170.124 <none> 9200/TCP 9h service/kibana-service ClusterIP 10.98.136.116 <none> 5601/TCP 9h service/loghoi-backend-service ClusterIP 10.105.120.228 <none> 7776/TCP 9h service/loghoi-frontend-service ClusterIP 10.109.64.227 <none> 7777/TCP 9h service/loghoi-syslog-service LoadBalancer 10.99.214.63 10.55.80.138 7515:31508/TCP,5066:31476/TCP 9h NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/elasticsearch 1/1 1 1 9h deployment.apps/kibana 1/1 1 1 9h deployment.apps/loghoi-backend 1/1 1 1 9h deployment.apps/loghoi-frontend 1/1 1 1 9h deployment.apps/loghoi-syslog 1/1 1 1 9h NAME DESIRED CURRENT READY AGE replicaset.apps/elasticsearch-546d769b69 1 1 1 9h replicaset.apps/kibana-6d6778c97b 1 1 1 9h replicaset.apps/loghoi-backend-74c797cd79 1 1 1 9h replicaset.apps/loghoi-frontend-6d57c96cfd 1 1 1 9h replicaset.apps/loghoi-syslog-555bff66d9 1 1 1 9h
$ kubectl get ingress -n loghoihoi NAME CLASS HOSTS ADDRESS PORTS AGE loghoi-ingress kommander-traefik * 10.55.80.137 80 9h
おまけ:カタログから削除するには
やはりここを参照
blog.ntnx.jp
Management クラスタに向けて、Namespaceを指定する
$ kubectl get apps.apps.kommander.d2iq.io -n hoihoi-workspace-xbjfn-k9xgv NAME APP ID APP VERSION SOURCE AGE loghoihoi-0.0.1 loghoihoi 0.0.1 nkp-catalog-loghoihoi 10m nutanix-ai-2.4.0 nutanix-ai 2.4.0 nutanix-ai-2.4.0 18m
$ kubectl delete apps.apps.kommander.d2iq.io -n hoihoi-workspace-xbjfn-k9xgv loghoihoi-0.0.1 app.apps.kommander.d2iq.io "loghoihoi-0.0.1" deleted
さいごに
ここまでログほいほいを育ててきました
現状すべてのログを網羅しているわけではないですが、対象のログを増やすことはそんなに難しくないので
ここまできたらちょっとした改良もCI/CDで〜みたいこともやってみたいですね
ログほいほいをHelmでパッケージ化する

この記事は、Nutanix Advent Calendar 2025の2日目として書きました。
ログほいほいの機能自体は、前回の記事で概ね開発完了したわけですが、
ここから次の目標として、Nutanix Kubernetes Platform(NKP)アプリケーションカタログにチャレンジしたいと思います
まず最初の壁として必ず出てくるのが Helm 化です
ここからは、新しい本番用(?)リポジトリを切り出して実装していきます
github.com
とにかくすぐ実装したい人はGithub上を参照しよう
なんで NKP カタログって Helm チャート必須なの?
NKP アプリケーションカタログが Helm チャートを必須としている理由は、「アプリを標準化された形式で配布する」ためです。
NKP は内部的に FluxCD の HelmRelease を使っており、アプリを登録・Enable する仕組み自体が Helm を前提に設計されています。
単なる YAML の集合では、環境差分・アップグレード・Rollback を統一できず、バージョン管理も困難で「アプリ」として扱うには不十分です。
Helm なら values による上書き、OCI レジストリ配布、SemVer によるバージョン管理が可能になり、NKP が UI でのインストール/更新/削除を正確に制御できます。
つまり、NKP カタログで流通する“アプリ”の単位は Helm チャートであり、
これはプラットフォームとしての安定性と互換性を担保するための必須要件なのです。
Helmって何?
Helm は Kubernetes でアプリケーションを配布・管理するための“パッケージマネージャー” というものです
多数の YAML を1つの「チャート」としてまとめ、インストール・アップグレード・削除を統一的に扱えるようになります
values.yaml による変数のように設定変更が可能で、環境ごとに柔軟にパラメータを上書きできます
また、チャートにバージョンを付けて OCI レジストリに公開したり、依存コンポーネントをまとめて管理することもできます
これにより、Kubernetes でのアプリ展開が再現性高く、標準化された形で行えるようになるのが Helm の最大の利点です
Helmでインストールするとこんなイメージ
helm install loghoihoi ./helm/loghoihoi
Helm使うための前提条件
1. Helm CLI がインストールされていること
2. Kubernetes クラスタにアクセスできること
3. デプロイ先の namespace に権限があること
4. OCI レジストリ利用権限
ログほいほいをHelmで実装
helm実装の構造
helm/
└── loghoihoi
├── Chart.yaml
├── templates
│ ├── backend-deployment.yaml
*** 各種 yamlファイル
│ └── syslog-deployment.yaml
└── values.yaml
Helm実装に必要なファイル達
・Chart.yaml
パッケージ情報、チャートのメタデータ(名前、バージョン、依存関係など)
・values.yaml
テンプレートに渡す デフォルト値を定義
・templates/ ディレクトリ
Helmを使ってインストール
なんやかんやで実装してみまして、helm/配下必要なファイルは配置してあるので
gitcloneしてきて、これだけです
helm install loghoihoi ./helm/loghoihoi
Helmインストール後の状況
$ kubectl get all -n loghoihoi NAME READY STATUS RESTARTS AGE pod/elasticsearch-546d769b69-vfl64 1/1 Running 0 10h pod/kibana-6d6778c97b-zzw88 1/1 Running 0 10h pod/loghoi-backend-74c797cd79-nzj65 1/1 Running 0 10h pod/loghoi-frontend-6d57c96cfd-66kgz 1/1 Running 0 10h pod/loghoi-syslog-555bff66d9-zbhlg 1/1 Running 0 10h NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/elasticsearch-service ClusterIP 10.102.83.105 <none> 9200/TCP 10h service/kibana-service ClusterIP 10.98.76.110 <none> 5601/TCP 10h service/loghoi-backend-service ClusterIP 10.107.56.61 <none> 7776/TCP 10h service/loghoi-frontend-service ClusterIP 10.104.45.80 <none> 7777/TCP 10h service/loghoi-syslog-service LoadBalancer 10.105.21.6 10.42.153.138 7515:32239/TCP,5066:32397/TCP 10h NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/elasticsearch 1/1 1 1 10h deployment.apps/kibana 1/1 1 1 10h deployment.apps/loghoi-backend 1/1 1 1 10h deployment.apps/loghoi-frontend 1/1 1 1 10h deployment.apps/loghoi-syslog 1/1 1 1 10h NAME DESIRED CURRENT READY AGE replicaset.apps/elasticsearch-546d769b69 1 1 1 10h replicaset.apps/kibana-6d6778c97b 1 1 1 10h replicaset.apps/loghoi-backend-74c797cd79 1 1 1 10h replicaset.apps/loghoi-frontend-6d57c96cfd 1 1 1 10h replicaset.apps/loghoi-syslog-555bff66d9 1 1 1 10h
Web UIにアクセスするURLは下記で確認できます
INGRESS_IP=$(kubectl get ingress -n loghoihoi -o jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}') echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "🌐 WebブラウザでアクセスするURL" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "フロントエンド: https://${INGRESS_IP}/" echo "API ドキュメント: https://${INGRESS_IP}/docs" echo "API ドキュメント: https://${INGRESS_IP}/redoc" echo "Kibana: https://${INGRESS_IP}/kibana" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
Helmでアンインストール
クリーンアップもこれだけで、Namespaceごと削除してくれます
helm uninstall loghoihoi
とても楽ちんな気がする
ログほいほい Kubernetes化へ ! 、、、ついに完成なのか

Nutanix ログほいほいがついにKubernetes化に対応です
Kubernetes化に合わせて、大きくリファクタリングも行いました
変更点をピックアップします
- リファクタリング方針(Tidy First)で基盤整備を優先し、以降の機能追加を速く安全にする
- バックエンドAPIの変更 Flask から FastAPI へ
- Kubernetes化の実装(Dockerfile分離、StorageClass戦略、Recreate戦略、Traefik/MetalLB、ヘルスチェック)
- 認証用SSH鍵の管理方式を統一(K8sはSecret、composeはホストパス)
いつもの今回のブログの内容はココ
起動の仕方などもGithub上に書いてます、実装しながら試した人はまず Github へ
github.com
- リファクタリング方針:Tidy First で「掃除してから進む」
- バックエンドAPI をFlask から FastAPIへ変更
- Kubernetes 化の実装ポイント
- 認証用SSH鍵の管理方式(K8s=Secret、compose=ホストパス)
- ストレージ比較表
- さいごに
リファクタリング方針:Tidy First で「掃除してから進む」
Tidy First は、「まず掃除してから進めよう」という開発方針です
いきなり新機能を足す前に、コードや設定を少し整理しておくことで、後の変更を楽にしようというものみたいです
たとえば変数名をわかりやすくしたり、ファイルを整理したり、不要なコードを消したりなどなど
このあたりきっと元ネタです
Software Design: Tidy First? | Kent Beck | Substack
まぁ、キレイに書きたいって思ってても正解がわからず、勝手にコードは汚くなっていくわけですが
最近は、AIにコード書かせるのもだいぶこなれてきたので、そのAIにTidy Firstを意識させるのがよいのでしょう
今回のTidy例:
- Dockerfileの用途分離(`dockerfile`=開発、`Dockerfile.k8s`=本番)
- ストレージとデプロイ戦略の統一(HostPath=Recreate、RWXがない前提を明文化)
- ドキュメントの相対パス化(複製先でもデプロイ可能に)
フォルダ構造刷新と汎用コンポーネントの独立
Kubernetes対応にむけて、役割ごとにフォルダを明確に分離させる
コンポーネント間の境界が明確になり、CI/CDやローカル開発の導線も一本化する
- scripts/
- shared/
バックエンドAPI をFlask から FastAPIへ変更
`Swagger UI` と `ReDoc` の実装(FastAPI標準機能)
API を作るときに、自動でドキュメント(説明書)を生成してくれるのです
このドキュメントを表示する画面が Swagger UI と ReDoc です
Swagger UI

- ReDocとは: 同じOpenAPI仕様をもとにした静的ドキュメントビューア。情報密度が高く、階層化されたメニューで仕様を読み込みやすい
- 同じく FastAPI が自動生成する 別のスタイルのドキュメント画面
- こちらは 読み物としてわかりやすい構成
- 見やすいUIで、主に「API仕様書」として利用される
ReDoc

FastAPIでの提供方法
基本的には自動生成してくれるので、バックエンドのIPアドレス`/docs`、`/redoc`にアクセスするだけで楽チン
http://バックエンドIP/docs → Swagger UI
http://バックエンドIP/redoc → ReDoc
Kubernetes 化の実装ポイント
- Dockerfile分離
- 開発: `backend/dockerfile`, `frontend/dockerfile`, `syslog/dockerfile`
- 本番: `*/Dockerfile.k8s`
- 誤運用を防ぎつつ、CI/CDとローカル開発の両立を容易に
- イメージレジストリ: GHCR(`ghcr.io/konchangakita/*`)
- Docker hubに比べて Pull安定性、公開設定で認証レス Pull
- ストレージ戦略
- Ingress / ネットワーク
- ヘルスチェック
- Backend: `/health`(liveness), `/ready`(readiness)
docker-compose 開発環境の維持
docker-compose開発環境とKubernetes本番環境のように、同一コードでそのまま使えるようにしたく
開発と本番で「ファイル/環境変数/ストレージ/起動方法」が明確に分離され、相互の混入を防止
認証用SSH鍵の管理方式(K8s=Secret、compose=ホストパス)
- 共通方針: 鍵はGit管理せず、ホストディレクトリ `config/.ssh/` に生成・保持
- Kubernetes
- docker-compose
- フロント連携: SSH認証失敗を検知したら、UIで自動的に公開鍵表示を促す
ストレージ比較表
| 環境 | StorageClass | アクセスモード | デプロイ戦略 | 備考 |
| 開発・検証 | `manual` (HostPath) | `ReadWriteOnce` | `Recreate` | ノードローカルに永続化、手軽さ優先 |
| 本番 | `STORAGE_CLASS` 環境変数で指定 (CSI) | クラスタ標準のアクセスモード | `RollingUpdate` (Elasticsearchは環境依存) | NKPなどのクラスタストレージに合わせて差し替え |
さいごに
このみちのりは相当大変でした、、、もともと kubernetes のよくわからないまま開発を続け
そこからの kubernetes化は、ある意味いちから作り直しに近かったのです
もうそこは紆余曲折ありすぎて、ログほいほいからははずれるので書きませんが
今度何かを新しく作る時は、kubernetes化をはじめから意識しておけば、そんなに大変でないはず
Kubernetes化までできたので、一旦開発日記ブログ的なものはここまですが
クラウドネイティブ Webアプリケーションとしては、もう少しチャレンジしてみたいことはあるので
新しくブランチを切り出して、CI/CDやhelm-chartなど開発側勉強に取り組んでみたいと思います
FastAPI で SocketIO

Socket.IOは、リアルタイム双方向通信を実現するJavaScriptライブラリなのですが、FlaskとFastAPIでは実装がまた別でここも作り直しになります
時間のない人向けの最終形態は githubへ
github.com
最小の実装
バージョン情報
バックエンドの requirement.txt
Pythonバージョン: 3.11
fastapi==0.104.1 uvicorn[standard]==0.24.0 python-socketio==4.3.1 python-engineio==3.9.0 python-multipart==0.0.6
フロントエンドの package.json
Node.jsバージョン: 18
{
"dependencies": {
"next": "^14.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"socket.io-client": "^4.7.2"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@types/socket.io-client": "^1.4.36",
"typescript": "^5.0.0",
"eslint": "^8.0.0",
"eslint-config-next": "^14.0.0"
}
}
FastAPIでの実装のとっかかり
from fastapi import FastAPI import socketio sio = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*") sio_app = socketio.ASGIApp(sio) app = FastAPI() app.mount("/socket.io", sio_app) # クライアントは /socket.io に接続 @sio.event async def connect(sid, environ): await sio.emit("welcome", {"msg": "hello"}, to=sid) if __name__ == "__main__": uvicorn.run(socket_app, host="0.0.0.0", port=8000)
SocketIO接続テスト環境の実装
最小限のテスト環境として、フロントエンドとバックエンドでSocketIOで接続と切断だけを行う簡単なテスト
バックエンド側の実装
main.py
'use client' import React, { useState } from 'react' import io from 'socket.io-client' type Socket = ReturnType<typeof io> export default function Home() { const [socket, setSocket] = useState<Socket | null>(null) const [connected, setConnected] = useState(false) const connectSocket = () => { if (socket && connected) { console.log('既に接続されています') return } console.log('SocketIO接続を開始します...') const newSocket = io(`${window.location.protocol}//${window.location.hostname}:8000`) newSocket.on('connect', () => { console.log('サーバーに接続しました') setConnected(true) }) newSocket.on('disconnect', () => { console.log('サーバーから切断されました') setConnected(false) }) newSocket.on('connect_error', (error: any) => { console.error('接続エラー:', error) setConnected(false) }) setSocket(newSocket) console.log('SocketIOオブジェクトを設定しました') } const disconnectSocket = () => { if (socket) { socket.close() setSocket(null) setConnected(false) console.log('手動で切断しました') } } return ( <div className="container"> <h1>SocketIO テスト環境 (Next.js App Router)</h1> <div className="card"> <h2>接続状態</h2> <div className={`status ${connected ? 'connected' : 'disconnected'}`}> {connected ? '接続中' : '切断中'} </div> <div style={{ marginTop: '10px' }}> <button onClick={connectSocket} className="button connect" disabled={connected} > 接続 </button> <button onClick={disconnectSocket} className="button disconnect" disabled={!connected} > 切断 </button> </div> </div> </div> ) }
フロントエンド
page.tsx
'use client'
import React, { useState } from 'react'
import io from 'socket.io-client'
type Socket = ReturnType<typeof io>
export default function Home() {
const [socket, setSocket] = useState<Socket | null>(null)
const [connected, setConnected] = useState(false)
const connectSocket = () => {
if (socket && connected) {
console.log('既に接続されています')
return
}
console.log('SocketIO接続を開始します...')
const newSocket = io(`${window.location.protocol}//${window.location.hostname}:8000`)
// 接続成功時の処理
newSocket.on('connect', () => {
console.log('サーバーに接続しました')
setConnected(true)
})
// 切断時の処理
newSocket.on('disconnect', () => {
console.log('サーバーから切断されました')
setConnected(false)
})
// エラー時の処理
newSocket.on('connect_error', (error: any) => {
console.error('接続エラー:', error)
})
setSocket(newSocket)
console.log('SocketIOオブジェクトを設定しました')
}
const disconnectSocket = () => {
if (socket) {
socket.close()
setSocket(null)
setConnected(false)
console.log('手動で切断しました')
}
}
return (
<div className="container">
<h1>SocketIO チャットルーム (Next.js App Router)</h1>
<div className="card">
<h2>接続状態</h2>
<div className={`status ${connected ? 'connected' : 'disconnected'}`}>
{connected ? '接続中' : '切断中'}
</div>
<div style={{ marginTop: '10px' }}>
<button
onClick={connectSocket}
className="button connect"
disabled={connected}
>
接続
</button>
<button
onClick={disconnectSocket}
className="button disconnect"
disabled={!connected}
>
切断
</button>
</div>
</div>
</div>
)
}
接続テスト
ブラウザでhttp://localhost:3000へ接続
「接続」ボタンをクリックし、状態が「接続中」に変わることを確認

「切断」ボタンをクリックし、状態が「切断中」に変わることを確認

socketio バージョンによって、テストがうまくいかないことがあるので
うまくいかない時は、フロントエンドとバックエンドのバージョンを変えてテストを色々試してみるとよいです
今回の構成では、python-socketio 5.13.0(Engine.IO 4.xプロトコル)とsocket.io-client 4.7.2(Engine.IO 4.xプロトコル)を使用しており、互換性がある
チャットルーム実装
SocketIOでは双方向通信できるので、クライアント同士を「部屋(room)」に参加させて、同じ部屋にいる人だけにメッセージを届けるちょっとしたチャットルームを作ることができます
チャットルーム基本の流れ
1.クライアントが接続する
接続時にユーザーを特定の「room」に参加させる
2.メッセージを受信する
受け取ったメッセージを「同じroom」にいる人だけにブロードキャストする
3.退出処理する
切断時や退出時にsocketIO切断
おわりに
まだFastAPIによりパフォーマンス向上の実感はないですが、バックエンドをリファクタリングしつつ
テスト用プログラムも実装していきたい。。。
Kubernetes化への道のりは、まだもう少し時間がかかりそう
FastAPIを試してみる

今までなんとなくFlask使っていましたが、どうやら遅いらしい
というわけで、簡単なAPIを作るならFastAPIが良いというのを聞いて早速お試し
いつもどおり、Docker Composeを使ってフロントエンド(Next.js、ポート7777)とバックエンド(FastAPI、ポート7776)を構築し、簡単なCRUDアプリケーションを作成してみる
時間がない人むけに まずgithub おいておきます
github.com
FastAPIとは
FastAPIが選ばれる理由はこんなとこらしい
1. **自動生成されるAPIドキュメント**: Swagger UIとReDocが自動で生成される
2. **型安全性**: Pydanticを使った型ヒントによるデータ検証
3. **高性能**: 他のPythonフレームワークと比較して高速
4. **現代的な設計**: async/await対応、OpenAPI準拠
とりあえずDockerで作ってみる
必要最低限でFastAPI作ってみる
どうやらFlaskとほぼ同じ構文でいけるっぽい、これは楽
main.py
from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from typing import List, Optional import uvicorn from datetime import datetime # FastAPIアプリケーションのインスタンスを作成 app = FastAPI( title="FastAPI Test API", description="FastAPIのテスト用API", version="1.0.0" ) @app.get("/") async def root(): return {"message": "FastAPI Test API はじめました", "status": "running"} if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=7776)
Dockerイメージの準備
requirements.txt
fastapi==0.104.1 uvicorn[standard]==0.24.0 pydantic==2.5.0 python-multipart==0.0.6
Dockerfile
FROM python:3.11-slim WORKDIR /app # 依存関係をインストール COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # アプリケーションファイルをコピー COPY . . # ポート7776を公開 EXPOSE 7776 # FastAPIアプリケーションを起動 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7776", "--reload"]
Docker イメージの作成
docker build -t fastapi-test-backend . docker run -p 7776:7776 fastapi-test-backend
フロントエンドも実装してそれっぽくしてみる
フロントエンドは個人的定番のNext.js+Tailwindでサクッとな
docker-composeで一気に起動できるいつも検証構成です
実装のステップ・バイ・ステップの様子はこちらにおいておきます
github.com
では、テストの最終段階の実装方法です
githubからクローン
git clone https://github.com/konchangakita/fastapi-test.git
fastapi-test/ ├── backend/ │ ├── main.py # FastAPIアプリケーション │ ├── requirements.txt # Python依存関係 │ └── Dockerfile # バックエンド用Dockerfile ├── frontend/ │ ├── app/ # Next.js App Router │ ├── components/ # Reactコンポーネント │ ├── lib/ # APIクライアント │ ├── types/ # TypeScript型定義 │ ├── package.json # Node.js依存関係 │ └── Dockerfile # フロントエンド用Dockerfile └── docker-compose.yml # Docker Compose設定
Docker Composeで起動
一気に起動してしまう
docker-compose -f "docker-compose.yml" up --build -d
> docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 19b1082e51ff fastapi-test-frontend "docker-entrypoint.s…" About an hour ago Up About an hour 0.0.0.0:7777->7777/tcp, [::]:7777->7777/tcp fastapi-frontend 016840bdb614 fastapi-test-backend "uvicorn main:app --…" About an hour ago Up About an hour (unhealthy) 0.0.0.0:7776->7776/tcp, [::]:7776->7776/tcp fastapi-backend
ブラウザで接続
起動後は、ブラウザから接続するだけ
そこそこそれっぽいWebアプリになっています
http://localhost:7777

テスト用なのでストレージ、DBなんかは使わずに、Pythonのリストを利用したメモリ内で追加削除を実装しています
いつものelasticを使うのもアリですね
あと、テスト用なのでどこからでもバックエンドに接続できるようにCORS設定はワイルドカード["*"]にしています
さいごに
あいかわらずぼちぼちログほいほいしているわけですが、長期かかりすぎているので
ちょっとづづバージョンなどを見直していたら気になるものをみつけたので、勢いで検証してみました
リファクタリングしながら、ログほいほいにも実装してみようと思います
いずれKubernetes化にむけて
【Nutanix ログほいほい】コレクトログ ほいほい
Nutanix Advent Calendar 2024 21日目の記事です!
Nutanix Advent Calendar 2024
adventar.org
さて、みなさん待望のログほいほいの続報です
前の記事から随分と間隔もあいてしまい、ログほいほいも長期プロジェクト化してきました
いまさらログほいほいがなんなのかは説明はしませんが
去年のAdvent Calendarもログほいほい関連でしたし、2年連続です
なんとなく今年のAdevent Calendarあたり完成するだろうとは思っていたのですが
ようやくやりたいことのカタチには近づいてきました、完全体まではまだもう少し掛かりそうです
飽きてさわっていないのではなく、単純にNutanix機能が増えすぎて他のことやっているうちに時間が経ってしまいました
増えすぎた機能は、他の方 Advent Calenarを要チェックです!
というわけで、今回は最後の目玉機能コレクトログです

計画段階ではこんな絵でした

当初は、至ってシンプルなイメージだけでした
実際に作り始めたら、色々盛り込みたい機能があって捗ってしまいました
コレクトログとは
やりたいことは、トラブルシューティングに必要そうな「ログ」と「CVMでコマンド実行結果」をまとめてZIPでダウンロード、なんなら、ダウンロードせずともログの中身をGUI上でも表示!
トラブル時に実際に自分で集めたり中身を確認するにはまぁまぁ手間のかかることなので
実現できたらなかなか便利そうな機能だと思ってます
今回の仕組み

今回は、外部のツールを使うことなくログはバックエンドサーバにシンプルに保管する方法を採用してます
収集するログは代表的なものを選んでみたけど、バックエンドコンテナ上に jsonファイル で定義しているので、そこをいじれば自由に取得対象を増やすこともできます
Nutanix的には、ログ保管場所に Objects とか使っても面白いかもなので、いつかやってみたい
取得するログ設定ファイル
この json ファイルで設定してます
実行するコマンドを指定する json
取得するログファイルを指定する json
ログコレクトの利用イメージ
ソースコードは、コチラの github から入手できます
起動方法は、Github上にも記載があるので参照すること
今回は特にNutanixクラスタ側では準備の必要はないです
github.com
コレクトログの画面
トップのメニュー画面に「Collect Log」のメニューを追加
メニューも充実してきました

ログ収集
まずは何も表示されていない状態です
「START COLLECT LOG」でログ収集を開始

けっこうお気に入りな Loading画面が表示され、1分くらい待つ
(いつかはちゃんとプログレスバーとかつけてみたい)

zipファイルの中身をみる
取得完了すると、zipファイル名を選択できるようになります

zipファイルを選択すると、取得したログ一覧が表示される

ログビューワー
ログを選択すると、Collect Log Viewに表示されます
ダウンロードせずに表示できる、わりとこだわりです

ダウンロード
もちろんzipファイルのダウンロードもできます





