NextJSでprismaを使用してDBを操作した際にTypeError: The "payload" argument must be of type object. Received null`
背景
- NextJS製のアプリケーションを作っていて、prismaでDBを操作した際にタイトルのエラーが発生した
- NextJS 15で発生するものらしい
対応方法
GitHubのissueを参照
- 下記のissueが作成されている
- issue作成者の方は
prisma.someTable.createで発生したと言っているが、私はprisma.someTable.createManyで発生した- もしかしたら他のメソッドでも発生するのかも
解決方法
- 参考情報
To temporarily work around this issue, you need to remove the line //# sourceMappingURL=library.js.map from node_modules/@prisma/client/runtime/library.js.
node_modulesディレクトリの中にある依存ライブラリのスクリプトを直接編集することで解消できたnode_modules/@prisma/client/runtime/library.js.の以下の部分を削除する
//# sourceMappingURL=library.js.map // これを削除
patch-packageの実施
node_modulesを削除して再度npm ciなどをした場合に自動的に上記の変更が適用されるようにしておきべき- これについても issue のコメントに記述がある
- ただしコメントのとおり
npm i -D patch-packageとすると、production環境向け環境でnpm ciしたときにpatch-packageがインストールされない-D (--save-dev)は開発環境向け(NODE_ENV=development)のときにインストールされるnpm i --save patch-packageでインストールしたほうがいいのかな(またはNODE_ENV=development npm ciとしてインストールするとか?)
参考情報
関連issue
【自宅kubernetes】Argo CDでGiteaをデプロイする
目次
概要
今回やること
今回はやらないこと
- GitOpsの体験
Argo CDのデプロイ
helm を用いてデプロイする
# values.yaml server: ingress: enabled: true hostname: argocd.nkiri.internal ingressClassName: nginx path: / pathType: Prefix configs: params: "server.insecure": true # 後述
$ helm repo add argo https://argoproj.github.io/argo-helm $ helm install argocd argo/argo-cd -n argocd --create-namespace -f values.yaml
Podの状態を確認
STATUSがRunningになることを確認- 別のPodの起動を待つこともあるのでしばらくは再起動を繰り返すこともある
$ kubectl get pods -n argocd NAME READY STATUS RESTARTS AGE argocd-application-controller-0 1/1 Running 0 6h35m argocd-applicationset-controller-766769bf76-sntnb 1/1 Running 0 6h35m argocd-dex-server-6df46f4b79-fmtw6 1/1 Running 0 6h35m argocd-notifications-controller-54dd575496-gc2tp 1/1 Running 0 7h32m argocd-redis-6ddc76cf75-4c59l 1/1 Running 0 7h32m argocd-repo-server-79f78f6844-mw9ns 1/1 Running 0 6h35m argocd-server-5cc5569c8-9c8ws 1/1 Running 0 6h35m
ブラウザからの確認
- ingress-nginxのLoadBalancerに割り当てられたExternalIPと、
values.yamlのserver.ingress.hostnameで指定したドメインの紐付けを/etc/hostsに追記 https://<values.yamlで指定したホスト名>でアクセス- ユーザ名:
admin - パスワード: 以下のコマンドで確認
- ユーザ名:
$ kubectl -n argocd get secret/argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
【おまけ】ERR_TOO_MANY_REDIRECTSが出た場合
ERR_TOO_MANY_REDIRECTSとなってArgo CDのWeb UIにアクセスできないことがある- その場合は
values.yamlにてconfig.params."server.insecure"をtrueにすると解消するらしい - 詳細は StackOverflow を参照
- その場合は
Giteaのデプロイ
Argo CDの画面からGiteaのHelmChartを登録
- Argo CDにログインし、トップ画面 >
Applications>NEW APPと遷移し、以下を入力(言及してない部分はデフォルトのまま)Application Name: giteaProject Name: DefaultSYNC POLICY: AutomaticSYNC OPTIONS:AUTO-CREATE NAMESPACEのみチェックRepository URL: https://dl.gitea.io/charts/ (右側のプルダウンはHELMを選択)Chart: gitea (右側のプルダウンは10.4.0(記事作成時点での最新)を選択)Cluster URL:https://kubernetes.default.svcNamespace: gitea
- 画面上部の
CREATEをクリックするとクラスタ上にGiteaがデプロイされる
確認
- デプロイしたアプリケーションの状態

- 各リソースの状態

このあとやりたいこと
- Giteaでアプリケーションを管理し、リポジトリの更新をトリガーとして自動的にクラスタにデプロイするみたいなことをやりたい
- アプリケーションの登録手順とかは多分今回のGiteaのやり方と同じだと思っているので、設定の更新などが自動で動く様子を確認したい
【追記】Argo CDのデータ永続化について
- ArgoCDデプロイ時の設定項目に
persistenceに関する情報がなかったので気になって調べた- こちらのIssueによると、CRDやConfigMap/Secretsにデータを保存する仕組みになっているらしく、PVCを作成する必要はないらしい
自宅kubernetesクラスタをとりあえず動くようにする
やりたいこと
- 自宅にkubernetesクラスタを作りたい
- 作った後のことは作った後に考える
構成
- クラスタ:
- VM2台(ControlPlane1台、Worker1台)
- OS: Ubuntu 22.04
- 記事作成時点で最新は24.04だけど理由あって22.04を使用している
- OS: Ubuntu 22.04
- kubernetes: v1.30.3
- VM2台(ControlPlane1台、Worker1台)
- NW
- 自宅NW: 192.168.11.0/24
- ControlPlaneノード: 192.168.11.110
- Workerノード: 192.168.11.115
- PodNetworkCidr: 172.244.0.0/16
kubeadm init実行時に指定
- 自宅NW: 192.168.11.0/24
クラスタ環境のセットアップ
CRIとしてCRI-Oをインストール
インストール後はこちらの手順も忘れ時に実施
kubernetes関連の諸々をインストール
- 公式手順に従ってインストールする
クラスタの起動
ControlPlaneノードでのみ以下を実行
kubeadm init <args>をするkubeadm initのオプションは以下の通り(不要なものもあるかもしれないがとりあえず動いたのでヨシ)
$ sudo kubeadm init --apiserver-advertise-address=192.168.11.110 --control-plane-endpoint=192.168.11.110 --pod-network-cidr=172.244.0.0/16
Workerのjoin
- Workerノードでのみ実行
- 実行するコマンドはControlPlaneで
kubeadm initしたときに出力されるのでそれをコピペ
動作確認
$ kubectl get nodes
を実行してControlPlaneとWorkerがそれぞれReadyになっていればクラスタの起動はOK
Pod / ReplicaSet / Deployment を動かす
Ubuntu24.04でKVMの環境を作る
概要
目的
やること
触れないこと
- LVM周り
- とりあえず
virt-installでVMを起動するところまで
- とりあえず
前提情報
HW構成
- CPU: Ryzen9 7900
- OS: UbuntuServer24.04
- ホストもゲストも同じ
- メモリ: 32GB
- ストレージ: SSD 1TB
NW構成
- サブネット: 192.168.11.0/24
- ホストOS: 192.168.11.100/24
- インストール時にManual設定
手順
パッケージインストール
sudo apt update sudo apt upgrade sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virtinst
- ここでインストールしたパッケージが何のためのものなのかは以下を参考にさせていただいた
ユーザをlibvert/kvmグループに追加
sudo usermod -aG libvirt $USER sudo usermod -aG kvm $USER # 反映のために一度 logoutし、再度loginする
libvertdの起動と自動起動設定
sudo systemctl start libvertd sudo systemctl enable libvertd
ネットワーク設定
- 今回はブリッジ接続を行うため、それ用のI/Fを作成する
ホストOSインストール時にNW設定をしたためか、デフォルトで
/etc/netplan/50-cloud-init.yamlが作成される- 本来このファイルは手動で編集するべきではないらしいが、筆者はエラーを解消することができなくてイライラしたのでこのファイルを編集した
- たぶん本当は
99-manual-config.yamlとか作って設定するのが正しいんだけど、どう書けばよかったんすかね。誰か教えて。
以下の通り編集(冒頭のコメントを消したのは「自動生成したもの」という情報が嘘になるから)
$ sudo diff -u /etc/netplan/50-cloud-init.yaml.orig /etc/netplan/50-cloud-init.yaml --- /etc/netplan/50-cloud-init.yaml.orig 2024-08-01 12:42:21.662094055 +0000 +++ /etc/netplan/50-cloud-init.yaml 2024-08-01 12:44:23.154575704 +0000 @@ -1,11 +1,11 @@ -# This file is generated from information provided by the datasource. Changes -# to it will not persist across an instance reboot. To disable cloud-init's -# network configuration capabilities, write a file -# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following: -# network: {config: disabled} network: ethernets: enp3s0: + dhcp4: false + bridges: + br0: + interfaces: + - enp3s0 addresses: - 192.168.11.100/24 nameservers:
- 反映
sudo netplan apply
VMの起動
前提
- isoファイルは
/tmpに置いておく
コマンド
sudo virt-install \
--name ubuntu-vm \
--ram 4096 \
--disk path=/var/lib/libvirt/images/ubuntu-vm.qcow2,size=20 \
--vcpus 2 \
--os-variant ubuntu24.04 \
--network bridge=br0 \
--graphics none \
--console pty,target_type=serial \
--location '/tmp/ubuntu-24.04-live-server-amd64.iso,initrd=casper/initrd,kernel=casper/vmlinuz' \
--extra-args 'console=ttyS0,115200n8 serial'
トラブルシュート
ERROR Couldn't find kernel for install tree.
- こんな感じのやつ
ERROR Couldn't find kernel for install tree. Domain installation does not appear to have been successful.
- 解決策
--locationオプションで指定している値にあるとおり `initrd=casper/initrd,kernel=casper/vmlinuz'を追記すると発生しなくなった- これが何のための設定なのかは未確認
ゲストOSのセットアップ
- これは通常のセットアップと同じなので割愛
まとめ
- 昔と色々状況が変わっているのか、「こんなんだっけ…?」となる部分が多かった
- virt-installのオプションなど、よくわかっていないで使っているものもあるのでこれから学びながら遊んでいく
ヒアドキュメントで入力した文字列がコマンドとして実行されてしまう件を調査してみた
背景
業務でbashのヒアドキュメントを使う場面があったが、知らない挙動があったのでマニュアルを読んで理解する
知りたいこと
- ヒアドキュメントの入力として書いたコマンドが文字列として解釈される場合とコマンドとして解釈される場合の違いについて
# 1パターン目 $ cat <<EOS ... EOS # 2パターン目 $ cat <<'EOS' # 終端語がquoted ... EOS
bashのマニュアルを読む
原文
意訳
- 以下の形式で書く
[n]<<[-]word
here-document
delimiter
wordには変数、コマンド、算術計算、ファイル名展開は使えないwordのいずれか部分がquotedの場合delimiterはquoteを除外したwordとなるhere-documentの行は展開されない
wordがunquotedの場合here-documentはパラメータの展開、コマンド、算術計算などが行われる- 改行のための
\newlineは無視される - 「\」、「$」、「`」の文字を引用するには「\」を使用する必要がある
試す
wordがquoted
$ WORD=hoge cat <<"EOS" > echo $WORD > EOS # -> echo $WORD ( 変数が展開されずに出力される
wordがunquoted
$ WORD=hoge cat <<EOS > echo $WORD > EOS # -> echo hoge ( 変数が展開されて出力される
レガシーシステムと向き合う
この記事で扱うこと
- 自身が所属しているチームが開発・運用しているレガシーシステムに我慢ができなくなり、作り直しはできずともちょっとだけマシにするためにやったことのメモ
- 既に対応実施から数年たったが「以前のほうがよかった」という声は一切ないので、取り組みは成功だったと思っている
実施前の状態について
システムについて
開発するための環境
コーディング環境
秘伝のVM運用
-
- テーブルは作成済みで、データも投入済み(過去に使用したゴミデータも含まれる)
- DBに更新があった場合は誰かが更新を適用したVMイメージを作成してチーム内に配布
- テーブルは作成済みで、データも投入済み(過去に使用したゴミデータも含まれる)
コーディング規約
- 口伝のルールがあったらしいが、実質ないようなものだった
エディタ
コーディング環境の問題点
テスト環境
動作確認
自動テスト
- なし
テスト環境の問題点
- ローカルのVM環境でのテストが信頼できない
- その人の環境でだけ動くコードなどが発生する
- 共用開発環境は他の誰かが使っているときは使えない
- VM上に過去の作業によって生じたゴミデータが永遠に蓄積される
- DBにゴミを貯めたままVMイメージが配布される
- 簡単な関数処理の動作確認ですら共用開発環境で動かさないとミスに気付けない
- 想定していない箇所への影響が検知できない
運用
デプロイ
作業の流れ
- 更新対象のファイルを手動でリストアップ
- リストアップしたファイルについて、既存ファイルをサーバ上でバックアップ
- 何らかのサフィックスをつけてcpするだけ
- 更新対象ファイルを置き換え
- 切り戻しの際はリストアップしたファイルをバックアップファイルで置き換え
デプロイの問題点
- リストアップした一覧に漏れが生じる
- 作業を全て手作業で実施するため、ミスや実施漏れが生じる
- 前述の共用の開発環境へのデプロイや戻し作業も同様の方法で行うため、戻し作業にも漏れが生じる
- 後続作業で想定外の事象が起きた時の切り分けが困難
- ミス防止のため(?)、チームメンバー全員で作業の見守りをしていた
- 5人で60~90分くらい作業してたので工数的も問題あり
- それでもミスは頻発していたのでマジで無意味な時間だった
目的意識
やりたかったこと
デプロイ作業の簡易化
- 作業工数の削減
- ミスを削減したかった
自動テスト
- 簡単な処理の動作保証
コードフォーマットの自動化
静的解析
- 文法チェック
- インタプリタ言語がメインだったので実行前にエラーを検知
- linterの導入
- 文法チェック
やらないようにしたかったこと
取り組みについて
前提
システム全ての作り直しはしない
- そんな時間も金もない
- 最低限として「数年以内に破綻しないための施策」を検討
今までの開発と並行して実施
- 「内部改善をしてるのでエンハンスできません」はダメ
ゴール
開発のための環境的な話
- VM配布を廃止し、dockerを利用できるようにする
- 共用dockerイメージの作成
- 動作環境としての仕組みだけを持たせて、DBの変更による更新は発生させない
- 共用dockerイメージを使用した最低限の動作確認方法の確立
- 共用dockerイメージの作成
運用的な話
CIによるチェックで最低限の品質を担保する
- 文法チェック
- linter
- 単体テスト
デプロイスクリプトを作成する
- スクリプトを一つでファイル配置を実施
デッドコードの削除
- どこからも呼び出されてない処理の削除
やったこと
dockerイメージの作成
perl用イメージ
DB用イメージ
- DBインスタンスを起動するだけのイメージ
docker-compose.yml
- perlイメージのDBイメージからコンテナを起動し、お互いに疎通できる状態を作る
メリット
デメリット
- レガシーシステムをやってる人だとdockerに触れたことがない人が多いかも
- 「dockerよくわからん」って人でも容易に使えるように手順を固める必要はありそう
CI環境の構築
内容の定義
- 変更されたファイルに対して、
perl -cwで文法チェックを実施- そもそも実行できないものをここで弾く
- 変更されたファイルに対して、
perlcriticでlint- 推奨されない記述はここで弾く
- 変更されたファイルに対して、
perltidyでフォーマットチェック- 動かすことはできるけど書き方に問題があるものはここで弾く
- リポジトリ全体のテストコードの実行
- 既存処理に影響を与える変更をした場合はここで検知する
"変更されたファイルに対して" について
- リポジトリ全体に
perl -cwやperlcriticの処理を実行すると大量のファイルで問題が検知される- 一つ一つ直すとエンハンスが止まるため、前提に反する
- →何らかの変更を加えた際、その人の書いたコードに問題がなくても既存コードのせいでエラーになることがある
- 運が悪かったと思ってその人が修正を実施するというルールを定めて運用
perltidyだけは最初に全体に適用した- 同様に大量のファイルで問題が検知されるが比較的影響は少なくすぐに対応が可能だったため
perltidyについて
- perltidyは自動的にフォーマットを整えてくれるツールだと思っている
a.plを対象に実行すると元ファイルはa.pl.bakみたいな名前に置き換えられ、a.plは整形された状態になるa.plが整形された状態であれば、a.pl.bakは生成されない
- なのでCI上では
*.bakが存在する場合にエラーとしている - → CIで整形したものをcommitすればいいのでは?という声もあった
- 同チーム内で 「perltidyで整形したものが信頼できないから一通り目で確認したい」という声もあった
- 「信頼できない」と言っているものを自動commitさせる意味がわからなかったため却下とした
- コードを書いた人間が責任を持って整形した状態をcommitすることとした
テストについて
- 今までテストコードなんて書いたことがないという人ばかりだったので、実際にサンプルとしてテストコードを作成して動かした
- 全ての既存コードのテストをいきなり書くことは無理なので、「今日以降変更したところはテストを書きましょう」というルールを定めて運用
- ただしどうしてもテストコードを書く手間が大きい部分(画面操作のテストとか)は無理してテストコードは書かず、共用環境などで打鍵テストを行うこととした
メリット
- 共用開発環境を使う前にある程度確認ができるため、共用環境が長時間占有されることがなくなった
- → 品質担保、共用環境の待ち行列の解消
デメリット
- 既存コードのせいでエンハンス時に余計な手間がかかる
- 最初に
perl -cw/perlcriticを実行し、エラーを解消してから実装に着手すると比較的マシ
- 最初に
CI環境のDBセットアップについて
- セットアップ用のスクリプトを作成した
メリット
- DB更新があってもdockerイメージ自体の更新は不要
デメリット
- CIを実行するたびにテーブルから作り直すので若干時間がかかる
デプロイスクリプトの作成
スクリプトの処理内容
- gitリポジトリの特定のブランチの状態を常に正とし、そのブランチに含まれるコードで全てを置き換える
置き換え処理について
git cloneで取得したリポジトリはリポジトリ名_バージョンのディレクトリに保存し、実行されるcgiやperlスクリプトはこのcloneしたリポジトリ内のファイルをシンボリックリンクで参照するdockerコンテナや共用開発環境へのデプロイも同じスクリプトで実施
メリット
デメリット
- とくになし
デッドコードの削除
不要コード候補の抽出
- 全ての関数定義と関数呼び出し処理の抽出を行い、愚直に調査を行った。
- ひたすら
grepを駆使
- ひたすら
- どこからも呼び出されてなさそうな処理を不要コード候補としてリストアップ
不要コード候補の監視
- 不要コード候補にログ出力処理を追加する
- 「ここの処理は不要コード候補だったけど、○○から呼び出されたぜ」みたいな
- この状態で一定期間運用を続けて、仕込んだログが出力されなければ不要コードと判断する
不要コードの削除
- 不要コードが確定したらコードを削除する
- もしかしたら監視期間中に偶々呼び出されなかっただけで、実際には不要ではないコードを消してしまう可能性もあるので、切り戻し準備はしておく
- そういうリスクがあることを理解し、過剰に恐れず対応を進めるしかない
繰り返し
- 不要コードを削除することにより、再度不要コード候補が発生する可能性がある
- 定期的に削除と抽出を繰り返していく必要がある
まとめ
- 対応を実施してから3年くらい経つが問題は発生していないし、むしろテストを書いてたおかげでトラブルを事前に防ぐことができたこともある
- とはいえ「やって!」と言ってやってくれる人なんていないと思うので、最初のうちは自らが行動を起こしていくしかない。具体的には以下のようなことをする。
- 取り組みを理解してくれる味方をつくる
- dockerイメージやCIの設定の作成
- テストコードのサンプル作成
- 既存コードのめっっっちゃ簡単な処理のテストでOK
perl -cwやperlcriticでエラー対応方法をまとめる- 他の担当者の対応ハードルを下げる
- 継続してもらうためにはできるだけめんどくさい手順を省くとよいかも
- 全てを作り直すことはできなくても最低限守るべきところを守ることで、作り直しができるようになるまでの延命は可能になると思う
参考
nextjs + react-chartjs-2 でウォーターフォールチャートを描く
やりたいこと
- ウォーターフォールチャートを描きたい
- next.jsで
環境
- "next": "13.4.9",
- "chart.js": "^4.3.0",
- "react-chartjs-2": "^5.2.0",
今回触れないこと
- react / nextjs の話
- tailwindcss の話
- chart.js の話
- TypeScriptの話(勉強中なので許して…)
実装
WaterfallChartのコンポーネントを作成
方針
- 普通にBarChartを描くコードを作り、dataをいじるだけ
準備
$ npx create-next-app # nextjsアプリケーションの雛形を作成 $ cd <dir> # プロジェクトのディレクトリに移動 $ npm install --save chart.js react-chartjs-2 # 必要なパッケージのインストール $ npm run dev # 起動
WaterfallChart コンポーネントの作成
まずは普通の棒グラフ
src/app/WaterfallChart.tsxを作成
"use client"; import { Bar } from "react-chartjs-2"; import { Chart as ChartJS, BarElement, CategoryScale, LinearScale, Title, Tooltip, Legend, } from "chart.js"; ChartJS.register( CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend ); const options = { plugins: { title: { display: false, }, }, responsive: true, interaction: { mode: "index" as const, intersect: false, }, scales: { x: { stacked: true, }, y: { stacked: true, }, }, }; const data = { labels: ["期初", "1Q末", "2Q末", "3Q末", "期末"], datasets: [ { label: "在庫数", data: [100, 300, 200, 50, 200], // 数値は適当です。「在庫数がそんな遷移するわけねーだろ」などの指摘はしない }, ], }; export const WaterfallChart = () => { return <Bar options={options} data={data} />; };
src/app/page.tsxを変更- 自動生成されたもののmain要素内を削除
tailwindcss関連は今回は変更していない
- 作成したWaterfallChartを埋め込む
- 自動生成されたもののmain要素内を削除
import { WaterfallChart } from "./WaterfallChart"; export default function Home() { return ( <main className="flex min-h-screen flex-col items-center justify-between p-24"> <WaterfallChart /> </main> ); }
- 結果表示

WaterfallChart形式にする
- data.datasets[0].data を
number[]から(number|number[])[]に変更する
--- a/src/app/WaterfallChart.tsx +++ b/src/app/WaterfallChart.tsx @@ -41,11 +41,18 @@ const options = { }; const data = { - labels: ["期初", "1Q末", "2Q末", "3Q末", "期末"], + labels: ["期初", "1Q", "2Q", "3Q", "4Q", "期末"], datasets: [ { label: "在庫数", - data: [100, 300, 200, 50, 200], + data: [ + 100, // number型の要素は通常の棒グラフとして描画 + [100, 300], // number[]型の要素は0番目の要素~1番目の要素の区間のみをグラフに描画 + [300, 200], + [200, 50], + [50, 200], + 200, + ], }, ], };
- 結果表示

増減による色の変更
- 増加した期間は青、減少した期間は赤でグラフを表示する
diff --git a/src/app/WaterfallChart.tsx b/src/app/WaterfallChart.tsx index df2f7e1..38f517e 100644 --- a/src/app/WaterfallChart.tsx +++ b/src/app/WaterfallChart.tsx @@ -53,10 +53,26 @@ const data = { [50, 200], 200, ], + backgroundColor: backgroundColor(), }, ], }; +function backgroundColor() { // datasets.dataの各要素の状態によって色を分岐 + return (ctx: any) => { + if (!ctx.parsed._custom) { // number型要素の場合は黄色で表示 + return "rgba(255, 255, 102, 0.5)"; + } + + const start = ctx.parsed._custom.start; + const end = ctx.parsed._custom.end; + + return end >= start + ? "rgba(102, 178, 255, 0.5)" // 増加 + : "rgba(255, 102, 178, 0.5)"; // 減少 + }; +} + export const WaterfallChart = () => { return <Bar options={options} data={data} />; };
- 結果表示
