後ろを向いて後退します

これって前に進んでいることになりませんか?

KubernetesとIstioを使ったDX改善

Recruit Engineers Advent Calendar 2020

本記事は Recruit Engineers Advent Calendar 2020 17日目の記事です。

adventar.org

16日目の記事はsangotaroさんの

zenn.dev

でした。

目次

前置き

この記事では、KubernetesとIstioを利用したPR環境(プルリクエスト環境, プレビュー環境)の自動デプロイをするための基盤整備を通したDX改善(developer experience, 開発体験)の取り組みについてお話しします。

PR環境というのは「プルリクエストごとに用意されている個別環境」というようなニュアンスで、今回の取り組みでは「機能開発をしているfeatureブランチをremoteにpushした際に自動的に立ち上がるプレビュー用環境」のことを指しています。

何故このような仕組みを整備するに至ったのか、どのように実装されているのか、どのような問題にぶつかったのか、を背景などを踏まえて説明します。

話すこと

  • Istioの一部のコンポーネントに関する説明
  • 背景・経緯
  • Kubernetes, Istioを使ってプレビュー環境の自動デプロイを実現する方法

話さないこと

KubernetesとIstioを使ったシステムのアーキテクチャ

下記のGCPの公式ドキュメントでは、Anthos Service Mesh(Istioベースのマネージドサービスメッシュ)とGoogle Cloud Load Balancingを組み合わせて、アプリケーションをGoogle Kubernetes Engine上でホスティングする方法について説明されています。

cloud.google.com

Kubernetes上にデプロイしたサービスをKubernetesのネットワーク外のHTTPクライアントからアクセスできるようにする場合、一般的にはk8s Service*1のNodePortやLoadBalancerを利用して外部に露出させたエンドポイントを通してHTTPクライアントからのアクセスを受け付けるようにします。

一方でIstioを併用する場合は、Kuberenetesクラスタ内にはIstio IngressGatewayという「クラスタの外部」と「クラスタの内部」の間に立つルーターのような役割をするコンポーネントが存在します。

https://cloud.google.com/solutions/images/exposing-service-mesh-apps-through-gke-ingress-standard-approach.svg?hl=ja

Istio IngressGatewayは、k8s ServiceのNodePortやLoadBalancerなどの代わりにKuberenetesクラスタ外からのトラフィックを受けた上で、そのトラフィックをKuberenetesクラスタ内の適切なk8s Serviceに対して転送します。トラフィックの適切な転送先を判別するために、Istioは下で説明するIstio GatewayIstio VirtualServiceという2つのリソースを利用します。

Istio Gateway

istio.io

Istio IngressGatewayが受けたHTTPリクエストは、ホスト名, URL, User Agent, プロトコル, ポート番号, クエリパラメータ, その他HTTPヘッダーなどの情報を元に適切なIstio Gatewayに振り分けられます。

1つのIstio Gatewayには、リクエストの振り分けを行うための詳細な条件を1つ以上設定することができます。例えば、下の例のように example.comfoobar.com の2つのホスト名を設定すれば複数のホスト名に宛てたリクエストを1つのIstio Gatewayに集約することも可能です。

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

Istio VirtualService

istio.io

Istio VirtualServiceは、Istio Gateway, ホスト名, k8s Serviceをもとにルーティング先を決める役割を持ちます。

以下の例では、

  1. Istio Gatway sample-gateway で受けた、
  2. ホスト名 example.com, foobar.com に宛てたHTTPリクエストを、
  3. k8s Service nginx-service の80番ポートにルーティングする。

といったような挙動をします。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: sample-virtual-service
spec:
  gateways:
    - sample-gateway
  hosts:
    - example.com
    - foobar.com
  http:
    - route:
      - destination:
          host: nginx-service.default.svc.cluster.local
          port:
            number: 80

システムの機能を並行開発するときの悩み

ある開発現場での話です。

この開発現場では、GitHubに対して変更をpushするとCI/CDのワーカーが自動でテストを実行し、Dockerイメージをビルドしてdev(開発)環境のDeploymentにローリングアップデートをかけるような仕組みが整備されています。

手元の変更をすぐにdev環境に反映できることによって、ローカルでのモックよりも更にprd(本番)環境に近い状況で簡単に動作検証をできるようになりました。また、実際に稼働しているKubernetesクラスタ上に変更が反映されると、エンジニア以外のメンバーに対して簡単に新しいUIや機能を触ってもらうようなことも可能になります。

今となってはCI/CDツールの普及によってこういった開発プロセスを円滑に進めるための仕組みも容易に導入できるようになり、この一連の仕組みやプロセスはGitOpsという言葉でCloudNative時代のDX向上を目指すものとして表現されています。

しかし今回の開発現場では、GitOpsを導入し容易にdev環境に対するデプロイができるようになったことにより、別の課題が発生してしまいました。

Bob「俺はこのイケてる新UIをdev環境にデプロイするぜ!」

Alice「私はこの最高に便利な新機能をdev環境にデプロイするわ!」

〜 5分後 〜

Bob「あれ、俺が開発した新UIがdev環境に反映されていないぞ?」

Alice「あっ、私が後追いでデプロイしたから上書きされちゃったのかも…!」

何がどうなったかは二人の会話を見れば一目瞭然です。

CDは同じdev環境に対するローリングアップデートを続けるので、BobとAliceがそれぞれ別々のブランチで作業をしているとBobの変更はAliceの変更によって上書きされてdev環境からは消えてしまうわけです。

これに対応するためには、例えばdev環境をナンバリングして2つ以上並べて用意しておくとか、事前に他のエンジニアと連携をとった上でdev環境にデプロイするタイミングを調整するとか色々な方法があるわけですが、これを更にスマートに解決してみようというのが今回の本題になります。

目指した理想の開発環境の姿

BobとAliceの例のように、五月雨式にそれぞれのブランチに変更を重ねたとしてもお互いの変更が上書きしないようにするためには、やはり環境自体が分離されている必要があります。

かといってブランチの数だけdev環境を複製しようものなら、(特にKubernetesを使っている場合には)莫大なパブリッククラウドの利用料金と果てしない手間がかかることは間違いないでしょう。

やはり理想は、

  • ブランチの数だけ
  • 動作確認に必要な最低限のコンポーネント
  • 自動的に作成/削除される

という状態です。

機能開発をしているブランチをGitHubにpushすると自動的にPR環境が立ち上がり、PullRequestがマージされてブランチが削除されると自動的にPR環境が削除される、といったようなことが実現できればGitOpsにおけるDXもかなり改善できるのではないかと考えました。

理想のPR環境環境を目指すにあたって、動作確認に必要な最低限のコンポーネントをどうやってデプロイするのかというのは、システム全体のアーキテクチャやネットワークの話も絡んできそうで少し難しい問題かに思えました。

しかし今回のようにKubernetesを使っている例では、

  • リバースプロキシサーバー(必要であれば)
  • フロントエンドサーバー
  • バックエンド(API)サーバー

あたりをk8s Service + Deploymentのベーシックな構成で複製することができるなら、あとはロードバランサーから適切にルーティングさえできればOKです。

PR環境に割り当てられたホスト名からどのようにルーティングするかどうかについては、Istio GatewayとIstio VirtualServiceを設定すればIstio IngressGatewayが適切なk8s Serviceにルーティングをしてくれるでしょう。

そう、実はそんなに難しくないのです。

自動化したこと

今回の取り組みによって、以下のことが自動化されました。

  • PR環境の立ち上げ
    • 新しいfeatureブランチがpushされたときに、そのブランチ名に応じた k8s Service, Deploymentが作成される。
    • 同時に、Istio GatewayとIstio VirtualServiceも作成してIstio IngressGatewayからのルーティングをできるようにする。
    • featureブランチ名に応じたPR環境用のドメイン名のレコードをDNSに登録する。
    • PR環境用の証明書をワイルドカードで取得してIstio IngressGatewayにマウントする。
  • PR環境の更新
    • featureブランチに変更を加えてpushしたときに、立ち上げ時に作成したリソースを全てローリングアップデートして、変更がすぐに反映されるようにする。
  • PR環境の削除
    • GitHub上に存在するfeatureブランチの一覧と、Kubernetesクラスタ上にデプロイされているPR環境用のリソースを一覧で比較し、「featureブランチは存在しないけどPR環境用のリソースは存在する」という条件に合うPR環境のリソースを全て削除する。これをCronのような形で一日数回実行する。

f:id:mic_psm:20201216013750p:plain
fig2: PR環境を構成するためのリソース

fig2の図では、このPR環境を構成しているリソースを表しています。オレンジ色の部分がfeatureブランチの数だけ複製されて同じKubernetesクラスタ内にデプロイされるリソースです。

どう実現したのか、具体的なサンプルソースコードや注意点なども含めて説明していきます。

k8s Service, Deploymentを作成する

アプリケーションの本体を担うこの2つについて考えることは、そんなに多くありません。

同じnamespace内にデプロイするのであればk8s ServiceDeploymentmetadata.name さえ重複しないような仕組みになっていれば、dev環境に適用されているmanifestをそのままコピーして使えます。

以下は deployment.yaml の例です。%FEATURE%, %DOCKER_TAG% というのは、CI/CD上で実行する直前に sed コマンドなどでブランチ名と置き換えることを想定しています。(例: sed -e s/%FEATURE%/new-ui/g -e s/%DOCKER_TAG%/${DOCKER_TAG}/g ./deployment.yaml | kubectl apply -f - --record

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-%FEATURE%
  namespace: default
  labels:
    component: app
    feature: "%FEATURE%"
spec:
  replicas: 1
  selector:
    matchLabels:
      name: app-%FEATURE%
  template:
    metadata:
      labels:
        name: app-%FEATURE%
    spec:
      restartPolicy: Always
      containers:
      - name: app-%FEATURE%
        image: asia.gcr.io/sample-project/app:%DOCKER_TAG%
        env:
        - name: PROJECT_ENV
          value: dev
        imagePullPolicy: Always
        livenessProbe:
          httpGet:
            path: /healthz
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 15
        readinessProbe:
          httpGet:
            path: /healthz
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 15
        ports:
        - containerPort: 80
          protocol: TCP
        resources:
          limits:
            cpu: 200m
            memory: 400Mi
          requests:
            cpu: 100m
            memory: 200Mi

同様に、以下は service.yaml の例です。こちらも特に気にせずDeploymentと同じようにほぼコピーで大丈夫ですが、k8s ServiceとDeploymentを正常に繋ぐために spec.selector.name が動的に差し替えられていることに注意が必要です。

apiVersion: v1
kind: Service
metadata:
  type: NodePort
  name: app-%FEATURE%
  namespace: default
  labels:
    component: app
    feature: "%FEATURE%"
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    name: app-%FEATURE%

Istio Gateway, Istio VirtualServiceを作成する

作成したk8s ServiceとDeploymentに対して正常にトラフィックをルーティングできるようにするために、Istio GatewayIstio VirtualServiceのルールをそれぞれ用意する必要があります。

new-ui ブランチでPR環境を立ち上げようとする場合を例にとると、

  • Istio Gateway
    • new-ui.prenv.example.com を受け付けるGateway new-ui-gateway
  • Istio VirtualService
    • new-ui-gateway で受け付けた new-ui.prenv.example.comk8s Service app-new-ui にルーティングするVirtualService

の2つを作成することになります。

以下は gateway.yamlvirtualservice.yaml の例です。k8s Service, Deploymentのmanifestと同じように、適用時にsedコマンドなどでブランチ名を差し込んでやるようにしましょう。

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: %FEATURE%-gateway
spec:
  servers:
    - port:
        number: 80
        name: http
        protocol: HTTP
      hosts:
        - %FEATURE%.prenv.example.com
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: app-%FEATURE%
  namespace: istio-system
  labels:
    component: app
    feature: "%FEATURE%"
spec:
  hosts:
  - "%FEATURE%.prenv.example.com"
  gateways:
  - %FEATURE%-gateway
  http:
  - route:
    - destination:
        port:
          number: 80
        host: app-%FEATURE%.default.svc.cluster.local

ドメイン名のレコードをDNSサーバーに登録する

これは2つの方法があります。

1つは、ワイルドカードDNSレコードを作成してしまうことです。 *.prenv.example.com などでロードバランサーに向けたAレコードやCNAMEレコードを作成してしまえば、どのような名前のfeatureブランチが作成されたとしても prenv.example.com の任意のサブドメインに対するアクセスをロードバランサーを経由してIstio IngressGatewayにルーティングできます。

ワイルドカードDNSレコードが作成できるのであれば、この方法が一番手軽でよいでしょう。

もう1つは、ExternalDNSを利用して動的にDNSレコードの追加/削除をすることです。

ExternalDNSは、Kubernetesに対してサードパーティーDNSサービスに対するアクセス権限を付与すると、k8s ServiceやIngressリソースにAnnotationを追加するだけで自動的にDNSサーバーに必要なレコードを作成してくれます。

以下はIstio GatewayとIstio VirtualServiceで設定されているホスト名を自動的にGoogle Cloud DNSに登録する deployment.yaml の例です。RBAC(=Role-based access control)が有効化されているかどうかによってセットアップの方法は異なりますが、基本的に公式のチュートリアルに従って進めると正常に動作するようになります。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: external-dns
spec:
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: external-dns
  template:
    metadata:
      labels:
        app: external-dns
    spec:
      serviceAccountName: external-dns
      containers:
      - name: external-dns
        image: registry.opensource.zalan.do/teapot/external-dns:latest
        args:
        - --log-level=debug
        - --source=service
        - --source=ingress
        - --source=istio-gateway
        - --source=istio-virtualservice
        - --domain-filter=prenv.example.com
        - --provider=google
        - --registry=txt
        - --txt-owner-id=prenv-external-dns

ただし、ExternalDNSを利用する場合はオンデマンドでDNSレコードを作成するため、一番最初にPR環境を立ち上げるときにはDNSレコードを作成してから疎通するようになるまで少し時間がかかってしまうのが難点です。

証明書を取得・更新する

PR環境はfeatureブランチの名前ごとにサブドメインを増殖させるので、証明書もそれに応じたものを用意してロードバランサーにアタッチする必要があります。オンデマンドで1つずつ取得していたのでは大変なので、ここではワイルドカード証明書を取得してそれを全てのPR環境で使い回すようにしました。本番環境では適切ではない方法かもしれませんが、これはdev環境なので気にすることではないでしょう。

GCPの場合でいうと、Google-managed SSL certificatesはワイルドカード証明書を取得することができません。そこで、PR環境ではcert-managerを利用することにしました。

cert-manager.io

cert-managerは、Let's Encrptでの証明書の取得から更新を全て自動で行ってくれる画期的なKubernetesアドオンです。公式ドキュメントのマニュアル通りにインストールとセットアップを進めると、HTTP-01やDNS-01チャレンジ方式で取得された証明書のキーペアがKubernetesのSecretsとして作成されます。あとはIstio IngressGatewayにマウントすればOKです。

この証明書の取得については、更新は確かに自動で行われますが取得自体を特に自動化する必要はなく、最初の一回だけ実行すればあとは放置しておくだけで大丈夫です。

apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
  name: letsencrypt-prod
  namespace: istio-system
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: user@example.com
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - dns01:
        clouddns:
          project: sample-project
          serviceAccountSecretRef:
            name: cert-manager-credentials
            key: prenv-certmanager.json
---
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: istio-gateway
  namespace: istio-system
spec:
  secretName: istio-ingressgateway-certs-prenv
  issuerRef:
    name: letsencrypt-prod
  dnsNames:
  - "*.prenv.example.com"
  - "prenv.example.com"

環境を自動削除する

これに関しては、そこまで開発サイクルが激しくないのであれば手作業で必要がなくなったPR環境を削除してもOKですし、Kubernetes CronJobやArgo CronWorkflowなどを使って自動的にクリーンアップするような仕組みを作ってもよいでしょう。

今回この記事で紹介したmanifestには、全て共通してPR環境用のリソースであることを判別するためのlabelを設定してあります。これを使って以下のような流れで動くジョブを定期的に実行すれば、使われなくなったPR環境がKubernetesクラスタの計算資源を食いつぶすようなこともなくなるでしょう。

  1. GitHubAPIを使用して、リモートリポジトリに存在するfeatureブランチを全て取得する。
  2. kubectl コマンドを使用して、labelでフィルタリングしてKubernetesクラスタ上に存在するPR環境のリソースを全て取得する。
  3. 1と2で取得したfeatureブランチ名の集合の差分を計算し、削除するべきPR環境の一覧を取得する。
  4. 3で取得したPR環境を構成するリソース(k8s Service, Deployment, Istio Gateway, Istio VirtualService)を全て削除する。

Istio Gatewayの別の問題

さて、これらの仕組みを自動化したことによって、開発現場のBobとAliceはPR環境に各々の変更をデプロイして動作確認をできるようになりました。

Michael「2人とも、いいニュースだ!featureブランチをremoteにpushすると自動的にPR環境が立ち上がる仕組みを作ったぞ!」

Bob「本当かい!?じゃあ早速俺のnew-uiブランチをpushしてみるよ!」

Alice「わあ、すごい!私のnew-featブランチもそのPR環境で確認してみるわ!」

〜 5分後 〜

Bob「これはすごい。ブランチ名のサブドメインが勝手に切られて、そこに俺が開発した新UIがデプロイされているぞ!」

Alice「私の新機能もちゃんと動いているわ!見てちょうだい、Bob!」

Bob「どれどれ、 https://new-feat.prenv.example.com っと…。あれ?404エラーになるぞ?」

Alice「えっ?どうしてかしら…。私のブラウザでは正常にアクセスできるのに…。」

Alice「おかしいわ、私のブラウザだと逆にBobがデプロイした https://new-ui.prenv.example.com にアクセスできないみたい!」

Michael「何だって!?」

PR環境をハシゴできない

このPR環境の仕組みを作ってしばらくしてから、ある問題が発生していることに気が付きました。

それは、「複数のPR環境をハシゴしてアクセスしようとしたときに、最初にアクセスしたPR環境以外は全て404エラーになってしまう」という問題。

どの環境にアクセスができてどの環境にアクセスできなくなるかが完全に各人が最初にアクセスしたPR環境に依存しているということ、プライベートブラウズモードでアクセスし直すとアクセスできることから、恐らくブラウザキャッシュかセッション周りで何かよく分からないことが起きているという目星はつけてはいたのですが、詳しい原因についてはもう少し調査をする必要がありました。

404を返却しているHTTPレスポンスヘッダを見たところレスポンスの返却元がenvoyであることが分かったので、ロードバランサーからIstio VirtualServiceまでのどこかでこのエラーが返却されていると仮定し、唯一アクセスログを吐いているIstio IngressGatewayのenvoyのログを見てみることにしました。

[2020-12-07T07:56:51.762Z] \"GET / HTTP/2\" 404 NR \"-\" \"-\" 0 0 0 - \"xxx.xxx.xxx.xxx\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X ...)" \"xxxxx-xxxxx-xxxxx-xxxxx\" \"new-feat.prenv.example.com\" \"-\" - - xxx.xxx.xxx.xxx:80 xxx.xxx.xxx.xxx:1234 new-ui.prenv.example.com -\n

上記がそのアクセスログです。Istioの公式ドキュメントによると、 NR というのは no route configured を意味しており、Istio VirtualServiceの設定が適切な状態ではないことを示しています。また、ログの中に2つの違うホスト名が記録されている点も気になります。

ワイルドカード証明書は複数のIstio Gatewayとの相性が良くない

これまでに得た情報をもとに調査をしていると、調査に協力してくださっていた orisanoさんint-ttさん から公式ドキュメント内のある情報を提供していただきました。Istioの公式ドキュメントには、このように書かれています。

istio.io

404 errors occur when multiple gateways configured with same TLS certificate

Configuring more than one gateway using the same TLS certificate will cause browsers that leverage HTTP/2 connection reuse (i.e., most browsers) to produce 404 errors when accessing a second host after a connection to another host has already been established.

For example, let’s say you have 2 hosts that share the same TLS certificate like this:

  • Wildcard certificate *.test.com installed in istio-ingressgateway
  • Gateway configuration gw1 with host service1.test.com, selector istio: ingressgateway, and TLS using gateway’s mounted (wildcard) certificate
  • Gateway configuration gw2 with host service2.test.com, selector istio: ingressgateway, and TLS using gateway’s mounted (wildcard) certificate
  • VirtualService configuration vs1 with host service1.test.com and gateway gw1
  • VirtualService configuration vs2 with host service2.test.com and gateway gw2

Since both gateways are served by the same workload (i.e., selector istio: ingressgateway) requests to both services (service1.test.com and service2.test.com) will resolve to the same IP. If service1.test.com is accessed first, it will return the wildcard certificate (*.test.com) indicating that connections to service2.test.com can use the same certificate. Browsers like Chrome and Firefox will consequently reuse the existing connection for requests to service2.test.com. Since the gateway (gw1) has no route for service2.test.com, it will then return a 404 (Not Found) response.

要約すると、

「同じTLS証明書を利用して2つ以上のIstio Gatewayを構成する場合に、2回目以降のアクセスが正しいIstio GatewayとIstio VirtualServiceにルーティングされずに404 Not Foundエラーが起きる」

ということです。

つまり、 new-ui.prenv.example.com を受け付けるIstio Gateway new-ui-gateway と、new-feat.prenv.example.com を受け付けるIstio Gateway new-feat-gateway があって、同じIstio IngressGatewayで同じワイルドカードTLS証明書 *.prenv.example.com を使っているようなケース。このようなケースにおいてHTTP/2のconnection reuseの仕様の影響で、 new-ui.prenv.example.com にアクセスしたあとに new-feat.prenv.example.com にアクセスしようとすると本来ルーティングされるべきIstio Gateway new-feat-gateway ではなく、Istio Gateway new-ui-gateway にルーティングされてしまってエラーとなってしまいます。

envoyのログに no route configured のエラーが記録されていたのはそういうことで、 new-feat.prenv.example.com にアクセスをしたのに Istio Gateway new-ui-gateway にルーティングされてしまい、そこからルーティング可能なVirtualServiceには new-feat.prenv.example.com に対応したk8s Serviceが設定されていなかったためにこのエラーが返却されてしまったということなのです。

f:id:mic_psm:20201216014705p:plain
fig3a: 1回目に https://new-ui.prenv.example.com でnew-uiブランチのPR環境にアクセスを試みるケース

f:id:mic_psm:20201216015314p:plain
fig3b: 2回目に https://new-feat.prenv.example.com でnew-featブランチのPR環境にアクセスを試みるケース

解決方法

この問題を解消するためには、上記のドキュメント内で

You can avoid this problem by configuring a single wildcard Gateway, instead of two (gw1 and gw2). Then, simply bind both VirtualServices to it like this:

  • Gateway configuration gw with host *.test.com, selector istio: ingressgateway, and TLS using gateway’s mounted (wildcard) certificate
  • VirtualService configuration vs1 with host service1.test.com and gateway gw
  • VirtualService configuration vs2 with host service2.test.com and gateway gw

と書かれているように、

という2つの設定をすることで回避できます。

以下は回避手段を講じた gateway.yaml の例です。注意する必要があるのは、上記で例示した gateway.yaml は全てのfeatureブランチについて別個に適用する必要があったのに対し、以下の gateway.yaml は1つのKuberenetesクラスタにつき一度だけ適用すればOKという点です。

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

f:id:mic_psm:20201216015452p:plain
fig4: Istio Gatewayの問題を解消した場合のアーキテクチャ

Bob「Aliceの新機能が俺のブラウザでも見れるようになったよ!」

Alice「私のブラウザでもBobの作ったUIが見れたわ!」

今後やりたいこと

勘の良い方なら疑問に思ったかもしれません。

「あれ、データベースってどうするの?」

実は、ここはまだ取り組めていない部分です。現状はdev環境に存在する1つのデータベースに対して全てのPR環境から接続するような形になっています。

「どうやってデータベースを複製するのか?」「複製したときにかかるコストは?」「複製にかかる時間は?」

など壁も多いですが、データベースのスキーマ構造の変更に対しても耐えられる柔軟なPR環境の整備を次の目標にして改善活動に励んでいこうと思います。

まとめ

こういった取り組みは前例もあまりないことから、やってみようと足を踏み出すのは中々難しいかもしれません。

ですが今回このPR環境を整備したことによって、開発チーム内でのエンハンスのスピードは目に見えて向上したように感じます。ピーク時は10個近いPR環境が並行稼働していることもありました。

この記事を通して伝えたかったのは、一見難しいように思える仕組みづくりもエコシステムの恩恵を受けて簡単に実現できる可能性があること、その取り組みを実現して不の部分を解消したときにそのソリューションがスケールすることが分かっているならば、それは長期的に見ると大きな価値を生み出す可能性があるということです。

KubernetesとIstioという開発現場において採用事例のあまり多くなさそうなケースでの紹介となってしまいましたが、この仕組みを実現するための取り組みのエッセンスが読者の方の開発現場でも役に立つことを願っています。

18日目の記事はka2jun8さんが執筆予定です。乞うご期待!

*1:Istio VirtualServiceと区別するために、ここではk8s Serviceと呼称しています。