LoginSignup
14

More than 3 years have passed since last update.

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

Posted at

はじめに

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の本領はマイクロサービス間のトラフィック制御、セキュア化、可視化についての機能であり、今後更に注目されていく技術と考えられる。私個人としても、継続的に学習を行っていきたい。

参考資料

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14