こんにちは。
食べログで2008年からコードを書いているおおいしつかさです。
プログラムは中学一年生の頃からMSX2で書いていました。ぼくも同じだ! という人はお友達になってください。1画面プログラムの話題で盛り上がりましょう。
(CDがいっぱい) Photo by Lucía Garó on Unsplash
CI/CDについては「LeanとDevOpsの科学」などで言及されている通り、サービスの価値をユーザーへ届け続けるという部分で重要であり、組織全体のパフォーマンスとも相関があることが示されています。みなさんのサービスでもCI/CDが導入されていることかと思います。
食べログではインフラ基盤のKubernetes化を進めています。(このことについては去年のAdventCalendarで弊社の@tkyowaが記事にしています)。
Kubernetes化にともない、食べログのCI/CDパイプラインも進化してきました。
今回は食べログのCloud NativeなCI/CDパイプラインについてお話ししようと思います。
Kubernetesに関しては、以降K8sと表記しますね。
GitOps
食べログではGitOpsによるCI/CDパイプラインを採用しています。
CIの部分は、アプリケーションのコードが変更されることをきっかけに動きます。
テストやコンテナイメージのビルド、コンテナレジストリへの登録などが実施されます。また、K8sマニフェストを変更するためのPullRequestが自動で生成されます。
CDの部分は、K8sマニフェストが変更された(CIで作られたPullRequestがマージされるなど)ことをきっかけに動きます。
ここでは変更されたK8sマニフェストを自動でK8sクラスタへ適用するようになっています。
こうすることで、GitリポジトリにあるK8sマニフェストが、実際のクラスタの状態を表していることになります(Single Source of Truthですね)。
セキュリティの観点からSecretを直接Gitリポジトリに置くことは禁止しています。代わりにSealedSecretを利用しています。
SealedSecretはSecretを暗号化したもので、K8sクラスタ上でのみ復号できるため安全に秘匿情報を扱うことができます。
これらによって、デリバリーに関する特別な作業をエンジニアがする必要はなくなります。
もちろん、kubectl
などを触ることもありません。
食べログのCI/CD全体図
食べログのCI/CDは上記のような構成で実現しています。
個々の要素についてさらに詳しくお話をしていきますね。
CI (CircleCI)
食べログでは昔からCIツールとしてCircleCIを利用しています。
テストや各種Lintの実行などは、K8s基盤で動くアプリケーションでもCircleCI上で実施しています。
この部分に関してはインフラ基盤がなんであろうと実施する内容に差はないため、無理にすべてをKubernetes-nativeなCI/CD環境にしようとは考えていません。
また、テストやLintはどのブランチであろうともリポジトリへpushされるたびに実行する必要があるため、その頻度や並列数はかなりのものになります。こういう理由もあって、K8sクラスタとは独立してリソースを利用できるCircleCIをうまく利用しています。
CI (Tekton)
ソフトウェアデリバリーに関わるCI処理については、Tektonを利用してK8sクラスタ上にパイプラインを構築しています。
具体的には以下の処理を行っています。
- コンテナイメージのビルド (buildkit)
- コンテナイメージの脆弱性スキャン (Aqua)
- コンテナレジストリへのpush (GitLab)
- kuberntesマニフェストを更新するPullRequestの作成 (kustomizeなど)
Aquaは、コンテナイメージの脆弱性スキャンを行ってくれます。
また、食べログではコンテナレジストリとしてGitLabを利用しています。
Tekton
TektonはK8sのCustomResourceとして、パイプラインを宣言的に定義することができます。
以下はTaskの例。
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: imagebuild-and-scan
spec:
steps:
- name: image-build
image: tabelog/buildkit:xxxxxx
volumeMounts:
- name: docker-auth
mountPath: /home/user/.docker
- name: shared-file
mountPath: /tmp/shared
workingDir: $(workspaces.source.path)
script: |
IMAGE_TAG=`cat /tmp/shared/image_tag`
buildctl-daemonless.sh --debug \
...
Tektonでの最小の実行単位であるリソースはTaskと呼ばれています。
Taskの中では、個々の処理をStepという形で定義することができます。Taskの中で定義したStepは順番に実行されます。
Taskの実体はPodであり、StepはPod内のコンテナという形で実現されています。これにより、個々のStepの処理はそれぞれ異なるコンテナで実行されるため、環境的に分離して扱うことができます。
複数のTaskを組み合わせてPipelineというリソースを定義できます。
Pipelineは、Taskの実行フローをグラフのように指定することができるので、実行順番やTask間の依存関係、並列処理などパイプラインの構成を自由にくみ上げることができます。
Tektonで定義したTaskやPipelineを実際に実行するには、TaskRunやPipelineRunというリソースを適用する必要があります。
このままだとGithub等と連携させることが難しい(都度kubectl create
しないといけない)ので、Tekton TriggersというWebHookを利用します。これもCustomResourceで宣言的に定義することができます。
Tekton Triggersは、イベントを受け取ると対応したTaskRunやPipelineRunリソースを適用してくれます。
また、TektonにはWebUIも用意されているのでパイプラインの実行結果などをブラウザで確認することもできます。
buildkit
CI/CDパイプラインではコンテナイメージのビルドが大事な処理のひとつになると思いますが、K8s上でコンテナイメージをビルドすることは、やり方によってはセキュリティ的な問題になることもあります。
この問題に対応するためにbuildkitを使用しています。
Tektonで構築するパイプラインの中でbuildkitを使うには、パイプラインとは別にbuildkitが動くDeploymentやStatefulSetを用意する方法と、TektonのTask内でbuildkitを利用する方法のふたつがあります。
食べログでは後者の、Task内でbuildkitを利用する方法を採用しています。
buildkitを共有して使うと並列してパイプラインが動いたときの負荷を気にする必要が出て来ますが、Task内でbuildkitを使えばその処理専用として動くのでこの懸念がなくなります(もちろんクラスタのリソースは気にする必要が出て来ます)。
Taskごとにbuildkitコンテナが新たに稼働するため、イメージビルド時のキャッシュをリモート環境に持たせる必要があります。コンテナレジストリにキャッシュを保持する方法もありますが、ローカルにキャッシュを置いてそれをリモートストレージに保持しておくやり方を採用しています。
K8sマニフェストの修正とPullRequestの作成
食べログのK8sマニフェストは、すべてのアプリケーションをひとつのリポジトリで管理しています。
環境の差分を取り扱うためにkustomizeを利用しており、ディレクトリ構成は以下のようになっています。
application_1 --- base --- (マニフェスト)
|
- overlays --- development --- (マニフェスト)
|
-- staging --- (マニフェスト)
|
-- production --- (マニフェスト)
application_2 --- base --- (マニフェスト)
|
- overlays --- development --- (マニフェスト)
|
-- staging --- (マニフェスト)
|
-- production --- (マニフェスト)
アプリケーションのリポジトリと1対1で結びついたディレクトリが用意されています。
各環境のkustomization.yml
で実際に使用するコンテナイメージを宣言しています。
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
images:
- name: fluent-bit
newTag: xxxxx
- name: mtail
newTag: xxxxxx
- name: prometheus_exporter
newTag: xxxxxx
- name: tasogare-app
newTag: 9e742268f045613b89659a622a846f7ad73aab49
- name: tasogare-web
newTag: 9e742268f045613b89659a622a846f7ad73aab49
kustomizeを使っているので、環境に対するイメージを変更するには、以下のコマンドを実行するだけです。
kustomize edit set image (イメージ名)
また、PullRequestの作成に関しては、GithubAPIを叩く簡単なスクリプトで実現しています。
CD
CDにはArgoCDを利用しています。
ArgoCD自体はK8sクラスタ上で稼働しており、一定間隔で対象のリポジトリをチェックしています。変更があればそれをpullしてクラスタに適用します。
ArgoCDはkustomizeにも対応しているため、リポジトリのコードにkustomize buildをかけて正式なマニフェストを生成するといったような作業は必要がありません。
K8sマニフェストを変更するPullRequestをCIで作成しましたが、このPullRequestをマージすることはK8sクラスタへ対象アプリケーションをリリースすることと同義になります。
ArgoCDにはロールバックの機能がありますが、マージしたPullRequestをリバートすることでもロールバックを実現できます。
ArgoCDで管理する各アプリケーションについてもCustomResourceで宣言することができます。食べログでもArgoCDが提供しているApplicationというリソースを定義することでアプリケーションを管理しています。
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: tasogare
spec:
project: tabelog
source:
repoURL: 'https://github.com/something'
path: something
targetRevision: master
kustomize:
version: v4.1.3
destination:
server: 'https://kubernetes.default.svc'
namespace: tabelog
ArgoCDにはWebUIが存在しているので、ブラウザで確認や操作を行うことも可能です。
まとめ
食べログではTektonやArgoCDを使用してKubernetes-nativeなCI/CD環境を構築しています。
CircleCIなどの従来のCIツール、Aqua、GitLabのコンテナレジストリなどもうまく組み合わせることで適材適所にツールを使い分けています。
食べログでK8s化されているのはまだ一部のアプリケーションだけですが、このCI/CD環境によってエンジニアは従来のデプロイ作業から解放されました(K8sマニフェストをマージするだけでリリースできるようになった)。
このCI/CDパイプラインはほとんどのアプリケーションでうまく回ります。
しかし、食べログのメインのリポジトリは複数のアプリケーションを含んでおり、その構成も複雑です。テストにもある程度の時間がかかるため、複数のPullRequestを連続してリリースするとなるとすぐにデプロイできないなどの問題を含んでいます。
これに対応するために「かんたんデプロイあんしんデプロイ」というスローガンの元、多数のPullRequestのリリースが立て込んでも時間を意識せずに済むような仕組みを構築中です。
もちろん、この仕組みを実現する上でも今回紹介したCI/CDパイプラインは重要な役目を果たします。まだまだチャレンジできることがたくさんあるので楽しみです!
最後に
今回の取り組みはSREチームが行ってきました。
そのSREチームではエンジニアを募集しています! インフラやDevOpsの経験がない方でも、ソフトウェアエンジニアなら活躍できる環境です。
Cloud Nativeな領域でできることはまだまだたくさん残されているので、ご興味のある方はぜひご応募お願いします。
もちろん、まずはカジュアル面談で情報交換をしてみたいという方も大歓迎です。その場合はご応募いただくときに、フリーテキスト記入欄に「カジュアル面談希望」とご記載くださいね。
明日は@weakbosonの「食べログのレストラン検索を支える Change Data Capture 基盤」です。お楽しみに!