Kubernetes-nativeなアーキテクチャ導入の手引き 先進的なクラウド環境を最強テストベッドで体験
Kubernetes-nativeなエコシステムを実現する最強テストベッド環境です。さまざまなミドルウェアを運用したマイクロサービスをフルgRPCなサービス間通信で実現するだけでなく、CI/CDと開発環境も用意しています。
こんにちは。株式会社サイバーエージェントのAI事業本部でインフラエンジニアをしている青山真也(@amsy810)と漆田瑞樹(@zuiurs)です。今回は、Kubernetesが好きな2人が考える最強のKubernetes-nativeなお試し環境を構築してみました。記事公開時点で、総コミット数が900に迫るリポジトリになっています。
現在、Kubernetesとそれを取り巻くエコシステムは急速に発達しており、便利なツールやミドルウェアが日々生まれています。これはとてもよいことですが、一方で「どのミドルウェアをどうやって運用すればよいのか?」「どのようにアプリケーションと結合すればよいのか?」と混乱してしまう方も多いかもしれません。
そうした課題を解決するため、自前のクラスタにさまざまなミドルウェアや、それを利用したマイクロサービスをデプロイして、動作や運用感を確認できるテストベッドを作りました。
これを参照することで、マニフェストの設定例や実装例を知ることができ、さらに自分で変更して試すこともできるようになります。
また、このテストベッドではKubernetesのCRDやOperatorなどの機能をフル活用し、自動的にミドルウェアの管理を任せるKubernetes-nativeな手法を数多く利用しています。ぜひ先進的な実現方法を体験してみてください。
本記事では、このテストベッドの利用方法、アーキテクチャ、使用したミドルウェアの運用、実装などについて説明していきます。
- もう迷わない! 必要なミドルウェアの盛り合わせ
- クラウドネイティブなCI/CD! その名はGitOps
- クラウドネイティブ時代のステートフルなミドルウェア
- 次世代の標準 フルgRPC通信なモダンなアプリケーション
- 最後に ─ クラウドネイティブを実現するひとつの答え
もう迷わない! 必要なミドルウェアの盛り合わせ
クラウドネイティブに関連するミドルウェアは、CNCF(Cloud Native Computing Foundation)によるCloud Native Interactive Landscapeというページで確認できます。数多くのミドルウェアが存在しているため、これを見た多くの方が、導入で述べたように「どれを選択すればよいか分からない」という気持ちになると思います。
幸い、私たちはKubernetes界隈に長らく身を置いているため、KubeConなどさまざまなクラウドネイティブ系カンファレンスから伺える特定のコミュニティの盛り上がり具合や、CNCFプロジェクトへの採択状況から、「どのような技術・ミドルウェアが流行しているのか」をある程度認識していました。その知識をもとに、Kubernetes上で開発する際によく使われそうなミドルウェアを中心にピックアップして、このテストベッドで動かすことにしました。
しかしながら、クラウドネイティブなミドルウェアを選定するだけでは不十分です。クラウドネイティブとは、管理の容易さや、可観測性、疎結合性、回復性などの特徴を持つシステムを指すため、ミドルウェアを素の状態でKubernetes上で動かそうとすると、リソースの作成から運用まで、多大な時間を使うことになるでしょう。そこでこのテストベッドでは、そういった面倒ごとを自動的に担ってくれるOperator(カスタムコントローラ)を最大限に利用します。
KubernetesのCRDとOperatorを最大限に利用
もう少し詳しく説明しましょう。
Kubernetesには、CRD(CustomResourceDefinition)とOperatorという仕組みが用意されています。CRDは、Kubernetesがデフォルトで扱うことが可能なリソース以外の独自リソースを定義することができる機能です。例えば下記のMysqlCluster
リソースのようなものも作ることができます。
apiVersion: mysql.presslabs.org/v1alpha1 kind: MysqlCluster metadata: name: product-db namespace: product spec: replicas: 2 secretName: product-db mysqlVersion: "5.7" backupSchedule: "0 0 */2 * *" backupURL: s3://product-db/
Operatorは、このCRDからリソースが実際に作られた際に処理を行うプログラムです。上記の例では、マニフェスト登録後に自動的にMySQLクラスタを作成し、運用もし続けてくれます。現在のKubernetesを前提としたシステムの多くは、このように動作させるものが多くなってきています。
海外でも「Kubernetes-native」という単語を見かけるようになりましたが、私たちはこの語句を、広義では「Kubernetesを前提として作られているもの」、狭義では上記の例のように「KubernetesのCRDを用いてマニフェストで全て定義や設定することが可能なもの」だと認識しています。
今回は、狭義でのKubernetes-nativeなテストベッドを作るべく、全体を設計しています。
Kubernetes-nativeインフラ、秘伝の書
このテストベッドでは、上述したようなミドルウェアを全て組み込むため、ECサイトを題材に構成しています。ECサイトは万人が理解しやすく、コンポーネントも多いためマイクロサービスにしやすいことも採用の意図です。
それでは、アーキテクチャ図をご覧ください。
ここではCI/CDやコンテナレジストリ含め、全てがKubernetes上だけで完結しています。ストレージ基盤にはRook(Ceph)を使用しており、その上で各種マイクロサービスのデータベースなど、ステートフルアプリケーションが稼働します。
サービス間の通信は、Message Queueを除く全てをgRPCで行っており、ユーザーとそのリクエストを受け付けるL7 LoadBalancerであるContour間の通信も、gRPC(厳密にはgRPC Web)で行っています。
マイクロサービスは11個に分かれており、基本的にデータストアはそれぞれ異なったものを使用しています。各サービスの役割については、このプロジェクトの本質ではないため説明を省きますが、サービス名でいくらか理解していただけると思います。
今回ミドルウェアとして利用したOSSは次の一覧の通りです。圧巻ですね。
このテストベッドを手元の環境やクラウドで動かしたい場合は、テストベッドのリポジトリの指示に従って、準備してください。
クラウドネイティブなCI/CD! その名はGitOps
クラウドネイティブなテストベッドを開発していくにあたり、まず準備しなければならないのはCI/CD環境です。CNCFが公開しているクラウドネイティブへの道標となるCNCF TrailMapでも、CI/CD環境を準備した方がいい旨は序盤に書かれています。
アプリケーションのデプロイ、インフラの構成変更といった作業は、オーケストレーションツールであるKubernetesを使った環境でも非常に面倒です。アプリを修正するたびにコンテナイメージをビルドして、マニフェストのタグを書き換えるというのはとても単調で退屈な作業ではないでしょうか。
その作業を序盤のうちから排除することで、アプリの開発に費やす時間を増やすことができます。今回はKubernetes-nativeをテーマとして掲げているため、CI/CD環境には下記のOSSを選定しました。
OSS名 | 用途 |
---|---|
Tekton(Pipeline、Triggers) | Kubernetes-nativeなCIツール |
ArgoCD | Kubernetes-nativeなCDツール |
Harbor | コンテナイメージを保管しておくコンテナレジストリ (CNCFのIncubatingプロジェクト) |
Clair | コンテナイメージの脆弱性スキャナー |
Kaniko | Kubernetes上のコンテナ上でコンテナイメージをビルドする |
上記のOSSを用いて、今回はGitOpsの環境を構築しています。Kubernetes環境におけるCI/CD環境といえばGitOpsが一番有名ではないでしょうか。
GitOpsでは「アプリケーションソースコードのリポジトリ」と「マニフェストファイルのリポジトリ」を用意しておき、CIとCDが適切に分離された状態でパイプラインを構成していきます。なお、リポジトリではなくディレクトリなどでもかまいません。
今回は、テストベッドの性質上、モノレポ構成でディレクトリを切って、1つのリポジトリの中にソースコードもマニフェストも格納しています。
なお、実務でGitOps環境を構築する場合には、ブランチ戦略を踏まえてステージング用のクラスタとプロダクション用のクラスタを用意したり、ソースコードのリポジトリに対してプルリクエストが作られた際にKubernetesの環境を作るなどして、より複雑なGitOps環境を用意することもあります。
それでは、実際に今回のテストベッド環境でGitOps環境がどのように作られているか、下図の内容を順に解説していきます。
1-2. Modify application source code & Webhook
アプリケーション開発者によってソースコードのリポジトリにコミットが行われると、GitHubからTekton TriggersにWebhookが送られるように設定しておきます。
このとき、Tekton Triggersでは下記のようなEventListenerリソースのマニフェストを記載しておくだけで、Webhook先のURLをKubernetes Serviceとして払い出してくれます。
apiVersion: tekton.dev/v1alpha1 kind: EventListener metadata: name: github-listener namespace: tekton-pipelines spec: serviceAccountName: tekton-sa serviceType: ClusterIP triggers: - template: name: microservice-ci-trigger bindings: - name: admin-ci interceptors: - github: secretRef: secretName: github-webhook-credentials secretKey: github-webhook-secret namespace: tekton-pipelines eventTypes: - push - cel: filter: body.ref == 'refs/heads/develop' && !body.commits[0].message.startsWith('[Update manifest]')
今回はContourでエンドポイントを公開するためtype: ClusterIP
で指定してありますが、type: LoadBalancer
で指定しておけば、払い出されるグローバルIPをGitHubのWebhook先URLとして指定するだけで、トリガーを設定することが可能です。
このEventListenerを契機にして、どういったイベントタイプ(push、pullrequestなど)やペイロードで発火するかなどを設定可能です。
3. Kick pipelines
Tekton TriggersがWebhookを受けた後は、Tekton Pipelinesが発火します。Tektonでは下記のように、Pipeline定義をKubernetesのマニフェストとして定義することが可能です。もちろんGitリポジトリの登録情報やコンテナレジストリの登録情報なども、Kubernetesのリソースとして定義できるようになっています。
apiVersion: tekton.dev/v1alpha1 kind: Pipeline metadata: name: ci namespace: tekton-pipelines spec: ...省略... tasks: - name: build-and-push taskRef: name: kaniko-build-and-push ...省略... conditions: - conditionRef: check-is-target-microservice ...省略... - name: pull-request-manifest taskRef: name: pull-request-manifest runAfter: - build-and-push ...省略...
今回はコンテナイメージをビルドし、その後にマニフェストを更新するプルリクエストを送るパイプラインを組んでいます。
また、今回はモノレポ構成なので、このパイプラインが特定のマイクロサービスに対する更新かどうかを判断するためにTektonのConditionという仕組みも利用しています。
4-5. Build and Push container image
コンテナのビルドは、Kubernetes上に起動するコンテナ上でビルドを行うKanikoを利用しています。Kanikoではイメージをビルドした後で、コンテナレジストリに対してPushも行います。今回は、Kubernetes上で動作しているHarborに対して、ビルドしたイメージを保存します。
6. Scan container image
Harborへの登録後は、コンテナイメージの脆弱性スキャナーであるClairによってチェックされます。
なお、このプロジェクトの開始時点でHarborのデフォルトスキャナーはClairでしたが、今後は個人が開発しはじめてAqua Securityに買収されたことでも有名なTrivyがデフォルトとなっていくようです。本テストベッドでも将来的には置き換えていく予定です。
7. Update manifest (Pull requests)
イメージビルドが成功した後は、そのイメージを利用するようマニフェストリポジトリを書き換え、プルリクエストを作成します。このプルリクエストをマージすればクラスタに反映され、クローズすれば破棄することが可能です。

また、コンテナイメージには、ソースコードに対する変更を行ったコミットハッシュを利用して、admin:3e5a988d908f7e726c9830d5d687673307c9f636
のようなタグを付けています。
そのため、プルリクエストの例にあるように変更差分や、現在クラスタにデプロイされているアプリケーションがどの時点のものなのかを、容易に確認できるようになります。
spec: containers: - name: admin - image: registry-harbor-core.infra.svc.cluster.local/library/admin:3e5a988d908f7e726c9830d5d687673307c9f636 + image: registry-harbor-core.infra.svc.cluster.local/library/admin:4184ce936ac3c357c9b86766e358e402ef8fe8d7 resources: requests: cpu: 100m
8-9. Pull & Apply manifests
マニフェストのリポジトリにマージされた後は、CDフェーズに入ります。
ArgoCDは、Kubernetesクラスタ上でコンテナとして動作しています。マニフェストリポジトリを監視しており、変更があるとクラスタの内部からKubernetesに対してマニフェストを適用するPull型のアーキテクチャを取っています。
ArgoCDなどを用いず、クラスタ外のCIツールがKubernetesクラスタに対してマニフェストを適用するPush型のアーキテクチャは、CIOpsと呼ばれています。このアーキテクチャの場合、CIツールがKubernetesクラスタに対して強い権限を持ってしまったり、複数クラスタに対する認証情報の管理が必要であったりと、セキュリティ上の問題が起きやすいため注意が必要です。
DNSの参照について
Kubernetesクラスタ上でコンテナを起動させる際には、KubernetesノードがコンテナイメージをPullできないといけません。
今回のテストベッドでは、ノード上で起動しているHarbor上に保存されているコンテナイメージ(harhor.infra.svc.cluster.local/library/admin:xxxxxなど)を参照するようにしている関係上、Kubernetesノード自体がCluster内DNSを参照できるようにしています。
そこで、Cluster内DNSのClusterIPを、DaemonSetを用いて/etc/resolv.conf
に書き込むように実装しています。
開発環境の整備: Telepresenceの役割
CI/CDから少し話題が逸れますが、今回は下記の2 つのOSSを用いて開発環境も整備しています。
OSS名 | 用途 |
---|---|
Telepresence | リモートKubernetes上でローカルマシン上のプロセスをトンネリングして透過的に起動させる開発ツール |
Skaffold | コードの変更時に自動的にコンテナイメージのビルドを行う開発ツール |
例えば、adminとuserのマイクロサービスがあったときに、外部からのリクエストはadminが受け、userに対してリクエストを送る構成だったとします。その場合、下記のように数珠つなぎでリクエストが伝搬される構成をとるのが一般的です。

この前提があるなかでuserマイクロサービスを開発する場合、全てのマイクロサービスをローカルで動作させるのはマシンリソースの問題やデータの問題で難しい場合があります。今回のテストベッド環境もシステム全体では大量の計算資源を必要とするため、Macなどの端末上で全てを動作させることはできません。
ここで活躍するのがTelepresenceです。TelepresenceではローカルのプロセスがあたかもリモートのKubernetesクラスタのDeploymentリソースのコンテナとして存在するかのような構成を再現します。つまり、userを開発する際、リモートクラスタでadminからuserへのリクエストが送られた場合には、リモートクラスタのadminからローカルのプロセスへリクエストが転送されてきます。
また、ローカルのプロセスがuser-dbに接続しようとした際には、リモートのuser-dbに対してリクエストが送られます。TelepresenceではKubernetesのPod NetworkやService Networkに疎通性があるだけではなく、クラスタ内DNSでの名前解決も行えるようになっています。
開発環境のローカルKubernetesとSkaffold
ローカルのプロセスを立ち上げるには、docker runを実行する方法が一般的です。しかし今回はローカル側でもKubernetesクラスタを立ち上げることで、docker runの代替を行いました。この方法は既存のマニフェストを利用できるため、環境変数やSecretの設定などもそのまま流用できるといったメリットがあります。
Telepresence+ローカルKubernetesで環境を構築する場合には、下記の通りいくつか考慮しなければならない点があります。
- マニフェスト適用時に、Skaffoldでkustomizeでパッチを当てる
- Skaffoldがビルドしたローカルのイメージを利用するように変更する
- ホスト側のDNS設定を利用するようにspec.dnsPolicyをDefaultに設定する
- Skaffold port-forwardを使ってホスト宛のリクエストをクラスタ内に転送されるように設定する
今回は、ローカルマシンでの開発中に生じたファイル変更をコンテナイメージのビルドやKubernetesクラスタへ反映させるためにSkaffoldを利用しているため、上記もSkaffoldの機能を使って解決することが可能です。
リポジトリに保存されているマニフェストには本番環境用のイメージが指定されているため、そのままではローカル環境での開発に適していません。そのため、Skaffoldがビルドするイメージを指定して適用するようにしています。
また、このままではローカルクラスタ上のコンテナはローカルクラスタのクラスタ内DNSを参照してしまうため、リモートクラスタのServiceに対する名前解決に失敗してしまいます。Telepresenceによってローカルのホスト側でリモートクラスタのクラスタ内DNSが利用できるようになっているため、ローカルクラスタ内のコンテナをホスト上のDNSに向けることでこの状態を解消します(spec.dnsPolicy: Default)。KubernetesのdnsPolicyのデフォルトは「ClusterFirst」であり「Default」ではないため、ローカルクラスタ内のコンテナがリモートクラスタのDNSで解決できるように明示的に設定を行っているのです。
最後に、Telepresenceはホストまでトラフィックを転送してくれますが、ローカルクラスタ内のPodまでトラフィックを転送してくれません。そのため、Skaffoldのport-forward機能を利用して疎通性を確保しています。なお、port-forwardのポート設定はServiceリソースの情報をもとに自動的に検知します。
それ以外は、開発環境でも既存のマニフェストをそのまま使えるでしょう。
クラウドネイティブ時代のステートフルなミドルウェア
続きをお読みいただけます(無料)。

- すべての過去記事を読める
- 過去のウェビナー動画を
視聴できる - 企業やエージェントから
スカウトが届く