git-browse-remote を長らく使っていたが、最近の Ruby では動かなくなっており、インストール後に手で該当箇所を修正してしのいでいた。
ただ、もう Ruby / Gem はこれにしか使ってなかったというのがあり、環境を新しくした機会に Python で書き直した。
Git の操作で libgit2 / pygit2 を使ったが、結構コマンドインターフェイスと感覚が違って難しかった。 GitPython のほうが楽だったかも。
プライベートで Google Cloud を使っていて、今まではあまり IaC は使わずポチポチでやっていた。GKE ぐらいしかまともに使ってるリソースがなかったのでそれでよかったけど、最近 VPS をやめて各種クラウドに寄せるようにしており、クラウドのリソースが増えてきた。
ポチポチだと見落とす設定もあったりするので、IaC (Pulumi) を本格的に取り入れることにした*1。
ついでに k8s のリソースも kpt と Helmfile から Pulumi 管理にしたので、そのあたりの話。
Pulumi とは IaC ソリューションの一つで、要するに Terraform 的なもの。 はじめは Terraform とか知ってるし普通すぎるし Pulumi のほうが面白そう、といった理由で使い始めたのだけれど、今でも満足して使っていている。
Terraform との大きな違いは、Python や TypeScript などの一般的な言語を使って記述すること。
※ いろいろ言語が選べるが、この記事では自分が使っている Python での話をしていく
一般的な言語が使えることの何が嬉しいかというと…
Terraform だと、HCL という Hashicorp 独自の言語を使う必要があり、ちょっと変わった処理をしようとするとすぐリファレンスを見る必要がある*2。また、Provider のバージョン更新やディレクトリ構成など、 Terraform での作法を色々調べないといけない。
一方 Pulumi は普段プログラムを書くときと同じ感覚で IaC の記述ができる。 やることは非常に明快で、リソースに対応するクラスを使ってインスタンスを作るだけ。各種 Provider は Python module として提供されており、それらの更新やインストールは pip 経由で行うので、いつも通り requirements.txt
や pyproject.toml
でバージョンの管理ができる。
この "いつも通り" が頭を使わずに済んで便利。
もちろん HCL より色々柔軟に書けて便利というのもあるが、それは個人レベルだとどっちでもいいかな。
TypeScript や Python、Go など人気のプログラミング言語は IDE のサポートが強力で、補完やドキュメントの参照などがスムーズに行うことができる。 Pulumi は Provider にちゃんと型ヒントやコメントをつけてくれているので、VSCode などで快適に IaC のコーディングができる。
Terraform にももちろんそういった VSCode 拡張はあるけど、どうしても MS が公式で提供しているものよりはクオリティやパフォーマンスが落ちてしまう。
あれは確かにつらいけど、背景が違うと思う。今は型があるし、IDE も強いし、Ruby ベースのよくわからない DSL ではない。また、生成したいものは基本的に宣言的なリソースであり Linux サーバーのような変化しうるものを相手にするものとは違うので、複雑な独自処理を書く機会は少ない。
CDK が便利だよね、というのと同じ感覚で見れば良いと思う。
Pulumi を使い始める前は Terraform で使えるものが使えないような事態を危惧していたけど、Provider は想像以上にたくさんある。
これらすべてを Pulumi が毎秒更新しているかというとそんなことはなく、多くは Terraform provider から自動生成されたものらしい。なので Terraform でできることはだいたいできる。
一部 Native provider というものがあり、これは Pulumi 開発のもので API から自動生成しているため対応の速さが自慢とのこと。
文字列の差分が色付きで出てくれて非常に見やすい。細かいパラメータの変更があったときにどこが変わったのかを目 grep せずに済む。
補足: 書いてる途中で Terraform にも似た機能が最近 Experimental リリースされたことを発見した Import - Generating Configuration | Terraform | HashiCorp Developer 。
Pulumi のほうが気楽そうではあるけど、TF でも自動生成してくれるようになるのはよさそう。
最初はポチポチで作って後から IaC で…ってのはよくあることだと思う。こういうときはポチポチで作ったリソースを State に Import することになる。
Pulumi ではこれが楽で、 Import 完了後にリソースに対応するコードを出力してくれるから、これをコピペすれば IaC 対応が完了する。
Please copy the following code into your Pulumi application. Not doing so will cause Pulumi to report that an update will happen on the next update command. Please note that the imported resources are marked as protected. To destroy them you will need to remove the `protect` option and run `pulumi update` *before* the destroy will take effect. import pulumi import pulumi_gcp as gcp sa = gcp.serviceaccount.Account("sa", account_id="sa", display_name="sa", project="XXXXX", opts = pulumi.ResourceOptions(protect=True)) ...
また、ドキュメントには Import コマンドの例が基本的に書かれているのも嬉しい(例: https://www.pulumi.com/registry/packages/gcp/api-docs/serviceaccount/account/#import)。
Terraform は最近ライセンスが BSL に変わってしまった。普段エンドユーザーとして使ってる分にはあまり変わらないが、やっぱりオープンソースだと嬉しいし、応援していきたい。
Terraform では plan すると refresh が必ず行われるので、IaC の外で変更があったときに気づきやすい。Pulumi は flag をつけないと自動で行われないので変更を見逃して適切な対応ができない可能性がある。
常に IaC でしか変更しないならいいけど、GKE のアップグレードとか色々あるよね。
Google Cloud 向けには Native provider と Terraform から変換した Classic Provider がある。今回はチュートリアルに従って Classic Provider を使っている*3。
使ってみた感想としては普通に便利に使えるのだけど、 Google Cloud では Terraform を使ったほうが良さそう…😇
というのも、Google Cloud は Terraform を公式サポートしており、ドキュメントにも Terraform に関する記載が豊富だし、Provider の更新に Google の社内チームが関わっている。
また、最近はリソースを作っていると同等の TF 定義が右のパネルに出てきてくれたりする。
Pulumi を使っているとこれを参考しながら Pulumi のコードに落としていくことになるので、ちょっと悲しい…。普通に使えるんだけどね。
※ Terraform の k8s provider は使ったことないです
自分は k8s の Manifest 自体は Kustomize などで YAML で書けばいいと思っており、こういうのを使う嬉しさは主に Pruning (不要になったリソースの削除)だと考えている。
その用途として kpt を使っていたけど、しょっちゅうよくわからない感じで壊れてイヤになってきたので Pulumi 管理にすることにした。そもそも kpt の主目的はそういうとこじゃない、というのもある *4。
Manifest は Kustomize で組んでいるので移行は非常に簡単で、 Pulumi の Kustomize 対応を使って kustomization.yaml
があるディレクトリの指定をするだけだった。
from pulumi_kubernetes.kustomize import Directory Directory( "main", directory=".", )
こうすると Kustomize をビルドして、ビルド結果に含まれる Deployment などの各リソースをそれぞれ管理してくれる。
使った感触としては非常に良くて、Server side apply してくれて安心だし、kpt よりも apply が早いし、diff も前述の通り見やすくなった。 変更時は READY になるかをちゃんと見てから古いリソースを削除したりと、安心して適用がしやすいのも良い。
Kustomize と Pulumi の組み合わせ、おすすめです!
Helm はもともと Helmfile で管理していた。それほど不満はなかったが、Pulumi でも Helm の管理ができるので Manifest と一元管理することにした。
Pulumi は Helm を入れるためのリソースとして Chart と Release があり、Chart が Pulumi 側で一旦展開され、てインストールするもの、Release がインストールも Helm に任せるものとなっている。
↑にあるように一長一短あり、Chart だと Pulumi が各リソースを管理しているのでリソースの細かい diff まで確認することができる一方、Hooks など Helm 関連の機能が使えない、 Release だと Helm の全機能が使えるが diff は values の範囲に限られてしまう。
今回は互換重視で Release にした。これも良い感じに使えていて、Helmfile より UI が良くなった上に $ pulumi up
コマンド一回で k8s Manifest と一緒にデプロイできるようになり、楽になった。
一方、これは cdk8s でも感じたことだが、values を Python で書くことになるので REAMDE とかにある例をそのままコピーできないのは面倒くさい。せっかく Pulumi ・ Pythonなので、 values は YAML として置いておき、PyYAML を使って読むようにしても良いかもしれない。
Pulumi のユーザーは Terraform より少ないのでナレッジは少ないけど、ドキュメントがしっかりしているので特に困ることはなかった。同じような話で Google Cloud などサポート的に Terraform を使ったほうが便利な局面もあるものの、 TF の Provider を自動変換してることもあって機能面では不自由なく使うことができる。なにより自分に馴染んだ言語で書けるというのがすばらしく、そして楽しい!
k8s のサポートもよくできているし、普通とはちょっと違うものを使ってみたい欲も満たせて*5なかなか良いものだった。これからも使って & 推していきたい。
かなり昔に Elasticsearch ベースの検索システム(Heineken)を作っていた。
Elasticsearch で部内 Wiki 検索高速化 - Speaker Deck
特に更新せず数年動かしていたのだけど、サーバーの置き換えに伴って Kubernetes に置きたいよねという話になり、ついでに Elasticsearch も新しくしたいよね、となった結果、現状のフロントエンドだと最新の Elasticsearch では動かないということがわかった。
フロントエンドの改修が必要なわけだが、ここでフロントエンドの構成を見ると…
古すぎる!絶対アップデート難しいし触りたくない技術しかない。
フロントまわりの構成を変えたいとずっと思っていた(フロントから Elasticsearch に直接アクセスする構成のやめたかった)こと、また SSR あんまり触ってなかったことがあり、1から作り直すことにした。
~~ここから2年弱が経過~~
React を Vanilla で使うことは Webpack 頑張ればできるのだけど、最近は何かしらのフレームワークを使うことが主流らしい。
SSR もやりたいし、はじめは Next.js で作ろうと思っていた…のだけど、 Next.js は色々聞いてると考えることが多くて大変だな…という印象になっていた。検索アプリなので SSG は不要だし、キャッシュも静的ファイルの範囲内で十分だし、 fetch の override とかしないでほしい。もっと軽量なものが使いたい。自分は薄いフレームワークが好きなのです。
そこで Remix を使うことにした。 Remix は SSG はやらなくて SSR 専門だし、React router がベースなので馴染みもあるし、 Web standards はいいぞってトップページで主張していて好感度高い。超薄くはないけどまあ SSR するならこんなもんでしょう。
ということで数日かけて Remix で Heineken のフロントエンドを再実装した。今まではブラウザが fetch で直接 Elasticsearch にアクセスする構成だったが、 Remix のサーバーが Elasticsearch に問い合わせつつ SSR をして、 react-select などフロントで一部だけ描画する感じになった。
置き換えた感想としては、
描画が早い!!
あまりパフォーマンスのことを考えずに実装しても Elasticsearch の検索含めて 100ms 程度で描画されるようになった。SSR がすごいという話もあるし、各種コンポーネントのデータを並列で取得しているのも見ごたえあるし、パフォーマンスをあまり何も考えていない実装でそれを実現できているのが偉い。
結構ファーストビューの速さって体感に大きく関わるんだなと実感した。
Remix は外部ナレッジこそ少なめなものの、公式ドキュメントやチュートリアルが充実していて、 SSR 初心者でも割とスムーズに書くことができた。
とはいえ、色々思ったところ・つまづいたところはあったので書いていく。
Remix では routes 以下にファイルを配置すると自動的にファイル名がパスとして認識され、そこで定義したコンポーネントにルーティングされる。SPA でルーティング書くの意外と面倒なので、これはなかなか便利だった。React router を内部で使っているんだろうけど、実際に意識することは全然なく、良い感じにやってくれている。
ファイル名ベースとディレクトリ名ベース両方使えるのも便利で、自分はディレクトリベースの route 指定を主に行うようにした。ディレクトリの中にそのパス関係のコードを全部置くことで Code colocation が上がってわかりやすい。
Remix では Nested routing という機能があり、 /foo/bar/baz/1
といった URL では /foo
のコンポーネントを描画し、その中に /foo/bar
を描画、 /foo/bar/baz
、 /foo/bar/baz/1
と続くように描画させることができる。
URL 設計をきれいにつくるときれいな階層型のサイトができるということなんだろうが、これはちょっと微妙だなと思った。Example にあるようなシンプルな構成のサイトだといいけど、(React で作りたくなるような JS ベースのサイトは特に!)そんなことはなく、下部のパスが同じだからといって上部のガワが共通であるとは限らず(特に上部パス)、結局下から上に伝えたり、上から下に伝えたりする必要が出てくると思う。コンポーネントが同じだったとしても、Props の内容とか変わってくるんじゃないですかね。それを親側の route で URL をパースして書き換えるのか?いやうーん…。
今回もそうで、 Nested routes として共通化できたのは上部のヘッダーぐらいで、検索関係のページの Route は共通化されたコンポーネントを組み合わせた大きなコンポーネントとなり、いつもの SPA といった感じになった。
URL や Nested routes にはこだわらず、どのようなデータをサーバーから取得したいか(loader がどのようなデータを返すのか)、で Route を分けるのが並列性を考えるとよいのかもしれない。
Remix のエラーハンドリングは Route の中に ErrorBoundary という関数を定義して、そこでエラー内容を通常のコンポーネントの代わりに描画するというものだが、このやり方がなかなか難しかった。
例えば検索フォームの Route を考える。ここで遷移(Search params の変更)が起きたときに loader で何かおかしなことがあればエラーを出したいのだが Route ファイル内に ErrorBoundary を定義して単純にエラーを出すとフォームが丸ごとエラー表示に置き換わるのでフォームの修正や再送信ができなくなる。
かといって通常の Route と同じ Component を流用して描画を行うのも難しい。ErrorBoundary 内では loader
から得られるデータが使えないので描画に必要なデータを得ることができない。これは環境変数まわりで特に困った(後述)。
その他、loader 内のエラーがうまくクライアント側に伝わらないような挙動もあり、Remix でのエラーハンドリングは難しいな…という印象が残った。
ローディングは defer / Suspense という仕組みが用意されており、loader 内で時間のかかるデータを待たずに一旦レスポンスを返しつつ、データが取得できるまでブラウザ側で代わりに表示するコンポーネントを指定できる。これ自体は非常に便利なのだが、Search params を変更したときはローディング状態にならないという仕様になっている。
今回作るような検索アプリでは URL に Search params として検索クエリを持つのが一般的だが、クエリを変更し Search params を変更しても Suspense はローディング状態にならため、ページ遷移が反映されていないように見えてしまう(最初のページロードだけローディングが出る形になる)。
対策として、上記コメントにある通り navigation の状態を見ることでローディングかどうかを自前でも判定することにした。うまく動くようになったものの、結局 Suspense の機能の外側でローディング中の文字を出す羽目になり、一体何をやっているのかという気持ちに。
SSR ではサーバー側には環境変数があるがクライアント側は持っていないので、安易に process.env
を使うとクライアント側で死ぬ。そこで Remix では loader で env を返すことを推奨されている。
ただこれだと前述の通りエラーハンドリングで問題になる。ErrorBoundary で表示されるエラーは loader がエラーになった場合も含まれるので、 loader を ErrorBoundary で呼ぶことはできず環境変数を取得する術がない。
どうすればいいかというと、上記 Thread にある通り Root Route など別の(基本的にエラーにならないであろう)上位 loader で必要な process.env を返すように記述して子 Route から読みに行くか、Root Route 内でで Env を Context に追加するかといったところ。 どちらでも動くことを確認したけど、今回は React で慣れている Context を使うようにした。
今回も Bootstrap を使ってスタイリングを行ったが、 SSR で Vanilla な bootstrap を使おう*1とするといくつかハマりポイントがあった。
Bootstrap の CSS は import すればサーバーでもクライアントでも動くが、JavaScript はサーバーでは動かず、 import "bootstrap"
すると document
がなくエラーになる。
Bootstrap のコードはクライアント側だけで動いてくれればいいので、クライアントのみにバンドルされるコードである entry.client.tsx
内で import することにした。
その後、 Component 内で Bootstrap が提供する JavaScript コードを onClick 内で使いたいという場面が出てきた。ただ、 (今回使おうとしていた) Bootstrap の Collapse はコード上で評価した瞬間に document にアクセスするので、クライアント上でしか評価しないようにしないとエラーを吐いてしまう。
これに関しても *.client.tsx
の仕組みを使い、以下のようにして解決した。
bootstrap.client.tsx
内で必要なものの import だけを行い即座に export するようにする。その export されたものをコンポーネント側で Import する
import { Collapse } from "bootstrap"; export { Collapse };
onClick
などクライアントでしか動かないコード上で使うその他、 bootstrap-select は BS5 で使えなくなっていて react-select に置き換えたりした。ただ react-select は emotion を使っていて SSR と相性が悪いのでそんなにおすすめはできない感(できるのだが *2、微妙 *3 *4) ...。 Downshift がいいらしいけど style 書くのめんどくさくてやめた。
…といった感じで、初めての SSR というのもあって、SSR 自体のつまづきポイントも含めて Remix のつまづきポイントは結構あった。まあ慣れてくるとどう対応すればいいのかもわかってくるけど、薄さに惹かれたわりには考えることは多かったなと思う。
最近の技術を触るのは楽しかったし、結果爆速なアプリケーションができたので満足はしている。食わず嫌いしている Nextjs や何もわからない Hono とかもまた触ってみたい。
*1:react-bootstrap だと React に寄ったコードになってしまい、後々のスタイルの流用がしづらい
*2:https://emotion.sh/docs/ssr
去年11月、Smiling Face with Halo というチームで ISUCON 13 に参加した。チームメンバーはいつもの id:utgwkk と id:wass80 。
スコアは 0! よって最下位!! 直前には 73k くらい出てはいた。
ちょっと遅くなってしまったけど、個人的に楽しい問題だったし振り返ってみる。
以下チームメイトの記事とレポジトリ。
いつものごとく謎のユーザーで Commit されているのがだいたい自分。基本インフラ周りやってた。
インフラ担当はたいていやることが決まりがちだったりするんだけど、今回は DNS 攻撃の対策担当になって色々考えていた。
まず MySQL の DB の参照を pdns と app で分けてみた結果(元から分かれてたかも?忘れました)、 DNS の参照負荷がかなり大きいことがわかった。そこで、一旦 DNS 関係を MySQL ともども一つのホストにまとめることにした。手始めに pdns が打つクエリにインデックスを貼ってみたけど、より高速に攻撃が来るようになっただけで一筋縄ではいかなさそう。
さすがに一台を DNS に潰しているのはよくないということで、 dnsdist (初見)を入れて真面目に対策を検討することにした。 tcpdump で見たところ大量の NXDOMAIN が来ているよう。
まずは、一定以上 NXDOMAIN が来たら Drop するなど BAN の仕組みを入れようとした。ただ、 dnsutil で送信元 IP アドレスとポート番号のペアで BAN を決定させる方法が分からず、うまくいかなかった。ベンチマークの IP アドレスは同じなので IP アドレスのみを基準として BAN をしてしまうと、正規リクエストも来なくなってしまう。異常なリクエストの送信元ポートは基本同じだったので、これがうまくできたらよかったんだけど。
さらにクエリを見ていると、NXDOMAIN のときのリクエストされるレコードは明らかにランダムっぽい。 regex で気合で防げるか・・・?と思いきや、アプリケーションが実際に使う record も(ベンチが走ったあとは)ランダム性が高く、確実に見分けるのは難しそうだった。
最終的に、NXDOMAIN なら多少遅延を入れてもいいのでは?ということに気づいた。攻撃が送られる送信元のポートが変わってないということは、レスポンスを遅延させると頻度も減るはず。
addResponseAction(RCodeRule(dnsdist.NXDOMAIN), DelayResponseAction(300))
これが非常に効果があり、攻撃頻度が無視できるレベルになり 1 台分のリソースがほぼほぼ空いた。うまく解決できてよかった~
pdns は実は使ったことがあったのだけど、dnsdist は初めてだったので楽しかった!設定言語が lua なのがよい。lua、 Python みを感じて割と好き。
いつも同じことしてるので細かい設定や各種ツールのインストールはスクリプトにしたほうがよさそう。
また、今回は前回やっていたような解析周りをあまり提供できていなかった(Kibana は用意していたが結局使わず)。需要があれば当日その場で作る、という気持ちでいたけど、ああいうのは突然欲しくなるものだし、なくてもなんとかなるけどあれば使うものなのでやっぱり準備しておいたほうが良かった。
さすがに前日深夜から全てを準備し始めるのはよくなかったね…。なんか GCP Ops Agent とか Prometheus 使ってみるかとちょっと頑張って諦めてたりしてたし…。
nonylene :tennouji_rina: 23:48 うおー立てるか 23:49 めちゃくちゃ風呂入ってた 01:39 google cloud managed prometheus 使おうかと思ったけど aws だと微妙だな~っていう結論になったので去年同様でいきます 03:26 とりあえず前回と同じ url でたてた(バージョンアップしたけどまあ大丈夫であろう) 03:30 会場 https://github.com/innocent-team/isucon13 03:37 tracer とかも変わらず使えそうだな
そうそう、ぷりんくんのこの記事がめっちゃよかったです。Grafana で pprof 見れるのすごすぎる。クラウド使うと OSS 版より機能よかったりするし快適そうでいいですね。でも Kibana も UI 使いやすくておすすめだよ!!
…ということで今回も楽しい問題でした、運営の皆さんありがとうございました!特にインフラ面でも触りがいがあるのは、さすが Sakura さんだなあと思いました。次は fail せずに勝ちたい!
久々に helmfile sync を行った結果、PodSecurityPolicy リソースなんか知らないという理由で Upgrade に失敗した。
$ helmfile dep && helmfile sync ... Error: UPGRADE FAILED: resource mapping not found for name: "descheduler" namespace: "" from "": no matches for kind "PodSecurityPolicy" in version "policy/v1beta1" ensure CRDs are installed first
「PSP? なにそれ CRD か何か?」と言われる有様。そういえば、k8s クラスタを最近アップグレードしたときに不要な PSP を消していた気がする。
一旦手動で helm uninstall をしようとするも、同じようなエラーが出て uninstall できない。
$ helm uninstall descheduler -n kube-system --debug uninstall.go:97: [debug] uninstall: Deleting descheduler uninstall.go:119: [debug] uninstall: Failed to delete release: [unable to build kubernetes objects for delete: resource mapping not found for name: "descheduler" namespace: "" from "": no matches for kind "PodSecurityPolicy" in version "policy/v1beta1" ensure CRDs are installed first] Error: failed to delete release: descheduler helm.go:84: [debug] failed to delete release: descheduler helm.sh/helm/v3/pkg/action.(*Uninstall).Run helm.sh/helm/v3/pkg/action/uninstall.go:120 main.newUninstallCmd.func2 helm.sh/helm/v3/cmd/helm/uninstall.go:60 github.com/spf13/cobra.(*Command).execute github.com/spf13/cobra@v1.6.1/command.go:916 github.com/spf13/cobra.(*Command).ExecuteC github.com/spf13/cobra@v1.6.1/command.go:1044 github.com/spf13/cobra.(*Command).Execute github.com/spf13/cobra@v1.6.1/command.go:968 main.main helm.sh/helm/v3/cmd/helm/helm.go:83 runtime.main runtime/proc.go:250 runtime.goexit runtime/asm_arm64.s:1172
どうやら、以前この Chart を install したときには PSP があったので、今回 uninstall するにあったっても PSP を消そうとするが、API の対応がないので落ちているらしい。
ここで、helm はインストールしたときの情報を secrets に保存している。この内容をデコードすると、インストール時の YAML がそのまま格納されている。
$ kubectl describe secret -n kube-system sh.helm.release.v1.descheduler.v15 Name: sh.helm.release.v1.descheduler.v15 Namespace: kube-system Labels: modifiedAt=1687875686 name=descheduler owner=helm status=uninstalling version=15 Annotations: <none> Type: helm.sh/release.v1 Data ==== release: 13804 bytes $ kubectl get secret -n kube-system sh.helm.release.v1.descheduler.v15 -o json | jq .data.release -r | base64 -d | base64 -d | gunzip | jq { "name": "descheduler", "info": { "first_deployed": "2021-05-25T22:28:45.6228274+09:00", ... }, ... "manifest": "---\n# Source: descheduler/templates/podsecuritypolicy.yaml\napiVersion: policy/v1beta1\nkind: PodSecurityPolicy\nmetadata:\n name: descheduler\n annotations:\n seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'docker/default,runtime/default'\n seccomp.security.alpha.kubernetes.io/defaultProfileName: 'runtime/de...(続く)" ... }
この manifest を使って uninstall しているのでは?と考えて、manifest から PodSecurityPolicy の部分を削って secrets の値を変更してみたところ、ビンゴだった。
$ gzip new_release.json --stdout | base64 -w 0 | base64 -w 0 | pbcopy $ kubectl edit secret -n kube-system sh.helm.release.v1.descheduler.v15 (コピーした内容を release に貼り付ける)
これで無事 PSP を除いた uninstall を行うことができた。
$ helm uninstall descheduler -n kube-system --debug uninstall.go:97: [debug] uninstall: Deleting descheduler client.go:478: [debug] Starting delete for "descheduler" CronJob client.go:478: [debug] Starting delete for "descheduler" ClusterRoleBinding client.go:478: [debug] Starting delete for "descheduler" ClusterRole client.go:478: [debug] Starting delete for "descheduler" ConfigMap client.go:478: [debug] Starting delete for "descheduler" ServiceAccount uninstall.go:150: [debug] purge requested for descheduler release "descheduler" uninstalled
全く同じ内容がドキュメントにあることにその後気づいた。事故らなくてよかったですね…。
今回の事象は Upgrade 時に何も考えずに非互換なリソースを消して済ましたのが原因であり、教訓としては以下になる。
curl で通信の内容を見ながら詳細なデバッグをしたいとき、 tcpdump や Wireshark でパケットキャプチャを行うのが定番だと思う。ただ、通信が多い環境だと実際にデバッグしたい通信を絞り込むのが面倒だし、TLS だと暗号化されていてさらに面倒。
curl のオプションを見ると通信内容のトレースを記録できる --trace
や --trace-ascii
があり、使ってみると便利だった。
$ curl https://www.google.com/ [--trace-time] --trace[-ascii] trace-result.txt
これで curl が行った通信の内容が trace-result.txt に記録される。-
にすると stdout に出力される。
また、--trace-time
をつけると trace に時刻が付与され、--trace
を --trace-ascii
にすると Hex は保存されず ascii の内容のみ確認することができる。
trace の内容は以下のような感じ(一部省略している)。
== Info: Trying 2404:6800:4004:80b::2004:443... == Info: Connected to www.google.com (2404:6800:4004:80b::2004) port 443 (#0) == Info: ALPN, offering h2 == Info: ALPN, offering http/1.1 == Info: successfully set certificate verify locations: == Info: CAfile: /etc/ssl/certs/ca-certificates.crt == Info: CApath: /etc/ssl/certs => Send SSL data, 5 bytes (0x5) 0000: 16 03 01 02 00 ..... == Info: TLSv1.3 (OUT), TLS handshake, Client hello (1): => Send SSL data, 512 bytes (0x200) 0000: 01 00 01 fc 03 03 ca 7f 00 66 6c 46 6f a9 2c 8f .......^?.flFo.,. 0010: 97 83 1a b8 c7 80 50 6d 0c c8 f8 73 bc 49 90 ab ......Pm...s.I.. 0020: ec 8b bc be 4f 0d 20 a4 f1 17 e2 8c 0d 6f 61 25 ....O. ......oa% 0030: 54 d8 13 27 b6 5d e2 0d 92 de 42 9f 93 2c ce c5 T..'.]....B..,.. 0040: 50 bd 3c 75 b9 bd 86 00 3e 13 02 13 03 13 01 c0 P.<u....>....... 0050: 2c c0 30 00 9f cc a9 cc a8 cc aa c0 2b c0 2f 00 ,.0.........+./. 0060: 9e c0 24 c0 28 00 6b c0 23 c0 27 00 67 c0 0a c0 ..$.(.k.#.'.g... 0070: 14 00 39 c0 09 c0 13 00 33 00 9d 00 9c 00 3d 00 ..9.....3.....=. 0080: 3c 00 35 00 2f 00 ff 01 00 01 75 00 00 00 13 00 <.5./.....u..... 0090: 11 00 00 0e 77 77 77 2e 67 6f 6f 67 6c 65 2e 63 ....www.google.c 00a0: 6f 6d 00 0b 00 04 03 00 01 02 00 0a 00 0c 00 0a om.............. ... <= Recv SSL data, 5 bytes (0x5) 0000: 16 03 03 00 7a ....z == Info: TLSv1.3 (IN), TLS handshake, Server hello (2): <= Recv SSL data, 122 bytes (0x7a) 0000: 02 00 00 76 03 03 1d 3c 9b 52 99 6f e5 05 b7 9f ...v...<.R.o.... 0010: 2b 35 94 0e 11 bd 4f 3a de e3 b8 1d d4 16 a4 45 +5....O:.......E 0020: c4 91 13 b0 18 ef 20 a4 f1 17 e2 8c 0d 6f 61 25 ...... ......oa% ...
TLS で暗号化された内容も、元の HTTP が平文で見れるので便利。
<= Recv header, 13 bytes (0xd) 0000: 48 54 54 50 2f 32 20 32 30 30 20 0d 0a HTTP/2 200 .. <= Recv header, 37 bytes (0x25) 0000: 64 61 74 65 3a 20 54 68 75 2c 20 31 35 20 4a 75 date: Thu, 15 Ju 0010: 6e 20 32 30 32 33 20 30 32 3a 33 37 3a 34 33 20 n 2023 02:37:43 0020: 47 4d 54 0d 0a GMT.. <= Recv header, 13 bytes (0xd) 0000: 65 78 70 69 72 65 73 3a 20 2d 31 0d 0a expires: -1.. <= Recv header, 35 bytes (0x23) 0000: 63 61 63 68 65 2d 63 6f 6e 74 72 6f 6c 3a 20 70 cache-control: p 0010: 72 69 76 61 74 65 2c 20 6d 61 78 2d 61 67 65 3d rivate, max-age= 0020: 30 0d 0a 0.. <= Recv header, 45 bytes (0x2d) 0000: 63 6f 6e 74 65 6e 74 2d 74 79 70 65 3a 20 74 65 content-type: te 0010: 78 74 2f 68 74 6d 6c 3b 20 63 68 61 72 73 65 74 xt/html; charset 0020: 3d 49 53 4f 2d 38 38 35 39 2d 31 0d 0a =ISO-8859-1.. <= Recv header, 245 bytes (0xf5) 0000: 63 6f 6e 74 65 6e 74 2d 73 65 63 75 72 69 74 79 content-security 0010: 2d 70 6f 6c 69 63 79 2d 72 65 70 6f 72 74 2d 6f -policy-report-o ...
eBPF の tc (classifier
) を使ってパケットを触ってみる話をしたので、資料を貼ります。
eBPF は一度慣れるとかなり便利な仕組みで、見るべき場所が分かっていればそれなりに気軽に使うことができる。ただ、ドキュメントがだいぶ整備されてきたとはいえまだまだ最初の一歩の敷居は高いと思う。
この資料は、eBPF 開発の最初の一歩を踏み出したい人向けに作った。 eBPF を書いてデバッグし組み込むには、一体何を行う必要があってどこを見たらいいか?をハマりポイントとともに重点的に書いてみた。