はじめに
Istioは、マイクロサービスにおけるサービスメッシュ機能(トラフィック制御・セキュア化・可視化など)を提供する。今回、Kubernetesクラスタ上でIstioのサンプルアプリケーションを試してみた際に、Kubernetesクラスタ外部からのアクセスの仕方で混乱した。
マイクロサービス間のトラフィック制御については、いくつか記事が書かれていたものの、クラスタ外部からのルーティングに主眼に置いた記事は少なかったため、通常のKubernetesを利用した場合との比較をまとめておく。
なお、本記事はある程度Kubernetesと各リソースの概念を理解していることを前提とする。また、Istioについては学習中の状態なので、誤解している箇所があれば指摘して頂けると幸いである。
TL;DR
- Istioを利用する場合、
Gateway
、VirtualService
、DestinationRule
などのCRDを利用することで、KubernetesのIngress
リソースを用いた場合より柔軟なL7ルーティングを実現できる。 -
istio-ingressgateway
が、Gateway
、VirtualService
、DestinationRule
を監視し、メッシュ内の各マイクロサービスへのルーティングを行う
環境
本記事の実行環境は以下の通り。Docker for desktopを利用しているため、GKEやEKSなどのマネージドKubernetesサービスを利用した場合とは異なる場合があるので注意。
- MacOS
- Docker for desktop
- Kubernetes v1.13.5
- Istio v1.1
Kubernetesが提供する外部アクセス方法
まず、Istioを利用せず、通常のKubernetesの機能を利用した場合の外部アクセス方法について簡単に述べる。Kubernetesでは、IPとポート番号によるL4ルーティングの手段と、パスベースのL7ルーティングの機能が提供されている。
L4ルーティング
最も用いられるのが、Service
リソースのType:NodePort
、Type:LoadBalancer
を利用する方法である。
Type:NodePortを用いる場合
Type:NodePort
では、Kubernetesの各ノードの特定のポートを通じて、Pod
にアクセスする。外部に公開したいワークロード(Deployment
、Pod
など)を作成したあと、Type:NodePort
のService
を作成することで、Kubernetesクラスタの各ノードの特定ポートでアクセスを受け付けるようになる(Kubernetesのシステムコンポーネントであるkube-proxy
が、ノード上のポートでLISTENし、アクセスを受け付けるとiptablesのルールによりPod
へフォワーディングされる)。
Type:LoadBalancerを用いる場合
Type:LoadBalancer
のService
を作成すると、Type:NodePort
の場合と同様に、各ノードでアクセスを受け付けるノードポートが公開される。これに加え、クラスタ外部にグローバルIPが付与されたロードバランサが作成され、ユーザーはこのロードバランサへアクセスすると、各ノードポートへ転送されたのち、Podへ到達する。つまり、外部に公開されたロードバランサと、KubernetesのService
により2段階のロードバランシングを経てアクセスすることになる。
なお、Type:LoadBalancer
の場合でも、直接ノードポートにアクセスし、Pod
と通信することが可能である。
L4ルーティングの問題点
- 外部公開する
Service
ごとにエンドポイントが必要になる- 管理が手間
- セキュリティ上、公開するポートは少なくしたい
- L4レイヤでルーティングを行うため、エッジでのSSL終端が行えない
Ingressを用いたL7ルーティング
Ingressの概要
Ingress
リソースを利用することで、KubernetesでL7ルーティングを実現することができる。通常、Ingress
リソースを作成するためには、Pod
へ通信を転送するためのルールを、YAML形式のマニフェストファイルを用いて記述する。以下より、マニフェストファイルの例を以下に示す。
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: test-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
rules:
- http:
paths:
- path: /apache
backend:
serviceName: apache
servicePort: 80
上記のマニフェストを作成後、Ingress
リソースを作成する。
kubectl apply -f apache-ingress.yaml
ingress.extensions/apache-ingress created
次に、順序が逆転するようではあるが、転送先となるApacheServerのDeployment
とService
リソースを作成するためのマニフェストファイルを記述する。
apiVersion: apps/v1
kind: Deployment
metadata:
name: apache
spec:
replicas: 3
selector:
matchLabels:
app: apache
template:
metadata:
labels:
app: apache
spec:
containers:
- image: httpd
name: apache
---
apiVersion: v1
kind: Service
metadata:
name: apache
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: apache
作成したマニフェストファイルを用いてデプロイする。
❯ kubectl apply -f apache-deployment.yaml
deployment.apps/apache created
service/apache created
以上でIngress
リソースの作成は完了したが、Ingress
リソースを作成するだけでは、外部からPod
にアクセスすることはできない。
Ingress
リソースは、あくまで転送ルールを指定するためのものであり、実際に転送の処理を行う仕組みが存在しないためである。外部にサービスを公開するためには、Ingress
リソースで定義したルールをもとに、実際の転送処理を行うingress-controller
が必要である。
ingress-controller
ingress-controller
は、Ingress
リソースのルールに基づき転送の処理を行う実体(Pod
)を表すもので、その実装はいくつか存在する。最も用いられているのは、nginxに転送処理を行わせるnginx-ingress-controller
である。
以下、nginx-ingress-controller
をクラスタにデプロイする。
# nginx-ingress-controllerのデプロイ
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/mandatory.yaml
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
namespace/ingress-nginx configured
configmap/nginx-configuration created
configmap/tcp-services created
configmap/udp-services created
serviceaccount/nginx-ingress-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created
role.rbac.authorization.k8s.io/nginx-ingress-role created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created
deployment.apps/nginx-ingress-controller created
# 動作確認
$ kubectl get po -n ingress-nginx
NAME READY STATUS RESTARTS AGE
nginx-ingress-controller-78474696b4-zjjd5 1/1 Running 0 1m
# Serviceの作成
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/provider/cloud-generic.yaml
service/ingress-nginx created
# 確認
$ kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx LoadBalancer 10.98.16.250 localhost 80:32326/TCP,443:30979/TCP 7m
以上の手順で、クラスタ外部からnginx-ingress-controller
を経由して、Apache Serverへアクセス可能になったので、実際に試してみる。
> curl http://localhost:80/apache
<html><body><h1>It works!</h1></body></html>
以上で、Apache Serverに接続できることを確認した。
上記で説明したIngress
リソースを利用したルーティング方法はやや煩雑かつ、istioの機能を利用した場合との差異を理解するために必要なので、下図にその概略を示した。
Istioが提供する外部アクセス方法
Istioでは、各Pod
にEnvoyで実装されたサイドカーコンテナを組み込むことで、APから透過的なサービスメッシュを構成できる。Istioのサービスメッシュ内では、Gateway
、VirtualService
、DestinationRule
などのCRDを利用することで、Ingress
を用いる場合より、柔軟なルーティングが可能である。
クラスタ外からメッシュ内のマイクロサービスにアクセスする場合の基本的な考え方は、Ingress
を用いる場合と類似しており、定義したルールをもとに、istio-ingressgateway
と呼ばれるPod
が転送処理を行う仕組みになっている。
この転送するルールを定義するのが、Gateway
、VirtualService
、DestinationRule
の3種のCustom Resource Definition
(CRD)である。
本稿では、Istioが公式で提供しているbookinfoというサンプルアプリケーションを利用する場合を例にとって説明していく。今回は、Istioのインストール、bookinfoのデプロイ手段はIstioの公式ドキュメントを参照されたい。
bookinfoの構成は以下の通りである。この例では、外部からproductpageサービスにアクセスする。この際、productpageサービスが、バックエンドの3種のいずれかのReviewsサービスを呼び出し、Ratingの情報を取得している。
各CRDの説明
クラスタ外からのアクセス方法を説明するため、Gateway
、VirtualService
、DestinationRole
の概要を簡単に紹介する。詳細は、Istioの公式ドキュメントなどを参照されたい。
Gateway
少々ややこしいが、サービスメッシュのエッジで、実際の転送処理を行うistio-ingressgateway
を外部に公開するためのがロードバランサーが、リクエストを受け付けるポート、プロトコル、FQDN名を指定するリソースである。
パスや、パスに基づく転送先は、Gateway
リソースでなく、VirtualService
リソースで指定する。以下のマニフェストファイルでは、istio-ingressgateway
が80番ポート、HTTPプロトコル、任意のFQDN名のリクエストを受け付ける設定を定義している。
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: bookinfo-gateway
spec:
selector:
istio: ingressgateway # use istio default controller
servers:
- port:
number: 80 # ポート番号
name: http
protocol: HTTP # プロトコル
hosts:
- "*" # FQDN名(任意)
VirtualService
その名の通り、Service
リソースに関連するリソースであり、Istioのサービスメッシュに対するリクエストのルーティング方法を定義するリソースである。1つのVirtualService
に対して1つ以上のService
リソースが紐づけられ、各Service
に対して、パスベースでルーティングを行う。(※パスだけでなく、HTTPヘッダの値による振り分けなども可能。)
またメッシュ間通信でのリトライ処理、フォールトインジェクション、カナリアリリースなどの設定は、VirtualService
にて指定することができる。
以下の例では、パスが/productpageから始まるリクエストを、productpageという名前のService
へルーティングする。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: bookinfo
spec:
hosts:
- "*"
gateways:
- bookinfo-gateway # Gatewayリソースの指定
http:
- match:
- uri:
exact: /productpage # パスの指定
route:
- destination:
host: productpage # 転送Serviceの指定
port:
number: 9080
DestinationRule
DestinationRule
は、VirtualService
のルールにより、振り分けるService
が決定したあと、そのService
に対する追加のポリシーを定義するリソースである。Pod
への振り分けのアルゴリズム(ラウンドロビン方式、ランダム方式、コネクション数など)、TLS認証、サーキットブレイカー、keepaliveの時間など、さまざまなポリシーを付加することができる。
以下の例では、version:v1のlabel
が付与されたreviewのService
に対し、サーキットブレイカーを設定している。このポリシーを作成すると、コネクション数が100になったPod
に対しては新たにリクエストを送信されないことが保証される。
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: reviews
spec:
host: reviews
subsets:
- name: v1
labels:
version: v1
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
3種のCRDの役割の整理
以上を簡潔にまとめると、Gateway
とVirtualService
は、istio-ingressgateway
が外部からアクセスを受け付けるエンドポイントと振り分け先のService
リソースを決定するためのリソースであり、またDestinationRule
はService
からPod
へリクエストを振り分けるポリシーを定義するためのリソースであるといえる。
ルーティング方法
以上のように、istioを用いて外部からアクセスする場合、3つのCRDを用いて転送ルールを指定する。istio-ingressgateway
は、この3つのCRDを監視し、そのルールに基づき実際の転送処理を行うCustom Controller
である。
それでは、3つのWorkerノードを持つクラスタにbookinfoをデプロイした際のルーティング経路を下図に示す。なお、デフォルトではistio-ingressgateway
のレプリカ数は1つだが、便宜上レプリカ数を2つとして表記している。
実際に、多数のリクエストが想定される場合はistio-ingressgateway
をスケールするケースは想定され、それぞれの動作に影響はない。
大まかな流れ
-
istio-ingressgateway
のLoadBalancerに到達したリクエストは、各ノードのNodePortへ転送される - 到達したノードで、iptablesによりポートフォワーディングが行われ、
istio-ingressgateway
のPod
に到達する(図ではレプリカ数は2) -
istio-ingressgateway
のPod
は、Gateway
、VirtualService
、DestinationRole
のルールをもとに、productpage
のService
にリクエストを転送する -
productpage
のPod
にリクエストが到達する(図ではレプリカ数は1)
実際に試してみる
bookinfoのデプロイ時に作成されるCRDの確認
作成されたGateway
では、istio-ingressgateway
の前段に置かれるロードバランサのIPアドレスと80番ポートに解決される任意のFQDN名を持つリクエストを受け付ける。
ここで、Docker for desktopの場合は、ロードバランサのipはlocalhostなので、FQDN名はlocalhost:80に解決されるものであれば、何でも構わない。また、FQDN名が任意の場合に限り、そのままhttp://localhost:80/としても構わない。(Spec.Servers.Hosts
にfoo.bar.comのように何らかのFQDN名を指定した場合、このようなリクエストは失敗する点に注意)
> kubectl describe gateway bookinfo-gateway
~~
Spec:
Selector:
Istio: ingressgateway
Servers:
Hosts:
*
Port:
Name: http
Number: 80
Protocol: HTTP
次に、VirtualService
では、パスが/productpage,/login,/logout,/api/v1/products/*のリクエストをproductpageのService
へ転送するルールを定義している。
~~
> kubectl describe virtualservice bookinfo
Spec:
Gateways:
bookinfo-gateway
Hosts:
*
Http:
Match:
Uri:
Exact: /productpage
Uri:
Exact: /login
Uri:
Exact: /logout
Uri:
Prefix: /api/v1/products
Route:
Destination:
Host: productpage
Port:
Number: 9080
~~
以上から、http://localhost:80/productpage に対するリクエストは、productpageのService
へ転送されるルールが適用される。
最後に、productpageのService
に対する追加のポリシーを確認するため、DestinationRule
を表示する。
> kubectl describe destinationrule productpage
~~
Spec:
Host: productpage
Subsets:
Labels:
Version: v1
Name: v1
~~
> kubectl get pod --show-labels | grep productpage
productpage-v1-97d4c545d-rpfrh 2/2 Running 0 22h app=productpage,pod-template-hash=538071018,version=v1
DestinationRule
では、version=v1のLabel
を持つproductpageのPod
に転送するルールが記述されているが、今回は該当するPod
が1つ存在だけであるため、結果としてルーティング結果には影響しない。
動作確認
ブラウザからhttp://localhost:80/productpage にアクセスすると、bookinfoのページが表示される。
同様に、
にアクセスすると、productpageのPod
に到達し、応答を返す。ただし、http://localhost:80/login, http://localhost:80/logout へのリクエストでは、Pod
側で処理が実装されていないため、WEBページは返却されない。
補足
本題とは少しずれるが、http://localhost:80/productpage へ数回アクセスすると、WEBページ右下のReviewsの部分の表示が毎回変化していることが分かる。これは、productpageのPod
がReviewsのVirtualService
を通じて、毎度異なるReviewsのPod
から情報を取得しているためである。
VirtualService
やDestinationRule
の定義を変更することで、柔軟なルールで情報を取得するReviewsのPod
を制御することができる。情報興味をもった方は、Istioの公式ドキュメント-Task
まとめ
Kubernetes/Istioの機能を利用してクラスタ外部からPod
へアクセスする方法を紹介した。
- Kubernetesの機能
- Type:NodePort, Type:LoadBalancerの
Service
を利用したL4ルーティング -
Ingress
を利用したパスベースのL7ルーティング- SSL終端が可能
- トラフィックの入り口を集約
- 処理を行う実体は
nginx-ingress-controller
など
- Type:NodePort, Type:LoadBalancerの
- Istioの機能
-
Gateway
、VirtualService
、DestinationRole
のルールに基づくL7ルーティング-
Ingress
より柔軟なトラフィック制御が可能 - 処理を行う実体は
istio-ingressgateway
-
-
今回は、Istioの機能のうち、外部からのアクセスに関わる部分にフォーカスを当てて説明したが、Istioの本領はマイクロサービス間のトラフィック制御、セキュア化、可視化についての機能であり、今後更に注目されていく技術と考えられる。私個人としても、継続的に学習を行っていきたい。
参考資料
- Istio公式ドキュメント
- Kubernetes公式ドキュメント
- nginx-ingress-controller公式ドキュメント
- Kubernetes完全ガイド (impress top gear) 青山真也氏 著
- API GatewayにIstioを使う