GKE Standard に登場した高スループット版の Logging エージェントはこれまでと何が違うのか

従来、GKE Standard の Logging エージェントは、ワークロードの出力ログ量に関わらず基本的には同じ設定の Logging エージェント (Fluent Bit) がデプロイされるようになっていました。
今回のアップデートでは、高いログスループットが求められる(1 秒あたりのログの処理量がより多い)ノードのために専用の Logging エージェントをデプロイする機能が追加されました。
Logging エージェントの何がどう変わったのか、実際にクラスタを用意して確認してみました。

今回のアップデートは下記のリリースノートから確認できます。
cloud.google.com

また、図らずも 前回 と少し関連した内容となったため、ノードあたりのPod数上限の拡張や、それに伴うコントロールプレーンの設定の違いなどにご興味がありましたらこちらも併せてご参照ください。

ユースケース

アップデートに併せて既に 公式ドキュメント にも記載がありますが、ノードあたりのログスループットが 100 KB/s 超となるようなノードのために用意されたようです。

ノードプール毎の指定となるため、クラスタ内すべてのノードで高スループット版の Logging エージェントを使うこともできますし、特定のノードプールのノードに限って高スループット版を利用する、といった使い方もできるようになっています。
具体的な差異は後ほどご紹介しますが、高スループット版ではより多くの CPU, メモリを消費します。
そのため、ノードプール毎にログの出力量の異なるワークロードが稼働している場合には、特に高いログスループットが求められるノードプールを指定して利用する、というケースが多いのではないかと思います。

なお、新規クラスタはもちろんのこと、既存クラスタからのアップデートでもデプロイ可能ですが、利用するには GKE Standard のコントロールプレーンが 1.24.2-gke.300 以降である必要があります。
現状 GKE Autopilot では利用できません。

実機検証

それでは、実際にクラスタを構築して、通常の Logging エージェントと高スループット版の Logging エージェントの違いを見ていきたいと思います。

1. クラスタを作成する

今回のアップデートの対応バージョンは 1.24.2-gke.300+ となっていますが、今回は 1.24.2 の最新のパッチバージョンである 1.24.2-gke.1900 を使用して GKE Standard のクラスタを作成しました。

まずは通常の Logging エージェントを利用するためのノードプールを e2-medium で用意しました。
説明の簡単のため、ノード数は 1 としています。

作成したクラスタは以下のとおりです。(見やすさのためリソースの表示順序は入れ替えています)

$ gcloud container clusters describe sample-gke
name: sample-gke
...
currentMasterVersion: 1.24.2-gke.1900
currentNodeCount: 1
currentNodeVersion: 1.24.2-gke.1900
...
loggingConfig:
  componentConfig:
    enableComponents:
    - SYSTEM_COMPONENTS
    - WORKLOADS
loggingService: logging.googleapis.com/kubernetes
...
nodePoolDefaults:
  nodeConfigDefaults:
    loggingConfig:
      variantConfig:
        variant: DEFAULT
nodePools:
- name: default-pool
  initialNodeCount: 1
  config:
    ...
    imageType: COS_CONTAINERD
    machineType: e2-medium
    ...
  maxPodsConstraint:
    maxPodsPerNode: '110'
  ...

2. 高スループット版 Logging エージェントをデプロイする

次に、クラスタに別のノードプールを追加し、高スループット版 Logging エージェントをデプロイすることで、ひとつのクラスタに通常の Logging エージェントと高スループット版の Logging エージェントが 1 ノードずつデプロイされている状態にしていきます。

以下のように、ノードプールの作成時に --logging-variant=MAX_THROUGHPUT オプションを指定することで、高スループット版の Logging エージェントをデプロイするノードプールを作成します。
インスタンスタイプは先ほどよりも大きな e2-standard-4 としました。

$ gcloud container node-pools create "additional-pool" --cluster "sample-gke" --logging-variant=MAX_THROUGHPUT
(他のオプションは割愛)
...
Note: Node version is specified while node auto-upgrade is enabled. Node-pools created at the specified version will be auto-upgraded whenever auto-upgrade preconditions are met.
Creating node pool additional-pool...done.     
Created [https://container.googleapis.com/v1beta1/projects/***********/zones/us-central1-a/clusters/sample-gke/nodePools/additional-pool].
NAME: additional-pool
MACHINE_TYPE: e2-standard-4
DISK_SIZE_GB: 100
NODE_VERSION: 1.24.2-gke.1900

$ gcloud container clusters describe sample-gke
name: sample-gke
...
currentMasterVersion: 1.24.2-gke.1900
currentNodeCount: 2
currentNodeVersion: 1.24.2-gke.1900
...
loggingConfig:
  componentConfig:
    enableComponents:
    - SYSTEM_COMPONENTS
    - WORKLOADS
loggingService: logging.googleapis.com/kubernetes
...
nodePoolDefaults:
  nodeConfigDefaults:
    loggingConfig:
      variantConfig:
        variant: DEFAULT
nodePools:
- name: default-pool
  initialNodeCount: 1
  config:
    ...
    imageType: COS_CONTAINERD
    machineType: e2-medium
    ...
  maxPodsConstraint:
    maxPodsPerNode: '110'
  ...
- name: additional-pool
  initialNodeCount: 1
  config:
    ...
    imageType: COS_CONTAINERD
    loggingConfig:
      variantConfig:
        variant: MAX_THROUGHPUT
    machineType: e2-standard-4
    ...
  maxPodsConstraint:
    maxPodsPerNode: '110'
  ...

なお、GKE クラスタの作成やノードプールの追加は GUI コンソールからも作成が可能ですが、高スループット版の Logging エージェントをデプロイする場合、現状はコマンドラインからの API リクエストが必要です。

3. 通常の Logging エージェントと高スループット版の Logging エージェントを比較する

それでは、各ノードプールにデプロイされた通常の Logging エージェントと高スループット版の Logging エージェントの YAML を取得し、主な差異を比較していきたいと思います。

3.1. イメージの比較

まずは各エージェントのコンテナイメージを確認してみましょう。
以下の図は、左を通常版の Logging エージェント、右を高スループット版の Logging エージェントとして、YAML の差異を VS Code で比較したものです。

スループット版の Logging エージェントでは、fluentbit-gke-max-... という名前になるようですね。
313 行目 (fluentbit-gke-max では 331 行目) で確認できるとおり、高スループット版 Logging エージェントでも、引き続き Fluent Bit が使われています。
イメージのパスも一致していることから、高スループット版 Logging エージェントは「従来の Logging エージェントと同様に Fluent Bit のイメージを使っており、設定の違い (チューニング) によって高スループットを実現している」ことがわかります。

3.2. Fluent Bit の設定の違い

Logging エージェントのイメージは同一であることがわかったため、次にコンテナ毎の設定の違いを見ていきましょう。
Daemonset でデプロイされる Logging エージェントの Pod には、以下の 3 つのコンテナが定義されています。

  • ノードからログを収集する Fluent Bit (fluentbit)
  • 収集したログを Cloud Logging に転送する Exporter (fluent-bit-gke-exporter)
  • 初期化処理を行う InitContainer (fluentbit-gke-init)

通常稼働時は Pod に Fluent Bit と Exporter の 2 つのコンテナが存在していることになります。
まずは Fluent Bit の設定の違いを見ていきましょう。
以下の図は、左を通常版の Logging エージェント、右を高スループット版の Logging エージェントとして、 YAML の Fluent Bit の定義付近を比較したものです。

Fluent Bit のコンテナ単位の Request / Limit (特に CPU Request) が大幅に引き上げられていることがわかります。
その他の定義には大きな差異はなく、設定ファイルも同じ ConfigMap を読み込んでいるため、Fluent Bit としては割り当てリソースの引き上げによって高スループット版を実現していると言えそうです。

3.3. Exporter の設定の違い

次に、Exporter の設定の違いを見ていきましょう。
以下の図は、左を通常版の Logging エージェント、右を高スループット版の Logging エージェントとして、 YAML の Exporter の定義付近を比較したものです。

Exporter の CPU Request もまた、大幅に引き上げられていることがわかります。
加えて、コマンドの引数には通常版の Logging エージェントにはなかった --pod-cache-size=512 というオプションが追加されています。
このオプションは ノードあたりの Pod 数上限を 111 以上に拡張した場合 にも付与されていることから、通常版の Logging エージェントよりもログスループットが要求される場合に設定されるオプションのようです。

その他の定義には大きな差異はなく、設定ファイルも同じ ConfigMap を読み込んでいました。

補足 (fluent-bit-gke-exporter の役割)

fluent-bit-gke-exporter に関しては Google Cloud がプライベートに管理しているイメージのため情報が少ないですが、設定ファイルなどからその役割を推測することができます。
Fluent Bit の設定ファイルである fluent-bit.conf を含む ConfigMap として fluentbit-gke-config-* が Namespace: kube-system にデプロイされており、その OUTPUT セクションの定義を見ると、2020 ポートで稼働している Fluent Bit から、収集したログを 2021 ポートで稼働している Exporter に渡していることがわかります。
設定値からの推測ではありますが、fluent-bit-gke-exporter は収集したログを Cloud Logging に転送する役割を持っていると考えられます。

[SERVICE]
    Flush         5
    Grace         120
    Log_Level     info
    Log_File      /var/log/fluentbit.log
    Daemon        off
    Parsers_File  parsers.conf
    HTTP_Server   On
    HTTP_Listen   0.0.0.0
    HTTP_PORT     2020

...

[OUTPUT]
    Name        http
    Match       *
    Host        127.0.0.1
    Port        2021
    URI         /logs
    header_tag  FLUENT-TAG
    Format      msgpack
    Retry_Limit 2

スループット版利用時の注意

スループット版の Logging エージェントを利用する際は、適用先のノードプールのインスタンスタイプに注意が必要です。
Fluent Bit と Exporter がそれぞれ Request で 1 CPU を要求しているため、少なくともスケジュール先のノードが 2 CPU 以上のリソースを持っていなければなりません。

したがって、例えば割当可能な CPU が少ない e2-medium や e2-standard-2 では高スループット版 Logging エージェントが利用できません。
インスタンスタイプが小さい場合は --logging-variant=MAX_THROUGHPUT のオプションを付与してしまうと Logging エージェントが Unschedulable (割り当て不可) の状態となってしまうため、通常の Logging エージェントを利用しましょう。

まとめ

新しく GKE Standard で選択可能となった高スループット版 Logging エージェントについて調べました。
内部の実装は従来どおり Fluent Bit を使っており、割り当てリソースの引き上げによって高スループット版を実現していることがわかりました。

GKE Standard の Node あたりの Pod 数上限が 256 に増えた

従来、GKE Standard における Node あたりの Pod 数上限は 110 でした。
2022 年 8 月にアップデートがあり、この上限が 256 に引き上げられたため、実際にクラスタを用意して確認してみました。

アップデートは下記のリリースノートから確認できます。

cloud.google.com

ユースケース

アップデート後も Node あたりの Pod 数上限のデフォルトは引き続き 110 となっていますが、ひとつの Node に対してより多くの Pod を収容可能にしたいというユースケースはいくつか考えられます。
例えば以下のようなケースが挙げられるでしょう。

  • Node 課金な SaaS / MW のコスト最適化
  • スケールアップ / アウトのバランス調整

なお、従来は /24 の Pod CIDR に対して 110 Pod が上限となっていたため、今回のアップデートによってネットワークアドレスの空きがより少ないクラスタ構成が可能になるのでは、という期待があるかもしれませんが、アドレスレンジは設定した Pod 数上限の倍以上が引き続き必要となります。
そのため、Node あたりの Pod 数上限を 129〜256 としたい場合は /23 を設定する必要があります。

実機検証

それでは、実際にクラスタを構築して Node あたりの Pod 数上限の引き上げが機能していることを確認したいと思います。

1. クラスタを作成する

今回のアップデートの対応バージョンは 1.23.5-gke.1300+ となっていますが、今回は最新の静的リリースである 1.24.3-gke.200 を使用して GKE Standard のクラスタを作成しました。
説明の簡単のため、ノードは 1台のみとしています。
また、Node あたりの Pod 数上限は 256 、Pod CIDR は /23 に設定しました。

作成したクラスタは以下のとおりです。

$ gcloud container clusters describe testcluster-1
...
currentMasterVersion: 1.24.3-gke.200
currentNodeCount: 1
currentNodeVersion: 1.24.3-gke.200
...
defaultMaxPodsConstraint:
  maxPodsPerNode: '110'
...
name: testcluster-1
...
nodePools:
- maxPodsConstraint:
    maxPodsPerNode: '256'
  name: default-pool
  networkConfig:
    podIpv4CidrBlock: 10.88.0.0/14
    podRange: gke-testcluster-1-pods-d26f61e4
  podIpv4CidrSize: 23
  ...

ちなみに、 Node あたりの Pod 数上限は Web コンソールの GKE クラスタ作成画面からも設定することができます。
対応していないバージョンで設定を行おうと試みたところ、図のようにエラーが表示され、この拡張がサポートされているバージョンであるかを確認できるようになっていました。

2. Pod を大量に作成する

クラスタが起動したので、1 台の Node に対して従来の上限である 110 を超える Pod を起動してみます。
下記の要領で、httpd の Pod を 245 作成しました。

$ for i in {1..245}; \
> do kubectl run httpd${i} --image=httpd; \
> done;
pod/httpd1 created
pod/httpd2 created
pod/httpd3 created
...
pod/httpd245 created

3. Pod の状態を確認する

Pod の作成が完了したので、各 Pod の STATUS を確認してみます。

$ kubectl get pods
NAME    READY   STATUS  RESTARTS     AGE
httpd1  1/1         Running 0            6m51s
httpd10 1/1         Running 0            6m18s
httpd100    1/1         Running 0                5m25s
httpd101    1/1         Running 0            5m24s
httpd102    1/1         Running 0            5m24s
httpd103    1/1         Running 0            5m23s
httpd104    1/1         Running 0            5m22s
httpd105    1/1         Running 0            5m22s
…

$ kubectl get pods | grep Running | wc -l
245

$ kubectl get pods | grep -v Running
NAME    READY   STATUS  RESTARTS     AGE

すべて起動しており、ひとつの Node に従来の上限 (110) を超える Pod を立ち上げられることが確認できました。

4. 更なる Pod の追加

更に Pod を追加してみます。
246 番目に追加した httpd の Pod はこれまでと違い、Pending になってしまいました。

$ kubectl run httpd246 --image=httpd
pod/httpd246 created

$ kubectl get pod httpd246
NAME    READY   STATUS  RESTARTS     AGE
httpd246    0/1         Pending 0            4m44s

5. kube-system の Pod を確認する

前項の httpd246 の Pod が起動できなかったのは、既にこの Node に上限いっぱいの 256 Pod が起動していたためです。
今回手動で追加した httpd の Pod は 245 まででしたが、kube-system Namespace を確認すると、Control Plane の一部としてシステム系の Pod が起動していることがわかります。
以下のように、1.24.3-gke.200 では 11 Pod が存在していました。

$ kubectl get pods -n kube-system | awk '{print $1}'
NAME
event-exporter-gke-...
fluentbit-gke-256pd-...
gke-metrics-agent-...
konnectivity-agent-...
konnectivity-agent-autoscaler-...
kube-dns-...
kube-dns-autoscaler-...
kube-proxy-gke-testcluster-1-pool-1-...
l7-default-backend-...
metrics-server-v0.5.2-...
pdcsi-node-...

$ kubectl get pods -A | grep Running | wc -l
256

Node あたりの Pod 数の上限は当然ながらシステム系の Pod を含むため、 httpd246 の Pod は起動できずスケジューリング待ちとなりました。

Control Plane の工夫

Fluent Bit の Request / Limit の引き上げ

今回のアップデートに対応するかたちで、Google Cloud が管理する Control Plane 側にもいくつかの変更が加えられています。
そのひとつが、先程の kube-system Namespace に起動している Fluent Bit です。

Fluent Bit は Pod のログを Cloud Logging に転送する役割を持っており、従来は fluentbit-gke-... という Pod 名で DaemonSet によって展開されていましたが、今回は Pod 名が fluentbit-gke-256pd-... となっています。
この違いは Node あたりの Pod 数の上限を引き上げたことに関係しているのではと考え、Pod 数の上限をデフォルトの 110 としたクラスタを別途作成し、Fluent Bit の Yaml を比較してみました。
主な差分は以下のとおりで、図の左側が Pod 数の上限をデフォルトの 110 としたクラスタ、右側が Pod 数の上限を 256 としたクラスタに展開されていた Fluent Bit の Yaml です。

fluentbit-gke-256pd-... では、Fluent Bit も Exporter もリソースの Request / Limit が引き上げられている事がわかります。 これは、Node に出力されたコンテナのログを収集する Fluent Bit の消費リソースが、Node 上に起動する Pod 数に大きく影響を受けるためと考えられます。

補足

GKE は Fluent Bit を DaemonSet で展開して各 Node のコンテナのログを Cloud Logging に転送する、という方式でクラスタレベルのロギングを実現しています。
Kubernetesクラスタレベルのロギングを行うためのアーキテクチャには 3 つの選択肢がありますので、気になる方は以下を参照ください。

kubernetes.io

Nodepool に応じた DaemonSet の展開

Node あたりの Pod 数の上限の拡張は Nodepool 毎に設定できるため、今後はひとつのクラスタに複数種類の Pod 数上限の Node が混在する可能性があります。
Fluent Bit は DaemonSet で展開されるため、fluentbit-gke の DaemonSet と fluentbit-gke-256pd の DaemonSet の使い分けが必要になります。
確認してみたところ、GKE は NodeAffinity によって Node 毎に展開する Fluent Bit を制御していることがわかりました。

以下の Yaml の差分がそれを表しています。

matchExpressions の設定を見るに、以下のような設計としているようです。

  • Pod 数上限が 8 ~ 110 の (あるいは明示的に上限をしていない) Node : 従来と同じ fluentbit-gke を展開
  • Pod 数上限が 111 ~ 256 の Node : リソースの Request / Limit を増やした fluentbit-gke-256pd を展開

まとめ

Node の Pod 数上限を 256 まで拡張できるようになったことを確認しました。
実機での確認を通じて、このアップデートに対応して Control Plane にも工夫がみられることがわかりました。

Google Cloud Certified Professional Machine Learning Engineer を取得しました

Google Cloud が提供している認定資格、Professional Machine Learning Engineer を取得しました。
受験準備や参照したリソースについて書き残しておきたいと思います。

試験概要

Professional Machine Learning Engineer は Google Cloud が認定するプロフェッショナル資格のひとつです。
Google Cloud の ML 系サービスやソリューションに関する知識を問われる内容となっています。

https://cloud.google.com/certification/machine-learning-engineer

  • 試験時間:2 時間
  • 出題形式:多肢選択式
  • 受験料:$200
  • 試験官との応対言語:英語
  • 試験問題の言語:英語

試験官との応対は英語で行います。
現状は、試験問題の文面も英語のみの提供となっています。

おすすめの学習リソース

Google Cloud の Medium ブログ

Google Cloud の中の方々が執筆した技術記事が集められている Medum ブログです。
ML 系の記事も豊富に提供されていますので、公式ドキュメントを読んだときや Qwiklabs などで手を動かしたときに疑問があれば、ここに助けになるような情報があるかもしれません。
いくつか読んでおくと良さそうな記事をご紹介します。

medium.com

模擬試験

Google Cloud から Web で受けられる模擬試験が提供されています。
何度でもチャレンジできますが、出題内容は変わりません。
事前に解いておくことで実際の試験問題のイメージが湧くと思います。

cloud.google.com

受験方法

今回はオンラインで受験しました。
オンラインの場合は Webassessor の試験配信システムを利用するため、受験に用いる端末に Sentinel というクライアントをインストールしておく必要があります。
その他、受験に関する注意事項はひととおり目を通しておくことをおすすめします。

https://support.google.com/cloud-certification/answer/9907852?hl=ja&ref_topic=9433463

受験するときのテーブル上には、許可されたもの(ディスプレイ、キーボード、マウスetc.)以外は一切置くことができません。
ただし、私が受験した際はキーボードとマウスにそれぞれアームレストを使うことは許可されていました。

受験時の所感

Vertex AI や Auto ML などの Google Cloud のサービスに関する知識を問う問題はもちろんのこと、状況に応じた学習アルゴリズムの選択・モデルが適切なパフォーマンスを発揮できない場合の対処方法・モデルの品質評価方法など、ML の一般知識に関する問題もそれなりに出題されている印象でした。
テキスト・自然言語・画像など、Google Cloud には多種多様なソースを目的に応じて分類・分析できる様々な ML サービスがありますので、ユースケースに応じて選択すべきサービスやソリューションがパッと思い浮かべられるようにしておくと良いかと思います。

おわりに

Professional Cloud Machine Learning Engineer を取得しました。
受験準備を通じて、Google Cloud の ML 関連の知識を再整理することができました。

Google Cloud Certified Professional Cloud Network Engineer を取得しました

Google Cloud が提供している認定資格、Professional Cloud Network Engineer を取得しました。
受験準備や参照したリソースについて書き残しておきたいと思います。

試験概要

Professional Cloud Network Engineer は Google Cloud が認定するプロフェッショナル資格のひとつです。
Google Cloud の Network に関するプラクティス・設計に関する知識を問われる内容となっています。

https://cloud.google.com/certification/cloud-network-engineer

  • 試験時間:2 時間
  • 出題形式:多肢選択式
  • 受験料:$200
  • 試験官との応対言語:英語
  • 試験問題の言語:英語

試験官との応対は英語で行います。
現状は、試験問題の文面も英語のみの提供となっています。

おすすめの学習リソース

Google Cloud の Medium ブログ

Google Cloud の中の方々が執筆した技術記事が集められている Medum ブログです。
Network 系の記事も豊富に提供されていますので、公式ドキュメントを読んだときや Qwiklabs などで手を動かしたときに疑問があれば、ここに助けになるような情報があるかもしれません。
いくつか読んでおくと良さそうな記事をご紹介します。

medium.com

Google のネットワークを知る

Google が持つグローバルネットワークの概要を紹介する動画が Youtube に公開されています。
Google のデータセンタ同士がどのように接続されているのか、目を通しておくことで Google のインフラストラクチャのイメージが湧くと思います。

youtu.be

受験方法

今回はオンラインで受験しました。
オンラインの場合は Webassessor の試験配信システムを利用するため、受験に用いる端末に Sentinel というクライアントをインストールしておく必要があります。
その他、受験に関する注意事項はひととおり目を通しておくことをおすすめします。

https://support.google.com/cloud-certification/answer/9907852?hl=ja&ref_topic=9433463

受験するときのテーブル上には、許可されたもの(ディスプレイ、キーボード、マウスetc.)以外は一切置くことができません。
ただし、私が受験した際はキーボードとマウスにそれぞれアームレストを使うことは許可されていました。

受験時の所感

VPC 内のルーティングや負荷分散の問題はもちろんのこと、Cloud Interconnect や Cloud Router など、オンプレミスとのハイブリッド構成に関する問題もそれなりに出題されました。
Network Interigence Center など、ネットワーク関連の補助的なサービスも学習しておくとよいのではと思います。

おわりに

Professional Cloud Network Engineer を取得しました。
受験準備を通じて、Google Cloud のネットワーク関連の知識を再整理することができました。

Google Cloud Certified Professional Cloud DevOps Engineer を取得しました

Google Cloud が提供している認定資格、Professional Cloud DevOps Engineer を取得しました。
受験準備や参照したリソースについて書き残しておきたいと思います。

試験概要

Professional Cloud DevOps Engineer は Google Cloud が認定するプロフェッショナル資格のひとつです。
Google の SRE や DevOps の考え方に基づいたプラクティス・設計に関する知識を問われる内容となっています。

https://cloud.google.com/certification/cloud-devops-engineer?hl=ja

  • 試験時間:2 時間
  • 出題形式:多肢選択式
  • 受験料:$200
  • 試験官との応対言語:英語
  • 試験問題の言語:英語

試験官との応対は英語で行います。
現状は、試験問題の文面も英語のみの提供となっています。

おすすめの学習リソース

Google の SRE 関連書籍

Google の SRE に関する情報を紹介している Web サイト(https://sre.google)にて、SRE のプラクティスが学べる 3 冊の書籍(いずれも英語版)が無料で閲覧できるようになっています。
インシデントコマンダーSLA / SLO / SLI など、SRE にまつわる用語の意味やプラクティスの生まれた背景などをざっと学ぶことができます。
"Site Reliability Engineering" と "The Site Reliability Workbook" は日本語版も販売されていますので、日本語の資料で学習されたい場合はそちらも有用です。

  • Site Reliability Engineering
  • The Site Reliability Workbook
  • Building Secure & Reliable Systems

sre.google

Art of SLOs

こちらは Google が提供しているワークショップで、座学 + 演習で SRE のプラクティスを学ぶことができるようになっています。
SLO やエラーバジェットといった指標を用いてサービスの信頼性を定義・測定・管理する方法の全体像を学ぶことができます。
上述の書籍を読んだうえで参加するとより学習効果が高いと思います。

sre.google

受験方法

今回はオンラインで受験しました。
オンラインの場合は Webassessor の試験配信システムを利用するため、受験に用いる端末に Sentinel というクライアントをインストールしておく必要があります。
その他、受験に関する注意事項はひととおり目を通しておくことをおすすめします。

https://support.google.com/cloud-certification/answer/9907852?hl=ja&ref_topic=9433463

受験するときのテーブル上には、許可されたもの(ディスプレイ、キーボード、マウスetc.)以外は一切置くことができません。
ただし、私が受験した際はキーボードとマウスにそれぞれアームレストを使うことは許可されていました。

受験時の所感

サービスの設計・開発・運用といった幅広いフェーズを対象に、Google の SRE の考え方に基づく回答が求められる出題に多く出会いました。
それに比べると出題数は抑えめだったように記憶していますが、Google Cloud のサービスの組み合わせや個々のサービスの設計に関する出題もありました。
イベント駆動型のシステムを構築する際に用いられるサービスを中心に学習しておくとよいのではと思います。

おわりに

Professional Cloud DevOps Engineer を取得しました。
受験準備を通じて、SRE のプラクティスを学び直すことができました。

Cloud Functions を別のプロジェクトから内部トラフィック扱いで実行する

通常、Cloud Functions を内部トラフィックからの呼び出しに限定したい場合は、Cloud Functions と呼び出し元(例えば GCE インスタンス)のプロジェクトが同一であることが必要です。
しかし、VPC Service Controls を使って Cloud Functions を境界の保護対象にすることで、Cloud Functions とその呼び出し元のプロジェクトが異なる場合でも、内部トラフィック扱いで実行することが可能になります。

Cloud Functions のアクセス制限

Cloud Functions のアクセス制限には大別してふたつの方法があります。

  • Identity-based
  • Network-based

cloud.google.com

詳しい説明は上記の公式ドキュメントを参照頂くと良いかと思いますが、簡単にまとめると以下のようになります。

  • アイデンティティ(サービスアカウントなど)に関数を呼び出す権限が必要
  • 関数の呼び出し元ネットワークを上り (Ingress) の設定で制限できる

関数の呼び出し元のネットワークが自身の組織やプロジェクトに限定される場合は内部トラフィックからの呼び出しのみを許可することで、関数のエンドポイントへの外部からのアクセスを防止することができます。
また、今回は触れませんが、関数から VPC への下り (Egress) の通信を行いたい場合は Serverless VPC Access Connector を利用します。

実装

Cloud Functions とその呼び出し元のプロジェクトが異なる環境を作り、VPC Service Controls を使ってプロジェクトをまたいで関数を内部トラフィックとして実行するまでの流れを示します。

1. プロジェクトの作成

まずは 2 つプロジェクトを作成します。
これらは同一の組織内に作成します。

$ gcloud projects list
PROJECT_ID: bar-127298
NAME: bar-127298
PROJECT_NUMBER: 785238314710

PROJECT_ID: foo-192847
NAME: foo-192847
PROJECT_NUMBER: 865726560248

2. GCE インスタンスの作成

関数の呼び出し元となる GCE インスタンスを各プロジェクトに作成します。

cloudshell:~$ gcloud compute instances list --project foo-192847
NAME: instance-foo
ZONE: us-central1-a
MACHINE_TYPE: e2-medium
PREEMPTIBLE:
INTERNAL_IP: 10.128.0.3
EXTERNAL_IP: 35.239.219.39
STATUS: RUNNING

cloudshell:~$ gcloud compute instances describe instance-foo --project foo-192847 --zone us-central1-a
...
serviceAccounts:
- email: sa-instance-foo@foo-192847.iam.gserviceaccount.com
...

cloudshell:~$ gcloud compute instances list --project bar-127298
NAME: instance-bar
ZONE: us-central1-a
MACHINE_TYPE: e2-medium
PREEMPTIBLE:
INTERNAL_IP: 10.128.0.3
EXTERNAL_IP: 34.69.40.102
STATUS: RUNNING

$ gcloud compute instances describe instance-bar --project bar-127298 --zone us-central1-a
...
serviceAccounts:
- email: sa-instance-bar@bar-127298.iam.gserviceaccount.com
...

3. Cloud Functions の作成

Cloud Functions の関数を一方のプロジェクト (foo-192847) に作成します。
関数の呼び出し元は 内部トラフィックのみ (ALLOW_INTERNAL_ONLY) に制限します。

cloudshell:~ (foo-192847)$ gcloud functions describe function-1
availableMemoryMb: 256
buildId: fc93c012-9859-4fc2-aa85-099012dc32c7
buildName: projects/865726560248/locations/us-central1/builds/fc93c012-9859-4fc2-aa85-099012dc32c7
dockerRegistry: CONTAINER_REGISTRY
entryPoint: helloWorld
httpsTrigger:
  securityLevel: SECURE_ALWAYS
  url: https://us-central1-foo-192847.cloudfunctions.net/function-1
ingressSettings: ALLOW_INTERNAL_ONLY
labels:
  deployment-tool: console-cloud
maxInstances: 3000
name: projects/foo-192847/locations/us-central1/functions/function-1
runtime: nodejs16
serviceAccountEmail: foo-192847@appspot.gserviceaccount.com
sourceUploadUrl: https://storage.googleapis.com/uploads-620971246542.us-central1.cloudfunctions.appspot.com/0be327c3-5853-40ae-96f1-766c3fccc3d7.zip
status: ACTIVE
timeout: 60s
updateTime: '2022-04-05T13:04:10.169Z'
versionId: '1'

各 GCE のサービスアカウントに関数の呼び出し権限を付与します。

$ gcloud functions get-iam-policy function-1
bindings:
- members:
  - serviceAccount:sa-instance-bar@bar-127298.iam.gserviceaccount.com
  - serviceAccount:sa-instance-foo@foo-192847.iam.gserviceaccount.com
  role: roles/cloudfunctions.invoker
etag: BwXb6E6wnRg=
version: 1

4. ネットワークによるアクセス制限の確認

この時点では、同じプロジェクト (foo-192847) に含まれる instance-foo からの HTTP トリガのみが内部トラフィックと判定されます。
HTTP トリガで関数を呼び出す際、呼び出し元は認証情報を明示的に指定する必要があるため、Authorization ヘッダに ID トークンを指定して関数の実行をリクエストしています。

参考:https://cloud.google.com/functions/docs/securing/authenticating

instance-foo:~$ gcloud config list
account = sa-instance-foo@foo-192847.iam.gserviceaccount.com
disable_usage_reporting = True
project = foo-192847

instance-foo:~$ curl -H "Authorization: bearer $(gcloud auth print-identity-token)" https://us-central1-foo-192847.cloudfunctions.net/function-1
Hello World!

Cloud Functions のデプロイされているプロジェクトとは別のプロジェクト (bar-127298) のインスタンス instance-bar は、IAM ポリシによる権限付与がされているものの、ネットワークによるアクセス制限を受けているため実行できません。

参考:https://cloud.google.com/functions/docs/troubleshooting#internal-traffic

instance-bar:~$ gcloud config list
account = sa-instance-bar@bar-127298.iam.gserviceaccount.com
disable_usage_reporting = True
project = bar-127298

instance-bar:~$ curl -H "Authorization: bearer $(gcloud auth print-identity-token)" https://us-central1-foo-192847.cloudfunctions.net/function-1
<html><head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>403 Forbidden</title>
</head>
<body text=#000000 bgcolor=#ffffff>
<h1>Error: Forbidden</h1>
<h2>Access is forbidden.</h2>
<h2></h2>
</body></html>

5. VPC Service Controls のサービス境界を作成

以下のように、2つのプロジェクトと Cloud Functions を指定して境界を作成します。

cloudshell:~$ gcloud access-context-manager perimeters describe dev_perimeter
name: accessPolicies/973808997062/servicePerimeters/dev_perimeter
status:
  resources:
  - projects/865726560248
  - projects/785238314710
  restrictedServices:
  - cloudfunctions.googleapis.com
  vpcAccessibleServices: {}
title: dev_perimeter

境界が有効になったことを確認してみます。
例えば、Cloud Functions のユーザアカウントによるテスト実行は境界外からのアクセスとなるため、VPC Service Controls により実行不可になっていることがわかります。
なお、実行したい場合は境界への Ingress ルールでユーザアカウントからの API アクセスを許可する必要があります。

f:id:polar3130:20220406002249p:plain

境界が有効になったので bar-127298 プロジェクトのインスタンス instance-bar からも内部トラフィック扱いで foo-192847 プロジェクトの関数を呼び出すことができるようになりました。
関数の実行をリクエストして確認します。

instance-bar:~$ curl -H "Authorization: bearer $(gcloud auth print-identity-token)" https://us-central1-foo-192847.cloudfunctions.net/function-1
Hello World!

おわりに

VPC Service Controls を使って Cloud Functions を境界の保護対象にすることで、Cloud Functions とその呼び出し元のプロジェクトが異なる場合でも、内部トラフィック扱いで実行する方法をご紹介しました。
理由は様々ですが Cloud Functions と呼び出し元を同一のプロジェクトに配置できないケースはそれなりにあると思いますので、そうした場合でもネットワークベースのアクセス制限を行えることがわかりました。

GKE 1.22 with Node Local DNS Cache で Alpine 3.13+ の名前解決に成功する

先月の記事 の解決編です。
GKE 1.22 における Node Local DNS Cache の挙動が修正されたようなので確認しました。

GKE 1.22 は現時点の最新リリースである 2022-R6 で 1.22.7-gke.1500 が登場していますので、今回はこちらで試してみます。

https://cloud.google.com/kubernetes-engine/docs/release-notes#2022-r6_version_updates

以下のように GKE クラスタを作成しました。

$ gcloud container clusters describe temp-cluster --zone us-central1-c
addonsConfig:
  dnsCacheConfig:
    enabled: true
  gcePersistentDiskCsiDriverConfig:
    enabled: true
  horizontalPodAutoscaling: {}
  httpLoadBalancing: {}
  kubernetesDashboard:
    disabled: true
  networkPolicyConfig:
    disabled: true
authenticatorGroupsConfig: {}
autoscaling:
  autoscalingProfile: BALANCED
clusterIpv4Cidr: 10.16.0.0/14
createTime: '2022-03-31T12:01:05+00:00'
currentMasterVersion: 1.22.7-gke.1500
currentNodeCount: 1
currentNodeVersion: 1.22.7-gke.1500
databaseEncryption:
  state: DECRYPTED
defaultMaxPodsConstraint:
  maxPodsPerNode: '110'
endpoint: ********************************
id: ********************************
initialClusterVersion: 1.22.7-gke.1500
instanceGroupUrls:
- https://www.googleapis.com/compute/v1/projects/*******************/zones/us-central1-c/instanceGroupManagers/gke-temp-cluster-default-pool-17d86d4d-grp
ipAllocationPolicy:
  clusterIpv4Cidr: 10.16.0.0/14
  clusterIpv4CidrBlock: 10.16.0.0/14
  clusterSecondaryRangeName: gke-temp-cluster-pods-fbb68f9f
  servicesIpv4Cidr: 10.20.0.0/20
  servicesIpv4CidrBlock: 10.20.0.0/20
  servicesSecondaryRangeName: gke-temp-cluster-services-fbb68f9f
  useIpAliases: true
labelFingerprint: a9dc16a7
legacyAbac: {}
location: us-central1-c
locations:
- us-central1-c
loggingConfig:
  componentConfig:
    enableComponents:
    - SYSTEM_COMPONENTS
    - WORKLOADS
loggingService: logging.googleapis.com/kubernetes
maintenancePolicy:
  resourceVersion: e3b0c442
masterAuth:
  clusterCaCertificate: ********************************
masterAuthorizedNetworksConfig: {}
monitoringConfig:
  componentConfig:
    enableComponents:
    - SYSTEM_COMPONENTS
monitoringService: monitoring.googleapis.com/kubernetes
name: temp-cluster
network: default
networkConfig:
  datapathProvider: LEGACY_DATAPATH
  defaultSnatStatus: {}
  network: projects/*******************/global/networks/default
  serviceExternalIpsConfig: {}
  subnetwork: projects/*******************/regions/us-central1/subnetworks/default
nodeConfig:
  diskSizeGb: 100
  diskType: pd-standard
  imageType: COS_CONTAINERD
  machineType: e2-medium
  metadata:
    disable-legacy-endpoints: 'true'
  oauthScopes:
  - https://www.googleapis.com/auth/devstorage.read_only
  - https://www.googleapis.com/auth/logging.write
  - https://www.googleapis.com/auth/monitoring
  - https://www.googleapis.com/auth/servicecontrol
  - https://www.googleapis.com/auth/service.management.readonly
  - https://www.googleapis.com/auth/trace.append
  preemptible: true
  serviceAccount: default
  shieldedInstanceConfig:
    enableIntegrityMonitoring: true
nodePoolAutoConfig: {}
nodePoolDefaults:
  nodeConfigDefaults: {}
nodePools:
- autoscaling: {}
  config:
    diskSizeGb: 100
    diskType: pd-standard
    imageType: COS_CONTAINERD
    machineType: e2-medium
    metadata:
      disable-legacy-endpoints: 'true'
    oauthScopes:
    - https://www.googleapis.com/auth/devstorage.read_only
    - https://www.googleapis.com/auth/logging.write
    - https://www.googleapis.com/auth/monitoring
    - https://www.googleapis.com/auth/servicecontrol
    - https://www.googleapis.com/auth/service.management.readonly
    - https://www.googleapis.com/auth/trace.append
    preemptible: true
    serviceAccount: default
    shieldedInstanceConfig:
      enableIntegrityMonitoring: true
  initialNodeCount: 1
  instanceGroupUrls:
  - https://www.googleapis.com/compute/v1/projects/*******************/zones/us-central1-c/instanceGroupManagers/gke-temp-cluster-default-pool-17d86d4d-grp
  locations:
  - us-central1-c
  management:
    autoRepair: true
    autoUpgrade: true
  maxPodsConstraint:
    maxPodsPerNode: '110'
  name: default-pool
  networkConfig:
    podIpv4CidrBlock: 10.16.0.0/14
    podRange: gke-temp-cluster-pods-fbb68f9f
  podIpv4CidrSize: 24
  selfLink: https://container.googleapis.com/v1/projects/*******************/zones/us-central1-c/clusters/temp-cluster/nodePools/default-pool
  status: RUNNING
  upgradeSettings:
    maxSurge: 1
  version: 1.22.7-gke.1500
notificationConfig:
  pubsub: {}
releaseChannel: {}
selfLink: https://container.googleapis.com/v1/projects/*******************/zones/us-central1-c/clusters/temp-cluster
servicesIpv4Cidr: 10.20.0.0/20
shieldedNodes:
  enabled: true
status: RUNNING
subnetwork: default
zone: us-central1-c

Alpine 3.13 をクラスタにデプロイし、コンテナから名前解決を試みます。

$ kubectl run alpine313 --image=alpine:3.13 --tty -i sh
If you don't see a command prompt, try pressing enter.

/ # nslookup www.google.com
Server:         10.20.0.10
Address:        10.20.0.10:53

*** Can't find www.google.com: No answer

Non-authoritative answer:
Name:   www.google.com
Address: 142.251.6.147
Name:   www.google.com
Address: 142.251.6.99
Name:   www.google.com
Address: 142.251.6.105
Name:   www.google.com
Address: 142.251.6.104
Name:   www.google.com
Address: 142.251.6.106
Name:   www.google.com
Address: 142.251.6.103

/ # wget www.google.com
Connecting to www.google.com (142.251.6.147:80)
saving to 'index.html'
index.html           100% |********************************************************************************************************************************************************************| 14053  0:00:00 ETA
'index.html' saved

/ #

無事に通りましたね。

Node Local DNS Cache の設定はどう変わったのでしょうか。
ConfigMap を確認してみます。

$ kubectl get configmap node-local-dns -n kube-system -o yaml
(以下、Corefile 内を抜粋)

  cluster.local:53 {
    errors
    
    template ANY AAAA {
      rcode NOERROR
    }
    
    cache {
            success 9984 30
            denial 9984 5
    }
    reload
    loop
    
    bind 169.254.20.10 10.20.0.10
    health 169.254.20.10:8080
    
    forward . __PILLAR__CLUSTER__DNS__ {
            force_tcp
            expire 1s
    }
    prometheus :9253
    }
in-addr.arpa:53 {
    errors
    cache 30
    reload
    loop
    
    bind 169.254.20.10 10.20.0.10
    
    forward . __PILLAR__CLUSTER__DNS__ {
            force_tcp
            expire 1s
    }
    prometheus :9253
    }
ip6.arpa:53 {
    errors
    cache 30
    reload
    loop
    
    bind 169.254.20.10 10.20.0.10
    
    forward . __PILLAR__CLUSTER__DNS__ {
            force_tcp
            expire 1s
    }
    prometheus :9253
    }
.:53 {
    errors
    
    template ANY AAAA {
      rcode NOERROR
    }
    
    cache 30
    reload
    loop
    
    bind 169.254.20.10 10.20.0.10
    
    forward . __PILLAR__UPSTREAM__SERVERS__ {
            force_tcp
    }
    prometheus :9253
    }

AAAA クエリに対するレスポンスが NOERROR に変更されました。
これで Pod が A + AAAA ペアクエリを投げても A クエリのレスポンスから名前解決に成功できますね。

ちなみに、以下のとおり Node Local DNS Cache のコンテナイメージは変更されていませんでした(k8s-dns-node-cache:1.21.4-gke.0)。

$ kubectl get daemonset node-local-dns -n kube-system -o yaml
(以下、一部抜粋)

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-local-dns
  namespace: kube-system
spec:
  template:
    spec:
      containers:
      - image: gke.gcr.io/k8s-dns-node-cache:1.21.4-gke.0
        name: node-cache
      volumes:
      - configMap:
          defaultMode: 420
          items:
          - key: Corefile
            path: Corefile.base
          name: node-local-dns
        name: config-volume

まとめ

GKE 1.22 における Node Local DNS Cache 利用時の名前解決の問題は解消しました。
利用している環境のライブラリやプラグインIPv6 対応が進むことで、似たような事象にまた出会うことがあるかもしれません。
特に DNS は多数の RFC を合わせて全体のセマンティクスが出来上がっていることがわかり、設計・実装に関わる場合は注意したいと思いました。