こんにちは、Mercari US Microservices Platform Teamの矢口です。
Mercariではこのたびテスト環境を簡単に作成できるツールをOSSとして公開しました!
KubeTempuraとは
KubeTempuraとはKubernetesクラスタにお試し用環境を自動で作成するためのKubernetes Operatorです。
GitHubでのPull Requestの作成をトリガーとしてKubernetesのリソースを作成できます。
Pull Requestを作成したりPull Requestにcommitをpushするだけで簡単に自分やQAのメンバーが変更したコードを試すことができます。
動機
なぜこういったツールを開発したかについて説明します。
PRごとに環境をつくるというアイデアは Heroku Review Apps など広く知られているものです。社内でもKubernetes向けに似たアイデアのものがあり浸透していました。ただし実装は大幅に異なるアプローチでした。
フレキシビリティー
以前のプログラムではKubernetesクラスタに存在するmain branch用のDeployment, Serviceなどを確認して「いいかんじ」にクローン・改変するというアプローチをとっていました。これは社内での典型的な設計のmicroserviceにはうまく動作するのですがそれ以外に対しては対応できません。
ServiceやDeploymentが複数あったり、Ingressがあったりなかったり、その他のCRDを利用していたりといったケースに対応する場合にはコードの変更と特別なアノテーションの追加が必要となっていました。プログラムには場当たり的な改修が増え生産性が低下しましたし、なにより各microserviceのチームが新しいチャレンジを行うことを難しくしていました。
KubeTempuraではReviewApp CRDを定義しこのなかで配置するリソースをテンプレートとして定義することで解決しています。PRに対してどういったリソースを配置するかはユーザーが決定できるためKubeTempura自身に機能を追加せずとも柔軟性が大幅に向上しました。
これはシンプルとイージー、implicitとexplicitの違いのようなものです。状況によってどちらが良いかは変化するのですがMercari USではシンプルなアプローチがよりフィットしていました。
なぜGithub Actionsを使わずにKubernetes Operatorをつくったのか
こういったReview App機能を実装する際にもっとも一般的なのはCIを利用したものではないでしょうか。たとえばGithub Actionsなどを使っても実現することができます。ではなぜこれはKubernetes Operatorとして実装したのでしょうか。
1個か2個のレポジトリで利用する場合であればGithub Actionsはうまくいきそうです。しかしMercari USでは現在50のmicroserviceを運用しており、50個のレポジトリがあります。
これらのレポジトリそれぞれでGitHub Actionsを設定するのはかんたんな作業ではありません。設定変更のたびに50箇所の変更が発生してしまいますし、Kubernetesにアクセスするsecret tokenも50個発行するか、強力な権限をすべてのrepositoryから利用できるようにするかという選択になってしまいます。
これに対しKubeTempuraではすべての設定はKubernetes上にReviewApp resourcesとして集約されており、新たなmicroserviceを作成する際にもsecret tokenなどについて考慮する必要はありません。組織とサービス数の拡大に対してスケーラブルな設計となっています。
使いかた
構成
KubeTempuraをセットアップするには以下の3つが必要になります:
- Kubernetesクラスタ
- GitHub Webhookをrepositoryまたはorganization単位で設定できる権限
- Webhook用のドメイン
READMEにしたがってKubeTempuraをインストールし、GitHubのWebhookを設定すれば準備完了です。
基本的な使いかた
- ReviewApp resourceを作成します。これはPRで作成されるリソースのひな形となるものです。internalなserviceの場合は、DeploymentとServiceリソースを置く以下のような構成になるでしょう。
apiVersion: kubetempura.mercari.com/v1
kind: ReviewApp
metadata:
name: reviewapp-sample
namespace: default
spec:
githubRepository: https://github.com/mercari/not-exists
resources:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: reviewapp-sample-pr{{PR_NUMBER}}
namespace: default
spec:
selector:
matchLabels:
app: reviewapp-sample-pr{{PR_NUMBER}}
template:
metadata:
labels:
app: reviewapp-sample-pr{{PR_NUMBER}}
spec:
containers:
- name: sample
image: ghcr.io/stefanprodan/podinfo:pr{{PR_NUMBER}}-{{COMMIT_REF_SHORT}}
ports:
- name: http
containerPort: 9898
protocol: TCP
- apiVersion: v1
kind: Service
metadata:
name: reviewapp-sample-pr{{PR_NUMBER}}
namespace: default
spec:
ports:
- name: http
port: 9898
protocol: TCP
targetPort: http
selector:
app: reviewapp-sample-pr{{PR_NUMBER}}
- 開発しているアプリケーションでPull Requestを作成します。ひな形にPR番号やcommit hashなどがapplyされてKubernetesクラスタにリソースとして配置されます。例としてPR123を作成します。
$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
reviewapp-sample-pr123 1/1 1 1 1m
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT AGE
reviewapp-sample-pr123 ClusterIP x.x.x.x <none> 9898,TCP 1m
- もうひとつPR124を作成します。すると対応するリソースがKubernetesにもうひとつ作成されます
$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
reviewapp-sample-pr123 1/1 1 1 5m
reviewapp-sample-pr124 1/1 1 1 1m
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT AGE
reviewapp-sample-pr123 ClusterIP x.x.x.x <none> 9898,TCP 5m
reviewapp-sample-pr124 ClusterIP x.x.x.x <none> 9898,TCP 1m
- PRに変更をpushするとbranchのlatest commit hashが更新されます。KubeTempuraはWebhookでこの変更を受け取りリソースをupdateします。
$ kubectl get deploy -o yaml | grep image:
image: ghcr.io/stefanprodan/podinfo:pr123-32f7bfe8b7279912ce1eb6dfb6653ee297936cf7
↓
$ kubectl get deploy -o yaml | grep image:
image: ghcr.io/stefanprodan/podinfo:pr123-d0451ad4d774c8fbc843d3251530dcabb3c635a9
- PRがmergeまたはcloseされると作成されたリソースはすべて自動でクリーンアップされます。PR123をmergeしたあとはこのようになります。
$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
reviewapp-sample-pr124 1/1 1 1 1m
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT AGE
reviewapp-sample-pr124 ClusterIP x.x.x.x <none> 9898,TCP 1m
画像
外部からHTTPなどでアクセスできるようにするには
KubeTempuraにはKubernetes上にリソースを配置するための機能しかありません。
ブラウザやスマートフォンからアクセスしたい場合に弊社でどのように利用しているか簡単に説明します。その際にセキュリティをどう考慮するかや、KubeTempuraでどう実現するかについても触れておきたいと思います。
構成
一般に外部からアクセスできるようにする場合、DNS recordやIngressの設定が必要となります。しかしPRを作成したら即座にテスト環境が利用可能にしたいという要件があります。そのためには構築、変更に時間がかかるリソースの作成を避ける必要があります。
Mercari USでは各アプリに pr123.review-app.example
のようなサブドメインを割り当て
wildcardでDNSとTLS証明書を設定しています (e.g, *.review-app.example
)。そしてresourceごとにIstioのVirtualServiceを作成することで一瞬でルーティングの設定変更が完了するようになっています。
apiVersion: kubetempura.mercari.com/v1
kind: ReviewApp
metadata:
name: reviewapp-sample
namespace: default
spec:
githubRepository: https://github.com/mercari/not-exists
resources:
... (略)
- apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: reviewapp-sample-pr{{PR_NUMBER}}
namespace: default
spec:
gateways:
- istio-system/default
hosts:
- pr{{PR_NUMBER}}.review-app.example
http:
- route:
- destination:
host: reviewapp-sample-pr{{PR_NUMBER}}.default.svc.cluster.local
IstioのようなServish Meshをもちいない場合は Kubernetes Gateway API などを利用すると同じことができるかもしれません。
セキュリティ
セキュリティについては利用者自身で確保する必要があります。
例えば弊社ではGKEを利用しているため、ブラウザからアクセスする場合は場合はIngressにGoogle Cloud IAPを設定することなどで対応できます。
APIサーバーなどでCloud IAPが利用できない場合にはsource IP rangeを指定しての制限をかけることもあります。この場合にはオフィスのIPからのアクセスやVPNを利用するなどクライアント側で追加の要件が必要になってしまいます。
まとめ
ぜひ試してみてください!
https://github.com/mercari/kubetempura