gRPC学習レポート new-learning-1

1週間(2024/09/22 ~ 2024/09/29):gRPCについて学習したのでそのレポート的なものを記述する。

参考文献

スターティングgRPC | インプレス NextPublishing

gRPC

gRPCとは?

RPC (Remote Procedure Call)を実現するための実装の一つ。googleが開発した。

RPCとは

リモートの機能を呼び出すための技術。似たようなものにRESTなどが挙げられる。

RESTとの違い

  • HTTP/2による高速通信
  • バイナリによる通信のため通信帯域が少なめ
  • ストリーミング通信
  • インターセプタによるgRPCのメイン処理前後への処理埋め込み (認証や入力検証に用いる)

利用手順

  1. protocol buffers スキーマファイルの作成 (RESTでいうところのopenapiと似たようなものという認識)

  2. 各サーバ、クライアントの実装

protocol buffersスキーマファイルをまず作成、これをコンパイルすることで各サーバ、クライアント用のライブラリが作成される。

実装

ひとまずシンプルにクライアント、サーバを作ってクライアントからサーバ側にある関数を呼び出すことができるか確認をする。

そのためのシナリオとして以下のようなものを検討。

サーバ側で検索用関数を実装、クライアント側からその関数を呼び出してデータベースを検索し結果を取得する。データベースには名前、年齢、メールアドレスが記載されているということにする。

データベースと記載したが本記事では文字列である名前をキーにして詳細な情報が入っているmap[string]Personという型の変数をデータの参照元とする。

Personは以下のような型 (goのPerson型作成はprotocが自動的に行うためわざわざこの型を用意する必要はない。)

type Person struct {
    Name string
    Age int32
    Email string
}

実装したサンプルは以下のリポジトリを参照のこと。

github.com

記事投稿当時のcommitへのリンク

https://github.com/ABC10946/grpc-learning/tree/d3111a1297c23c2f401f0837bd5401579f94d565

スキーマ作成

まずはスキーマを作成する。

SearchでSearchRequestを飛ばすとSearchResponseが返ってくるサービスを作成。

SearchRequestには文字列型query、SearchResponseにはプロフィール情報が入るPerson型と発見したか否かを示すbool型変数が定義される。

ここで注意したいことは、このprotobufファイルで記述したものは単なるインターフェイスに過ぎないというもので、このインターフェイスをやり取りするためのライブラリがprotocにより自動生成される。

実際に行いたい処理は人間が実装していく必要がある。(例えばここではSearch関数を実装する必要がある。)

syntax = "proto3";

option go_package = "gen/api";

package simple.maker;

service SimpleSearchService {
    rpc Search(SearchRequest) returns (SearchResponse) {};
}

message SearchRequest {
    string query = 1;
}

message SearchResponse {
    bool is_found = 1;
    Person person = 2;
}

message Person {
    string name = 1;
    int32 age = 2;
    string email = 3;
}

コンパイル

protoc --proto_path=. --go_out=. proto/simple.proto --go-grpc_out=.

サーバの実装

重要な部分のみを抜粋したコードが以下。

api.RegisterSimpleSearchServiceServer(s, &server{})でserverという構造体を指定しており、このserver構造体にSearch関数を実装している。

Register.*ServiceServerというものがprotocにより自動生成された関数であり、これの第二引数にはSearch関数がある構造体のinterfaceが指定されている。

このSearch関数こそが今回RPCされる対象の関数である。

このSearch関数自体もinterfaceのみ自動生成されており、これを満たすように内部を実装していく。

今回はSearchRequestを受け取ってそこからqueryを取得、そのquery文字列をキーにmap[string]Personから該当の人間のプロフィールを検索するというふうに実装する。

もちろんインターフェイス以外は、内部実装は自由にいじれるので他PostgreSQLなどとやりとりするORMを利用しても良い。

func (s *server) Search(ctx context.Context, req *api.SearchRequest) (*api.SearchResponse, error) {
    query := req.GetQuery()
    log.Printf("Query: %s", query)
    if data[query].Name != "" {
        person := data[query]
        return &api.SearchResponse{Person: &person, IsFound: true}, nil
    }

    return &api.SearchResponse{IsFound: false}, nil
}

func main() {
    port := 50051
    lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }

    s := grpc.NewServer()
    api.RegisterSimpleSearchServiceServer(s, &server{})

    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

クライアントの実装

クライアント側ではgrpcのclientを初期化すればSearch関数を呼び出すことができる。

まるで別のライブラリを呼び出しているかのようにリモートサーバ上にある関数を呼び出すことができる。

func main() {
    flag.Parse()
    conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
        log.Fatalf("failed to connect: %v", err)
    }

    defer conn.Close()

    c := api.NewSimpleSearchServiceClient(conn)
    r, err := c.Search(context.Background(), &api.SearchRequest{Query: *name})
    if err != nil {
        log.Fatalf("failed to search: %v", err)
    }

    if r.GetIsFound() {
        log.Printf("Person found: %v", r.GetPerson())
        return
    } else {
        log.Printf("Person not found")
        return
    }
}

実行結果

実行してみるとjohnというユーザを検索してそのレスポンスが返ってくることが確認できる。

$ https://github.com/ABC10946/grpc-learning
$ cd grpc-learning/simple
$ cd server
$ go run .
-------別ターミナル
$ cd grpc-learning/simple
$ cd client
$ go run .
2024/09/29 13:22:14 Person found: name:"john" age:20 email:"john@local"
$ go run . -name jane
2024/09/29 13:22:18 Person found: name:"jane" age:20 email:"jane@local"

calicoの設定はサボるな

TL;DR

  • NetworkManagerとの競合を防ぐためNetworkManagerで一部インターフェイスはマネジメントしないように設定する。

  • calico daemonsetsのIP_AUTODETECTION_METHODをfirst-foundからcidrsに切り替えることでちゃんと通信先のNICを指定する。


タイトルの通り。

kubernetesクラスタを構築する際にcalicoの設定をサボった結果あとから痛い目に遭遇したのでそれの共有でもしていきます。

我が家のkubernetesはCNIにcalicoを使っており、特別な設定をせずにQuick Startそのまんまでデプロイしていた。

しばらくはそれでPodにちゃんとIPアドレスが割り当てられていたし、Ingressを設定しても問題なく疎通があった。

さて、しばらくはそんな感じでいくつかのPodを上げては壊しを繰り返しいろいろ実験していました。

そうするとやはりというかなんというか、リソース不足に陥るわけです。

特にメモリが不足しており、OOM Killが走り他のノードに割り当て直されそこでもOOM Killが走り・・・ということが起きていました。

というわけで普段踏み台としてしか使っていないそれなりにスペックのあるミニPCと普段はゲームくらいでしか酷使されないメインマシンをKubernetesクラスタに突っ込んでしまうことにしました。

普段踏み台とかメインとして使っているホストをKubernetesのノードとして動かす試みというのはあまりないわけですので、まあいろいろ面倒は出てきます。

その最たる例はcalicoでした。

calicoはCNI Container Network Interface Pluginと呼ばれるものでコンテナ間のネットワークを司るKubernetesのネットワークプラグインです。

このcalicoが例の新しく追加しようとしたノード上ではうまく動きませんでした。

その原因は以下2つです。

  • NetworkManagerとの競合

  • Interfaceの疎通不可能NICを自動選択したことによる疎通不可能状態

NetworkManagerとの競合

これはかなり有名なお話のようでcalicoの公式ドキュメントのTrouble Shootingのところに記載あるのでこちらを参照してもらえればなと思います。

https://docs.tigera.io/calico/latest/operations/troubleshoot/troubleshooting

NetworkManagerがcalicoのルーティングテーブルと競合した結果、calicoのルーティングの方を優先させようとしたと思われますがデフォルトゲートウェイのmetricがものすごい大きい数が割り当てられており、あらゆる通信がcalicoの方に流れるという問題が生じました。

その結果ホスト自体の通信が落ちてしまいSSHもつながらないとひどい状態に陥りました。

というわけで、そうならないようにNetworkManager側にはそれを是正するような設定を記載しました。

[keyfile]
unmanaged-devices=interface-name:cali*;interface-name:tunl*;interface-name:vxlan.calico;interface-name:vxlan-v6.calico;interface-name:wireguard.cali;interface-name:wg-v6.cali

ひとまずNetworkManager関連の競合問題はこれで解決しました。

Interfaceの疎通不可能NICを自動選択したことによる疎通不可能状態

こちらは仕事の忙しさも相まってかなり長い期間悩まされてきましたが、昨日ようやく解決に至ったのでご報告いたします。

この問題はまず最初稀にIngressの疎通ができないな〜という問題から始まりました。

そのときは稀に通信ができない程度だったのでDNSが原因だろうかとcorednsの設定を見たりなんやらしていた記憶があります。

Pod自体の問題かとか、Serviceリソースの設定がなにかおかしいのかとかかなり的外れなことを調べていましたが、

なんだかんだいろいろ試していくうちに新しく追加したミニPCとメインマシンへのPodのみ通信ができなくなるということが分かりました。

その頃は、まあいろいろあって一部システムを止めていたこともあったので一旦ノードをcordonしてどう解決していこうか検討していこうということでお茶を濁しました。

その結果、いろいろ仕事が忙しかったり、なんか6月病みたいなものになって休日も何もできない日々がずーっと続いていたので放置していました。

k8s自体も放置していましたし、まだモニタリングシステムもなんの整備もしていないのでアラートが鳴るということもなかったわけです。

そうこう過ごしてるうちに、またおうちk8sをいじりたいという気持ちが昂ぶり、さあいじるか〜 -> あれリソース足らねえ -> あ、cordonしてたわ〜と長い間放置していた前の記憶を掘り起こし、重い腰上げて根本原因調査するか〜とはじめました。

お仕事でもKubernetesクラスタのメンテナンスや障害対応をしていたため、だいぶ見るべきところが見えてきて数ヶ月前になんとな〜く公式ドキュメント通りにデプロイしたcalicoが怪しいな〜とたどりつきました。

見てみると、なんと一部ホストでcalico-node-***というPodがちゃんと動いていないと

 $ k get pods -n calico-system
 NAME                                       READY   STATUS    RESTARTS        AGE
 calico-kube-controllers-558b7d9cf4-rslz8   1/1     Running   313 (22h ago)   227d
 calico-node-28xr4                          1/1     Running   23 (7h2m ago)   227d
 calico-node-45xkr                          1/1     Running   0               10d
 calico-node-5qrz5                          1/1     Running   16 (10d ago)    226d
 calico-node-6xjgq                          1/1     Running   0               10d
 calico-node-fkf9d                          1/1     Running   16 (10d ago)    227d
 calico-node-k62hq                          1/1     Running   22 (8d ago)     76d
 calico-node-nnjkr                          1/1     Running   25 (44d ago)    227d
 calico-node-smxkd                          0/1     Running   0               38m
 calico-node-snq2d                          1/1     Running   25              227d
 calico-typha-996fb9cc7-fgtms               1/1     Running   21 (44d ago)    227d
 calico-typha-996fb9cc7-m8ftm               1/1     Running   27 (10d ago)    227d
 calico-typha-996fb9cc7-tdg8k               1/1     Running   20 (44d ago)    227d
 csi-node-driver-4c62z                      2/2     Running   42 (8d ago)     81d
 csi-node-driver-6blck                      2/2     Running   26 (44d ago)    227d
 csi-node-driver-7rqxd                      2/2     Running   0               10d
 csi-node-driver-cnzcp                      2/2     Running   32 (10d ago)    226d
 csi-node-driver-d5mss                      2/2     Running   27 (44d ago)    227d
 csi-node-driver-fldqr                      2/2     Running   0               38d
 csi-node-driver-s2jpt                      2/2     Running   32 (10d ago)    227d
 csi-node-driver-t6f4z                      2/2     Running   0               10d
 csi-node-driver-xd8vc                      2/2     Running   26 (44d ago)    227d

で、そのPodをdescribeしてみるとなーんかBIRDのconnectionがうまくいってないことが分かります。

Events:
   Type     Reason     Age   From               Message
   ----     ------     ----  ----               -------
   Normal   Scheduled  41m   default-scheduler  Successfully assigned calico-system/calico-node-smxkd to einsteinium
   Normal   Pulled     41m   kubelet            Container image "docker.io/calico/pod2daemon-flexvol:v3.26.1" already present on machine
   Normal   Created    41m   kubelet            Created container flexvol-driver
   Normal   Started    41m   kubelet            Started container flexvol-driver
   Normal   Pulled     41m   kubelet            Container image "docker.io/calico/cni:v3.26.1" already present on machine
   Normal   Created    41m   kubelet            Created container install-cni
   Normal   Started    41m   kubelet            Started container install-cni
   Normal   Pulled     41m   kubelet            Container image "docker.io/calico/node:v3.26.1" already present on machine
   Normal   Created    41m   kubelet            Created container calico-node
   Normal   Started    41m   kubelet            Started container calico-node
   Warning  Unhealthy  41m   kubelet            Readiness probe failed: calico/node is not ready: BIRD is not ready: Error querying BIRD: unable to connect to BIRDv4 socket: dial unix /var/run/calico/bird.ctl: connect: connection refused
 W0626 11:17:59.370689      24 feature_gate.go:241] Setting GA feature gate ServiceInternalTrafficPolicy=true. It will be removed in a future release.
   Warning  Unhealthy  41m  kubelet  Readiness probe failed: calico/node is not ready: BIRD is not ready: Error querying BIRD: unable to connect to BIRDv4 socket: dial unix /var/run/calico/bird.ctl: connect: connection refused
 W0626 11:18:00.347606      62 feature_gate.go:241] Setting GA feature gate ServiceInternalTrafficPolicy=true. It will be removed in a future release.
   Warning  Unhealthy  41m  kubelet  Readiness probe failed: 2024-06-26 11:18:07.119 [INFO][365] confd/health.go 180: Number of node(s) with BGP peering established = 0

さて、ここでまずはこのBIRDが使うであろうポート179が空いてるかなぁと見ていきますが案の定繋がりはするわけです。

https://docs.tigera.io/calico/latest/getting-started/kubernetes/requirements

$ curl *****:179
つながりはするけどホストのサービス側(calico)から落とされる
$ telnet *****:179
上と同様

となると179ポート自体は閉じていない。

もちろんufw自体も停止しているので阻むものは何もありませんでした。

そうこうして調べていくうちに以下のstackoverflowにたどり着き、どうやらIP_AUTODETECTION_METHODを設定する必要があるということが分かりました。

jenkins - Kubernetes - Calico-Nodes 0/1 Ready - Stack Overflow

で、calicoインストール時にデプロイしたオペレータのコードを見ていくとnodeAddressAutoDetectionV4には他にもいくつか設定できる項目があり、その中にCIDRを設定できるということが分かりました。

https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/tigera-operator.yaml

お、これは勝利の予感。

というわけでcalicoのDaemonSetのnodeAddressAutoDetectionV4をホスト間通信用のNICが使っているCIDRにしてデプロイし直してようやく解決しました。

gnome-terminalのレスポンスが非常に遅くなる問題への対処方法

gnome-terminalのレスポンスが遅くなる問題にここ2ヶ月くらい悩まされていましたが、それがようやく解決できたので共有程度に記事を書きました。

issueやコミュニティフォーラムなどを調査すると、NVIDIAグラフィックボードが搭載されたPC上でUbuntu 22.04を動かしかつWindow ManagerにMutterを使っている場合に生じるということが分かりました。

https://askubuntu.com/questions/1509058/input-delay-on-terminal-ubuntu-22-04-4

原因

mutterのSyncオブジェクトの実装が不足していたようです。

journalctlでブートID 0のログを参照してみるとMetaSyncRingのアラートが出ていることが分かります。

$ journalctl -b0 | grep MetaSyncRing
May 26 03:31:34 oxygen gnome-shell[293854]: Window manager warning: MetaSyncRing: Sync object is not ready -- were events handled properly?

ソースコードの変更部分を見るとmeta_sync_ring_insert_waitという関数内でring->current_sync->stateがMETA_SYNC_STATE_READYでない場合はwarningを出し、meta_sync_ring_rebootをかけています。

おそらくこのmeta_sync_ring_rebootで最インスタンス化などをしており、そのせいでレスポンスが遅くなっていたと思われます。

今回の変更ではMETA_SYNC_STATE_WAITINGの場合、gpu_fenceというものを0にしてMETA_SYNC_STATE_READYをstateに入れ込んでいますが、ここらへんは追いきれていないです。

gpu_fenceとかはなんだろうカーネルとかx11の実装とかを見ておけばよいのでしょうか?)

gitlab.gnome.org

gpu_fenceについてはあまりよく分かりませんでしたが、とにかくこれが原因で、すでに修正コミットがMutterのメインブランチにマージされていますのでリリースを気長に待ちましょう。

といってもUbuntuの公式リポジトリに入るのはだいぶ先ですので、この不具合の修正コミットをあてたmutterを開発者の方がPPAで公開(https://bugs.launchpad.net/ubuntu/+source/mutter/+bug/2059847/comments/25)しているのでこちらをインストールして凌ぐことにしましょう。

対処方法

 sudo add-apt-repository ppa:vanvugt/mutter
 sudo apt update
 sudo apt upgrade
 sudo apt-get install gir1.2-mutter-10=42.9-0ubuntu7vv1 mutter-common=42.9-0ubuntu7vv1 libmutter-10-0=42.9-0ubuntu7vv1

一部コンポーネントは古いバージョンのものとなっているため、apt updateで更新されてしまいます。

このため以下のコマンドで更新を抑制しておきます。

 sudo apt-mark hold gir1.2-mutter-10 libmutter-10-0 mutter-common

DeepCool AK620-DIGITALとAORUS ELITE AX-WのLED周りをUbuntu上でいい感じにする方法

メインマシンを新しくしたのでUbuntuをインストールした。

その際にできればLED周りをWindowsと同様に設定したいと思いゴニョゴニョ調べたら出てきたのでやってみた。

GitHub - raghulkrishna/deepcool-ak620-digital-linux

Adam Honse / OpenRGB · GitLab

DeepCool AK620-DIGITAL

参考リンクのdeepcool-ak620-digital-linuxを使う。

中身は単純にPythonスクリプトとsystemd用のserviceファイル、あとインストール用のスクリプトが入っている。

Pythonスクリプトの中身を見るとCPUの温度、使用率を取得してハードウェアにwriteしているだけのように見える。

インストール用スクリプトで書かれているインストール先がなんか気持ち悪いので変更。

以下フォークリポジトリにpushした。

github.com

動作する。

マザボ(AORUS ELITE AX-W)のLED

これはOpenRGBを使う。

普通に入れようとするといらないudevルールも入るのでlsusbで該当のデバイスのベンダーID、プロダクトIDを取得して、そのudevルールを抽出。

これを/etc/udev/rules.d/60-openrgb.rulesに入れ込む。

AppImageを起動すると普通に使えるのでよしなに設定する。

devcontainerでssh-addやgit configが引っ張ってこれない問題 on WSL2 windows11

自分は普段wsl2上で作業しているのですが、会社の先輩からdevcontainerはいいぞと教えていただいたので試してみました。

しかし、git pushはおろかgit commitすらもできない状況で困っていました。

結論は、wsl2上のssh-agentやgit configを引っ張ってくるにはdevcontainerの設定をしないといけないということでした。

これでOKです。

github.com

実行中のプロセスを後からscreenに移動させる方法

実行中のプロセスを後からscreenに移動させる方法について紹介します.

reptyrパッケージが必要になりますのであらかじめインストールしておいてください.

$ sudo apt install reptyr

次に,何かしら時間がかかりそうなプロセスを走らせておきましょう.

$ <時間がかかるコマンド>

これで準備完了です.

では,screenに移動させてみましょう.

1 . Ctrl+Zでプロセスを一時停止

[1]+  Stopped <時間がかかるコマンド>

2 . jobsコマンドで該当プロセスのPIDを見つけてメモします.

$ jobs -l
[1]+ 7597 <時間がかかるコマンド>

3 . ptrace_scopeのモードの変更

Ubuntuでは他のプログラムにプロセスを奪わることを避けるためにptrace_scopeによりプロセスが操作できないようになっています.

https://www.kernel.org/doc/Documentation/security/Yama.txt

今回,プロセスを別のプロセスに移動させる操作をしたいので,

そのモードを変更する必要があります.

そのためには以下のコマンドを実行します.

$ echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
0

4 . screenを実行

5 . screen上でreptyrを用いてプロセスの親を切り替え

$ reptyr 7597

ちなみに,screenに移行したいプロセスが子プロセスを走らせていたりするときは,Tオプションを追加します.

このTオプションはターゲットの端末セッション全体を移動させるものです.

$ reptyr -T 7597

6 . fgコマンドで停止しているプロセスをフォアグラウンドで実行

$ fg
... プロセスの出力 ...

7 . ptraceによるロックを有効化

$ echo 1 | sudo tee /proc/sys/kernel/yama/ptrace_scope
1

参考

https://datawookie.netlify.app/blog/2017/12/moving-a-running-process-to-screen/

コンテキストメニューで手軽にdebパッケージのインストールをしたい。

Visual Studio Codeを使ってると定期的にアップデートが来ますよね。

Windowsだと自動でアプデしてくれた気がするんですけど、

Ubuntuだとdebパッケージダウンロードしてきてインストールする作業が必要になります。

Ubuntu Softwareでインストールがうまくいけばよいのですが、うまくいかなかったときはエラーメッセージを何も吐いてくれなかったりするので少し嫌いです。

そんなことが何回かあったので自分はdpkgコマンドを使ってのインストールを使うのが常になってます。

しかし、これもまた面倒くさい。

端末開いて、ダウンロードフォルダに移動して、コマンド打ってインストール。

いや、書いてみたらそこまで工程が多くなかった。

まあ・・・、気にせず書き続けます。

とりあえず、こんな面倒くさい工程をもう少し簡単にしたいので、

コンテキストメニューからdebパッケージのインストールコマンドを走らせるようにしてみました。

下みたいな感じに。

手順

FileManager-Actionsをインストールして、設定をしただけです。

1. FileManager-Actionsのインストール

$ sudo apt install filemanager-actions
$ sudo apt install nautilus-extension-fma

2. 設定

新しい設定を追加して、以下のようにコマンドとどの種類に反映させたいかの設定を行います。

Command -> Command -> Pathにterminatorまたはgnome-terminalなど好きな端末エミュを開くコマンドを記述します。

Command -> Command -> Parametersに引数を書いていきます。

自分の場合はterminatorを指定使うので引数は以下のようになります。

-e "echo %f; sudo dpkg -i %f; echo 'press any key...'; read"

%fに右クリックしたときに選択したファイルのパスが入ります。

sudo dpkg -i %fはインストールコマンドです。

echo 'press any key...'; readは、キー入力してから端末を閉じるようにしています。これがないとエラーメッセージが出たときに見れないので・・・。

どの種類のファイルに反映させたいかの設定

Basename -> basename filterってところを*.debとするだけです。

こうすることで今回追加したメニューをdebパッケージファイルを選択したときだけ表示させることができます。

これで上記に貼り付けた動画のように動くはずです。

余談

今回はdebパッケージのインストールだけを行いましたが、他にもVisual Studio Codeコンテキストメニューから開くOpen with vscodeを追加するなどができますので試してみると良いと思います。

qiita.com