サービスメッシュを使ってみよう
はじめに
前回は、Tekton(OpenShift Pipeline)を用いてCI/CDパイプラインを作成し、ソースコードの更新を契機に自動でパイプラインを動かす方法を紹介しました。
本連載は、今回で最終回となります。今回は、マイクロサービスを管理する手法の1つ「サービスメッシュ」について、これまでに開発してきたサービスをサービスメッシュを用いて実際に管理する方法を解説します。
サービスメッシュとは
第1回で、マイクロサービス導入における課題として以下の3つを挙げました。
- リソースの増加
- 運用負荷の増大
- 多様化・複雑化するランタイムソフトウェア/ミドルウェアの管理
これらのうち、運用負荷の増大についは「Kubernetesがある程度運用負荷を軽減できる」と述べました。しかし、Kubernetesのみで数十~数百のマイクロサービスを運用していくのは困難だというのが実情です。
このような運用負荷の増大に対する1つの解として「サービスメッシュ」があります。サービスメッシュとは、マイクロサービスの各サービス間の通信を専用のソフトウェアに仲介させることで、マイクロサービス特有の課題を解決する仕組みです。
サービスメッシュが提供する代表的な機能として、以下のようなものがあります。
- トラフィック管理
各サービスに流れるトラフィックを細かく制御できます。例えば、あるサービスの応答の遅延が他の多数のサービスにまで影響を及ぼすことがあります。
サービスメッシュによって応答しないサービスへのアクセスを遮断することで、応答遅延の波及を阻止できます。
また、リクエストの10%を新しいバージョンに割り振るといったことも可能なので、A/Bテストやカナリアリリースの実現にもとても有用です。 - セキュリティ
各サービス間で行われる通信の暗号化やアクセスの認証・認可機能を提供します。例えば、Kubernetesの内部では基本的に自由に通信できるため、脆弱性の悪用等であるPodが乗っ取られてしまうと、その影響がクラスター全体に波及してしまう可能性があります。
サービスメッシュの機能である相互TLS(mTLS)を利用するすることで盗聴や中間者攻撃(MITM)を抑止できます。
また認証・認可機能により予め通信できるサービスを制限しておくことで、開発環境から本番環境へのアクセスを防止したり、脆弱性の影響を局所化したりできます。 - 可観測性
各サービス間で行われる通信のメトリクスを容易に取得できます。マイクロサービスでは1リクエストを処理するのにも複数のサービスが連携して対応するため、問題発生時にチェックすべき箇所がコンテナ基盤や各サービスのログ等多岐に渡り、リクエストがどのように処理されているかの全体像を把握するのが難しくなります。サービスメッシュの分散トレーシング機能を活用すると、マイクロサービスで応答遅延や障害などが発生した際もボトルネックの特定が容易となり、迅速に対応できます。
このように豊富な機能を持つサービスメッシュを活用することで、各サービスの開発者はビジネスロジックの実装に集中できるます。
各サービスの開発言語に依存せずサービスメッシュの機能を提供する手法として、現在では「サイドカーパターン」が主流となっています。
サイドカーパターンでは各々のサービスに通信を仲介するプロキシを横付け、全ての通信をプロキシ(データプレーン)を経由して行います。プロキシはコントロールプレーンから指示を受けて、通信可否や通信先を決定します。
一方で、サービス側からは他のアプリケーションと直接やりとりをしているように見えるため、サービスメッシュの存在を意識する必要がありません。これにより、サービス側のソースコードを変更することなく、サービスメッシュの導入が可能となりました。
サービスメッシュを提供するソフトウェアは数多くありますが、その中でも注目を集めているのがIstioです。IstioはGoogle、IBM、Lyftが主体となり開発しているOSSで、プロキシのEnvoyと組み合わせて利用します。
他のサービスメッシュと比較してもIstioは特にトラフィック管理とセキュリティの面で機能が豊富なため、直近でもコミュニティにおいて活発に開発されています。
Istioの設定はKubernetesと同様にYAMLファイルで管理しますが、その機能の豊富さゆえに、サービスメッシュが拡大していくとともに設定が煩雑となりがちなのが欠点でした。しかし、Istioに特化したGUI管理ツールのKialiを用いるとGUI画面でIstioの設定変更が可能となります。
YAMLでの操作に慣れた運用者でも、Kialiの画面上でYAMLファイルを編集することで、IDEのような自動補完やアラート機能を活用できます。これらのツールに加え、分散トレーシングツールのJaegerを利用すると各サービス内外のリクエストの流れを可視化でき、マイクロサービスのトラブルシューティングも容易にできるようになっています。
Red Hat OpenShift Container Platform(以下、OpenShift)においては、Red Hat OpenShift Service Mesh(以下、OpenShift Service Mesh)としてIstio、Envoy、Kiali、Jaeger等をRed Hat社のサポートと合わせて利用できます。
今回のゴール
今回は、まずOpenShift Service MeshをOpenShiftに導入します。サービスメッシュの機能は非常に幅広く全てを紹介できないので、今回はセキュリティの面に特化して紹介します。具体的には、前回までに構築したマイクロサービスにmTLSによる相互認証、暗号化とアクセス制御を導入します。
これにより、マイクロサービス内で行われる通信を中間者攻撃等から守り、意図しない通信をブロックすることによるセキュリティ強化をゴールとします。
今回実装していくマイクロサービスの構成を下図に示します。なお、この図は分かりやすさを重視するため、直接関係する要素以外は簡略化しています。
- 利用者からのアクセスをOpenShift Ingressが受け取る…①
- OpenShift IngressがIstioのIngress Gatewayにアクセスを転送する。このルーティングはOpenShiftのRouteリソースを基にIngress Operatorが配布した情報を基に行う…②
- Ingress Gatewayは、Pilotから配布された情報を元にKubernetesのServiceを利用して各Podにアクセスをルーティングする。各Podと通信する際はmTLSで相互認証と暗号化を行う。…
- マイクロサービス内の各Podが通信する際にはPilotから配布された情報とServiceを利用する。この際もmTLSによる相互認証と暗号化を行う。…④
前提条件
利用ソフトウェアは、以下の通りです。
- OpenShift v4.5.7
- Elasticsearch Operator 4.5.0-202008100413.p0
- OpenShift Service Mesh v1.1.7
Red Hat社のドキュメントによると、OpenShift Service Mesh v1.1.7は以下のコンポーネントにより構成されています。- Istio v1.4.8
- Jaeger v1.17.4
- Kiali v1.12.7
- 3Scale Istio Adapter v1.0.0
なお、今回の内容は前回までに実施した作業の続きとなります。ぜひ、第1回からお読みください。
またsampleサービスと同様に、ソースコードのpushを契機にcallerサービスが動的にビルド・デプロイされるよう、事前にEventTriggerの設定が必要です。YAMLの記述および各種設定については第4回を参考に作成してください。
OpenShift Service Meshのインストール
前回のOpenShift Pipeline同様、OpenShift Service MeshもOperatorHubを利用して簡単にインストールできます。ただし、前回とは異なりOpenShift Service Meshが動作するためには以下のOperatorも必要です。
- Elasticsearch Operator
- Red Hat OpenShift Jaeger
- Kiali Operator
以下の手順では、OpenShift Service Meshの動作に必要なOperatorを全てインストールしていきます。なお、既に利用している等の理由で前提条件となるOperatorがインストール済みであれば、そのOperatorについてはインストールは不要です。
まず、OpenShiftのWebコンソールにcluster-admin
権限を持ったユーザーでログインします。Administratorのパースペクティブで「Operators」⇒「OperatorHub」の順に選択します。
なお、今回インストールするOperatorは全てRed Hatが提供するOperatorです。そのため、Red Hatのサポートが受けられないCommunity Operatorを誤って選択しないようにするため、事前に「Provider Type」の箇所で「Red Hat」のみにチェックを付けておくことをお勧めします。
Elasticsearchのインストール
OperatorHubの画面を開いたら、Filter by keywordボックスを使用して「Elasticsearch Operator」を検索します。続いて、Operatorの詳細が表示されるので、左上の「Install」ボタンをクリックして次の画面に進みます。
「Install Operator」画面では様々なオプションを設定しますが、基本的にデフォルトの設定で問題ないため、このまま「Install」ボタンをクリックするとインストールが完了します。
Jaegerのインストール
先ほどと同様に「Red Hat OpenShift Jaeger」をOperatorHubから検索します。Operatorの詳細が表示されるので、左上の「Install」ボタンをクリックして次の画面に進み、こちらもデフォルト設定のまま「Install」ボタンをクリックすればインストールは完了です。
Kialiのインストール
こちらも同様に「Kiali Operator」をOperatorHubから検索します(本稿執筆時では全く同名のCommunity Operatorがありましたので注意してください)。Operatorの詳細が表示されるので、左上の「Install」ボタンをクリックして次の画面に進み、こちらもデフォルト設定のまま「Install」ボタンをクリックすればインストールは完了します。
ひとまず、ここまでのインストール状況を確認してみましょう。Administratorのパースペクティブで「Operators」⇒「Installed Operators」の順に選択します。インストールが正常に行われていれば「Elasticsearch Operator」「Red Hat OpenShift Jaeger」「Kiali Operator」の3つのOperatorが表示され、「Status」欄が「Suceeded」「Up to date」となっているはずです。
各Operatorが正常にインストールされていることが確認できたら、先に進みましょう。
Service Mesh Operatorのインストール
OpenShift Service Meshを利用するにはOperatorのインストールが必要です。インストール方法は今までと同様に「Red Hat OpenShift Service Mesh」をOperatorHubから検索します。Operatorの詳細が表示されるので、左上の「Install」ボタンをクリックして次の画面に進み、こちらもデフォルト設定のまま「Install」ボタンをクリックすればインストールは完了です。
Operatorが正常にインストールされたか確認しましょう。先ほどの「Installed Operators」画面に「Red Hat OpenShift Service Mesh」が追加され、「Status」欄が「Suceeded」「Up to date」となっていれば、OpenShift Service Meshのインストールは正常に完了しています。
コントロールプレーンの準備
前述のサイドカーパターンの部分で説明した通り、OpenShift Service Mesh(Istio)はコントロールプレーンとデータプレーンの2つで構成されています。そこで、まずはコントロールプレーンを準備します。
ここからはCLIを使用して進めていきましょう。まずOpenShiftにcluster-admin
権限を持つユーザーでログインし、istio-system
プロジェクトを作成します。
[ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc whoami system:admin [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc new-project istio-system Now using project "istio-system" on server "https://api.sample.com:6443". You can add applications to this project with the 'new-app' command. For example, try: oc new-app ruby~https://github.com/sclorg/ruby-ex.git to build a new example application in Ruby. Or use kubectl to deploy a simple Kubernetes application: kubectl create deployment hello-node --image=gcr.io/hello-minikube-zero-install/hello-node
続いて、ServiceMeshControlPlane
リソースを作成します。Red Hat社からサンプルのYAMLが提供されているので(サンプルはこちらの「istio-installation.yamlの例」から入手可能)、今回はそれを利用してデプロイします。
なお、この後の手順で利用するIstio OpenShift Routing(IOR)と呼ばれるIstioとOpenShiftの連携機能を有効化するため、1箇所サンプルとは異なる設定を行います。
apiVersion: maistra.io/v1 kind: ServiceMeshControlPlane metadata: name: basic-install spec: istio: global: proxy: resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 128Mi gateways: istio-egressgateway: autoscaleEnabled: false istio-ingressgateway: autoscaleEnabled: false ior_enabled: true # ここをfalse --> trueに変更 mixer: policy: autoscaleEnabled: false telemetry: autoscaleEnabled: false resources: requests: cpu: 100m memory: 1G limits: cpu: 500m memory: 4G pilot: autoscaleEnabled: false traceSampling: 100 kiali: enabled: true grafana: enabled: true tracing: enabled: true jaeger: template: all-in-one
サンプルに沿ってistio-installation.yaml
を作成したら、実際にOpenShiftにデプロイしてみましょう。
[ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc create -n istio-system -f istio-installation.yaml servicemeshcontrolplane.maistra.io/basic-install created [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get smcp -n istio-system NAME READY STATUS TEMPLATE VERSION AGE basic-install PausingInstall default v1.1 13s [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get smcp -n istio-system NAME READY STATUS TEMPLATE VERSION AGE basic-install 9/9 InstallSuccessful default v1.1 2m32s [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get po -n istio-system NAME READY STATUS RESTARTS AGE grafana-577fd9ffc7-c8dph 2/2 Running 0 4m25s istio-citadel-6f9b74b754-t994m 1/1 Running 0 6m5s istio-egressgateway-64ffbdb8c8-r27hm 1/1 Running 0 4m47s istio-galley-7c6fb78655-pk4ds 1/1 Running 0 5m29s istio-ingressgateway-6c77fdbbd4-j7vnn 1/1 Running 0 4m47s istio-pilot-f74779745-hcx8j 2/2 Running 0 5m istio-policy-884697ff7-f9cxd 2/2 Running 0 5m16s istio-sidecar-injector-66fd9459d9-kh8v4 1/1 Running 0 4m40s istio-telemetry-5d8b8bf754-b6rpl 2/2 Running 0 5m16s jaeger-74f88db84f-vng5n 2/2 Running 0 5m30s kiali-597dc4968c-k6d4l 1/1 Running 0 3m38s prometheus-5fd75799b5-vrfss 2/2 Running 0 5m53s
初回インストールのため多少時間がかかりますが、2分程度でコントロールプレーンのデプロイが完了します。
データプレーンの準備
続いて、データプレーンの準備を進めていきましょう。
既存アプリケーションの確認
今回の様に既にアプリケーションをデプロイしているOpenShiftプロジェクト(Namespace)では、OpenShift Service Meshを有効化する前に、動作している既存のアプリケーションに影響が及ばないか必ず確認しましょう。
なお、今回はmicrosvc-sample
というプロジェクトにマイクロサービスをデプロイしている前提で進めていきます。別のプロジェクト名を使用している場合は、以下の説明を適宜読み替えてください。
・Routeの設定
OpenShift Service Meshを有効にすると、Istioを経由していないRouteはデフォルトでブロックされてしまうので、事前に現在有効にしているRouteを確認しましょう。oc get route
で現在の設定状況を見てみます。
[ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get route -n microsvc-sample NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD caller caller-microsvc-sample.apps.example.com caller 8080 None el-caller el-caller-microsvc-sample.apps.example.com el-caller http-listener None el-sample el-sample-microsvc-sample.apps.example.com el-sample http-listener None sample sample-microsvc-sample.apps.example.com sample 8080 None
ここに表示されているRouteを継続して機能させるには、以下のいずれかの操作を行う必要があります。
- サービスメッシュの管理対象とする
- サービスメッシュをインストールしない別のOpenShiftプロジェクト(Namespace)に対応するServiceやPodとともに移行する
- サービスメッシュの例外として追加し、今まで通り利用する
ここで表示されるRouteのうち、sampleとcallerは今回のサービスメッシュで管理する予定なので1番目の方法で良いのですが、el-sampleとel-callerはGitレポジトリからのWebhookを受け付けるEventListenerのためのRouteで、サービスメッシュとは直接の関係はありません。
もちろんこれらのRouteもサービスメッシュで管理できますが、今回は3番目のサービスメッシュの例外に追加する方針で進めていきます。
サービスメッシュの例外に追加するためには、maistra.io/expose-route=true
というラベルをPodに設定する必要があります。今回ラベルを設定するPodはEventListenerリソースによって管理されており、EventListenerリソースに付与されたラベルは自動的にDeploymentとPodに付与されるため、EventListenerにこのラベルを付与します。
[ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get el --show-labels -n microsvc-sample NAME AGE LABELS caller 9h <none> sample 26d <none> [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc label el caller sample maistra.io/expose-route=true -n microsvc-sample eventlistener.triggers.tekton.dev/caller labeled eventlistener.triggers.tekton.dev/sample labeled [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get el --show-labels -n microsvc-sample NAME AGE LABELS caller 9h maistra.io/expose-route=true sample 26d maistra.io/expose-route=true # EventListenerにラベルが付与された [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get po --show-labels -l eventlistener -n microsvc-sample NAME READY STATUS RESTARTS AGE LABELS el-caller-59cfb4994-tgs57 1/1 Terminating 0 2m51s app.kubernetes.io/managed-by=EventListener,app.kubernetes.io/part-of=Triggers,eventlistener=caller,pod-template-hash=59cfb4994 el-caller-6bc8475b46-fthpg 1/1 Running 0 22s app.kubernetes.io/managed-by=EventListener,app.kubernetes.io/part-of=Triggers,eventlistener=caller,maistra.io/expose-route=true,pod-template-hash=6bc8475b46 el-sample-684bb5db4-wd52g 1/1 Terminating 0 2m51s app.kubernetes.io/managed-by=EventListener,app.kubernetes.io/part-of=Triggers,eventlistener=sample,pod-template-hash=684bb5db4 el-sample-788b75787d-vqvld 1/1 Running 0 22s app.kubernetes.io/managed-by=EventListener,app.kubernetes.io/part-of=Triggers,eventlistener=sample,maistra.io/expose-route=true,pod-template-hash=788b75787d [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get po --show-labels -l eventlistener -n microsvc-sample NAME READY STATUS RESTARTS AGE LABELS el-caller-6bc8475b46-fthpg 1/1 Running 0 56s app.kubernetes.io/managed-by=EventListener,app.kubernetes.io/part-of=Triggers,eventlistener=caller,maistra.io/expose-route=true,pod-template-hash=6bc8475b46 el-sample-788b75787d-vqvld 1/1 Running 0 56s app.kubernetes.io/managed-by=EventListener,app.kubernetes.io/part-of=Triggers,eventlistener=sample,maistra.io/expose-route=true,pod-template-hash=788b75787d # Podが再作成されてPodにもラベルが付与された
・callerの接続先の確認
callerサービスがsampleサービスへアクセスする際のURLを確認しましょう。IstioはHTTPリクエストの内部まで確認してトラフィックを制御しているため、OpenShift内部で行われる通信にOpenShift内部のDNS名(cluster.local)を利用すると、外部からのアクセスと内部でのアクセスを分けて管理できるようになります。callerレポジトリのsrc/main/resources/application.properties
にsample_api.base_path=
で始まる行があるので、これを内部DNS名に書き換えます。
sample_api.base_path=http://sample.microsvc-sample.svc.cluster.local:8080
書き換えたら、Gitレポジトリにプッシュして変更をアプリケーションに反映しましょう。
コントロールプレーンと
OpenShiftプロジェクト(Namespace)の紐づけ
OpenShift Service Meshは、コントロールプレーンをインストールしても自動では有効にならず、サービスメッシュの適用有無をプロジェクト単位で手動で設定する必要があります。この理由はいくつかありますが、例えばサービスメッシュの適用を想定していないプロジェクトで突然サービスメッシュが有効になってしまうと、疎通できなくなることなどです。
他にも、OpenShiftを構成するAPIサーバー等のコンポーネントにIstioが干渉してしまうと、OpenShiftの動作に悪影響を与えます。そのため、本項で説明する手順でコントロールプレーンとOpenShiftプロジェクト(Namespace)を明示的に紐づけ、サービスメッシュを有効化する必要があります。
なお、OpenShift Service Meshでは1つのOpenShiftクラスターに複数のコントロールプレーンをデプロイすることも可能で、各プロジェクトをどのコントロールプレーンに紐づけるかを明示的に指定できるようになっています。
コントロールプレーンとOpenShiftプロジェクトの紐づけは、クラスター管理者が実施するServiceMeshMemberRoll
を利用する方法と各OpenShiftプロジェクトの管理者に権限を委譲するServiceMeshMember
を利用する方法の2つありますが、今回はServiceMeshMemberRoll
を利用して紐づけをします。
こちらもRed Hat社のドキュメントにあるサンプル(smmr-default.yaml)を利用します。
apiVersion: maistra.io/v1 kind: ServiceMeshMemberRoll metadata: name: default # nameは default が必須です。 namespace: istio-system spec: members: - microsvc-sample # 実際にマイクロサービスをデプロイしたプロジェクト名を指定します。
ファイルを作成したら、実際にデプロイしていきましょう。
[ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc create -f smmr-default.yaml servicemeshmemberroll.maistra.io/default created [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get smmr -n istio-system NAME READY STATUS AGE default 1/1 Configured 7s
こちらは作成後すぐに「Configured」となりました。これで、コントロールプレーンとOpenShiftプロジェクト(Namespace)の紐づけは完了です。
サイドカーの挿入
続いて、前回までに開発したアプリケーションにサイドカーを挿入していきます。コミュニティ版のIstioでは、サービスメッシュを導入したいNamespaceにラベルを付けることでサイドカーが自動挿入されますが、OpenShift Service MeshではPodにAnnotationを付与することで、OpenShiftのビルド機能等がサービスメッシュの影響を受けないようにしています。
今回のアプリケーションはfabric8により自動生成されたマニフェストファイルを利用していますが、fabric8では設定をカスタマイズできるので、これを利用してPodへAnnotationを付与します。
・サービス(sample)側の設定
sampleのGitレポジトリsrc/main/
以下にfabric8
ディレクトリを作成し、下記のsample-deployment.yaml
を配置してpushします。sample
の部分はDeploymentの名前と合わせてください。
spec: template: metadata: annotations: sidecar.istio.io/inject: "true"
このYAMLファイル単体ではDeploymentとして不完全ですが、不足している部分はfabric8が自動的に補足してくれるため、この内容で問題ありません。
それでは、実際にpushしていきます。前回でCI/CDは構築できているので、pushをすれば自動的にコンテナイメージのビルドとデプロイが実行されます。その過程をoc get
コマンドで見てみましょう。
[ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get po -n microsvc-sample NAME READY STATUS RESTARTS AGE caller-1-gbk2z 1/1 Running 0 14h el-sample-788b75787d-6nmls 1/1 Running 0 14h sample-26-m9dt2 1/1 Running 0 4m39s [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get po -o jsonpath='{.metadata.annotations.sidecar\.istio\.io/inject}' sample-26-m9dt2 # まだAnnotationを設定していないので何も表示されない [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get po -n microsvc-sample -o jsonpath='{.spec.containers[*].name}' sample-26-m9dt2 spring-boot # <-- Spring Bootのコンテナのみ [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get po -n microsvc-sample NAME READY STATUS RESTARTS AGE caller-1-gbk2z 1/1 Running 0 14h el-sample-788b75787d-6nmls 1/1 Running 0 14h sample-26-m9dt2 1/1 Running 0 4m55s # ここでgit pushしてしばらく待機 [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get po -n microsvc-sample NAME READY STATUS RESTARTS AGE caller-1-gbk2z 1/1 Running 0 14h el-sample-788b75787d-6nmls 1/1 Running 0 14h maven-build-sample-sample-zrfd4-sample-build-dbczs-pod-2588z 0/2 Completed 0 3m45s sample-28-9hcrj 2/2 Running 0 29s sample-28-deploy 0/1 Completed 0 33s sample-s2i-16-build 0/1 Completed 0 102s [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get po -n microsvc-sample -o jsonpath='{.metadata.annotations.sidecar\.istio\.io/inject}' sample-28-9hcrj true # 設定したAnnotationが反映された [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get po -n microsvc-sample -o jsonpath='{.spec.containers[*].name}' sample-28-9hcrj spring-boot istio-proxy # <-- istio-proxyコンテナが確かに入っている
無事にサイドカーがsampleのPodに挿入されたら完了です。
・呼び出し元サービス(caller)側の設定
先ほどと同様にcallerのGitレポジトリsrc/main/
以下にfabric8
ディレクトリを作成し、caller-deployment.yaml
を配置してpushします。内容は前述のsample-deployment.yaml
と同じです。
これで、sample, callerの両サービスにサイドカーが挿入されたはずです。実際に確認してみましょう。
[ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get po -n microsvc-sample -l app=sample NAME READY STATUS RESTARTS AGE sample-33-rjzlt 2/2 Running 0 5m4s [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get po -n microsvc-sample sample-33-rjzlt -o jsonpath='{.spec.containers[*].name}' spring-boot istio-proxy [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get po -n microsvc-sample -l app=caller NAME READY STATUS RESTARTS AGE caller-5-r59h2 2/2 Running 0 13m [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get po -n microsvc-sample caller-5-r59h2 -o jsonpath='{.spec.containers[*].name}' spring-boot istio-proxy
このように、sample、callerのPodがRunningとなり、istio-proxyコンテナが挿入されていれば完了です。
Gateway、VirtualServiceの設定
続いて、外部からサービスメッシュ内部へのアクセスを設定します。
サービスメッシュ内部への通信は全てIstioのIngress Gatewayを経由し、アクセス先のURL等に基づいてIstioが振り分けます。その振り分けに必要となる情報をIstioに読み込ませるためにGateway
とVirtualService
を設定します。今回は、以下のような設定を行うYAMLファイルを作成します。
そして、下記が今回使用するYAMLファイル(vs-gw.yaml)です。通常、OpenShiftで外部アクセスを受け入れる際はRoute機能を用いてURLとServiceを直接紐づけますが、サービスメッシュ内部へのアクセスの際はIstioのIngress Gatewayを経由するように設定する必要があります。
しかし、OpenShift Service MeshではGateway
リソースのhosts
に記載されたホスト名を自動的にRouteに反映するIstio OpenShift Routing(IOR)という機能があるため、この点は特に意識する必要がなくなり便利です。
# Ingress gatewayの80番ポートで'hosts'に記載されたホスト名に対するHTTPアクセスを待ち受ける。 apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: sample-gateway spec: selector: istio: ingressgateway servers: - port: number: 80 name: http protocol: HTTP hosts: # ここに記載したホスト名はIORによってRouteが自動設定される - sample.apps.example.com - caller.apps.example.com --- # 条件に一致するHTTPリクエストをsample Serviceに転送する apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: sample spec: hosts: # リクエストされたホスト名 - sample.apps.example.com gateways: # 使用するGateway - sample-gateway http: - match: - uri: prefix: "/" # "/"で始まるURI⇒全てのURIに対して有効 route: - destination: # リクエストの転送先 host: sample.microsvc-sample.svc.cluster.local port: number: 8080 --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: caller spec: hosts: - caller.apps.example.com gateways: - sample-gateway http: - match: - uri: prefix: "/" route: - destination: host: caller.microsvc-sample.svc.cluster.local port: number: 8080
YAMLファイルを作成したら、OpenShiftに適用します。
[ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc apply -f vs-gw.yaml -n microsvc-sample gateway.networking.istio.io/sample-gateway created virtualservice.networking.istio.io/sample created virtualservice.networking.istio.io/caller created [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get route -n istio-system NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD grafana grafana-istio-system.apps.example.com grafana <all> reencrypt None # Gatewayのhostsが反映されている ------------------------------------------------------ microsvc-sample-sample-gateway-5w5hq sample.apps.example.com istio-ingressgateway http2 None microsvc-sample-sample-gateway-72tm5 caller.apps.example.com istio-ingressgateway http2 None # ------------------------------------------------------------------------------------ istio-ingressgateway istio-ingressgateway-istio-system.apps.example.com istio-ingressgateway 8080 None jaeger jaeger-istio-system.apps.example.com jaeger-query <all> reencrypt None kiali kiali-istio-system.apps.example.com kiali <all> reencrypt/Redirect None prometheus prometheus-istio-system.apps.example.com prometheus <all> reencrypt None
この時点で、sample、callerどちらのサービスもIstioを経由してアクセスが可能となっているはずです。実際にデータをHTTP POSTで送信して、各サービスからの応答があるか確認してみましょう。
# sampleの確認 [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ curl http://sample.apps.example.com/api/sample/apply -v -X POST \ > -H "Content-Type:application/json" -d "{\"user_name\":\"Taro\", \"store_id\":1111, \"store_name\":\"Shop_A\"}" * About to connect() to sample.apps.example.com port 80 (#0) * Trying xx.xx.xx.xx... * Connected to sample.apps.example.com (xx.xx.xx.xx) port 80 (#0) > POST /api/sample/apply HTTP/1.1 > User-Agent: curl/7.29.0 > Host: sample.apps.example.com > Accept: */* > Content-Type:application/json > Content-Length: 60 > * upload completely sent off: 60 out of 60 bytes < HTTP/1.1 201 Created < content-type: application/json < date: Thu, 10 Sep 2020 22:15:45 GMT < x-envoy-upstream-service-time: 5 < server: istio-envoy # Istioを経由していることが分かる < transfer-encoding: chunked < set-cookie: a5b17077322e132d2c3d0951c9a48323=17b1cbd28f5c6314dbae7bed61cbe453; path=/; HttpOnly < * Connection #0 to host sample.apps.example.com left intact {"user_id":39} # callerの確認 [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ curl http://caller.apps.example.com/api/caller/apply -v -X POST \ > -H "Content-Type:application/json" -d "{\"user_name\":\"Taro\", \"store_id\":1111, \"store_name\":\"Shop_A\"}" * About to connect() to caller.apps.example.com port 80 (#0) * Trying xx.xx.xx.xx... * Connected to caller.apps.example.com (xx.xx.xx.xx) port 80 (#0) > POST /api/caller/apply HTTP/1.1 > User-Agent: curl/7.29.0 > Host: caller.apps.example.com > Accept: */* > Content-Type:application/json > Content-Length: 60 > * upload completely sent off: 60 out of 60 bytes < HTTP/1.1 201 Created < content-type: application/json < date: Thu, 10 Sep 2020 22:16:23 GMT < x-envoy-upstream-service-time: 28 < server: istio-envoy # Istioを経由していることが分かる < transfer-encoding: chunked < set-cookie: d68de88d3610a37cc14e49ce6c4e41d6=17b1cbd28f5c6314dbae7bed61cbe453; path=/; HttpOnly < * Connection #0 to host caller.apps.example.com left intact {"user_id":39}
このように、Istioが有効な状態で各サービスと通信できることが確認できました。これでサービスメッシュの構築は完了です。
mTLSを有効にしてみよう
続いて、Istioの機能の1つであるmTLSを有効にして、セキュリティを強化していきます。まずはmTLSが無効な状態で、サービスと同じNamespaceにcurlを叩くだけの簡単なPodを立ち上げてみましょう。
[ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc run -n microsvc-sample --image=rhel7:latest -it --rm --restart=Never test \ > -- curl caller.microsvc-sample.svc.cluster.local:8080/api/caller/apply -X POST -H "Content-Type:application/json" \ > -d "{\"user_name\":\"Taro\", \"store_id\":1111, \"store_name\":\"Shop_A\"}" If you don't see a command prompt, try pressing enter. {"user_id":39}pod "test" deleted
このPodにistio-proxyは入っていませんが、通常通り通信できることが分かります。mTLSの有効化はIstioのインストール時に使用したServiceMeshControlPlane
リソースを利用すると簡単にできます。インストール時に作成したistio-installation.yaml
を以下のように書き換えてください。
apiVersion: maistra.io/v1 kind: ServiceMeshControlPlane metadata: name: basic-install spec: istio: global: mtls: # この行と enabled: true # この行を追加 proxy: # 以下省略
あとはこのYAMLファイルを適用するだけなのですが、OpenShift Service Mesh内部におけるmTLSの仕組みをもう少し詳しく説明します。
mTLSの有効化には、内部でServiceMeshPolicy
とDestinationRule
リソースを利用します。これらのリソースの役割を説明したものが下図です。
ServiceMeshPolicy
はリクエストの受信側の設定でmTLSを利用していない通信をブロックします。なお、このServiceMeshPolicy
はOpenShift Service Mesh独自の設定であり、OSS版のIstio 1.4では類似の設定であるPolicy
リソースを利用します。一方のDestinationRule
はリクエストの送信側で指定したホスト名にmTLSを利用するために設定します。
それでは、更新したistio-installation.yaml
をOpenShiftに適用していきます。
[ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get servicemeshpolicy,destinationrule -n istio-system NAME AGE servicemeshpolicy.authentication.maistra.io/default 129m NAME HOST AGE destinationrule.networking.istio.io/disable-mtls-jaeger-collector jaeger-collector 128m destinationrule.networking.istio.io/disable-mtls-zipkin zipkin 128m destinationrule.networking.istio.io/istio-policy istio-policy.istio-system.svc.cluster.local 128m destinationrule.networking.istio.io/istio-telemetry istio-telemetry.istio-system.svc.cluster.local 128m [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc apply -f istio-installation.yaml -n istio-system servicemeshcontrolplane.maistra.io/basic-install configured [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get servicemeshpolicy,destinationrule -n istio-system NAME AGE servicemeshpolicy.authentication.maistra.io/default 129m # ServiceMeshPolicyが更新された # DestinationRuleが2つ新規作成された NAME HOST AGE destinationrule.networking.istio.io/api-server kubernetes.default.svc.cluster.local 12s destinationrule.networking.istio.io/default *.local 12s destinationrule.networking.istio.io/disable-mtls-jaeger-collector jaeger-collector 129m destinationrule.networking.istio.io/disable-mtls-zipkin zipkin 129m destinationrule.networking.istio.io/istio-policy istio-policy.istio-system.svc.cluster.local 128m destinationrule.networking.istio.io/istio-telemetry istio-telemetry.istio-system.svc.cluster.local
これでmTLSは有効になりましたが、本当に有効になったかを確認してみましょう。先ほどと同じPodを立ち上げてみます。有効になっていればIngress gatewayを経由したアクセスは通常通りできる一方、istio-proxyがいないtest Podからはアクセスができなくなるはずです。
# istio-ingressgateway経由でのアクセス [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ curl http://caller.apps.sample.com/api/caller/apply -X POST \ > -H "Content-Type:application/json" -d "{\"user_name\":\"Taro\", \"store_id\":1111, \"store_name\":\"Shop_A\"}" {"user_id":39} # 通常通りアクセス可能 # test Podからのアクセス [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc run -n microsvc-sample --image=rhel7:latest -it --rm --restart=Never test \ > -- curl caller.microsvc-sample.svc.cluster.local:8080/api/caller/apply -X POST -H "Content-Type:application/json" \ > -d "{\"user_name\":\"Taro\", \"store_id\":1111, \"store_name\":\"Shop_A\"}" If you don't see a command prompt, try pressing enter. curl: (56) Recv failure: Connection reset by peer # アクセスに失敗した pod "test" deleted pod microsvc-sample/test terminated (Error)
このように、各サービス間の通信は正常に出来ている一方で、mTLSが利用できないPodからはアクセスできなくなり、mTLSが有効になったことが確認できました。
アクセス制御を実装してみよう
それでは、最後にIstioを活用して簡単なアクセス制御を実装していきます。Istioでのサービス間の認可にはAuthorizationPolicy
を利用します。
AuthorizationPolicy
では、IPアドレスやmTLSから提供されるアクセス元の情報を基に、許可するHTTPリクエストの操作(GET、POST)やパス等を指定できます。ここでは下図のようにsampleサービスを外部に公開したくないサービスに見立てて、sampleへのアクセスを同一Namespace内からのみに制限するというアクセス制御を入れていきましょう。
【注】:sampleサービスを公開する設定をGatewayとVirtualServiceから削除すれば結果的には同様の効果が得られますが、このような設定を入れておくことで、誤ってサービスを公開してもアクセスを防ぐことができます。
まず、以下のようなauthz-policy.yaml
を作成します。
apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: to-sample namespace: microsvc-sample spec: selector: # このポリシーを適用するPodを決める matchLabels: app: sample rules: - from: - source: namespaces: ["microsvc-sample"] # microsvc-sampleのNamespaceからのアクセスを許可する to: [] # []の場合、他の条件に合致する全てのアクセスを許可する
なお、AuthorizationPolicy
はホワイトリスト形式で適用されるため、記載がないものは全てブロックされます。
それでは、外部からcaller, sampleにアクセスできる状態であることを確認したうえでAuthorizationPolicy
を導入し、sampleサービスへの直接アクセスがブロックされることを確認してみます。
[ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ curl http://caller.apps.example.com/api/caller/apply \ > -X POST -H "Content-Type:application/json" \ > -d "{\"user_name\":\"Taro\", \"store_id\":1111, \"store_name\":\"Shop_A\"}" {"user_id":39} [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ curl http://sample.apps.example.com/api/sample/apply \ > -X POST -H "Content-Type:application/json" \ > -d "{\"user_name\":\"Taro\", \"store_id\":1111, \"store_name\":\"Shop_A\"}" {"user_id":39} # caller, sample両方にアクセス可能 [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc apply -f authz-policy.yaml authorizationpolicy.security.istio.io/to-sample created [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ oc get authorizationpolicy -n microsvc-sample NAME AGE to-sample 29s [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ curl http://caller.apps.example.com/api/caller/apply \ > -X POST -H "Content-Type:application/json" \ > -d "{\"user_name\":\"Taro\", \"store_id\":1111, \"store_name\":\"Shop_A\"}" {"user_id":39} # caller経由では通常通りsampleにアクセス可能 [ec2-user@ip-xx-xx-xx-xx ws_miyazaki]$ curl http://sample.apps.example.com/api/sample/apply \ > -X POST -H "Content-Type:application/json" \ > -d "{\"user_name\":\"Taro\", \"store_id\":1111, \"store_name\":\"Shop_A\"}" RBAC: access denied # sampleへの直接のアクセスは拒否された
このように、AuthorizationPolicy
でアクセスを制御できることが確認できました。本稿ではこれ以上の詳しい解説はしませんが、この他にもサービスごとに許可する操作を指定して、より細かいアクセス制御を行うことも可能です。さらに詳しく知りたい方はIstioのドキュメント(英語)も合わせて参照ください。
おわりに
今回はOpenShift Service Meshを用いて、前回までに開発したサービスへサービスメッシュを導入する方法を紹介しました。加えて、サービスメッシュでmTLSやアクセス制御の設定を行うことで、マイクロサービス内で行われる通信を保護し、意図していない通信をブロックする方法も紹介しました。
ここまでお読みいただいた皆さまには、マイクロサービスにサービスメッシュを導入し、セキュリティを強化する方法が理解できたのではないでしょうか。今回は紹介しきれませんでしたが、サービスメッシュには他にも多彩な機能がありますので、ぜひ一度試してみてください。
さて、ここまで全5回にわたり「Red Hat OpenShift Container Platform with Runtimesによるマイクロサービス開発」と題して、マイクロサービスとは何かという話から、マイクロサービス開発手法、そしてマイクロサービスと密接な関係にあるCI/CDとサービスメッシュについて紹介してきましたが、いかがでしたか。
本連載が皆さまにとって、マイクロサービス導入への第一歩となれば、筆者としてこれ以上喜ばしいことはありません。
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- CI/CDを使ってみよう
- 3scaleをインストールしてみよう!
- マイクロサービスを連携してみよう
- 「Kyverno Chainsaw」で宣言的なE2Eテストを実施する
- コンテナ上のマイクロサービスの認証強化 ~IstioとKeycloak~
- Oracle Cloud Hangout Cafe Season5 #3「Kubernetes のセキュリティ」(2022年3月9日開催)
- Kubeflowを構築する
- KubeCon Europeでサービスメッシュの標準化を目指すSMIを発表。Istioの動向にも注目
- NGINX Ingress Controllerの柔軟なアプリケーション制御、具体的なユースケースと設定方法を理解する
- マイクロサービスを作ってみよう!