サービスメッシュを実現するIstioをEKS上で動かす - その3 EKSでRDSなど外部サービスと接続してみる

山崎 雅斗
4

これまでは、EKSクラスタ内で完結するBookinfoアプリケーションを使ってIstioの検証を進めてきました。ここでは、Istio上で外部サービスと連携するアプリケーションを動かしてみます。
なお、簡便化のために今回はmTLSを無効にした状態で進めていきます。

検証用に、外部のデータベースを利用する簡単なWebアプリケーションを作成し、それをEKSクラスタ上にデプロイしておきます。テスト用のアプリケーションのソースコードは、GitHubの mas9612/sampleapp に置いてあります。

シリーズ一覧

サンプルアプリケーションのデプロイ

まず、サンプルアプリケーションをデプロイしていきます。次のようなmanifestを用意して、 kubectl コマンドで適用します。なお、データベースはAWSのサービスの1つであるRDSを使用しています。

apiVersion: v1
kind: Service
metadata:
  name: sampleapp
spec:
  ports:
    - name: sampleapp-port
      port: 8080
      protocol: TCP
      targetPort: sampleapp-port
  selector:
    app: sampleapp

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sampleapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sampleapp
  template:
    metadata:
      name: sampleapp
      labels:
        app: sampleapp
    spec:
      containers:
        - image: mas9612/sampleapp:latest
          name: sampleapp
          ports:
            - containerPort: 8080
              name: sampleapp-port
              protocol: TCP
          env:
            - name: SAMPLEAPP_DB_HOST
              value: xxxxxx.ap-northeast-1.rds.amazonaws.com
            - name: SAMPLEAPP_DB_USER
              value: sampleapp
            - name: SAMPLEAPP_DB_PASS
              value: xxxxxxx
            - name: SAMPLEAPP_DB_NAME
              value: sampleapp

このmanifestを適用すると、ServiceリソースとDeploymentリソースが1つずつ作成されます。

Istioのネットワーク設定

アプリケーション本体をデプロイしたら、次はGatewayリソースとVirtualServiceリソースを作成していきます。

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: sampleapp-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
    - port:
        number: 80
        name: http
        protocol: HTTP
      hosts:
        - "sample.example.com"

---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: sampleapp
spec:
  hosts:
    - "sample.example.com"
  gateways:
    - sampleapp-gateway
  http:
    - route:
        - destination:
            host: sampleapp
            port:
              number: 8080

このmanifestを適用したら、istio-ingressgatewayに向けて Host: sample.example.com ヘッダを付けてリクエストを送信するとサンプルアプリケーションに接続できるようになります。

$ curl -H "Host: sample.example.com" xxxxxx.ap-northeast-1.elb.amazonaws.com

しかし、この状態でsampleappに接続してもエラーとなります。特に何も設定をしない場合、サービスメッシュ外にあるデータベースにうまく接続できずにエラーとなってしまいます。
サービスメッシュ外のサービスに接続するためには、サービスごとにServiceEntryと呼ばれるリソースを作成する必要があります。

ServiceEntryリソースを作成すると、サービスメッシュ内のアプリケーションが外部のサービスへアクセスできるように、Istioが持っているサービスレジストリにその情報を登録してくれます。

それでは、データベース用のServiceEntryリソースを作成していきます。次のようなmanifestを作成し、 kubectl コマンドで適用します。spec.addresses にはデータベースのIPアドレスを指定する必要があるので、事前に nslookup コマンド等でIPアドレスを調べておきます。

apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: mysql
spec:
  hosts:
    - xxxxxx.ap-northeast-1.rds.amazonaws.com
  addresses:
    - 10.0.0.1/32
  ports:
  - number: 3306
    name: mysql
    protocol: TCP
  location: MESH_EXTERNAL

このマニフェストでは spec.hostsspec.addresses をそれぞれ指定しています。実際には、プロトコルがHTTPもしくはHTTPS以外の場合、 spec.hosts は無視されて spec.addresses に書いてあるアドレスが用いられるようになっています。HTTPやHTTPSの場合、Hostヘッダをみればその通信の宛先がどのホストであるかがわかります。しかし、TCPの場合はHostヘッダのように宛先のドメイン名を示している部分はありません。そのため、HTTP/HTTPS以外では spec.hosts が無視される仕様となっています。

基本的に、ServiceEntryリソースでHTTP/HTTPS以外のサービスを利用する際は、 spec.addresses にそれらのIPアドレスをCIDR表記(プレフィックス /32 )で指定します。しかし、IPアドレスが動的に変わる環境の場合、これが難しい場合もあると思います。そのような場合は、次のような対応策があります。

  1. 対象サービスを名前解決した結果複数のIPアドレスが得られて、かつその内のいくつかは固定されている場合、固定されているIPアドレスを spec.addresses に指定する
  2. 対象サービスが取りうるIPアドレスの範囲がわかっている場合、その範囲をCIDR表記で spec.addresses に指定する
  3. KubernetesのServiceリソースを、type ExternalNameで作成する

これら3つにも当てはまらない場合は、ServiceEntryリソースを利用することができません。その場合は、Sidecar Proxyをバイパスして直接外部のサービスと通信するという手段を取ることになります。

このリソースを作成した後に再度サービスへ接続しに行くと、正常にデータベースとの接続が正常に確立されて 200 OK が返ってくるようになります。

$ curl -H "Host: sample.example.com" xxxxxx.ap-northeast-1.elb.amazonaws.com

Consuming External TCP Services

まとめ

外部のデータベースを参照するようなアプリケーションを使って、Istioのサービスメッシュ上で外部サービスとの連携で必要となる設定について見てきました。
次回は、Istioで取得できる様々なデータを可視化するアプリケーションについて見ていこうと思います。