🔎

finchでunexpected end of JSON inputエラーに対処する

2024/02/25に公開

エラー内容

mac上でのコンテナを使ったアプリケーション開発でFinch CLIを利用しているが、ある日、finch composeでコンテナを立ち上げようとしたとき、以下のようなエラーが起こり、コンテナを立ち上げることができなくなった。

$ finch compose up
FATA[0000] failed to check for default network: error parsing configuration list: unexpected end of JSON input
FATA[0000] exit status 1

発生環境

macOS: Sonoma 14.1.1
チップ: Apple M1 Max
finch version: v1.1.1

最終的にエラーを解消した方法

いろいろ調べてみたところ、最終的にはコンテナのネットワーク設定ファイルの一つが空ファイルになってしまっていたことがエラー発生の原因であることがわかったので、それを削除してみたところ、エラーは解消してコンテナが立ち上げられるようになった。

  • finch VMへ接続する
LIMA_HOME=/Applications/Finch/lima/data /Applications/Finch/lima/bin/limactl shell finch
  • ネットワークの設定ファイルを探して、削除する
$ sudo ls /etc/cni/net.d/
$ # catコマンド等で各ファイルの中身を確認する
$ sudo rm <JSON形式になっていない.conflistファイル>

エラーにたどり着くまでに調べたこと

とりあえずの再起動

  • VMの再起動
  • Macの再起動

を試したが、エラーは解消しなかった。

他に同じ事象が起こっている人がいるか

エラーメッセージで検索をしたが、ブログやIssueは見つからなかった。

どの機能でエラーが発生しているか

インターネット上で同じ事象の情報が得られなかったので、自分で調べていくことに。もとのエラーメッセージにfailed to check for default networkと書かれていたので、ネットワーク系のエラーか?と思い、ネットワークのリストを表示を試すと、

$ finch network ls
FATA[0000] error parsing configuration list: unexpected end of JSON input
FATA[0000] exit status 1

composeと同様のエラーが出力された。また、volume系のサブコマンドではエラーが起きなかったので、ネットワーク系のエラーということがわかった。

デバッグモードで詳細なエラーがでるか

finchの--helpで使えるオプションを見てみると、--debugオプションがあったので、デバッグモードで詳細なログを確認しようとしたが、有益な情報は出力されなかった。

$ finch network ls --debug
DEBU[0000] Creating limactl command: ARGUMENTS: [shell finch sudo -E nerdctl network ls], LIMA_HOME: /Applications/Finch/lima/data
FATA[0000] error parsing configuration list: unexpected end of JSON input
FATA[0000] exit status 1

どこのコンポーネントでエラーが起きているか

エラーの発生した箇所のコードがわかれば、原因を見つけられるかもしれないと思い、エラーメッセージを各コンポーネントのソースコード上で検索してみることにした。Finchのドキュメントによると、finch内部ではVM上でnerdctlを使ってコンテナランタイムcontainerdを操作しているようだったので、その辺りのコードを落としてきてエラーメッセージの一部で検索をかけた。

  • finch: ヒットなし
  • nerdctl: ヒットなし
  • containerd: 数件ヒット

ということで、containerdで起きているエラーであることがわかった。検索には何箇所かヒットしたが該当しそうだったのはconf.goのConfListFromBytes関数でJSONをパースしているところだった。

func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
	rawList := make(map[string]interface{})
	if err := json.Unmarshal(bytes, &rawList); err != nil {
		return nil, fmt.Errorf("error parsing configuration list: %w", err)
	}
    /* 略 */
}

cniというディレクトリ配下にあったが、CNI(Container Network Interface)はコンテナランタイムのネットワーク部分の仕様で、libcniはアプリケーションにCNIを組み込むためのライブラリらしい。このあたりはもう少し勉強したい。

元のJSONのデータはどこにあるか

JSONのパースで失敗しているところがわかったが、このJSONはどこからきているのかを突き止められれば悪さをしている原因のデータがわかりそうと思ったので、先ほどのコードの元データを読み込んでいるところを探してみると、どうやらLoadConfigListという関数で.conflistという拡張子のファイルを読み込んでいるようだった。

func ConfFiles(dir string, extensions []string) ([]string, error) {
    /* 略 */
}

func LoadConfList(dir, name string) (*NetworkConfigList, error) {
	files, err := ConfFiles(dir, []string{".conflist"})
	if err != nil {
		return nil, err
	}
    /* 略 */
}

ただ、LoadConfigListの第一引数dirの値はわからなかった。cni conflist locationのようにGitHub Copilotに聞いてみると/etc/cni/net.dにあると回答してくれ、そこのディレクトリを見てみると確かにconflistのファイルがいくつか格納されていた。

# finch vm上
$ sudo ls /etc/cni/net.d

各ファイルの中身はJSON形式だったが、1ファイルだけ中身が空になっているものが見つかった。これが悪さをしているファイルだと思ったので、そのファイルを削除したところエラーは解消した。このファイルがどういう操作をしたときに空になってしまったのかという根本原因は突き止められていない。
こういう再現方法がわかっていないものでも、Issueとしてあげた方がいいんだろうか、よくわかっていない。

所感

  • ログからエラー元がわからないときはソースが公開されているならソースコードまで追ってみる
  • 普段はGo言語をまったく触らないが、今回のコードではGo言語が読みやすいと感じた

参考

GitHubで編集を提案

Discussion