SpinnakerのHalyardからKleatへの移行

こんにちは、Microservices Platform Groupの@_k_e_k_e@micnncimです。

数ヶ月前に、私たちのチームはSpinnakerのデプロイ・管理のためのツールを Halyard から Kleat に移行しました。Kleat への移行は多くの利点をもたらしましたが、移行は簡単ではなくいくつかの懸念点がありました。

本記事では、その移行の全体像とKleatについて紹介します。

メルカリのSpinnaker

本題に入る前に、メルカリでのSpinnakerの使い方を紹介します。

下図のように、メルカリでは1つのSpinnakerを使用してほぼ全てのマイクロサービスをGoogle Kubernetes Engine(GKE)クラスタにデプロイしています。Spinnakerは専用のGKEクラスタ上でホストされています。

図1. メルカリにおけるマイクロサービスのデプロイワークフローの概略図

Spinnakerがメルカリに導入されたのは4年ほど前のことです。事業の成長に伴い、マイクロサービスアーキテクチャも成熟していました。この記事を書くにあたり、Spinnakerの使用状況を定量的に紹介するために改めてデータを集めてみました。

データが示すように、Spinnakerはメルカリで最も使われているプロダクトの一つになりました。

Spinnakerの管理・運用方法

Spinnakerを導入当初からHalyardによって管理しています。HalyardとはSpinnakerの設定、インストール、アップデートのためのツールです。

Google Compute Engine (GCE) インスタンス上で Halyard をホストし、ディストリビューションモードと呼ばれる、Spinnakerのそれぞれの機能をマイクロサービスとしてデプロイする方法で GKE クラスタにデプロイしています。

図2. メルカリにおけるHalyardの概要

Spinnakerの操作を行うにはVPN経由でSSH接続してログインする必要がありました。また、Spinnakerの設定を定期的にバックアップしていました。

Halyardの問題

4年近くこの方法で運用してきましたが、以下のような使い勝手の悪さがありました。

  • Infrastructure as Code (IaC)を実現できない: Halyardは、Halyardインスタンス内にあるHalconfigという設定ファイルを設定することでSpinnakerを管理・デプロイします。この設定ファイルには、Spinnakerのバージョン、特定の機能のオンオフ、認証認可の設定などが含まれています。これをGitで管理しようとすると大変で、Spinnakerの設定情報を確認するためにインスタンスにログインして、変更の際にはHalyardからSpinnakerをデプロイしなければなりませんでした。 例えば 認証機能を有効にすると、100以上のマイクロサービスの権限の設定を追加する必要があり、Halconfigを手動で更新しなければならず、IaCの恩恵を受けることができませんでした。 そのため、インフラの変更を見直すのが大変で、Spinnakerはスノーフレークサーバーになっていました。
  • Spinnaker自体のCI/CDが難しい: Spinnakerの変更を行うには、HalyardインスタンスへのSSH接続が大きな障害でした。AnsiblePuppetChefなどを試してみましたが、次はこのPlaybook、マニフェストやレシピなどを管理しなければならず、数珠つなぎで管理しなければならないので断念しました。そのため、手作業によるタスクが多く発生していました。
  • 各コンポーネントのKubernetes Deployments構成を柔軟に作れない : Spinnakerをディストリビューションモードでデプロイすると、Fiat、Clouddriver、Front50のようなSpinnakerのマイクロサービスはそれぞれKubernetes Deploymentsとしてデプロイされます。Halyardはkubectlをラップし、限られたAPIしか公開していないため、一部のコンポーネントの構成を変更することができませんでした。
  • Halyardの管理が難しい: そもそも設定オプションが多いため、設定ファイルの管理が難しいです。Halconfigは、Spinnakerに関連するすべての設定の単一のエントリーポイントになるように設計されています。Halyardに設定を複数のファイルに分けてマージできる仕組みはありますが、基本的には全てを一つのHalconfigファイルに書き込まなければなりません。しかし、すべての設定をHalconfigに載せることができるため、必要のないフィールドが多く、Halconfigは巨大で管理が大変です。

もちろん、Halyardはメリットも多かったのですが、ここで紹介したようにデメリットもありました。しかし、当時はSpinnakerを簡単に導入して管理する方法は他にありませんでした※。

しかし、そこにKleatが登場してきました。

Kleatとは?

KleatはSpinnakerの設定を管理するための軽量なツールで、Halyardの代替です。Kleat単体ではSpinnakerをデプロイしないので、Kubernetesの設定管理ツールであるKustomizeと併用することが多いことに注意してください。

この記事ではKleat自体を紹介していないので、興味のある方はRFC "Replacement of Halyard"を読んでみてください。簡単に説明すると、これまでHalyardで使っていたHalconfigをベースに、各コンポーネントの設定ファイルを出力して管理できるツールです。

図3に示すように、Kleatを使って各コンポーネントの設定ファイルを作成し、Kustomizeを使ってKubernetesマニフェストとしてバンドルします。そして最後にkubectlでデプロイします。

図3. KleatとKustomizeを使ったSpinnakerの設定・デプロイのフロー

マイグレーション計画

以下のようにしてマイグレーションは行いました。

1.Spinnakerのマイクロサービスのデプロイメント構成をKubernetesマニフェストに移植する

まず、既存のSpinnakerマイクロサービスのレプリカ数やメモリリソースなどの構成をKubernetesマニフェストに移植しました。Halyardでのデプロイ構成例は以下の通りです。例として、次のようなものがあります。

deploymentEnvironment:
  customSizing:
    echo:
      resources:
        requests:
          cpu: 2
          memory: 1Gi
        limits:
          cpu: 4
          memory: 1Gi

ここでは各マイクロサービスのリソースを設定しているのですが、以下のようなKubernetesマニフェストへ移植しました。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo
spec:
  template:
    spec:
      containers:
      - name: echo
        resources:
          requests:
            cpu: 2
            memory: 1Gi
          limits:
            cpu: 4
            memory: 1Gi

Spinnakerマイクロサービスの公式Kubernetesマニフェストであるspinnaker/kustomization-baseをフォークして、それと上記のようなKubernetesマニフェストをバンドルしました。

2. Kubernetes Secretsを作成し、Halconfigでバインドする

spinnaker/kustomization-baseは、すべての設定ファイルをKubernetes Secretとして管理することを目的としています。しかし、Spinnakerのマイクロサービスが依存しているJavaフレームワークであるSpring Bootは、アプリケーションプロパティで環境変数を解決することができます。この機能を利用することで、クレデンシャルのみをKubernetes Secretsとして定義し、それ以外はConfigMapとして定義しました。設定ファイルの必要な部分だけを環境変数に置き換えることで、ほとんどの設定ファイルをプレーンテキストとして管理できるようになりました。これによりGitHubなどGitホスティングサービスによるコードレビューが簡単に出来るようになりました。Halyardを使っていたときには出来なかったことです。

以下はGitHubトークンを使ったHalconfigのサンプルです。

artifacts:
  github:
    accounts:
    - name: <mercari-spinnaker-bot>
      token: ${GITHUB_TOKEN}
      username: <mercari-spinnaker-bot>
    enabled: true

GitHubトークン用のKubernetes Secretを作成してPodにマウントします。

apiVersion: v1
kind: Secret
metadata:
  name: artifacts
type: Opaque
data:
  GITHUB_TOKEN: xxx...
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: clouddriver
spec:
  template:
    spec:
      containers:
      - name: clouddriver
        envFrom:
        - secretRef:
            name: artifacts

クレデンシャルと設定を明確に分離し、設定をより簡単に管理をすることができるようになりました。

3. Spinnakerの1.20 リリースをサポートするためのパッチの追加

この部分が一番苦労しました。Kleat は Spinnakerの1.20リリース(以下、Spinnaker 1.20)を完全にはサポートしていないので、以下のように設定してパッチを当てる必要があります。

  1. SpinnakerのCustom Serviceの設定
    • Deck (SpinnakerのUI)のKubernetesのプロバイダ設定を追加
    • すべてのマイクロービスにRedisの設定を追加する
  2. Kleatの生成物へのパッチ
    • Kleat はSpinnaker 1.20を完全にはサポートしていないので、後ほど説明するパッチを当てる必要があります。

Spinnaker 1.20より前のリリースを使用していて、Kleat に移行したい場合は以下の手順に従ってください。

3.1 SpinnakerのCustom Serviceの設定

Kubernetes V1 プロバイダはSpinnakerの1.21リリースから完全にサポート対象外になっていましたが、メルカリにはまだいくつか使用しているアカウントが残っていました。それがブロッカーになってしまい、Spinnakerの新しいバージョンにアップグレードできていませんでした。

多くの選択肢がありましたが、V1プロバイダを完全に取り除くまで待つのではなく、Kleat が完全にサポートしていない Spinnaker 1.20 で使用できるように Kleat にパッチを当てることでKleatへマイグレーションを可能にしました。例えば、Deck のsettings-local.js に欠けている Kubernetesプロバイダの設定を設定します。

window.spinnakerSettings.providers.kubernetes = { defaults.{}}

またfiat-local.yaml などの各マイクロサービスの設定ファイルに以下のようなRedisの設定を追加しました。

redis
    connection: ${services.redis.baseUrl}

3.2 Kleatの生成物にパッチを当てる

Kleatの生成物にもパッチを当てる必要がありました。その一例として、先ほども紹介した Kubernetes V1プロバイダの設定がありますが、これは Kleat がサポートしていないため、削除されてKleatの生成物は出力されるからです。

そこで、以下のBashスクリプトによるパッチをcloudriver.yamlに適用しました。

inject_kubernetes_accounts_provider_version_v1() {
  local -r clouddriver_yml="clouddriver.yml"
  local -r kubernetes_accounts=($(yq r "${clouddriver_yml}" 'kubernetes.accounts[*].name'))

  for i in "${!kubernetes_accounts[@]}"; do
    local account_name
    account_name=$(yq r "${clouddriver_yml}" "kubernetes.accounts[${i}].name")

    for account_v1 in "${kubernetes_v1_accounts[@]}"; do
      if [[ "${account_name}" == "${account_v1}" ]]; then
        yq w -i "${clouddriver_yml}" "kubernetes.accounts[${i}].providerVersion" 'V1'
      fi
    done
  done
}

このスクリプトはKleatの生成物にKubernetes V1プロバイダを追加します。もちろん今では、メルカリは最新リリースであるSpinnaker 1.24を使用しているので、これらのパッチは不要になり、すでに削除されています。

4. Spinnakerの再デプロイ

メンテナンス期間をスケジュールして、一旦、Halyard CLIコマンドを使ってSpinnakerを完全に削除しました。そして、kubectlを使用して再デプロイし、フルリプレースを行いました。

Kleatは何をもたらしたのか?

マイグレーションのメリットは以下の通りです。

1. 各Spinnakerコンポーネントの調整が可能になった

図4. FiatのCPUリソースリミットを上げるためのGitHubのプルリクエスト(PR)

Spinnakerのすべてのコンポーネントの構成とデプロイを包括的にHalyardで管理するのではなく、それぞれのKubernetesマニフェストを独立して定義して適用できるようになりました。これにより、Kubernetesの構成をより柔軟に管理し、Spinnakerの各マイクロサービスの負荷に合わせてリソースを調整することができます。

上の図5のように、例えばFiatのCPUリソース制限を上げたいときはFiatのKubernetesマニフェストを更新するだけでよくなりました。

2. 宣言的な管理

デプロイ時にKubernetesのマニフェストを動的に生成するのではなく、HaconfigやKubernetesマニフェストとして事前に定義することで、Infrastructure as Code(IaC)のメリットを得ることができます。同時に、GitHubのによる設定変更のレビューが容易になり、運用が格段に楽になります。

先ほどの図のように、PRを通じてSpinnakerを管理・更新することができます。オペレータは、Halconfigを更新するためにHalyardインスタンスにSSH接続して手作業をする必要がなくなりました。

3. SpinnakerのCI/CD

図5. SpinnakerのCI/CDのフロー

HalyardのGCEインスタンスにSSH接続をして運用する必要がなくなり、他のマイクロサービスと同じように容易にSpinnakerをデプロイできるようになりました。GitHub Actions経由でSpinnakerをデプロイしているので、アップデートなども非常に簡単です。

これはGitHubで設定ファイルを管理できるKleatのメリットの一つです。

4. Kubernetesエコシステムの恩恵の享受

他のKubernetesマニフェストと同様に、viglesiasce/kube-lintなどを使ってKubernetesマニフェストをリントすることができるようになりました。

Kubernetesエコシステムには、様々なCloud Nativeアプリケーションを開発・運用するための強力なツールが用意されているので、それらを活用することによって、大きなコストをかけることなくSpinnakerの設定をより安全に変更することができるようになりました。

Kleatの懸念点

しかし、良いことばかりではなく、懸念点もあります。

1. コミュニティのspinnaker/kustomize-baseとの設計の違い

spinnaker/kustomize-baseのフォークを使用しているのは、Kubernetes SecretやConfigMapの定義方法などコミュニティのKustomize-baseとの大きな違いがあるからです。そのため、アップストリームの変更に追従しなければならず、Spinnakerのアップグレード時の負荷が大きくなってしまいます。spinnaker/kustomize-baseではHalconfigで直接クレデンシャルを設定しています。しかし、SpinnakerのクレデンシャルごとにKubernetes Secretとして専用の別のシークレットを作成したいのでフォークしました。

spinnaker/kustomize-baseを見てみて、設計・ユースケースに合わなければフォークするか、一から作り直す必要があります。

2. Halconfigを変更した場合の影響範囲の想定が難しい

Kleatを使ってもHalyardを使っても、Spinnakerの設定を行うにはHalconfigファイルが唯一のエントリーポイントであることに変わりはありません。しかし、Kleatを使うとKubernetesのマニフェストが分散して定義されるので、管理するものが増えます。例えば、認証機能を有効にする場合、認証に関わるKubernetes SecretをFiatやGateなどの環境変数に設定する必要があります。特定の機能を変更することによってSpinnakerの各コンポーネントにどのような影響があるのか知る必要があります。

図6. Halconfigを変更したときに考えること

Halconfigで特定の機能を有効にした後に、Spinnakerの各マイクロサービスにクレデンシャルをマウントするのを忘れてしまうことがよくありました。 コミュニティのkustomize-baseをベースにしていれば問題ないのですが、Halconfigでアップデートを行う際に注意が必要です。

最後に

Kleatが完全にサポートしているSpinnaker 1.21がリリースされ、Kubernetes V1プロバイダのサポート外になってから長い時間が経ちました。Kleat がSpinnaker のデプロイ・管理を改善するツールとなる理由がわかった今、移行を検討するには良いタイミングではないかなと思います。今すぐに移行に取り掛からなくても、 RFCで説明されているようにHalyardは将来的には非推奨になるので、移行の準備をしておくべきです。

継続的なデリバリーは、ソフトウェア開発において最も重要な要素の一つであり、競争力を測る上で最も重要な指標の一つです。今回の移行は一筋縄ではいきませんでしたが、Kleatを導入することでSpinnaker自体のデプロイ・管理コストが削減され、用途に応じてより柔軟にSpinnakerの各マイクロサービスを運用できるようになり、より安定した信頼性の高いプラットフォームを提供できるようになりました。まだまだ改善できる余地はありますが、Kleatによってプラットフォームの品質は大きく向上しました。

※ 近年はHelmやKubernetes Operatorによる設定・デプロイ方法がありますが、当時(4年ほど前)には存在しておらず、今でもそれらでSpinnakerを管理することは容易ではないと感じています