Docker
kubernetes
コンテナ
docker-for-mac
istio

Kubernetes/Istioの機能を利用したクラスタ外からのアクセス方法を比較する


はじめに

Istioは、マイクロサービスにおけるサービスメッシュ機能(トラフィック制御・セキュア化・可視化など)を提供する。今回、Kubernetesクラスタ上でIstioのサンプルアプリケーションを試してみた際に、Kubernetesクラスタ外部からのアクセスの仕方で混乱した。

マイクロサービス間のトラフィック制御については、いくつか記事が書かれていたものの、クラスタ外部からのルーティングに主眼に置いた記事は少なかったため、通常のKubernetesを利用した場合との比較をまとめておく。

なお、本記事はある程度Kubernetesと各リソースの概念を理解していることを前提とする。また、Istioについては学習中の状態なので、誤解している箇所があれば指摘して頂けると幸いである。


TL;DR


  • Istioを利用する場合、 GatewayVirtualServiceDestinationRuleなどのCRDを利用することで、KubernetesのIngressリソースを用いた場合より柔軟なL7ルーティングを実現できる。


  • istio-ingressgatewayが、GatewayVirtualServiceDestinationRuleを監視し、メッシュ内の各マイクロサービスへのルーティングを行う


環境

本記事の実行環境は以下の通り。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:NodePortType:LoadBalancerを利用する方法である。


Type:NodePortを用いる場合

Type:NodePortでは、Kubernetesの各ノードの特定のポートを通じて、Podにアクセスする。外部に公開したいワークロード(DeploymentPodなど)を作成したあと、Type:NodePortServiceを作成することで、Kubernetesクラスタの各ノードの特定ポートでアクセスを受け付けるようになる(Kubernetesのシステムコンポーネントであるkube-proxyが、ノード上のポートでLISTENし、アクセスを受け付けるとiptablesのルールによりPodへフォワーディングされる)。


Type:LoadBalancerを用いる場合

Type:LoadBalancerServiceを作成すると、Type:NodePortの場合と同様に、各ノードでアクセスを受け付けるノードポートが公開される。これに加え、クラスタ外部にグローバルIPが付与されたロードバランサが作成され、ユーザーはこのロードバランサへアクセスすると、各ノードポートへ転送されたのち、Podへ到達する。つまり、外部に公開されたロードバランサと、KubernetesのServiceにより2段階のロードバランシングを経てアクセスすることになる。

なお、Type:LoadBalancerの場合でも、直接ノードポートにアクセスし、Podと通信することが可能である。


L4ルーティングの問題点


  • 外部公開するServiceごとにエンドポイントが必要になる


    • 管理が手間

    • セキュリティ上、公開するポートは少なくしたい



  • L4レイヤでルーティングを行うため、エッジでのSSL終端が行えない


Ingressを用いたL7ルーティング


Ingressの概要

Ingressリソースを利用することで、KubernetesでL7ルーティングを実現することができる。通常、Ingressリソースを作成するためには、Podへ通信を転送するためのルールを、YAML形式のマニフェストファイルを用いて記述する。以下より、マニフェストファイルの例を以下に示す。


apache-ingress.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のDeploymentServiceリソースを作成するためのマニフェストファイルを記述する。


apache-deployment.yaml

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>

ブラウザからもアクセスしてみる。

スクリーンショット 2019-05-14 20.15.52.png

以上で、Apache Serverに接続できることを確認した。

上記で説明したIngressリソースを利用したルーティング方法はやや煩雑かつ、istioの機能を利用した場合との差異を理解するために必要なので、下図にその概略を示した。

スクリーンショット 2019-05-15 0.43.19.png


Istioが提供する外部アクセス方法

Istioでは、各PodにEnvoyで実装されたサイドカーコンテナを組み込むことで、APから透過的なサービスメッシュを構成できる。Istioのサービスメッシュ内では、GatewayVirtualServiceDestinationRuleなどのCRDを利用することで、Ingressを用いる場合より、柔軟なルーティングが可能である。

クラスタ外からメッシュ内のマイクロサービスにアクセスする場合の基本的な考え方は、Ingressを用いる場合と類似しており、定義したルールをもとに、istio-ingressgatewayと呼ばれるPodが転送処理を行う仕組みになっている。

この転送するルールを定義するのが、GatewayVirtualServiceDestinationRuleの3種のCustom Resource Definition(CRD)である。

本稿では、Istioが公式で提供しているbookinfoというサンプルアプリケーションを利用する場合を例にとって説明していく。今回は、Istioのインストール、bookinfoのデプロイ手段はIstioの公式ドキュメントを参照されたい。

bookinfoの構成は以下の通りである。この例では、外部からproductpageサービスにアクセスする。この際、productpageサービスが、バックエンドの3種のいずれかのReviewsサービスを呼び出し、Ratingの情報を取得している。

スクリーンショット 2019-05-14 22.10.18.png


各CRDの説明

クラスタ外からのアクセス方法を説明するため、GatewayVirtualServiceDestinationRoleの概要を簡単に紹介する。詳細は、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の役割の整理

以上を簡潔にまとめると、GatewayVirtualServiceは、istio-ingressgatewayが外部からアクセスを受け付けるエンドポイントと振り分け先のServiceリソースを決定するためのリソースであり、またDestinationRuleServiceからPodへリクエストを振り分けるポリシーを定義するためのリソースであるといえる。

スクリーンショット 2019-05-15 19.38.05.png


ルーティング方法

以上のように、istioを用いて外部からアクセスする場合、3つのCRDを用いて転送ルールを指定する。istio-ingressgatewayは、この3つのCRDを監視し、そのルールに基づき実際の転送処理を行うCustom Controllerである。

それでは、3つのWorkerノードを持つクラスタにbookinfoをデプロイした際のルーティング経路を下図に示す。なお、デフォルトではistio-ingressgatewayのレプリカ数は1つだが、便宜上レプリカ数を2つとして表記している。

実際に、多数のリクエストが想定される場合はistio-ingressgatewayをスケールするケースは想定され、それぞれの動作に影響はない。

スクリーンショット 2019-05-15 0.33.20.png


大まかな流れ



  • istio-ingressgatewayのLoadBalancerに到達したリクエストは、各ノードのNodePortへ転送される

  • 到達したノードで、iptablesによりポートフォワーディングが行われ、istio-ingressgatewayPodに到達する(図ではレプリカ数は2)


  • istio-ingressgatewayPodは、GatewayVirtualServiceDestinationRoleのルールをもとに、productpageServiceにリクエストを転送する


  • productpagePodにリクエストが到達する(図ではレプリカ数は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のページが表示される。

スクリーンショット 2019-05-15 21.14.29.png

同様に、

にアクセスすると、productpageのPodに到達し、応答を返す。ただし、http://localhost:80/login, http://localhost:80/logout へのリクエストでは、Pod側で処理が実装されていないため、WEBページは返却されない。


補足

本題とは少しずれるが、http://localhost:80/productpage へ数回アクセスすると、WEBページ右下のReviewsの部分の表示が毎回変化していることが分かる。これは、productpageのPodがReviewsのVirtualServiceを通じて、毎度異なるReviewsのPodから情報を取得しているためである。

VirtualServiceDestinationRuleの定義を変更することで、柔軟なルールで情報を取得するReviewsのPodを制御することができる。情報興味をもった方は、Istioの公式ドキュメント-Task

スクリーンショット 2019-05-15 21.32.47.png

スクリーンショット 2019-05-15 21.34.55.png


まとめ

Kubernetes/Istioの機能を利用してクラスタ外部からPodへアクセスする方法を紹介した。


  • Kubernetesの機能


    • Type:NodePort, Type:LoadBalancerのServiceを利用したL4ルーティング


    • Ingressを利用したパスベースのL7ルーティング


      • SSL終端が可能

      • トラフィックの入り口を集約

      • 処理を行う実体はnginx-ingress-controllerなど





  • Istioの機能



    • GatewayVirtualServiceDestinationRoleのルールに基づくL7ルーティング



      • Ingressより柔軟なトラフィック制御が可能

      • 処理を行う実体はistio-ingressgateway





今回は、Istioの機能のうち、外部からのアクセスに関わる部分にフォーカスを当てて説明したが、Istioの本領はマイクロサービス間のトラフィック制御、セキュア化、可視化についての機能であり、今後更に注目されていく技術と考えられる。私個人としても、継続的に学習を行っていきたい。


参考資料