こんにちは。SRE チームの山崎です。
SRE チームでは、Google Kubernetes Engine による Kubernetes 共通アプリケーション基盤を開発・運用し、各サービス開発チームへ提供しています。また、この基盤ではAnthos Service Mesh [1] (ASM) を利用しています。ASM は Google Cloud Platform (GCP) が OpenSource のサービスメッシュミドルウェアである Istio [2] をパッケージングして提供しているコンポーネントです。Istio を利用すると、柔軟なトラフィックルーティングや強力なサービス間認証、オブザーバビリティなどの機能や、リトライやレート制限、サーキットブレイカーなどサービスの可用性を高めるためのトラフィック制御を、アプリケーションに変更を入れることなく導入できるという恩恵があります。
私たちの Kubernetes 基盤開発のなかで ASM の動作検証を行なっていた際に、アプリケーションのコンテナから外部へ HTTPS での通信ができなくなる事象が発生しました。今回はこちらの事象についてまとめます。
事象の概要
今回発生した事象は、基盤上にデプロイされたアプリケーション Pod のコンテナから外部へ HTTPS 接続できなくなる、というものでした。コンテナ内から curl
コマンドを実行してみると、以下のようになりました。
$ curl https://www.google.com
curl: (35) error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol
command terminated with exit code 35
TLS のハンドシェイクが失敗しているように見えます。以前は問題なく外部と HTTPS の通信ができていたのですが、あるタイミングから突然コンテナからの HTTPS 通信だけエラーとなりました。HTTPS ではなく HTTP の接続先については、問題なく通信ができます。
ASM が利用している Istio はアプリケーション Pod に istio-proxy というサイドカーコンテナを自動的にインジェクションします (このコンテナが Istio のサービスメッシュの肝なのですが、今回は詳細を省きます。アプリケーションの通信をすべて中継するプロキシであるという理解で十分です)。試しにこの istio-proxy コンテナの中から同様に curl を実行してみると、こちらは問題なく HTTPS 通信が可能でした。
原因の調査
istio-proxy を経由するアプリケーションの通信のみ問題が発生するという状況から、おそらく istio-proxy の動作に何らかの問題が発生していることがわかりました。既知の問題を調べてみると次の Istio の Issue が見つかりました。ちなみに Istio 1.8 以降で修正されています。
これによると、 Istio を導入した Kubernetes 環境において、特定の設定によりサービスメッシュに属する全てのアプリケーション Pod から、Pod の外部に対する HTTPS 通信ができなくなる、とのことでした。 (条件については後で説明します)
ここでこの問題で深刻なのが、たとえ Kubernetes の namespace などが分離されていたとしても、同じ Istio の環境下であればすべての Pod が影響を受けるということです。私たちは各チームに namespace で分離して Kubernetes をインフラとして提供しますが、あるチームが投入した設定により他のチームにも影響が出てしまうという意味で非常にクリティカルな問題です。
また、昨今のアプリケーションは、そのアプリケーション単体でサービスを提供するというものは少なく、何かしら別のシステムと API 連携して動作することが一般的でしょう。そのため、HTTPS 通信が一切できなくなると影響は甚大です。
発生の条件
さて、特定の条件下で問題が発生すると書きましたが、条件は次のとおりです。
443 ポートを "http" という名前で定義した Kubernetes Service リソースを作成する
Kubernetes に馴染みがある方は YAML を見たほうがわかりやすいと思いますが、以下のようなものです。
apiVersion: v1
kind: Service
metadata:
labels:
app: my-app
name: my-app
namespace: default
spec:
type: ClusterIP
selector:
app: my-app
ports:
- name: http
port: 443
targetPort: 5000
protocol: TCP
非常に単純な YAML です。この YAML が apply されてしまうと、Istio サービスメッシュに属するすべてのアプリケーションから HTTPS 接続できない状態になります。
本来 Istio を利用していない場合は、 ports
に指定する name
の文字列には意味を持ちませんが、Istio を導入している場合 name
からそのポートのプロトコルが解釈されます [3]。Service は インバウンドのトラフィックに関するリソースですが、Istio の不具合によりアウトバウントの 443 ポートへの接続についても name
で指定されるプロトコルで解釈しようとしてしまうとのことでした。つまり、 name: http
とした場合、Istio は外部への 443 ポートへの HTTPS 通信を HTTP として解釈しようとして失敗し通信が切断される、というものでした。
とはいえ、問題は Istio 1.8 以降では修正されており、事象は発生しません。Istio で再現の確認を実施したところ、確かに Istio 1.7 までは事象が再現し、1.8 以降では発生しませんでした。ここでひとつ疑問が残りました。それは、私たちが利用していた Anthos Service Mesh のバージョンは Istio 1.9 に対応する ASM 1.9 だったためです。 念のため Istio 1.9 でも確認を行いましたが問題は発生しませんでした。こちらの事実について GCP サポートに確認し調査していただいた結果、ASM 1.9 固有の問題であることがわかりました。なお、ASM 1.10 では対応されており、1.10 以降へバージョンアップすることで問題は発生しなくなるようです。
回避方法
こちらの問題は、オープンソースの Istio であれば 1.8 以降では既に修正されていますので、アップグレードを実施して 1.8 以上になっていれば問題ありません。また、Anthos Service Mesh では 1.9 まで問題が発生するため、1.10 へアップグレードする必要があります。
問題のあるバージョンを利用している場合は、回避方法自体は単純で Service リソースの 443
ポートに http
ではなく https
といった適切な名前を設定すればよいです。
apiVersion: v1
kind: Service
metadata:
labels:
app: my-app
name: my-app
namespace: default
spec:
type: ClusterIP
selector:
app: my-app
ports:
- name: https # http ではなく https を指定する
port: 443
targetPort: 5000
protocol: TCP
443 ポートへ https という名前を指定するというのはごく普通なことなので、問題が発生することは稀かと思います。
今回の私たちのケースでは 443 ポートで受けた HTTPS を終端し、生の HTTP となった後のトラフィックを 443 ポートのままで サービスメッシュ内をルーティングしていたため、443 ポートに http
という名前を付けてしまっていました。稀なケースではあるものの、些細なことでこのような思いもよらない問題が発生してしまうことがあるので注意が必要です。
まとめ
今回は Service Mesh のミドルウェア Istio に関する不具合について紹介しました。 最新のバージョンでは修正されていますが、1.8 より古いバージョンを利用していると特定の条件化でアプリケーションから HTTPS 通信ができなくなるという大きな問題があります。また、GCP のプロダクトである Anthos Service Mesh ではバージョン 1.9 でも同様の問題が発生することがわかりましたが、こちらも最新バージョンへアップグレードすることで解消されます。
しかし、まだ古いバージョンを利用している環境では同様の問題に遭遇する可能性があるため注意が必要です。この記事が同様の事象で困っている方の参考になれば幸いです。
Kubernetes や Istio などはリリース速度が早く、最新バージョンに追従するのは大変ですが、セキュリティパッチなども含まれるため積極的にアップグレードを実施することは重要です。日経の SRE で運用している Kubernetes 基盤では、Kubernetes や Istio のバージョンアップを安全かつ容易に実施できるような工夫を行なっています。こちらについても別の機会にご紹介したいと思います。
[1] https://cloud.google.com/anthos/service-mesh
[2] https://istio.io/
[3] https://istio.io/latest/docs/ops/configuration/traffic-management/protocol-selection/