※本記事は、技術評論社「Software Design」(2023年11月号)に寄稿した連載記事「Google Cloudで実践するSREプラクティス」からの転載です。発行元からの許可を得て掲載しております。
はじめに
前回はArgo CDによるKubernetesへの継続的デリバリについて紹介しました。今回は、Google Cloudが提供するAnthos Service Meshを導入して、GKEで動くアプリケーションに可観測性やセキュリティなどの機能を追加する方法を紹介します(図1)。また、本記事に関するサンプルコードについてはGitHub1を参照してください。
▼図1 CADDiスタックにおける今回の位置付け
Anthos Service Meshとは
Anthos Service Mesh2(以降、ASM)とは、サービスメッシュのOSS製品であるIstio3をベースに機能を追加したフルマネージドのサービスメッシュのサービスです。GKEにアドオンとしてインストールし、Google Cloudコンソールと連携されるほか、Google Cloudの技術サポートを受けることもできます。 ASMはAnthos4というサービスの一部でもあります。Anthosはマルチクラウドやオンプレミス環境にGKEを中核としたGoogle Cloudのサービスを構築し一元管理するためのサービスです。 Google CloudのGKEでのみサービスメッシュを使用したいのであれば、Anthos全体ではなくASMを単体で使用したほうが低コストで済みます5。キャディでもASMのみを使用しています。ASMを使用する際に誤ってAnthosの課金を有効にしないように注意してください。
サービスメッシュとIstio
サービスメッシュは分散システムで動く複数のサービス間の通信を制御するためのインフラです。 信頼性の高いサービス間通信には、適切なリトライやタイムアウトの設定、ログやメトリクス、分散トレーシングなどの可観測性の向上、TLS通信などのセキュリティ向上など、さまざまな処理が必要です。これらの要素を各サービスへ個別実装するのは開発工数が増えるほか、設定変更のたびにサービスを再デプロイするといった運用負荷も増えます。サービスメッシュはこれらをインフラとして提供することで、サービスに対して高信頼で設定変更が容易な通信機能を透過的に提供します。 ASMのベースになっているIstioは代表的なサービスメッシュの製品であり、「サイドカー」と「コントロールプレーン」という2つのコンポーネントを使用します。これらのコンポーネント配置は図2を見てください。
▼図2 サービスメッシュのアーキテクチャ
サイドカーは各サービスのPodに通信プロキシとして挿入されます。その後、Podへの通信とPodから出ていく通信の両方がサイドカー経由となります。すべての通信がサイドカー経由となるので、通信制御やログ、メトリクスの出力がサイドカーに集約できます。 サイドカーへの通信設定を行うのがコントールプレーンです。Istio用のマニフェストファイルを使うことでその設定内容をカスタマイズできます。 サイドカーはenvoy6という通信プロキシを使用します。envoyは自身の設定をAPI経由で更新する機能を有しているため、Podを再起動することなく設定を変更できます。 Istioは、サイドカーの挿入を透過的にできることが特徴です。つまり、サイドカーの挿入はPodの起動時に自動で行われます。また、サイドカー導入のためにアプリケーションコードの変更や再起動は不要で、既存環境に対して低コストでサービスメッシュを導入できます。
サービスメッシュを導入する理由
Istioの導入は難しくありません。一方で、導入後にIstioを最新に保っていくためには、コントロールプレーンとすべてのPodのサイドカーの更新が必要なため、簡単なことではありません。Istioは多機能であり、明確な目的を持たずに導入すると運用コストがあとから負債となるでしょう。 キャディでの導入目的は「可観測性の向上」です。キャディでは、多数のサービスが単一のGKEクラスタ上で稼働しています。サービスの運用監視を効率的に行うには、各サービスが同じフォーマットでログやメトリクスを出力することが望ましいです。多数のサービスにこれらの処理を手作業で実装することは困難ですが、サービスメッシュの導入によって容易に実現できます。
図3はASMのダッシュボードのキャプチャです。ASMを導入することで、GKE内のPodの通信グラフやリクエスト統計が可視化されるほか、HTTPメトリクスも取得できるため、リクエストエラーに基づく監視アラートも設定できます。アクセスログの出力については後述します。
また、キャディではサービスメッシュとOpenTelemetry7を組み合わせ、Cloud Traceによる分散トレーシングの収集も行っています(図4)。これら可観測性の向上も、最小限のアプリケーションコードの変更で実現しています。また、筆者の過去の経験では、非常に高いセキュリティを求めるシステムで、全サービス間通信の暗号化を要求されたことがあります。サービスメッシュを使えばサイドカーでmTLS通信を強制できるため、セキュリティ向上の手段としてもサービスメッシュは有効です。 サービスメッシュが何をできるかを知るには、Istioのドキュメント8を見たり、実際に動かして試してみたりするのが良いでしょう。
▼図3 ASMダッシュボード
▼図4 分散トレーシング
コラム: Ambient Mesh
まだ安定版にはなっていませんが、IstioではAmbient Meshというサイドカーを用いないサービスメッシュが開発中です9。Ambient Meshでは、各ノードに配置するセキュアな通信用のエージェント(ztunnel)と、L7レイヤの通信処理を集中して行うenvoy Podを使用して、サイドカーと同等の機能を実現するようです。 サイドカーが不要になることで、リソース利用効率の向上やアーキテクチャの簡素化につながることが期待できます。とくに、サイドカー更新時のPod再起動が不要になることは、大きな利点です。 ASMでAmbient Meshが提供されるかはまだわかりませんが、ぜひとも利用したい機能です。
コラム: 分散トレーシング OpenTelemetry
Istioではサイドカーでの分散トレーシングをサポートしていますが、複数のサービス間通信を一連のトレーシングとしてまとめることはできません。Context Propagation10、という通信元から通信先へトレーシングに関する情報を引き継ぐ処理が必要で、現時点ではアプリケーションコードでの対応が必須となっています。 分散トレーシングのライブラリはOpenTelemetry11として標準化が進められていて、本記事のサンプルコードでも使用しています。また、対応する言語やカスタマイズ性は限られますが、OpenTelemetry Operator12を使えば、アプリケーションの対応が不要でKubernetesでのデプロイ時にOpenTelemetryを自動で組み込むことも可能です。
ASMを試す
それでは、実際にASMをインストールし、サービスメッシュの機能を試していきます。
ASMの種類とインストール
ASMには次の2種類のオプションがあります。
①マネージドAnthos Service Mesh(以降、マネージドASM) ②クラスタ内コントロールプレーン
両者の違いはコントロールプレーンの管理方法です。
①はコントロールプレーンがGKEクラスタの外にあるGoogle Cloudのマネージドサービスから提供されます。コントロールプレーンの運用やアップデートは自動で行われますが、バージョンの選択や使用できる機能に制限があります。 ②はGKEクラスタ内に自身でコントールプレーンをインストールするものです。Istioを自前でインストールして運用する形式に近く、運用やアップデートは自分で行う必要がありますが、細かくカスタマイズできます。 詳細は公式ドキュメント13を参照してください。 筆者としては、マネージドASMを選択することをお勧めします。最大の理由は「マネージドASMを使うと、コントロールプレーンの運用が大幅に簡単になるため」です。 通常、Istioのアップデートにはコントロールプレーンとサイドカー両方の更新が必要です。さらに、安全なアップデートのためには複数バージョンのコントールプレーンをインストールして段階的にアップデートするカナリアアップデートが必要です。 一方、マネージドASMではコントールプレーンのアップデートが自動で行われます。サイドカーにも、コントールプレーンの変更を検知して自動再起動するマネージドデータプレーン14というしくみが提供されます。これによって、コントロールプレーンとサイドカーが自動で安全に更新されるのが、大きなメリットです。 キャディでは、マネージドASMを使用していて、これまでに大きなトラブルなくコントロールプレーンが更新され続けています。 ASMのインストール方法は、Istioが提供する方法ではなくGoogle Cloudから提供されているものを使用します。マネージドASMでは、「デフォルト設定のASMをfleet APIでインストールする方法15」か「asmcliで細かくカスタマイズする方法16」を選択します。詳細はこれらのドキュメントとサンプルコードのインストールスクリプトを参照ください。
サービスメッシュ全体設定
マネージドASMでは、アクセスログや分散トレーシングなどのサービスメッシュ全般に関する設定を ConfigMapで行います。このConfigMapは、istio-systemネームスペースにistio-asm-managedという名前で作成します。 たとえば、アクセスログと分散トレーシングを有効化する場合の例はリスト1のとおりです。
▼リスト1 manifests/3_controlplane_config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: istio-asm-managed
namespace: istio-system
data:
mesh: |-
# アクセスログの出力と形式
accessLogFile: /dev/stdout
accessLogEncoding: JSON
accessLogFormat: (..割愛)
# デフォルトで使用する分散トレーシングの機能
defaultConfig:
tracing:
stackdriver: {}
ConfigMapの名称の後半のasm-managedは、マネージドASMのバージョンを表すリリースチャネル17の値です。マネージドASMは更新頻度が異なる3種類のリリースチャネルがあります。asm-managedは最新バージョンから数世代前の安定稼働を確認したバージョンのIstioをベースとした使いやすいバージョンになっています。 dataに記述する内容はマネージドASMのドキュメント18を参照してください。また、ドキュメントにない設定もIstioのMeshConfig19を参考に独自に設定できます。 キャディでは、分散トレーシングのサンプリングレートを変更するなど、マネージドASMのドキュメントに記載のない機能も検証し使用しています。
Ingress Gateway
Ingress Gatewayはサービスメッシュへの通信の入り口となるサービスで、サイドカーと同じくenvoyを使用しています。ロードバランサとの接続先となるサービスで、KubernetesのIngressリソースの代わりとなるものです。Istioが提供するGateway20リソース、Virtual Service21リソースを記述して、ドメインやパスに応じたサービスのルーティング、CORSやリクエストヘッダ加工、TLS終端といった処理ができます。ASMではインストール用のマニフェストが提供されていますので、必要に応じてインストールします。公式ドキュメント22かサンプルコードを参照してください。
Istioのマニフェスト
コントロールプレーンを通じてサイドカーの設定を変更するには、Istioが提供するマニフェストを作成してクラスタに適用します。数が多いので、よく使用する機能を中心に取り上げます。 最も利用頻度が高いのは、通信制御に関する設定でしょう。たとえば、リクエストルーティング、リトライ、サーキットブレーカなどです。Istioの通信制御に関するガイド23に設定例がまとまっていますので参照してください。 Gateway、VirtualService DestinationRule、ServiceEntryといったリソースを使用して通信をカスタマイズできます。 次に、キャディでは認証認可に関する設定をよく使います。サービスメッシュ上のPod間はmTLSで通信するので、各Podはサービスアカウントに基づくクライアント証明書を通信に付与します。クライアント証明書は通信元Podの身元保証に使用できるため、特定のPodからのみ通信を受け付けるといった制御ができます。 設定例はサンプルコードを参照してください。 そのほかにも、HTTPリクエストを検証して、特定ヘッダや、認証トークンがなければサイドカーでアクセスを拒否するという振る舞いも実現できます。これについては、Istioの認証認可に関するガイド24を参照してください。 AuthorizationPolicy、RequestAuthenticationなどのリソースを使用してリクエストの検証ができます。
アプリケーションをASMに対応する
アプリケーションをサービスメッシュに組み込むには、アプリケーションのマニフェストにも一部修正が必要です。 今回のサンプルコードでは、app というnamespaceに、frontおよびbackendという2つのPodをデプロイします。frontサービスはリスト2のように、backnedサービスのAPIを呼び出したあとに、その結果を加工してレスポンスを返します。
▼リスト2 samples/front/index.js(一部抜粋)
const BACKEND_SERVICE_URL = "http://backend:3100"
app.get('/hello', (req, res) => {
axios.get(`${BACKEND_SERVICE_URL}/api`).then(resp => {F
res.send({result: resp.data.answer * 2})
}))
app namespaceにデプロイするすべてのPodにサイドカーを挿入するようにするには、リスト3のとおり、app namespaceにistio.io/revラベルを追加します。ラベルの値は前述したリリースチャンネルの値です。このラベルが付与されたnamespaceに Podをデプロイすると、Podのマニフェストに対してサイドカーのimageやサービスメッシュの設定を組み込むための各種設定が自動的に追加されます。
▼リスト3 manifests/1_namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: app
labels:
istio.io/rev: asm-managed
また、ServiceリソースにnameまたはappProtocolフィールドを追加し、サービスの通信プロトコルを明示します(リスト4)。
▼リスト4 manifests/5_front.yaml(一部抜粋)
apiVersion: v1
kind: Service
metadata:
name: front
spec:
type: ClusterIP
ports:
- port: 3000
name: http-web
# appProtocol: http
protocol: TCP
selector:
app: front
プロトコルを明示することにより、メトリクスの強化が行われるほか、gRPCを使用している場合はクライアントサイド負荷分散ができるといった利点があります。プロトコルの種類や規則については、Istioのドキュメント25を参照してください。ここまでの内容を設定してアプリケーションをデプロイすると、アプリケーションPodは図5のように、istio-proxyというコンテナが追加された状態で動いていることがわかります。
▼図5 サイドカーコンテナの挿入を確認する
# Pod 一覧
$kubectl get pod -n app
# pod内部コンテナを表示
$kubectl get pod backend-nnnn -n app -o jsonpath="{.spec.containers[*].name}"
# istio-proxyと backend2つのコンテナがある。
istio-proxy backend
istio-proxyがサイドカーです。このとき、Podへの通信はすべてサイドカー経由となっています。前述のfrontサービスのサンプルコードでは、http://backend:3100のようにKubnetesのサービス名でほかのPodへ通信をしています。サービス名を使用した通信はKubernetesではよく使用しますが、これはサイドカーを導入したあとでもそのまま使用できます。そのため、アプリケーションコードはASMを導入しても変更する必要はありません。マネージドデータプレーンによりPodは定期的に再起動されることを考慮しておく必要があります。サイドカーの起動オプションに EXIT_ON_ZERO_ACTIVE_CONNECTIONS
というフラグを有効化すると、Pod終了時にPodへの接続がなくなることを待ってから終了するように指示できます。詳しい設定例はサンプルコードのDeploymentリソースを参照してください。
Google Cloudとの統合
デプロイしたアプリケーションにリクエストを送って稼働確認を行うと、サイドカー経由でログやメトリクスが出力されます。これらの情報はGoogle Cloudの次の機能で利用できます。
- Anthos Service Meshダッシュボード : サイドカーを導入したPodの可視化やリクエスト統計を確認できる
- Cloud Logging : サイドカーのアクセスログが収集される
- Cloud Monitoring : サイドカーのメトリクスを参照、監視できる
- Cloud Trace : 分散トレーシングを設定した場合のみ、トレーシングの確認ができる
これで、GKEのPod単位での運用監視がGoogle Cloudの標準ツールでできるようになりました。ASMの導入を通して、GKE上のサービスの可観測性を向上できたことがわかると思います。
今回のまとめ
サービスメッシュの導入は、サイドカーを使うことから、アーキテクチャの複雑性やパフォーマンスへの影響を心配されることがあります。 確かにアーキテクチャは複雑ですが、開発者から見れば、アプリケーションコードの変更なくデプロイできるように配慮されています。一方でASMの運用者は、アーキテクチャを理解し、サイドカーの実装であるenvoyを理解しておくと、運用がしやすくなるでしょう。 クラウドインフラとアプリケーションの間にあるサービスメッシュは、通信エラーなどが起きたとき悪者にされがちです。筆者の経験から言えば、通信エラーの原因はクラウドかアプリケーションのどちらかに適切な通信設定をされていないことが大半であり、サービスメッシュによって強化されたログやメトリクスでエラーを検知できるようになったというだけでした。 通信エラーの調査をする際には、envoyの知識があると役に立ちます。アクセスログやメトリクスにあるResponse Flags26という値を見ると、通信断が起きたとき、どちら側からどのような理由で切断されたのかがわかります。 パフォーマンスの影響については、筆者はこれまでに3回、プロダクトにサービスメッシュを導入した経験がありますが、サービスメッシュによってパフォーマンスが極端に落ちたということはありません。結局はそのサービスの性能指標を満たせるかどうかを実際に計測してみるのが大事です。 また、サイドカーによってもたらされる可観測性やセキュリティの機能を、サイドカーなしで各サービスに実装するとなったら、その開発工数は膨大なものとなるでしょう。よって筆者は、サービスメッシュの導入コストは「各サービスに横断で必要となる機能を実装するコストのトレードオフ」と考えています。
◆ ◆ ◆
今回は、サービスメッシュおよび、ASMのインストールとサンプル実行までを紹介しました。 キャディではGKEの可観測性向上を目的にサービスメッシュを導入し、その後も認証認可やセキュリティ強化のために利用する機能を増やしていく予定です。導入目的をはっきりしないといけないと述べましたが、それを明確にするためにも一度ASMを試してみて、みなさんが運用しているサービスの運用向上に役に立つ部分がないかを検証してみると良いと思います。 次回は、モニタリング基盤について紹介する予定です。お楽しみに。
- https://github.com/caddijp/sd-asm-example/ ↩︎
- https://cloud.google.com/anthos/service-mesh ↩︎
- https://istio.io/ ↩︎
- https://cloud.google.com/anthos ↩︎
- AnthosとASMの料金比較https://cloud.google.com/anthos/pricing https://cloud.google.com/service-mesh/pricing ↩︎
- https://www.envoyproxy.io/ ↩︎
- https://opentelemetry.io/ ↩︎
- https://istio.io/latest/docs/ ↩︎
- https://istio.io/latest/blog/2022/introducing-ambient-mesh/ ↩︎
- https://istio.io/latest/docs/tasks/observability/distributed-tracing/overview/ ↩︎
- https://opentelemetry.io/ ↩︎
- https://opentelemetry.io/docs/kubernetes/operator/ ↩︎
- https://cloud.google.com/service-mesh/docs/managed/supported-features-mcp, https://cloud.google.com/service-mesh/docs/supported-features ↩︎
- https://cloud.google.com/service-mesh/docs/managed/provision-managed-anthos-service-mesh#managed-data-plane ↩︎
- https://cloud.google.com/service-mesh/docs/managed/provision-managed-anthos-service-mesh ↩︎
- https://cloud.google.com/service-mesh/docs/managed/provision-managed-anthos-service-mesh ↩︎
- https://cloud.google.com/service-mesh/docs/managed/select-a-release-channel ↩︎
- https://cloud.google.com/service-mesh/docs/managed/enable-managed-anthos-service-mesh-optional- ↩︎
- https://istio.io/latest/docs/reference/config/istio.mesh.v1alpha1/#MeshConfig ↩︎
- https://istio.io/latest/docs/reference/config/networking/gateway/ ↩︎
- https://istio.io/latest/docs/reference/config/networking/virtual-service/ ↩︎
- https://cloud.google.com/service-mesh/docs/gateways ↩︎
- https://istio.io/latest/docs/tasks/traffic-management/ ↩︎
- https://istio.io/latest/docs/tasks/security/ ↩︎
- https://istio.io/latest/docs/ops/configuration/traffic- management/protocol-selection/ ↩︎
- https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage ↩︎