記録。

めも。

期待を超えるためには自身であるべき姿を描く必要があった

※自身の最近の反省をつらつらと書きました。

エンジニアリングマネージャー的なロールになり、エンジニアを評価する側になり、CTOの期待以上に働けている人とそうでない人の違いはなんなんだろうと考えていた。

期待以上に働くためにはあたり前だが、その人の想定している範囲外のアクションや成果が必要だったりする。

その人の想定している範囲内での活動では期待を超えることができない。

期待を超えて働くためには、時間軸を今まで以上に長く持ち自分の中であるべき姿やWillを描いて実行する必要があると思う。

私は、去年の1年間エンジニアの評価制度を作成して導入したり、1on1をはじめてみたり、目標設定を導入して運用したりしていた。(今、すでに終わっているわけではなくてまだまだ改善の途中である)

今日まで走ってみて期待を超えるというところまでは至っていないと思っている。

この活動が理想通りに行っていないところはもちろんあるが、これらの活動に留まってしまったということが問題だと思っている。

これらの活動はもともと、エンジニアリングマネージャーを始めるときに期待値調整的に話したことだからだ。

活動1つひとつの質をかなり上げて、成果にも現れてくれば期待を超えたと言えるかもしれないが、エンジニアリングマネージャーとして本当にそれでいいのだろうか??

なぜこう思うのかというと、これらの活動というのは、エンジニアリングマネージャー目的・ミッションである組織のアウトプットを最大化するためのアプローチの一部を最適化しているに過ぎないからだと最近思っているからだ。

エンジニアリングマネージャーとして本来のミッションを追うために、組織全体に対してもっと大局的に自分なりのあるべき姿を持つ必要があった。

そして、それを周囲にぶつけながらも前進させなければならなかった。

描くだけはできるかもしれないが、ぶつけるということは自身も批判されたりと痛みを伴う。

そのような痛みもあるし、大局的にあるべき姿を描いて実行するということは大きな責任も伴う。

だから自身で腹をくくるというイベントがないと大局的に描いて実行することができない。期待を超えるスタート地点に立つことができない。

時間はかかったが、最近腹をくくることができた。動きも変わってきた。(むしろ変えたと言ってもいい。)

腹をくくることができたことで、期待を超えるために必要なことがどんなことなのかに気づくことができた。

EMの役割を自分なりに定義してみる(前編)

この記事は Engineering Manager Advent Calendar 2023の21日目の記事です!

はじめまして、yokishavaといいます。 EMというロールをはじめて1年が経とうとしています。

このポストではEMの役割というものを自分なりに言語化してみようと思います。 背景としては、EMとして取り組んでいることに対して、以前「どこに向かっているんだっけ。。?」「この取り組みはどこに作用させようとしているんだっけ?」とわたしが疑問を思ったことがあったからです。 マネジメントに関する本を読んで、私自身EMの役割を理解しているつもりではありましたが、それがいつの間にか自身の中から忘れられてしまっていることに気が付きました。 自分なりに言語化すること、このブログに残すことで、今後同じようなことが発生したときも立ち戻れるようにしたいと考えています。

補足

EMといっても各社で求められる役割や取り組んでいる内容は異なるかと思います。 わたしは、あるプロダクトチームに所属してEMというロールを全うしているというよりは、各プロダクトチームを横断して、エンジニアがパフォーマンスを最大化し続けられるようにすることを目指しています。 実際にわたしは、下記のようなことに取り組んでいて、様々あるマネジメント領域でもピープルマネジメントの比重が大きいと認識しています。

  • エンジニア採用(予算計画やダイレクトスカウト、面談...etc)
  • エンジニア向けの人事制度の作成と運用
  • 目標設定の導入や推進
  • 1on1の実施 など(ちょっと人事っぽいですね。)

※ピープルマネジメントに偏った取り組みを日々行っているため、読者の方によってはこれから書かれている内容に漏れがあると思うかもしれません。 ※自身のことを初心者EMだと認識しています。これから書かれている内容に対してベテランEMの方は「ここが見えてないぞ」と思うかもしれません。読んだ後にアドバイスをいただけるととても喜びます。

EMの役割とは

先に結論から書いていこうと思います。私にとってのEMの役割とはこちらになります。

「エンジニア組織がパフォーマンスを最大化し続けられるようにすること」

この言葉にした理由を今回は書いていきます。 この言葉は裏を返すと、最大化し続けることを揺るがす何かがあります。 その何かは変化であり、大きく2つの変化に分けることができるとわたしは思っています。

1つは人の変化です。環境など目の前のことをどう解釈するかで個人のパフォーマンスは変わってくると考えています。 2つ目は、事業や組織の変化です。プロダクトは成長すればフェーズが当然変わってきますし、それに応じて人が増えたり、チームの状況も少しずつ変わってきます。それらの要因を見極めながら対応しなければパフォーマンスはたちまち変わってきてしまうと思います。

今回のポストでは前編として1つ目の人の変化について書いて、後編で2つ目の事業や組織の変化について書こうと思います。

人の変化について

1人のパフォーマンスは今置かれている状況と自己実現欲求のつながりの解釈によって変化する

時間軸は人それぞれ異なるにせよ個人は、「これをやりたい」「こうなりたい」などと何かしらの欲求を持っています。 所属している会社で、今会社で個人が取り組んでいることとその欲求が一致していたらそれはモチベーション*1ややる気など上がるかと思います。もしくは夢中になって取り組むことができる人もいるでしょう。それはとてもパフォーマンスが発揮されやすい状態になります。 しかし、そうではない場合どうでしょうか? 個人の欲求と全く一致しないようなことを取り組んでいるとしたら、モチベーションは下がり、パフォーマンスが発揮されにくい状況になってしまうのではないでしょうか?

個人が置かれている状況をどう解釈するかで感情が揺さぶられ、パフォーマンスに影響を及ぼしてしまいます。

「じゃあその人がやりたいことをやらせてあげよう」「その人がやりたいお仕事を提供してあげよう」と言いたいわけではありません。(そのようなケースがあるとは思いますが。。)

もしかしたら、マネージャーがしっかり期待を伝えていない、伝えていたとしてもそれを理解していなかったのかもしれません。 個人がどんなことをやりたいのかマネージャーは理解していたつもりですが、もう少し深ぼってみると理解が異なっていたかもしれません。 まずは対話することによってどこに原因があるのか探ることから始めるのがいいのではと考えています。

ジョハリの窓を参考に「自身が知っていて相手が知らない」「自身が知らなくて相手が知っている」これら2つの領域に存在しているものを把握し、「自身も相手も知っている」という領域を大きくしていくことで相互理解*2*3が深まり、パフォーマンスが発揮しやすい心理的な状況を生み出しやすくなります。

「あなたはまだ見えてないかもしれないけどxxxという強みがあるからやってみては?」「中長期的にxxxというようになりたいならば、yyyというのは実はこうつながっているよ」「xxxという取り組みはyyyという意義があるよ」などのコミュニケーションをマネージャーはできるようになり、個人が「やりたいこと」と会社が「期待していること・やってほしいこと」2つの重なりを大きくできるようになります。

どう解釈するかは個人が悩んだり、考えたりする中で変化させることはもちろんできると思っていますが、第三者との対話を通して見えていなかったことを理解することがより効果的であると考えています。 対話などを通して、目の前の状況に対して個人がいい解釈を行えるように支援して、個人がパフォーマンスを発揮しやすいような状況に持っていくことがEMの役割の1つだと考えています。

1人の持っている能力は変化する

モチベーションに関連のありそうな問題を解決することができたとしてもそれではまだ足りないと思っています。 能力に焦点をあてていないからです。 個人がパフォーマンスを出すために足りてない能力があれば、それを習得しようとします。 個人がパフォーマンスを出すための能力をすでに持っていたとしても新しいチャレンジをしてパフォーマンスを出せるようにするために、足りてない能力を習得しようとします。 能力はチャレンジを伴う取り組みと時間の経過とともに蓄積されていきます。 その能力を発揮することができたとき、会社にとっての成果にも結びつき、活躍したという状態になります。

個人が能力を獲得する・発揮できるようにし、なるべく早く「活躍している」という状態に持っていき、そのサイクルをできる限り続けられるようにすることもEMとしての役割の1つだと考えています。

ある組織に所属するという文脈で人にはライフサイクルが存在しているとわたしは考えています。

入社して、チャレンジをして成長を通して活躍していき、チャレンジ〜成長〜活躍が繰り返され、もしかしたらいつか退社という状態に行き着きます。 このライフサイクルを考えながら現状どの状態にいてどうしたら次の状態に移行できるのか考えています。 観察などを通して「活躍している」という状態に持っていくためにはどこがボトルネックになっているのか?という問いかけてみます。

  • 能力が足りていないのか?
  • 能力を発揮する方向性を間違えてしまっているのか?
  • 今のチーム状態ならば、チームとして足りてない機能はAではなくBという能力ではないのか?
  • 能力を発揮する状態までに至っていないのか?

例えば、新しく入社した方が技術的には十分な能力を持っていたとしても、新しい事業ドメインの理解に苦しみ能力を十分に発揮することができていないかもしれません。新しく入社した方が新しい事業ドメインを理解できるようにオンボーディングを改善したりするなどが考えられるかもしれません。

ライフサイクルの図にあるようにいかに「活躍」という状態に持っていくか、さらなる成長のために次にチャレンジする機会や場を創り、「活躍」のサイクルを繰り返し回せるようにするかということもEMとして重要な役割だと思っています。

前編のまとめ

自分なりに定義したEMの役割に対しての理由を書いてみました。 EMは1人の人物を俯瞰的に見ることが求められるのだと、私は書きながら改めて思いました。 後編はEMの役割の理由について事業や組織の変化という観点から書いていきたいと思います。(役割に「し続ける」という表現を用いたのも後編で書くことになりそうです)

それでは明日のブログもお楽しみに!

*1:モチベーションで仕事をするなという意見もあると思いますが今回は触れません。

*2:言われたことを受け入れてみる姿勢を持っていることも前提として必要と思ってます。

*3:前提として信頼関係の構築も必要と思ってます。

`Engineering Manager Meetup #11` に参加しました

数年ぶりにオフラインのイベントに参加してきました!

engineering-manager-meetup.connpass.com

参加したきっかけ

今まさに評価制度を社内で作ろうとしていて、わからないことも多かったり、

自身がチームビルディングをうまくできてないという実感があって、周りの方々はどんな動きをしているのか率直に知りたいと思っていたところに、このイベントを見て、早速申し込んで参加しました。

どんなイベントだったか

今まではLTだったりと数人が登壇して資料を用いて発表する人と聴講する人みたいな形式みたいなイベントしか参加したことがなかったんですが、

このイベントは、OST(オープン・スペース・テクノロジー)という形式で進められてました。

ja.wikipedia.org

参加者の自発的な発言がベースとなって進められる議論の場のため、参加してみて発表会の聴講者のような比較的インプットの姿勢とは別のスタンスでとても新鮮でした!

セッションの前半後半で本当に様々なテーマがあがっていました。

  • チームマネジメント
  • 1on1
  • 評価
  • EMの働き方の定義
  • EMのマネジメントの責任分界点
  • うまいFBの方法
  • 開発のROI
  • 意思決定にMVVをどう利用するか
  • 組織生産性のための取り組み
  • 自己組織化したチームにするための取り組み
  • より良いキャリアゴールを設定するためのためのベストプラクティス

※イベント時のテーマ名から少し言葉を省略したりしてます

後半のセッションで「自己組織化したチームにするための取り組み」をテーマにあげてセッションオーナーをさせていただきましたが

どのテーマも最後のまとめの発表だけではなく、議論に参加したくなるような興味をそそられるテーマばかりでした!

参加したテーマ

目標・評価における開発マネジメント定量指標をどうしているか

冒頭でも書いたように評価制度を作るところに携わろうとしている身だったので、このテーマに参加しました。

各社どのように定量評価をしているのか/しようとしているのか、やってみて難しいことなどなどディスカッションしました。

(ふせんに書いていただいてありがとうございました!)

最終的に、定量評価しなくてもいいんじゃないか〜みたいな結論になりました。

valueに紐付いた行動指針だけではなく、成果や能力も見た上で評価していきたいが、どう定義するのかやどんな指標を設定するのかというところは本当に苦しんでいるんだなぁと感じました。

定量評価をどうやるのか以外にも評価者は誰が担当するのか、キャリアラダー設定しているのか、目標設定をどう成長と結びつけてマネジメントするかまで評価から話が色々と広がったかもしれませんが、社内での失敗談やリアルな話もありました。これから評価制度を作っていく上でアンチパターンの1つとして知ることができたと思ったので、とてもよかったです。

自己組織化したチームするための取り組み

これは私自身が、セッションオーナーとしてテーマをあげさせていただきました。

昨年からスクラムを社内で進めたり、今まで以上にチーム全体にベクトルを向けて、チームビルディングみたいなところをやってきたのですが、 なかなかうまくいってない実感や難しさを感じていたので、どんな取り組みをしているのかを知りたかったという背景でした。

自身があげた議題だったので、とてもたくさんの知見や経験、アドバイスを聞けたのでとても勉強になったのですが、

セッションオーナーというよりかは参加いただいた諸先輩方に本当に悩みを聞いてもらい、相談にのっていただいた30minになってしまいました。

(ふせんに書いていただいてありがとうございました!)

(忘れないように...)こんなことを話しました。

  • チームはどこに向かっているのか全員で共有しているのだろうか?
  • そのチームはなんでそのメンバーで組成されたんだろうか?
  • メンバーはどんなところに関心があったり、どの領域にベクトルが向いているのだろうか?

課題を深堀りしていくと、チームの目的や目標、Whyみたいな部分の共有がとても弱かったことがわかりました。

加えて、メンバーそれぞれ興味・関心のベクトル(例えば、「顧客課題を解決したいのか」「事業を大きくしたいのか」「技術そのもの」など)が違いますが、一人ひとりのパーソナリティをあまり理解していなかったと感じました。

それらを共有したり、知るためにどんなことをやっているのかというところで、

などなどの方法をやっているという話があがりました。

それらは知ることよりも、実際どう取り組んでいるかというところを詳しく聞けたのが目から鱗でした。

覚えているものだと、

  • インセプションデッキを作る際に、経営と現場の齟齬をなくすために、社長も巻き込んで1日使って作っている
  • MTG以外でチームの時間を毎回決めて作っている(例えば、3時に必ず休んで雑談などをするみたいな)
  • 顧客の声をチャットツールに流したり、動画を撮影したりしてどの職種の人も情報の質や量、粒度に差異が少なくする

などなど今まで実践していなかったものもあれば、実践したことはあっても中途半端やあっさりで終わったり、続いていなかったりしたものもありました。

もっとしっかり取り組まなければならないんだと振り返ることができました。

議論が進む中で、共有する際のコミュニケーションの方向が、トップダウンなのか双方向なのかということも話しの中であがり「メンバーが意見やFBを気軽に言えるような関係は構築できているのだろうか?」という領域にも広がっていきました。

FBをあげてもらうことがとても難しく感じている中で、どんなメンバーが誰に対してもFBを言えるような文化形成をできているのはとても強力だということを実感しました。

個人としてもやれる方法論はいくつもあると思いますが、会社の文化にもかなり依存するものだと思っていて、そのことを改めて確認することができたような気がしています。

懇親会でもFBの話は盛り上がったのですが、「スキがある人がいることでFBがしやすくなる」という名言も生まれたのが強く頭の中に残っています!

エンジニアリングマネージャーが感じていたり、考えているチームの課題というのは、実はメンバーが同じくらい自覚することが難しく、それを理解してもらい、チーム改善するために色々と考えて実行されているんだと学びがありました。

「向かっている理想像に到達する階段の一段は大きいので、その一段をいくつにも小さく分けて敷いてあげることがエンジニアリングマネージャーの仕事」という言葉が個人的にとても印象に残りました。

セッション後の懇親会

セッション後の懇親会も参加したのですが、セッション延長線かと思うくらいみなさんの色々な悩みも含めて色々な議論が飛び交って、これまた濃い時間だったと思いました!

どんなことを話したか

EMも開発するかどうか

もう開発にはあまり関わっていないような方もいれば、がっつり開発もしている方もいたり、MTGで一日が埋まっていたりと色々な事例がありました。

組織にも依存するところはあるとは思いますが、EMが開発もしている背景は評価する立場として、メンバーから認められてないと厳しいという意見がありました。

ただ、メンバーの中には技術的スキルで採用されたメンバーもいるため、チームで1番技術に長けているスーパーマンである必要はなく、評価もその人の立場になってできているということで、衝突が起こらないようにEMの役割やふさわしい姿を考えられて定義されているのだと感じました。

EM自身のマネジメント

日々EMはメンバーのモチベーションマネジメントなどをサポートしていますが、じゃあEM自身のモチベーションやストレスに対するマネジメントはどうするのか?ということも話があがりました。

Slackなどにマイナスな発言をしてしまうとチームの士気を下げる恐れがあり、簡単に吐き出すことはできません。他部署のマネージャーと横のつながりを作って吐き出せるようにしたり、EMの上司に当たる人物に相談していたり、色々なやり方で対応されていました。

一方でメンバーからのアラートは早く摘みたいので、普段から直接伝えていたり、Slack Reacji Channelerでそれらの問題を解決するための人たちが所属するチャンネルに流す仕組みを作っている、日報を書くようにしているなど各社様々な取り組みをされていて勉強になりました。

最後に感想

オフラインイベント自体がかなり久しぶりだったので、とても新鮮な気分になりました。

はじめてOST形式でディスカッションをしましたが、セッションオーナーとして手をあげることでフォーカスが自身の関心ごとに向くため学びがかなりあったので、手を上げてテーマを出してよかったなと思いました。

セッションでも、懇親会でもEMの先人の方々の生の声を聞くことができたのは大きな学びとともに、とても刺激的でした。

このイベントで得た知見を風化させないためにも普段の仕事でまたいろいろと考え、実行してみたいと思います!

次の機会があったときは今以上に学びを得て議論に参加できればと思いました!(早くもあれもこれも聞いてみたかったとなってますが...)

IstioのSidecarリソースについてのちょっとメモ。

なぜ書いているのか?

Istioをドキュメントを読んだり、動かしてみたりしていました。

しかし、タイトルに記載したSidecarリソースについてはいまいち理解できていませんでした。

個人的に抱えているこのモヤモヤをなくすためにIstioのSidecarリソースについて理解したことを書いていこうと思います。

IstioのSidecarリソースはいったい何者なのか

Istioの1.1にリリースされたカスタムリソースです。

ドキュメントには下記のように記載されています。

Sidecar describes the configuration of the sidecar proxy that mediates inbound and outbound communication to the workload instance it is attached to.

The Sidecar configuration provides a way to fine tune the set of ports, protocols that the proxy will accept when forwarding traffic to and from the workload. In addition, it is possible to restrict the set of services that the proxy can reach when forwarding outbound traffic from workload instances.

https://istio.io/latest/docs/reference/config/networking/sidecar/より抜粋

  • Fine-tune the set of ports and protocols that an Envoy proxy accepts.
  • Limit the set of services that the Envoy proxy can reach.

You can specify that you want a sidecar configuration to apply to all workloads in a particular namespace, or choose specific workloads using a workloadSelector.

https://istio.io/latest/docs/concepts/traffic-management/#sidecarsより抜粋

デフォルトの状態でアプリケーションをデプロイするとistio proxyはメッシュ内のすべてのワークロードと通信できるように設定を持ちます。

このSidecarリソースは、istio proxyが持つ通信の設定を調整したりすることができるリソースです。

例えば、ある特定のNamespaceにデプロイされているPodはそのNamespace内の他のPodとしか通信を行わないのに

デフォルトのままだと他のNamespaceと通信できるようにistio proxyに設定が注入されてしまいます。

セキュリティ面や後述するistio proxyのリソースの面でもSidecarリソースを使うことによって通信できる範囲を絞ることができます。

このあたりは実際にデプロイされているistio proxyの設定を確認したほうが見たほうがよさそうです。

しかし、設定をどう見るかについはEnvoyを少し知る必要があったので、少し整理してみたいと思います。

Envoyをざっくり理解する

ここではEnvoyが何者なのかは割愛しますが、istio proxyの設定を見るためにEnvoyで出てくる用語などについて書きます。

主な用語

downstream:Envoyに接続する。 Envoyをプロキシとしたサイドカーアプリケーションがデプロイされている場合

リクエストを送るクライアントがダウンストリームにあたる。 レスポンスを返すサイドカーアプリケーションがダウンストリームにあたる。

upstream : Envoyが受け取ったリクエストを転送する先を指す。 Envoyをプロキシとしたサイドカーアプリケーションがデプロイされている場合

Envoyがクラアントから受け取ったリクエストを転送する先であるサイドカーアプリケーションがアップストリームにあたる。 Envoyがサイドカーアプリケーションから受け取ったレスポンスを転送する先であるクライアントがアップストリームにあたる。

listener : Envoyがダウンストリームからの接続を受け入れるためのIPやポートなどの情報。

cluster : Envoyがアップストリームへ接続するため(転送するため)のエンドポイントのグループ

endpoint : クラスタが持つエンドポイントグループの1つ。クラスタとは親子関係になる。

router: Envoyが受け取ったリクエストをどのクラスタを選択してアップストリームに転送するかを決める経路情報。 HTTPヘッダで転送先を切り替えたり、リクエストのリトライの回数を制御したり、 パーセンテージをベースにしたトラフィックの分割、優先順位ベースのルーティング、、、などルートで設定できることは多岐にわたります。

Envoyがリクエストを受け取る情報はリスナーで管理され、そのリクエストを転送するための情報がクラスタで管理されている形になります。 routerによってリスナーとクラスタが紐づき、ダウンストリームからアップストリームへと転送できるようになります。

参考 :

https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request

EnvoyのxDSについて

Envoyは静的にも、動的にも設定を適用することができますが、ここではIstioが採用している形式に合わせて動的な設定にフォーカスします。

Envoyはファイルシステムによる設定変更やREST APIや、gRPCなど様々な手段で設定を変更することができますが、

特にREST APIやgRPCによる設定変更では、Envoyが管理サーバーに問い合わせて動的に設定を受け取り更新します。

この設定を提供するサービスとAPIをまとめてxDSと呼ばれています。

xには用語で説明したclusterやlistenerがあてはまります。

LDS(Listener Discovery Service) : Listenerの設定を受け取るためのサービス

CDS(Cluster Discovery Servide) : Clusterの設定を受け取るためのサービス

EDS(Endpoint Discovery Service) : Endpointの設定を受け取るためのサービス

RDS(Route Discovery Service) : Routeの設定を受け取るためのサービス

(ADSやSDSは省略しています。)

設定を提供するAPIのサーバーをコントロールプレーンと呼び、問い合せるEnvoyプロキシをデータプレーンと呼びます。

Istioのコントロールプレーン内のPilotというコンポーネントがまさにIstio Proxyの設定を提供していますね。

Istio Proxyの設定を見てみる

Envoyについてざっくりと確認したため実際にIstio Proxyにはどんな設定されているか見てみたいと思います。

例として Namespace foobar にそれぞれdeployment(Pod1台)をapplyしてみます。

(それぞれのNamespaceにデプロイされたPod間での内部通信する要件はないとします。)

Namespace.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: foo
  labels:
    istio-injection: enabled
---
apiVersion: v1
kind: Namespace
metadata:
  name: bar
  labels:
    istio-injection: enabled

Deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-app
  namespace: foo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-app
  template:
    metadata:
      labels:
        app: test-app
    spec:
      containers:
        - name: test-app
          image: test-app
          ports:
            - containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-app
  namespace: bar
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-app
  template:
    metadata:
      labels:
        app: test-app
    spec:
      containers:
        - name: test-app
          image: test-app
          ports:
            - containerPort: 80

Service.yaml

apiVersion: v1
kind: Service
metadata:
  name: foo
  namespace: foo
spec:
  selector:
    app: test-app
  ports:
    - port: 80
      targetPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: bar
  namespace: bar
spec:
  selector:
    app: test-app
  ports:
    - port: 80
      targetPort: 80

これでIngressの設定次第などでは、Port 80で通信できるような構成になりました。

NamespaceとDeploymentをapplyしたので、istio-proxyの設定を確認してみたいと思います。

Istioにはデバッグ目的も含めproxyのステータスを確認するコマンドが用意されています。

% istioctl proxy-status

NAME                                                   CDS        LDS        EDS        RDS          ISTIOD                     VERSION
istio-ingressgateway-7576658c9b-gqrgh.istio-system     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-c85d85ddd-6nfmg     1.8.1
test-app-9dfd8f5-58t4d.foo                             SYNCED     SYNCED     SYNCED     SYNCED       istiod-c85d85ddd-6nfmg     1.8.1
test-app-9dfd8f5-x9m2q.bar                             SYNCED     SYNCED     SYNCED     SYNCED       istiod-c85d85ddd-6nfmg     1.8.1

前述のEnvoyのxDSに沿ってそれぞれのステータスが記載されています。

test-appのistio-proxyはどのサービスディスカバリでも SYNCED になっているためistio-proxyは正常にinjectされているようです。

ちなみにステータスの状態は3種類存在します。

SYNCED : Istiodが最後に送った情報をistio-proxyが受け取り、確認応答を返した状態

NOT SENT : 送信するべき設定がなく、Istiodが何も送ってないことを示す状態

STALE : Istiodが送った設定の確認応答が返ってきていない状態

istio-proxyが正常に動いていることを確認することができたため、各設定も確認していこうと思います。

Listener

 % istioctl proxy-config listener test-app-9dfd8f5-58t4d.foo
ADDRESS      PORT  MATCH                                                               DESTINATION
10.68.0.10   53    ALL                                                                 Cluster: outbound|53||kube-dns.kube-system.svc.cluster.local
0.0.0.0      80    Trans: raw_buffer; App: HTTP                                        Route: 80
0.0.0.0      80    ALL                                                                 PassthroughCluster
10.68.13.187 80    Trans: raw_buffer; App: HTTP                                        Route: bar.bar.svc.cluster.local:80
10.68.13.187 80    ALL                                                                 Cluster: outbound|80||bar.bar.svc.cluster.local
10.68.4.198  80    Trans: raw_buffer; App: HTTP                                        Route: foo.foo.svc.cluster.local:80
10.68.4.198  80    ALL                                                                 Cluster: outbound|80||foo.foo.svc.cluster.local
10.68.0.1    443   ALL                                                                 Cluster: outbound|443||kubernetes.default.svc.cluster.local
10.68.0.60   443   ALL                                                                 Cluster: outbound|443||istio-ingressgateway.istio-system.svc.cluster.local
10.68.10.133 443   ALL                                                                 Cluster: outbound|443||istiod.istio-system.svc.cluster.local
10.68.11.98  443   Trans: raw_buffer; App: HTTP                                        Route: metrics-server.kube-system.svc.cluster.local:443
10.68.11.98  443   ALL                                                                 Cluster: outbound|443||metrics-server.kube-system.svc.cluster.local
0.0.0.0      15001 ALL                                                                 PassthroughCluster
0.0.0.0      15001 Addr: *:15001                                                       Non-HTTP/Non-TCP
0.0.0.0      15006 Addr: *:15006                                                       Non-HTTP/Non-TCP
0.0.0.0      15006 Trans: tls; App: TCP TLS; Addr: 0.0.0.0/0                           InboundPassthroughClusterIpv4
0.0.0.0      15006 Trans: raw_buffer; Addr: 0.0.0.0/0                                  InboundPassthroughClusterIpv4
0.0.0.0      15006 Trans: tls; App: HTTP TLS; Addr: 0.0.0.0/0                          InboundPassthroughClusterIpv4
0.0.0.0      15006 Trans: raw_buffer; App: HTTP; Addr: 0.0.0.0/0                       InboundPassthroughClusterIpv4
0.0.0.0      15006 Trans: tls; App: istio-http/1.0,istio-http/1.1,istio-h2; Addr: *:80 Cluster: inbound|80||
0.0.0.0      15006 Trans: raw_buffer; App: HTTP; Addr: *:80                            Cluster: inbound|80||
0.0.0.0      15006 Trans: tls; App: TCP TLS; Addr: *:80                                Cluster: inbound|80||
0.0.0.0      15006 Trans: raw_buffer; Addr: *:80                                       Cluster: inbound|80||
0.0.0.0      15006 Trans: tls; Addr: *:80                                              Cluster: inbound|80||
0.0.0.0      15010 Trans: raw_buffer; App: HTTP                                        Route: 15010
0.0.0.0      15010 ALL                                                                 PassthroughCluster
10.68.0.60   15012 ALL                                                                 Cluster: outbound|15012||istio-ingressgateway.istio-system.svc.cluster.local
10.68.10.133 15012 ALL                                                                 Cluster: outbound|15012||istiod.istio-system.svc.cluster.local
0.0.0.0      15014 Trans: raw_buffer; App: HTTP                                        Route: 15014
0.0.0.0      15014 ALL                                                                 PassthroughCluster
0.0.0.0      15021 ALL                                                                 Inline Route: /healthz/ready*
10.68.0.60   15021 Trans: raw_buffer; App: HTTP                                        Route: istio-ingressgateway.istio-system.svc.cluster.local:15021
10.68.0.60   15021 ALL                                                                 Cluster: outbound|15021||istio-ingressgateway.istio-system.svc.cluster.local
0.0.0.0      15090 ALL                                                                 Inline Route: /stats/prometheus*
10.68.0.60   15443 ALL                                                                 Cluster: outbound|15443||istio-ingressgateway.istio-system.svc.cluster.local

routes

% istioctl proxy-config routes test-app-9dfd8f5-58t4d.foo
NOTE: This output only contains routes loaded via RDS.
NAME                                                          DOMAINS                               MATCH                  VIRTUAL SERVICE
80                                                            bar.bar                               /*
80                                                            default-http-backend.kube-system      /*
80                                                            foo                                   /*
80                                                            istio-ingressgateway.istio-system     /*
15010                                                         istiod.istio-system                   /*
15014                                                         istiod.istio-system                   /*
istio-ingressgateway.istio-system.svc.cluster.local:15021     istio-ingressgateway.istio-system     /*
metrics-server.kube-system.svc.cluster.local:443              metrics-server.kube-system            /*
foo.foo.svc.cluster.local:80                                  foo                                   /*
bar.bar.svc.cluster.local:80                                  bar.bar                               /*
                                                              *                                     /stats/prometheus*
InboundPassthroughClusterIpv4                                 *                                     /*
InboundPassthroughClusterIpv4                                 *                                     /*
inbound|80||                                                  *                                     /*
                                                              *                                     /healthz/ready*
inbound|80||                                                  *                                     /*

cluster

% istioctl proxy-config cluster test-app-9dfd8f5-58t4d.foo
SERVICE FQDN                                            PORT      SUBSET     DIRECTION     TYPE             DESTINATION RULE
                                                        80        -          inbound       STATIC
BlackHoleCluster                                        -         -          -             STATIC
InboundPassthroughClusterIpv4                           -         -          -             ORIGINAL_DST
PassthroughCluster                                      -         -          -             ORIGINAL_DST
agent                                                   -         -          -             STATIC
bar.bar.svc.cluster.local                               80        -          outbound      EDS
default-http-backend.kube-system.svc.cluster.local      80        -          outbound      EDS
foo.foo.svc.cluster.local                               80        -          outbound      EDS
istio-ingressgateway.istio-system.svc.cluster.local     80        -          outbound      EDS
istio-ingressgateway.istio-system.svc.cluster.local     443       -          outbound      EDS
istio-ingressgateway.istio-system.svc.cluster.local     15012     -          outbound      EDS
istio-ingressgateway.istio-system.svc.cluster.local     15021     -          outbound      EDS
istio-ingressgateway.istio-system.svc.cluster.local     15443     -          outbound      EDS
istiod.istio-system.svc.cluster.local                   443       -          outbound      EDS
istiod.istio-system.svc.cluster.local                   15010     -          outbound      EDS
istiod.istio-system.svc.cluster.local                   15012     -          outbound      EDS
istiod.istio-system.svc.cluster.local                   15014     -          outbound      EDS
kube-dns.kube-system.svc.cluster.local                  53        -          outbound      EDS
kubernetes.default.svc.cluster.local                    443       -          outbound      EDS
metrics-server.kube-system.svc.cluster.local            443       -          outbound      EDS
prometheus_stats                                        -         -          -             STATIC
sds-grpc                                                -         -          -             STATIC
xds-grpc                                                -         -          -             STATIC
zipkin                                                  -         -          -             STRICT_DNS

endpoints

% istioctl proxy-config endpoints test-app-9dfd8f5-58t4d.foo
ENDPOINT                         STATUS      OUTLIER CHECK     CLUSTER
10.64.0.10:80                    HEALTHY     OK                outbound|80||foo.foo.svc.cluster.local
10.64.0.4:53                     HEALTHY     OK                outbound|53||kube-dns.kube-system.svc.cluster.local
10.64.0.8:443                    HEALTHY     OK                outbound|443||metrics-server.kube-system.svc.cluster.local
10.64.0.9:8080                   HEALTHY     OK                outbound|80||default-http-backend.kube-system.svc.cluster.local
10.64.1.3:53                     HEALTHY     OK                outbound|53||kube-dns.kube-system.svc.cluster.local
10.64.1.4:8080                   HEALTHY     OK                outbound|80||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:8443                   HEALTHY     OK                outbound|443||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:15012                  HEALTHY     OK                outbound|15012||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:15021                  HEALTHY     OK                outbound|15021||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:15443                  HEALTHY     OK                outbound|15443||istio-ingressgateway.istio-system.svc.cluster.local
10.64.2.2:15010                  HEALTHY     OK                outbound|15010||istiod.istio-system.svc.cluster.local
10.64.2.2:15012                  HEALTHY     OK                outbound|15012||istiod.istio-system.svc.cluster.local
10.64.2.2:15014                  HEALTHY     OK                outbound|15014||istiod.istio-system.svc.cluster.local
10.64.2.2:15017                  HEALTHY     OK                outbound|443||istiod.istio-system.svc.cluster.local
10.64.2.3:80                     HEALTHY     OK                outbound|80||bar.bar.svc.cluster.local
127.0.0.1:80                     HEALTHY     OK                inbound|80||
127.0.0.1:15000                  HEALTHY     OK                prometheus_stats
127.0.0.1:15020                  HEALTHY     OK                agent
35.189.153.189:443               HEALTHY     OK                outbound|443||kubernetes.default.svc.cluster.local
unix://./etc/istio/proxy/SDS     HEALTHY     OK                sds-grpc
unix://./etc/istio/proxy/XDS     HEALTHY     OK                xds-grpc

どの設定もたくさんあります。

ちょっと気になるのがfooのPodはbarのPodとは通信する予定はないのに、通信できるような設定があります。

冒頭に記載しているようにデフォルトではサービスメッシュ内のすべてのワークロードを通信できるような設定が適用されてしまいます。

本来であればbarのPodとは疎通できなくても問題ないため上記のbarに関する設定は消したいところです。

消すためにはSidecarリソースを使用します。

やっと本題であるSidecarリソースを使っていきたいと思います。

Sidecarリソースを使ってみる

ドキュメントを参考にSidecarリソースをapplyしてみようと思います。

今回はメッシュ内の、istio-systemと疎通できるような設定にしてみます。

Sidecar.yaml

apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
  name: foo
  namespace: foo
spec:
  workloadSelector:
    labels:
      app: test-app
  ingress:
  - port:
      number: 80
      protocol: HTTP
    defaultEndpoint: 127.0.0.1:80
  egress:
  - hosts:
    - "istio-system/*"
---
apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
  name: bar
  namespace: bar
spec:
  workloadSelector:
    labels:
      app: test-app
  ingress:
  - port:
      number: 80
      protocol: HTTP
    defaultEndpoint: 127.0.0.1:80
  egress:
  - hosts:
    - "istio-system/*"

適用後のendpointsの設定を確認してみます。

% istioctl proxy-config endpoints test-app-9dfd8f5-58t4d.foo
ENDPOINT                         STATUS      OUTLIER CHECK     CLUSTER
10.64.1.4:8080                   HEALTHY     OK                outbound|80||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:8443                   HEALTHY     OK                outbound|443||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:15012                  HEALTHY     OK                outbound|15012||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:15021                  HEALTHY     OK                outbound|15021||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:15443                  HEALTHY     OK                outbound|15443||istio-ingressgateway.istio-system.svc.cluster.local
10.64.2.2:15010                  HEALTHY     OK                outbound|15010||istiod.istio-system.svc.cluster.local
10.64.2.2:15012                  HEALTHY     OK                outbound|15012||istiod.istio-system.svc.cluster.local
10.64.2.2:15014                  HEALTHY     OK                outbound|15014||istiod.istio-system.svc.cluster.local
10.64.2.2:15017                  HEALTHY     OK                outbound|443||istiod.istio-system.svc.cluster.local
127.0.0.1:80                     HEALTHY     OK                inbound|80||
127.0.0.1:15000                  HEALTHY     OK                prometheus_stats
127.0.0.1:15020                  HEALTHY     OK                agent
unix://./etc/istio/proxy/SDS     HEALTHY     OK                sds-grpc
unix://./etc/istio/proxy/XDS     HEALTHY     OK                xds-grpc

先ほどまで存在していたbarへの通信の設定がなくなりました。

Sidecarリソースを使うことで設定を必要以上に持たなくて済みました。

Sidecarリソースをもう少し理解したい

先ほどのSidecarリソースを使ってみた例ではいまいち恩恵を受けることができているのか、いないのかという印象は薄かったかもしれません。

しかし、istio-proxyのリソース使用量などパフォーマンスの側面で見るとSidecarリソースを使用することはとても大切になるようです。

大規模なシステムでistio-proxyが必要以上に設定を持ってしまうとCPUやメモリの消費も増えてしまいます。

issueなどにもVitualServiceリソースが増加するごとにメモリの使用量が増加した事例などがありました。

自身もどれくらいの負荷の増減が発生するのか検証してみました。

今回はVirtualServiceやリクエストによる負荷ではなく、単純にSidecarリソースを使用せずにNamespaceとPodを増やしてみてリソースの使用量に差異が見られるのか検証してみました。

Pod数を初回6台くらいから徐々に増やしていき最終的に120台まで増やしてみました。120台まで増やした後にSidecarリソースを適用してみました。

以下がその際のメモリの使用量の推移です。

f:id:jksdaba:20210329010836p:plain

全体として見ると増減は見られますが、5MB~10MBほどの増減でした。

時刻で見ると19:00頃から少しずつ使用量が増加しています。

19:00頃の Podの台数は50~60台ほどでした。そこから120台まで徐々に増加していったことが確認できました。

2:00前くらいから使用量が減少していますが、このときにSidecarリソースを適用しました。

一応メモリ使用量の変化を確認することができましたが、増減の値が小さかったり、、と少し検証内容は不十分だったのでは?と考えています。

絶えずリクエストを送ったり、VirtualServiceを使用して、サーキットブレーカーや割合によるリクエスト制御など様々な機能を利用した複雑なProxy設定を行った上でメトリクスを確認したら、より違った効果が見られたかもしれません。

Podを増やしていく上で、追加されたPodのistio-proxy、既に存在しているPodのistio-proxyどちらにも設定の更新が発生するためControl PlaneのIstiodにも負荷がかかってしまうということもわかりました。このあたりはPodの立ち上がりに影響が出てしまうかもしれないため別途検証してみたほうがよさそうです。

最後に

今回はなんとなく、Sidecarリソースを理解することができましたが、パフォーマンスに関する領域についてはもう少し確認したほうがいいことや、不十分なことが多く見られました。

また、時間を取って今回以上の検証ができればと思います。

もしくは、とても詳しい方がいたら教えてほしいですし、より詳しい記事があったら教えてほしいです。

参考

https://github.com/istio/istio/issues/19925

https://banzaicloud.com/blog/istio-sidecar/

Everything We Learned Running Istio In Production — Part 2 | by Craig Huber | HelloTech

envoyproxy - Reducing memory usage by ISTIO side car - Stack Overflow

Istio / Announcing Istio 1.1

https://linkerd.io/2020/12/03/why-linkerd-doesnt-use-envoy/

Potential memory leak on istio-proxy sidecar · Issue #29505 · istio/istio · GitHub

https://istio.io/latest/docs/concepts/traffic-management/#sidecars

High memory utilization of istio-proxy proportional to number of services · Issue #7912 · istio/istio · GitHub

https://tech.bigbasket.com/bigbaskets-experience-with-istio/

High memory usage with long HTTP/2 connections (GRPC) · Issue #9891 · envoyproxy/envoy · GitHub

Sidecar uses a large amount of memory · Issue #9367 · istio/istio · GitHub

https://github.com/istio/istio.io/issues/7700

https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request

kube-dnsとDNS resolveのことで気になったこと

個人的に気になる記事を発見したので、読んだり、試してみたのでそのメモなど。

気になった記事

NodeLocal DNSCacheを導入するまでの過程. この記事は ミクシィグループ Advent Calendar… | by Naohiro Kurihara | mixi developers | Dec, 2020 | Medium

Karan Sharma | DNS Lookups in Kubernetes

kube-dnsがどのようにクエリをさばいているか確認してみる

環境

GKE 1.16.15-gke.6000

どんなクエリが生成されるか見てみる

通信先のserviceやdeployment

apiVersion: v1
kind: Service
metadata:
  name: hoge-go
  namespace: hoge
spec:
  selector:
    app: hoge-go
  ports:
    - port: 80
      targetPort: 80

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hoge-go
  namespace: hoge
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hoge-go
  template:
    metadata:
      labels:
        app: hoge-go
    spec:
      containers:
        - name: hoge-go
          image: hoge-go
          resources:
            limits:
              memory: "50Mi"
              cpu: "50m"
          ports:
            - containerPort: 80

通信するクライアント用のpod生成

kubectl run multitool --image=praqma/network-multitool --replicas=1

tcpdumpでどれくらいクエリが発行されるか見てみる

  1. nslookup hoge-go.hoge
bash-5.0# tcpdump -n dst port 53


10:40:14.158155 IP 10.0.1.10.48385 > 10.4.0.10.53: 54428+ A? hoge-go.hoge.default.svc.cluster.local. (56)
10:40:14.159412 IP 10.0.1.10.41017 > 10.4.0.10.53: 35215+ A? hoge-go.hoge.svc.cluster.local. (48)
10:40:14.161212 IP 10.0.1.10.47116 > 10.4.0.10.53: 40310+ AAAA? hoge-go.hoge.svc.cluster.local. (48)
  1. nslookup hoge-go.hoge.
10:42:21.884964 IP 10.0.1.10.58678 > 10.4.0.10.53: 8623+ A? hoge-go.hoge. (30)

※この場合は解決できない

  1. nslookup hoge-go.hoge.svc.cluster.local

10:42:59.527023 IP 10.0.1.10.44223 > 10.4.0.10.53: 58441+ A? hoge-go.hoge.svc.cluster.local.default.svc.cluster.local. (74)
10:42:59.529878 IP 10.0.1.10.59164 > 10.4.0.10.53: 37994+ A? hoge-go.hoge.svc.cluster.local.svc.cluster.local. (66)
10:42:59.532175 IP 10.0.1.10.60062 > 10.4.0.10.53: 37258+ A? hoge-go.hoge.svc.cluster.local.cluster.local. (62)
10:42:59.533090 IP 10.0.1.10.40975 > 10.4.0.10.53: 57453+ A? hoge-go.hoge.svc.cluster.local.asia-northeast1-a.c.test.internal. (98)
10:42:59.536537 IP 10.0.1.10.45882 > 10.4.0.10.53: 20638+ A? hoge-go.hoge.svc.cluster.local.c.test.internal. (80)
10:42:59.539721 IP 10.0.1.10.57718 > 10.4.0.10.53: 39085+ A? hoge-go.hoge.svc.cluster.local.google.internal. (64)
10:42:59.542635 IP 10.0.1.10.48161 > 10.4.0.10.53: 49588+ A? hoge-go.hoge.svc.cluster.local. (48)
10:42:59.544235 IP 10.0.1.10.42390 > 10.4.0.10.53: 37242+ AAAA? hoge-go.hoge.svc.cluster.local. (48)
  1. nslookup hoge-go.hoge.svc.cluster.local.
10:45:12.725623 IP 10.0.1.10.51546 > 10.4.0.10.53: 49162+ A? hoge-go.hoge.svc.cluster.local. (48)
10:45:12.727908 IP 10.0.1.10.41173 > 10.4.0.10.53: 49705+ AAAA? hoge-go.hoge.svc.cluster.local. (48)

AAAAレコードをいったん抜きにしてみてみると最後の . まで加えたFQDNでの名前解決は発行数が少ない。

FQDNを使用していない場合は発行数が多い(1と3の発行数が異なる理由は後述)

クライアント用のpodのresolv.confを確認してみる

bash-5.0# cat /etc/resolv.conf

nameserver 10.4.0.10
search default.svc.cluster.local svc.cluster.local cluster.local asia-northeast1-a.c.test.internal c.test.internal google.internal
options ndots:5

nameserverはkube-dnsのservice

% kubectl get svc -n kube-system | grep kube-dns

kube-dns               ClusterIP   10.4.0.10    <none>        53/UDP,53/TCP   16d

ndotsは5に設定されている。

そのため前述の1~3までの問い合わせはドット数が5より少ないため searchで設定されている検索リストを順番に試すことになる。

1と3がクエリの発行数が違うのは1の場合、searchの左から2番目( svc.cluster.local )を試して成功したから少ないということだった。

3番目のクエリはsearch全て試して解決できなく、最後のFQDNで解決できるまでにたくさんのクエリが発行されてしまった。

もしかしたらDNSで障害が発生する可能性があるかもしれない

例えば、マイクロサービスでたくさんの内部通信が発生していて

気づかぬうちに大量のDNS解決のクエリが発行されていたらDNSによる障害が起きるかもしれない。

検証はしていないが、こちらの記事に障害が発生したことや、クエリ量を少なくする対応を行ったことによってトラフィック量が減少したことを示されている。

Kubernetes pods /etc/resolv.conf ndots:5 option and why it may negatively affect your application performances

クエリ量を減少させるための対応方法について考えてみる

これらがあると思う。

  1. 内部通信先のエンドポイント設定をFQDNにする
  2. dnsConfigを用いる

1. 内部通信先のエンドポイント設定をFQDNにする

これはあまりオススメはされていない。(たしかに「FQDNまで設定されているね。」と確認したくない。。。)

下記のコメントにはndotsがデフォルト5で設定されているの理由やsearchのリストについての背景などが記載されていたりしている。

Kube-dns add-on should accept option ndots for SkyDNS or document ConfigMap alternative subPath · Issue #33554 · kubernetes/kubernetes · GitHub

2. dnsConfigを用いる

dnsConfigを用いることでPodのresolv.confをカスタマイズすることができる。

この機能はkubernetesのversion 1.14からはstableになっている。

https://kubernetes.io/ja/docs/concepts/services-networking/dns-pod-service/#pod-dns-config

ドキュメントの例を見てもsearchやndotsなど様々な設定ができることがわかる。

最後に

2つの方法で対応できると記載したが、GKEにはNodeLocalDNSCacheというものが提供されていた。

これもkube-dnsの負荷を軽減してくれそうな仕組みなので、今度動かしてみてみたいと思う。

cloud.google.com

午前4時に障害対応をやらないために

注意

この記事はタイトルに対して汎用的に使用できるものではありません。

GCPのconfig connectorに関する記事です。

本文に多く「リソース」という言葉が出てきますが、これはGCPのconpute engine, gcs, bigquery, gke, memory store...など様々なサービスを想定して書いています。

config connectorはとても便利ではあるが、、

cloud.google.com

個人的にはそう信じています!

GKE上からyamlを定義してapplyするだけで、今までconsoleから作成していたリソースを簡単に作成することができます。

ということは、gitでソースコード同様に管理することができます。

これは嬉しいですよね?

これによって、GCPで使用しているサービスに依存してやりにくかったテストなど、開発のプロセスで少し難しく感じているプロセスを解消することができそうです。

ただ、注意も必要だということも私は、障害を起こしてしまったことで学びました。

先ほども記載したように「簡単に作成することができる」ということは、一方で簡単に削除もできてしまいます。

この特徴をちゃんと理解していないと本当に危険な目に遭います。

本番環境で運用しているBigQueryなどをデータごと吹っ飛ばしてしまうかもしれません。

何を起こしてしまったか

今の職場でconfig connectorを使用できないかということで、検証していました。

その中でプロジェクトのIAMを吹っ飛ばしてしまいました。

サービスアカウントを作成して、必要な権限を与えるためのyamlを定義してapplyしました。

コンソールを確認した時、目を疑いました。

全てのIAMがなくなっているのです。

目を疑いましたが、夢ではありませんでした。

既に運用しているアプリケーションに割り当てているサービスアカウントの権限だけでなく、

各サービスのデフォルトのサービスアカウントやサービスエージェントのIAMがなくなってしまっていました。

数分後には、使用しているサービスがアラートをあげ始める事態となりました。

原因

この2つを理解していなかったことが原因でした。

  • config connectorの適用スコープと適用時の動作
  • config connector独自の削除操作に関するアノテーション

config connectorの適用スコープと適用時の動作

cloud.google.com

全てのリソースではないですが、一部のリソースではプロジェクト全体で上書きされる形で適用されてしまいます。

私はIAM Policyを使用してIAMを全て吹っ飛ばしてしまいました。

IAMPolicy  |  Config Connector Documentation  |  Google Cloud

このexampleのコメントに以下のように記載されています。

    # **WARNING**: The bindings here represent the full declarative intent for the project.
    # It will fully overwrite the existing policy on the given project.
    #
    # For finer-grained control over the project's IAM policy, it is recommended
    # that the IAMPolicyMember resource be used instead.
    #
    # This sample assumes the following additional APIs are enabled:
    #   - compute.googleapis.com
    #   - container.googleapis.com
    #   - containerregistry.googleapis.com
    #   - redis.googleapis.com
    #
    # Replace ${PROJECT_ID?}, ${PROJECT_NUMBER?}, and ${PROJECT_NAME?} with your desired project ID,
    # that project's project number, and your Google Cloud account email respectively.

これを知らずに権限を与えたいサービスアカウントだけ権限を付与するようなyamlを定義して適用させたため、

上書きされてしまい、今まで存在していたサービスアカウント(各サービスのサービスアカウントやサービスジェントも含む)がすべてなくなってしまいました。

ただこのように動作するのはIAM Policyだけのようです。使用する際はドキュメントに記載されているexampleのyamlまでしっかり読んでから動かしてみることを強くお勧めします。

config connector独自の削除操作に関するアノテーション

これは、障害を起こしてしまった際に直接的に作用したものではないですが、その時ちゃんと理解しておらず、もしかしたら他のリソースを動かした時に

このアノテーションによる障害も起こしてしまっていたかもしれないと思い、記載しました。

cloud.google.com

config connectorは新しくリソースを作成することはもちろんですが、既に作成、運用されているリソースに対してconfig connectorの機能によって

インポートして紐づけることもできます。

既存の Google Cloud リソースのインポート  |  Config Connector のドキュメント

作成や紐付けたリソースを削除した場合、

もちろんk8sクラスタのリソースから削除されることはもちろんですが、GCP上のリソースからも削除されてしまいます。

本当に必要ないのであればGCP上のリソースから削除されてしまっても問題ないのですが、ケースによってはconfig connectorの紐付けだけ外して

リソース自体は残しておきたい場面があるかもしれません。(リソースに消えてはまずいデータが保管されているのであれば尚更です)

ドキュメントにも赤く警告文として記載されていますが

...
metadata:
  annotations:
    cnrm.cloud.google.com/deletion-policy: abandon
...

このアノテーションを付けることで、リソース自体を残すことができます。

障害を起こしてしまった時の対応

これは結構特例ですが、

サービスエージェントが失われてしまい、ほとんどのサービスのAPIを使用することができなくなってしまいました。

この消えてしまったサービスエージェントたちは、手作業で復旧しました。

手作業で同じようなIAMを復活させて本当に障害以前のように安定稼働するのか不安でしたが、問題なく動きました。 (後にサポートの方に聞き、状態など確認してもらいましたが問題なく動いているとの連絡をもらいました)

最後に

使用上の注意を正しく読んでお使いください。

以上夏の思い出でした。

今更RBAC

タイトルの通り、いまさらkubernetesのRBACをいじってみた。

Using RBAC Authorization - Kubernetes

ロールベースのアクセス制御  |  Kubernetes Engine ドキュメント  |  Google Cloud

KubernetesのRBACについて - Qiita

Kubernetes道場 20日目 - Role / RoleBinding / ClusterRole / ClusterRoleBindingについて - Toku's Blog

「RBACとは」的な説明は省略しているので、そのようなことを知りたい方は上記のリンクなどに記載している。

いじる前の準備(ベースの環境作成)

namespace

apiVersion: v1
kind: Namespace
metadata:
  name: wakashiyo

service account

apiVersion: v1
kind: ServiceAccount
metadata:
  name: sample
  namespace: wakashiyo

検証用Pod

pod

apiVersion: v1
kind: Pod
metadata:
  name: sample-kubectl
  namespace: wakashiyo
spec:
  serviceAccountName: sample
  containers:
    - name: kubectl-container
      image: lachlanevenson/k8s-kubectl:latest
      command: ["sleep", "86400"]

1. 検証用のPodからkubectlコマンドを使用してPodを作成してみる

この段階では何もRoleは設定されていない

作成してみる

kubectl exec -it sample-kubectl -n wakashiyo -- kubectl run nginx --image=nginx:latest

Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:wakashiyo:sample" cannot create resource "pods" in API group "" in the namespace "wakashiyo"
command terminated with exit code 1

作ることができなかった

Podのリソースを作成することが禁じられていると言われてしまったので、Roleを付与してみる

role

kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-role
  namespace: wakashiyo
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create"]

role binding

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: sample-role-binding
  namespace: wakashiyo
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: sample-role
subjects:
  - kind: ServiceAccount
    name: sample
    namespace: wakashiyo

apply する

kubectl apply -f role.yml
kubectl apply -f rolebinding.yml

再度作成し直してみる

kubectl exec -it sample-kubectl -n wakashiyo -- kubectl run nginx --image=nginx:latest

pod/nginx created


kubectl get pods -n wakashiyo -o wide

NAME             READY   STATUS    RESTARTS   AGE     IP           NODE                                       NOMINATED NODE   READINESS GATES
nginx            1/1     Running   0          10s     10.48.1.12   gke-wakashiyo-default-pool-29ce2b10-b5gc   <none>           <none>
sample-kubectl   1/1     Running   0          9m55s   10.48.0.10   gke-wakashiyo-default-pool-29ce2b10-gx26   <none>           <none>

作成できた

2. 検証用のPodから get pods したりしてみる

kubectl exec -it sample-kubectl -n wakashiyo -- kubectl get pods -n wakashiyo

Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:wakashiyo:sample" cannot list resource "pods" in API group "" in the namespace "wakashiyo"
command terminated with exit code 1



kubectl exec -it sample-kubectl -n wakashiyo -- kubectl get pods -n wakashiyo

Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:wakashiyo:sample" cannot list resource "pods" in API group "" in the namespace "wakashiyo"
command terminated with exit code 1



kubectl exec -it sample-kubectl -n wakashiyo -- kubectl get pods nginx -n wakashiyo

Error from server (Forbidden): pods "nginx" is forbidden: User "system:serviceaccount:wakashiyo:sample" cannot get resource "pods" in API group "" in the namespace "wakashiyo"
command terminated with exit code 1



kubectl exec -it sample-kubectl -n wakashiyo -- kubectl describe pods nginx -n wakashiyo

Error from server (Forbidden): pods "nginx" is forbidden: User "system:serviceaccount:wakashiyo:sample" cannot get resource "pods" in API group "" in the namespace "wakashiyo"
command terminated with exit code 1

できない。

特定のPodを指定してdescribeしたり、getしたりするためにはgetの操作権限、

pod一覧を出したりするためにはlistの操作権限が必要そう。

roleを変更してみる

role

kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-role
  namespace: wakashiyo
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create", "get", "list"] # get, listを追加

再度getしたりしてみる

% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl get pods -n wakashiyo

NAME             READY   STATUS    RESTARTS   AGE
nginx            1/1     Running   0          8m5s
sample-kubectl   1/1     Running   0          17m



% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl get pods nginx -n wakashiyo

NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          8m13s



% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl describe pods nginx -n wakashiyo
Name:         nginx
Namespace:    wakashiyo
Priority:     0
... 以下省略

getできた。

ちなみに今まで設定していたのはClusterRoleではなく、Roleだったので、 --all-namespaces でのget操作はできない。

% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl get pods --all-namespaces

Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:wakashiyo:sample" cannot list resource "pods" in API group "" at the cluster scope
command terminated with exit code 1

cluster roleを追加する

cluster role

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-cluster-role
  namespace: wakashiyo
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list"]

cluster role binging

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: sample-cluster-role-binding
  namespace: wakashiyo
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: sample-cluster-role
subjects:
  - kind: ServiceAccount
    name: sample
    namespace: wakashiyo

applyして再度 --all-namespace でgetしてみる

% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl get pods --all-namespaces

NAMESPACE     NAME                                                        READY   STATUS    RESTARTS   AGE
kube-system   event-exporter-v0.2.5-599d65f456-jn2t4                      2/2     Running   0          4h
kube-system   fluentd-gcp-scaler-bfd6cf8dd-cj6m5                          1/1     Running   0          4h
kube-system   fluentd-gcp-v3.1.1-jml9r                                    2/2     Running   0          3h59m
kube-system   fluentd-gcp-v3.1.1-mvhhf                                    2/2     Running   0          3h59m
kube-system   heapster-gke-ddccfc6d-xtgkv                                 3/3     Running   0          3h59m
kube-system   kube-dns-5995c95f64-dclwz                                   4/4     Running   0          4h
kube-system   kube-dns-5995c95f64-qh26s                                   4/4     Running   0          4h
kube-system   kube-dns-autoscaler-8687c64fc-xsh7x                         1/1     Running   0          4h
kube-system   kube-proxy-gke-wakashiyo-default-pool-29ce2b10-b5gc         1/1     Running   0          4h
kube-system   kube-proxy-gke-wakashiyo-default-pool-29ce2b10-gx26         1/1     Running   0          4h
kube-system   l7-default-backend-8f479dd9-r2xgs                           1/1     Running   0          4h
kube-system   metrics-server-v0.3.1-5c6fbf777-26kst                       2/2     Running   0          3h59m
kube-system   prometheus-to-sd-4bkqh                                      2/2     Running   0          4h
kube-system   prometheus-to-sd-lhb4t                                      2/2     Running   0          4h
kube-system   stackdriver-metadata-agent-cluster-level-6f6775c594-qmcx7   2/2     Running   0          3h59m
wakashiyo     nginx                                                       1/1     Running   0          14m
wakashiyo     sample-kubectl                                              1/1     Running   0          24m

できた。

3. 検証用のPodから作成したnginxのPodを削除してみる

% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl delete pods nginx -n wakashiyo

Error from server (Forbidden): pods "nginx" is forbidden: User "system:serviceaccount:wakashiyo:sample" cannot delete resource "pods" in API group "" in the namespace "wakashiyo"
command terminated with exit code 1

当然deleteの権限がないのでできない。

roleを変更する

role

kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-role
  namespace: wakashiyo
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create", "get", "list", "delete"] # deleteを追加

applyして再度delete podを実行してみる

% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl delete pods nginx -n wakashiyo

pod "nginx" deleted




% kubectl get pods -n wakashiyo

NAME             READY   STATUS    RESTARTS   AGE
sample-kubectl   1/1     Running   0          27m

削除できた

4. podをスケールさせてみる

% kubectl get pods -n wakashiyo

NAME                    READY   STATUS    RESTARTS   AGE
nginx-cd9dcdd6c-jwnvn   1/1     Running   0          78s
sample-kubectl          1/1     Running   0          55m




% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl scale deploy nginx --replicas 2

Error from server (Forbidden): deployments.extensions "nginx" is forbidden: User "system:serviceaccount:wakashiyo:sample" cannot get resource "deployments" in API group "extensions" in the namespace "wakashiyo"
command terminated with exit code 1

できない。

roleに権限を追加する

role

kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-role
  namespace: wakashiyo
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create", "get", "list", "delete"]
  - apiGroups: ["apps", "extensions"]
    resources: ["deployments"]
    verbs: ["get"] # getを追加

再度スケールさせてみる

% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl scale rs nginx --replicas 2

Error from server (Forbidden): deployments.extensions "nginx" is forbidden: User "system:serviceaccount:wakashiyo:sample" cannot patch resource "deployments/scale" in API group "extensions" in the namespace "wakashiyo"
command terminated with exit code 1

まだできない

再度roleを変更する

role

kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-role
  namespace: wakashiyo
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create", "get", "list", "delete"]
  - apiGroups: ["apps", "extensions"]
    resources: ["deployments"]
    verbs: ["get"]
  # 以下を追加
  - apiGroups: ["apps", "extensions"]
    resources: ["deployments/scale"]
    verbs: ["patch"]

再度スケールさせてみる

% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl scale deploy nginx --replicas 2

deployment.extensions/nginx scaled




% kubectl get pods -n wakashiyo
NAME                    READY   STATUS    RESTARTS   AGE
nginx-cd9dcdd6c-jwnvn   1/1     Running   0          4m27s
nginx-cd9dcdd6c-zs7f9   1/1     Running   0          7s
sample-kubectl          1/1     Running   0          58m

やっとできた。

スケールさせるには deploymentsではなく、 deployments/scale というリソースに対する権限が必要だった。

その他

RoleBindingはRoleを1つしか設定することができない。

role binding

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: sample-role-binding
  namespace: wakashiyo
roleRef:
  apiGroup: rbac.authorization.k8s.io
  - kind: Role
    name: sample-role
  - kind: Role # 2つ目のrole
    name: sample-role2
subjects:
  - kind: ServiceAccount
    name: sample
    namespace: wakashiyo
% kubectl apply -f rolebinding.yml
error: error parsing rolebinding.yml: error converting YAML to JSON: yaml: line 7: did not find expected key

※ エディタで書いている段階でエラー出る

RoleBindingで紐付けるservice accountは複数設定できる

role binding

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: sample-role-binding
  namespace: wakashiyo
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: sample-role
subjects: # 複数設定している
  - kind: ServiceAccount
    name: sample
    namespace: wakashiyo
  - kind: ServiceAccount
    name: sample2
    namespace: wakashiyo

1つのRoleで同じリソースに対してのルールを複数設定することができる

role

kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-role
  namespace: wakashiyo
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create", "delete"]
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list"]

上記で設定した操作ができる。

% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl run nginx --image=nginx
pod/nginx created



% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl get pods nginx -n wakashiyo
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          19s



% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl delete pods nginx -n wakashiyo
pod "nginx" deleted

cluster roleに限ってはaggregationできる。

cluster role 1

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-cluster-role
  labels:
    app: sample-role
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list"]

cluster role 2

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-cluster-role2
  labels:
    app: sample-role
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["delete"]

cluster role 3

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-cluster-role3
  labels:
    app: sample-role
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create"]

aggregated cluster role

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: sample-aggregated-cluster-role
aggregationRule:
  clusterRoleSelectors:
    - matchLabels:
        app: sample-role

aggregateされている

% kubectl get clusterrole sample-aggregated-cluster-role -o yaml
aggregationRule:
  clusterRoleSelectors:
  - matchLabels:
      app: sample-role
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"aggregationRule":{"clusterRoleSelectors":[{"matchLabels":{"app":"sample-role"}}]},"apiVersion":"rbac.authorization.k8s.io/v1beta1","kind":"ClusterRole","metadata":{"annotations":{},"name":"sample-aggregated-cluster-role"}}
  creationTimestamp: "2020-05-05T12:24:26Z"
  name: sample-aggregated-cluster-role
  resourceVersion: "76217"
  selfLink: /apis/rbac.authorization.k8s.io/v1/clusterroles/sample-aggregated-cluster-role
  uid: 5c5491eb-8ecb-11ea-a4d9-42010a9200fb
rules:
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - get
  - list
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - delete
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - create

実際にcreate, get, deleteできる

% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl run nginx --image=nginx
pod/nginx created



% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl get pods -n wakashiyo
NAME             READY   STATUS    RESTARTS   AGE
nginx            1/1     Running   0          17s
sample-kubectl   1/1     Running   0          102m




% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl get pods nginx -n wakashiyo
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          26s




% kubectl exec -it sample-kubectl -n wakashiyo -- kubectl delete pods nginx -n wakashiyo
pod "nginx" deleted

おしまい。