konchangakita

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

NKE で Kubeflow してみる

こちらの記事は、Nutanix Advent Calendar 2022 3日目の記事です


NKEに再入門中ですので、なんか作ってみようと、MLOps入門として、KubeflowをとりあえずNKE上に構築してみようと思います
Kubeflowまだ何よくわかってないですが

Kubeflowとは
Kubeflowは、Kubernetesクラスター上で機械学習(ML)ワークフローを構築、実行、管理するためのオープンソースプラットフォームです。Kubeflowは、Kubernetesに特化したMLフレームワークとして構築されており、Kubernetesを使用したアプリケーションと同様に、スケーラブルで拡張可能なMLワークフローを作成することができます。Kubeflowを使用することで、データサイエンティストやMLエンジニアは、MLモデルのトレーニングやデプロイ、監視、更新を行うことができます。また、Kubeflowは、様々な環境やクラウドプロバイダーで利用できるため、場所に縛られないMLワークフローの構築が可能です。

って、はやりのchatGPT さんが教えてくれました

1.NKE で Kubernetesクラスタの作成

まずは、NKEでKubernetesクラスタを作ります
クラスタ名を「kon-nke」にして、あとはひたすらデフォルトの設定で進めてみます
 
 
 

2.Kubernetesクラスタssh接続する

Kubeflow をデプロイするためにまずデプロイする

ssh接続用の設定ファイルをダウンロード

kubernetesクラスタの画面より、接続用の設定ファイルをダウンロード

クラスタ名-access.sh になります
例)kon-nke-ssh-access.sh

設定ファイルを使って ssh接続

シェルが使える環境で(Windows だと WSL とか)

sh kon-nke-ssh-access.sh 

(実行例)

% sh kon-nke-ssh-access.sh 
Enter KARBON VM IP: 10.38.55.10
================== 10.38.55.10 ==================
The authenticity of host '10.38.55.10 (10.38.55.10)' can't be established.
RSA key fingerprint is SHA256:HR+kRDk4b/4bUANtEMs4wnSKoqLMSrbCi/3iW1Hdi2I.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.38.55.10' (RSA) to the list of known hosts.

[nutanix@kon-nke-5b294f-master-0 ~]$ 


3.Kubeflowデプロイの下準備

基本的には、Kubeflowのサイトに書かれている方法に沿ってすすめていくのですが、
Kubeflow on Nutanix Karbon | Kubeflow

書かれているままの方法でいくと、いくつか躓くところがあったので事前に準備必要です

Terraformインストール
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo yum -y install terraform
sudo yum -y install unzip

kustomizeインストール
cd /tmp
wget https://github.com/kubernetes-sigs/kustomize/releases/download/v3.2.0/kustomize_3.2.0_linux_amd64
chmod +x kustomize_3.2.0_linux_amd64
sudo mv kustomize_3.2.0_linux_amd64 /usr/local/bin/kustomize
cd

gitインストール
sudo yum -y install git
git clone https://github.com/nutanix/karbon-platform-services.git
cd karbon-platform-services/automation/infrastructure/terraform/kcs/install_kubeflow

env.tfvarsの作成
vi env.tfvars
prism_central_username = "admin"
prism_central_password = "password"  # PCのパスワード 
prism_central_endpoint = "xxx.xxx.xxx.xxx"  # クラスタのエンドポイントIP
karbon_cluster_name    = "kon-nke"  # クラスタ名に置き換える
kubeconfig_filename    = "config"
kubeflow_version       = "1.6.0"

Kubeflow のデプロイ
terraform init
terraform plan --var-file=env.tfvars
terraform apply --var-file=env.tfvars

Kubeflow のデプロイ状態の確認

kubeflowの namespaceがあることを確認

$ kubectl get namespaces 
NAME               STATUS   AGE
auth               Active   93s
cert-manager       Active   93s
default            Active   100m
istio-system       Active   93s
knative-eventing   Active   93s
knative-serving    Active   93s
kube-node-lease    Active   100m
kube-public        Active   100m
kube-system        Active   100m
kubeflow           Active   93s
ntnx-system        Active   96m

すべてが Running になるのを待つ

$ kubectl -n kubeflow get pods
NAME                                                     READY   STATUS              RESTARTS      AGE
admission-webhook-deployment-7978f87497-9jjzp            1/1     Running             0             86s
cache-server-7df4ccc756-z6dg4                            2/2     Running             0             86s
centraldashboard-78dc66bf79-gxkm2                        0/2     PodInitializing     0             84s
jupyter-web-app-deployment-54f5ff876-grkqt               1/1     Running             0             86s
katib-controller-6478fbd64c-x4pwc                        1/1     Running             0             85s
katib-db-manager-78fc8b7895-jqwr9                        1/1     Running             0             85s
katib-mysql-6975d6c6c4-tcppj                             1/1     Running             0             85s
katib-ui-5cb6cc4d97-qr4tz                                1/1     Running             0             84s
kserve-controller-manager-0                              0/2     ContainerCreating   0             77s
kserve-models-web-app-5454bfdb86-v78nk                   2/2     Running             0             86s
kubeflow-pipelines-profile-controller-5b8474b7bc-fdlbr   1/1     Running             0             86s
metacontroller-0                                         1/1     Running             0             77s
metadata-envoy-deployment-f4c868c97-bxfk4                1/1     Running             0             86s
metadata-grpc-deployment-679b49cc95-skwbd                0/2     PodInitializing     0             85s
metadata-writer-7459bcd96b-vtbkw                         2/2     Running             0             85s
minio-7955cfc9fc-qp7r9                                   0/2     PodInitializing     0             84s
ml-pipeline-799556bd9f-k4bsm                             1/2     Running             0             87s
ml-pipeline-persistenceagent-848b7bbc88-9dggl            2/2     Running             0             87s
ml-pipeline-scheduledworkflow-546fc65b4c-zglls           2/2     Running             0             86s
ml-pipeline-ui-555c4f4f5d-4r2vk                          2/2     Running             0             86s
ml-pipeline-viewer-crd-5c79ccf5b6-4sljj                  2/2     Running             1 (36s ago)   85s
ml-pipeline-visualizationserver-8666b88867-rmj58         0/2     PodInitializing     0             85s
mysql-75f4964b48-6thjv                                   2/2     Running             0             85s
notebook-controller-deployment-6f645f457b-nlt65          1/2     Running             1 (19s ago)   84s
profiles-deployment-678fbc8b55-wwcqf                     0/3     PodInitializing     0             84s
tensorboard-controller-deployment-f4bfb987b-bhshx        2/3     Running             1 (9s ago)    83s
tensorboards-web-app-deployment-65b8646ff6-bw55b         1/1     Running             0             83s
training-operator-5cc8cdfdd6-6dsfq                       1/1     Running             0             83s
volumes-web-app-deployment-c49cd595f-scblx               1/1     Running             0             87s
workflow-controller-555f64865-qzkx9                      2/2     Running             1 (46s ago)   86s

すべてがRunningになったら完了


NKE の管理画面で、Persistent Volumeを確認すると Namespace 「kubeflow」、「istio-system」が増えてますね

4.KubeflowにWeb GUIにログイン

まずはポートフォワードして

kubectl port-forward svc/istio-ingressgateway -n istio-system 8080:80 --address 0.0.0.0


ブラウザで「http://master-node:8080」へアクセスしてみます

サンプルユーザーが用意されていますので、これでログインしてみます
Email Address:user@example.com
Password:12341234


ログインできたーーー



今回はとりあえず Kubeflow 環境が作れたここまで

NKE(旧Nutanix Karbon)のアップデートでハマった話

こちらの記事は、Nutanix Advent Calendar 2022 2日目の記事です

ちょっと昔に Nutanix Karbon で Kubernetes入門していました
【Nutanix Karbon】Xi IoTの為のKubernetes環境を作ってみる - konchangakita

今は Nutanix Karbon から NKE(Nutanix Kubernetes Engine) と名前にリブランドされています
この NKE を使って何かしてみようと思います


アイコンはそのまま継続の模様

だがしかし、その前にまずは NKE を最新版にアップデートしようとして、ハマったお話をご披露します

==アップデート前の環境=========
AOS 6.5
AHV 20201105.30398
Prism Central pc.2022.6
NKE 2.2.3
LCM 2.5.0.2
===========================

LCMを使ったアップデート

NKE をアップデートするには、Prism Central の LCM を使用します

1.メニューから LCM - Inventory

2.Perform Inventory

3.PCとNKEを選択してアップデートする


通常ならば、LCM を使ってこれで簡単に PC も NKE もなんならその他の機能も一発アップデートで簡単になったなぁ!、、、のはずが。。。

NKEが失敗する

PrismCentralのアップデートは正常に完了するのですが、NKEはなんだか失敗してサービスも落ちている模様
NKEのUIも落ちて無反応

いろいろ調べたところ、本当にいろいろ調べたところ、どうやら NKE 2.2.x や 2.3 からのLCMを使ったアップデートには、ちょっと作戦が必要の模様 。。。

対処法

NKE 2.2.x, 2.3 で LCM を実行すると Karbon サービスがダウンしてしまうようなので、Prism Central 上でおまじないを仕込んでやります
Karbon サービスが落ちている状態で、Prism Central に ssh 接続して、下記コマンドを順番に実行します

pcvm$ sudo cp /home/docker/karbon_core/karbon_core_config.json /home/nutanix/tmp/karbon_core_config.json.bkup
pcvm$ sudo cp /home/docker/karbon_core/karbon_core_config.json /home/nutanix/tmp/karbon_core_config_modified.json

次に、Prism Central で Karbon サービスの起動を行います

pcvm$ cluster start
2022-11-28 13:30:37,068Z INFO MainThread zookeeper_session.py:190 cluster is attempting to connect to Zookeeper
2022-11-28 13:30:37,073Z INFO Dummy-1 zookeeper_session.py:629 ZK session establishment complete, sessionId=0x184bda305f40d39, negotiated timeout=20 secs
2022-11-28 13:30:37,078Z INFO MainThread cluster:2918 Executing action start on SVMs 10.42.69.39
Waiting on 10.42.69.39 (Up, ZeusLeader) to start:  KarbonUI KarbonCore
Waiting on 10.42.69.39 (Up, ZeusLeader) to start:  KarbonUI KarbonCore
Waiting on 10.42.69.39 (Up, ZeusLeader) to start:  KarbonUI KarbonCore
・
・
・

Waiting on 10.42.69.39 (Up, ZeusLeader) to start: 
The state of the cluster: start
Lockdown mode: Disabled

        CVM: 10.42.69.39 Up, ZeusLeader
                                Zeus   UP       [8514, 8561, 8562, 8563, 8573, 8590]
                           Scavenger   UP       [10423, 11546, 11547, 11550]
                    SysStatCollector   UP       [19449, 19531, 19532, 19533]
                           IkatProxy   UP       [19894, 19980, 19981, 19982]
                    IkatControlPlane   UP       [20192, 20271, 20272, 20273]
                       SSLTerminator   UP       [20197, 20315, 20316]
                              Medusa   UP       [20422, 20484, 20485, 20548, 20845]
                  DynamicRingChanger   UP       [21116, 21223, 21224, 21301]
                          InsightsDB   UP       [21127, 21259, 21260, 21345]
                              Athena   UP       [21135, 21294, 21295, 21296]
                             Mercury   UP       [21915, 21995, 21996, 22031]
                              Mantle   UP       [21921, 22027, 22028, 22054]
                          VipMonitor   UP       [25652, 25653, 25654, 25655, 25659]
                InsightsDataTransfer   UP       [21978, 22238, 22239, 22255, 22256, 22257, 22258, 22259, 22260, 22261, 22262, 22263]
                               Ergon   UP       [22011, 22305, 22306, 22307, 22327]
                             GoErgon   UP       [22084, 22435, 22436, 22467]
                               Prism   UP       [22199, 22510, 22511, 22595, 23760, 23796]
                              Adonis   UP       [22243, 22566, 22567, 22568, 22569]
                   PrismNotification   UP       [22316, 22641, 22642, 22643]
                        AlertManager   UP       [22398, 22674, 22675, 22737]
                             Catalog   UP       [22731, 22985, 22986, 22987, 23011]
                               Atlas   UP       [22761, 23045, 23046, 23047]
                               Uhura   UP       [22835, 23075, 23076, 23077]
                       ClusterConfig   UP       [22861, 23114, 23115, 23117]
                         APLOSEngine   UP       [22907, 23146, 23147, 23148]
                               APLOS   UP       [23390, 23493, 23494, 23495]
                        StatsGateway   UP       [23401, 23555, 23556, 23637]
                               Lazan   UP       [23418, 23604, 23605, 23606]
                               Kanon   UP       [23437, 23669, 23670, 23671]
                             Polaris   UP       [24044, 24154, 24155, 24219]
                              Delphi   UP       [24054, 24190, 24191, 24192, 24206]
                          Metropolis   UP       [24574, 24670, 24671, 24808]
                                Flow   UP       [24582, 24709, 24710, 24711, 24728]
                             Magneto   UP       [24593, 24774, 24775, 24776]
                              Search   UP       [25657, 25746, 25747, 25748, 25754, 25755, 25756, 25924]
                               XPlay   UP       [25672, 25784, 25785, 25832]
                            KarbonUI   UP       [146155, 146238, 146239]
                          KarbonCore   UP       [147155, 147212, 147213]
                               XTrim   UP       [26456, 26616, 26617, 26618]
                 DataProviderManager   UP       [26473, 26667, 26668, 26711]
                              Pollux   UP       [26489, 26727, 26728, 26755, 26756]
                       ClusterHealth   UP       [26511, 26822, 27189, 27191, 27195, 27202, 27258, 27259, 27268, 27280, 27281, 27291, 27292, 27294, 27300, 27301, 27306, 27346, 27347, 27351, 27387, 27388, 27423, 27424, 27428, 27429, 27431, 27432, 27626, 27627, 27628, 27629, 27631, 27632, 27641, 27642, 27658, 27659, 27762, 27765]
                              Neuron   UP       [26563, 26879, 26880, 27097, 27126, 27127, 28236, 28249, 28272, 28273, 28284, 28292, 28293, 28303, 28326, 28327, 28334]
                          Categories   UP       [26621, 26920, 26921, 26922, 26938]
                       DomainManager   UP       [27844, 27959, 27960]
2022-11-28 13:31:16,441Z INFO MainThread cluster:3079 Success!


Karbon サービス起動を確認後に下記コマンドを実行

pcvm$ sudo sed -i '/^\s*"config".*/a \\t \t"env":{},' /home/nutanix/tmp/karbon_core_config_modified.json
pcvm$ sudo cp /home/nutanix/tmp/karbon_core_config_modified.json /home/docker/karbon_core/karbon_core_config.json


再度アップデートを試みる

LCMを使って再度アップデートを試みます



無事成功!
NKE 2.5.0 では、ちょっとインタフェースも変わったようですね

結構苦労したので今回はここまで

【Nutanix UUIDエクスプローラーを作ってみよう】Next.js特訓 Layout を作る

SSR で Fetch して表示するデータはそろってきました

ここでページ移動しても共通で表示させる「ナビゲーションバー」を Layout コンポーネントを作ってみます

【Nutanix UUIDエクスプローラーを作ってみよう】Next.js特訓
ログインページをデザイン
フォーム制御
とりあえずFetch
FetchからRouterでページ遷移
SSR で Fetch する
・Layoutつくる ←イマココ


準備

今回の最終形態はコチラ(Github)で公開しています

Layoutファイルの設置場所

Next.js のプロジェクト直下に「components」フォルダを作って、「layout.tsx」ファイルを作ります


Layoutの作成

まず初めに、tailwindcss のコンフィグファイルに Layoutファイルの場所を追記します

...
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
...


layout.tsx をまず {children} (ベースとなるページ)を表示するだけの空っぽ状態を作ってみます
<components/layout.tsx

import type { NextPage } from 'next'

import { ReactElement } from 'react'

type LayoutProps = Required<{
  readonly children: ReactElement
}>

const Layout: NextPage<LayoutProps> = ({children}) => {

  return (
    <>
        {children}
    </>
  )
}

export default Layout


あとはレイアウトを使う側のページで、ファイルの場所をインポートし タグで挟んでやるだけです
<pages/home.tsx

import Layout from '../components/layout'

    <Layout>
      ...
    </Layout>


ナビゲーションバーの実装

daisyUI の Navbar を参考に作ってみます
Navbar — Tailwind CSS Components

まだナビゲーションバーとしての役目は、まだ何もないですが、とりあえず見栄えだけ整えてみました
<components/layout.tsx

<pages/home.tsx


http://localhost:7778/home?cluster_name=クラスタ名>」
Elasticsearchからデータが取得できると一覧がドワーっと表示されます

うまくデータが取得できなかった時

さいごに

デザイン的には本当は CSS 使うのでしょうが、tailwindCSS と DaisyUI いれてるので、ほとんど使っていません
(きっともうちょっと大規模になれば必要かと)

【Nutanix UUIDエクスプローラーを作ってみよう】Next.js特訓 SSR で Fetch する

Fetch で得た レスポンスデータ を使ってページ遷移までを行いました
次に、ページ遷移した後に表示するデータをバックエンド(Flask、Elasticsearch)から取得して サーバサイドレンダリングSSR)を行います


【Nutanix UUIDエクスプローラーを作ってみよう】Next.js特訓
ログインページをデザイン
フォーム制御
とりあえずFetch
FetchからRouterでページ遷移
SSR で Fetch する ←イマココ
Layoutつくる



SSR は、ユーザからのリクエストごとに動的にデータを取得してページが作成されます
今回でいうと、Elasticsearchからデータを取得しページに表示させることになります


Next.js で SSR するには 「getServerSideProps」 を 使います

準備

今回の最終形態はコチラ(Github)で公開しています

1.コンテナ起動

docker-compose.yml からコンテナたちを起動します

2.プログラム起動

コンテナ frontend、backend のコンテナ内で
・フロントエンドの起動
"/usr/src/next-app/uuid"

# npm run dev

・バックエンドの起動
"/usr/src/flaskr"

# python app.py
3.ブラウザアクセス

ブラウザから「http://localhost:7778/」にアクセス

4.Nutanixクラスタの IP/ユーザー/パスワード を入力してログイン


getServerSideProps の使い方

遷移先のページで 「getServerSidePropsType」作ってやることで、データ取得してからページレンダリングされます
(なのでページ表示まで時間がかかる)

以下の例では、URL の GETパラメータを Next.js コンソールに表示するようにしています

import type { GetServerSideProps, InferGetServerSidePropsType } from 'next'
type Props = InferGetServerSidePropsType<typeof getServerSideProps>

export const getServerSideProps: GetServerSideProps = async context => {
  console.log('query: ', context.query)

  /* ここに fetch などの処理 */

  return {
    props: {}
  }
}

TypeScript では 型を気にしないといけないので、いろいろつけてます

こんな URL なら

こんな 表示


SSR で Fetch

では、この GET パラメータを使って Fetch してみます
この記事のバックエンドで作ったAPI /api/latestdataset を少し改良したデータを取ってきます
※まだ return していません
フロントエンド<pages/home.tsx

export const getServerSideProps: GetServerSideProps = async context => {
  //console.log('query: ', context.query)
  const keyword = context.query

  const requestOptions = {
    method: "POST",
    headers: {'Content-Type' : 'application/json'},
    body: JSON.stringify(keyword)
  }

  console.log(requestOptions)

  const fetchUrl = "http://backend:7777/api/latestdataset"
  const response = await fetch(fetchUrl, requestOptions)
  if (response.ok) {
    var res:dict = await response.json()
  } else {
    var res:dict = { 'list' : '' }
  }

  console.log(res)

  return {
    props: {}
  }
}

Next.js のコンソール


SSRでデータを表示

getServerSidePropsType を使って、取得したデータを使ってページレンダリングするには、return で変数を返してやります

  ...

  return {
    props: { res }
  }


次に受け取る側の関数で、return された同じ変数名で受け取ります

const Home: NextPage<Props> = ({res}) => {
  console.log(res)

  ....
}

export default Home


バックエンドから受け取ったデータをそのまま表示する流れがコチラ

フロントエンド:<pages/home.tsx

バックエンド:<flaskr/app.py>

getServerSidePropsType を使ってバックエンドから取得したデータをただ表示しているだけなのでだいぶブサイクです
次回、表示を整えます


SSRは終了

これでバックエンドとのやりとりの部分は完了です
ここからは Next.js の中でどんな風に表現したいのかを考えながら、データを取り扱っていきます

【Nutanix UUIDエクスプローラーを作ってみよう】Next.js特訓 FetchからRouterでページ遷移

前回はとりあえず fetch してレスポンスを確認しただけだったので、フォーム入力されたデータを使って、ページ遷移します

下記エントリで Native React で Fetch を作り込んだ「/api/connect」、「/api/latestdataset」からデータひっぱってきたいと思います
konchangakita.hatenablog.com



【Nutanix UUIDエクスプローラーを作ってみよう】Next.js特訓
ログインページをデザイン
フォーム制御
とりあえずFetch
・FetchからRouterでページ遷移 ←イマココ
SSR で Fetch する
Layoutつくる


準備

今回の最終形態はコチラ(Github)で公開しています

この docker-compose.yml からコンテナたちを起動します

コンテナ frontend、backend のコンテナ内で
・フロントエンドの起動
"/usr/src/next-app/uuid"

# npm run dev

・バックエンドの起動
"/usr/src/flaskr"

# python app.py

ブラウザから「http://localhost:7778/」にアクセス

POST で fetch

前回とりあえず fetch の練習だけおこなっていましたが、
onConnect関数 にフォームにインプットされたデータを使って POST します

POSTで渡す値は、json形式にします

const requestOptions = {
  method: "POST",
  headers: {'Content-Type' : 'application/json'},
  body: JSON.stringify(data)
}


関数に組み込んでみると
フロントエンド:<pages/index.tsx

  const onConnect: SubmitHandler<FormValues> = async data => {
    console.log(data)
    const requestOptions = {
      method: "POST",
      headers: {'Content-Type' : 'application/json'},
      body: JSON.stringify(data)
    }

    const response = await fetch('/api/connect', requestOptions)
    if(response.status === 200) {
      const res_json = await response.json()
      console.log(res_json)
      if(res_json.info === 'success') {
        console.log(res_json)
      } else { alert('login failed') }
    }
  }


CORS問題対策に間に挟む別ファイルの関数にも、POSTで受け取ったデータを使って、バックエンドへ POST で fetch しています

フロントエンド:<pages/api/connects.ts>

import { NextApiRequest, NextApiResponse } from 'next'

export default async (req: NextApiRequest, res: NextApiResponse) => {
  const requestOptions = {
    method: "POST",
    headers: {'Content-Type' : 'application/json'},
    body: JSON.stringify(req.body)
  }

  const response = await fetch("http://backend:7777/api/connect", requestOptions)
  const data = await response.json()

  // response.status > success 200
  res.status(response.status).json(data);
}

fetch でデータをとってくる先は、バックエンドの flask で作った 「Nutanixクラスタに接続しクラスタ名を取得し、データを Elasticsearch へインプットする」関数です
関数の中身の説明はココ
【Nutanix UUIDエクスプローラーを作ってみよう】Flask表示までのまとめ - konchangakita


Nutanixクラスタのログイン情報をインプットし、無事接続できると Elasticsearch にデータ入力後にクラスタ名がレスポンスで返ってきます


fetch 後にページ遷移

fetch してデータ取得に成功後にページ遷移をさせたい場合には、Next.js機能の Router を使います

import { useRouter } from 'next/router'

router.push({
  pathname: '/home',
  query: {
    cluster: res_json.cluster_name,
  }
})

こうすることで、ページ遷移の際に fetcth のレスポンスとして受け取ったクラスタ名を含んだ「http://~~/?cluster=クラスタ名」という GET のURLとなります


関数に組み込んでみます

  const onConnect: SubmitHandler<FormValues> = async data => {
    console.log(data)
    const requestOptions = {
      method: "POST",
      headers: {'Content-Type' : 'application/json'},
      body: JSON.stringify(data)
    }

    const response = await fetch('/api/connect', requestOptions)
    if(response.status === 200) {
      const res_json = await response.json()
      console.log(res_json)
      if(res_json.info === 'success') {
        console.log(res_json)
        router.push({
          pathname: '/home',
          query: {
            cluster: res_json.cluster_name,
          }
        })
      } else { alert('login failed') }
    }
  }


遷移先のページ作成

遷移先のページを「pages/home.tsx」で作ります
URL の GETパラメータをページ上に表示するだけのページを作ります

フロントエンド:<pages/home.tsx

import type { NextPage } from 'next'
import { useRouter } from 'next/router'

const Index: NextPage = () => {
  const router = useRouter()
  const {cluster} = router.query
  return (
    <div className="">
      <main data-theme="white" className="h-screen flex justify-center items-center">
        {cluster}
      </main>
    </div>
  )
}

export default Index

fetch に時間をかける

fetchしてページ遷移しましたが、次はページ遷移した先で fetch して SSR(サーバサイドレンダリング) を行います

【Nutanix UUIDエクスプローラーを作ってみよう】Next.js特訓 とりあえずFetchする

フォームで受け取ったデータ(Prism IP、ユーザ名、パスワード)を使ってバックエンドにリクエスト行い、レスポンスを取得する Fetch を実装します
SSR(サーバサイドレンダリング)までは実装は次で

Next.jsの実装とともに、フロントエンドとバックエンドのコンテナを分けたことによって少々手間がかかります(CORS問題)


【Nutanix UUIDエクスプローラーを作ってみよう】Next.js特訓
ログインページをデザイン
フォーム制御
・とりあえずFetch ←イマココ
FetchからRouterでページ遷移
SSR で Fetch する
Layoutつくる


準備

今回の最終形態はコチラ(Github)で公開しています

この docker-compose.yml からコンテナたちを起動します

コンテナ frontend、backend のコンテナ内で
・フロントエンドの起動
"/usr/src/next-app/uuid"

# npm run dev

・バックエンドの起動
"/usr/src/flaskr"

# python app.py

ブラウザから「http://localhost:7778/」にアクセス


curl でレスポンスチェック

プログラムに組み込む前に、curlコマンドを使ってチェックします

まずはバックエンド側にテスト用に簡単なレスポンスを仕込みます
バックエンドコンテナ:<flaskr/app.py>

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

.........
@app.route('/api/fetchtest')
def fetchtest():
    return make_response(jsonify('Fetch Success'))
.........

これでフロントエンドコンテナのコンソールから「http//コンテナ名:ポート番号/api/fetchtest」向けに、curlコマンド実行してやると、仕込んでおいたレスポンスを得られます

# curl http://backend:7777/api/fetchtest
"Fetch Success"


フロントエンドに実装

前回までで、フォームをサブミットすると、入力内容をコンソールに表示するだけだったこの部分の関数を編集していきます

フロントエンド:<pages/index.tsx
コレを

  const onConnect: SubmitHandler<FormValues> = data => console.log(data)

こんな感じで

  const onConnect: SubmitHandler<FormValues> = async data => {
    console.log(data)
    const requestOptions = {
      method: "POST",
      headers: {'Content-Type' : 'application/json'},
      body: JSON.stringify(data)
    }
    const response = await fetch('http://backend:7777/api/fetchtest')
    if(response.status === 200) {
      const res_json = await response.json()
      console.log(res_json)
    }
  }


そうすると、、、なぜだかうまくいきません。。

いろいろ調べてみると、フロントエンドとバックエンドのコンテナを分けたことによってCORS問題が発生している模様
さらにいろいろ調べてみましたが、正直なところ生半可な知識では解決方法は見つかりませんでした。。。

なので、直接このプログラムファイルから Fetch するのではなく、あいだにファイルを一個噛ませて2段階でやるとうまく取得できました

仕込みの手順

1.pagesフォルダ配下にapiフォルダを作成
2.connect.tsを作成
3.index.tsxでfetch(pages/api/connect.ts向け)
4.connect.tsでfetch(backend向け)

フロントエンド:<pages/index.tsx


フロントエンド:<pages/api/connect.ts>

ブラウザのコンソールログ

next.jsのログ


とりあえずの Fetch

CORS問題で一晩以上悩んだ結果、別ファイルを用意するという回りくどい方法をとりました
次でフォームのデータを使っての、リクエストを行います

【Nutanix UUIDエクスプローラーを作ってみよう】Next.js特訓 フォームの実装

Nutanixクラスタに接続するための「Prism IP」、「ユーザ名」、「パスワード」フォーム入力の制御には、通常入力を必須にしたり、エラーメッセージの実装を作り込む必要があります
そうすると、フォームごとになかなかコードが増えてくし、なによりそれぞれ作るのはメンドウです。。。
というわけで、フォームの制御に Next.js に対応するReact Hook Formを使ってみます
ホーム | React Hook Form - Simple React forms validation


【Nutanix UUIDエクスプローラーを作ってみよう】Next.js特訓
ログインページをデザイン
・フォーム制御 ←イマココ
とりあえずFetch
FetchからRouterでページ遷移
SSR で Fetch する
・Layoutつくる

今回の成果物

今回の最終形態はコチラ(Github)で公開しています

React Hook Formのインストール

インストールは npm だけなので、簡単
next.jsのプロジェクトのディレクトリにて

# npm install react-hook-form


テスト実装

公式サイトの クイックスタート を参考にしながら進めていきます
サンプルコードの TS を押すと TypeScript 版での実装方法も書かれていて、親切で初心者にもウレシイ
これを参考に、現行のページに導入してみます


1つ目のポイントは、フォームの input に対応する Value を設定

type FormValues = {
  prism_ip: string
  prism_user: string
  prism_pass: string
}

あとは、React Hook Form のお作法に従う

  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormValues>()


フォームの input には 「{...register("prism_ip")}」という書き方で 設定した Value を呼び出します

<input {...register("prism_ip")} type="text" placeholder="Cluster IP" className="input input-info input-bordered m-1 w-64 text-lg" />
inputのオプション

React Hook Form の機能で入力必須にしたり、エラー表示も簡単に
入力必須:{...register("prism_ip", {required: true})}

<input {...register("prism_ip", {required: true})} type="text" placeholder="Cluster IP" className="input input-info input-bordered m-1 w-64 text-lg" />
エラー表示:{errors.prism_ip && 表示する内容}
{errors.prism_ip && <p className='text-red-600'>required.</p>}

インプットされた内容の処理

フォームで入力された内容は、Submitボタンを押されたときに onSubmit={handleSubmit(関数名)}で処理されます

<form onSubmit={handleSubmit(onConnect)}>


一旦、入力内容をコンソールに表示するだけならば

const onConnect: SubmitHandler<FormValues> = data => console.log(data)



実装

ここまでの実装をまとめると

いったんここまで

そもそもフォーム制御するライブラリというものがあるなんて考えてなかったので、はじめはとまどいながら実装してみましたが、一回慣れる手放せません
フォームで受け取ったデータを使っての fetch を次回