Kubernetesネイティブアプリケーションを安心してリリースする
Amir Moualem
2019年11月14日
0 分で読めます数か月前、私たちのチームは新しいプロダクトの開発を始めました。
この新しいプロダクトには、これまでに開発してきたほかのソフトウェアプロジェクトとは異なる、いくつかの特徴があります。
Kubernetesネイティブであるため、Kubernetes APIと密接に連携しており、実行するには(テストする場合でも……その点については後ほど説明します)その特定のAPIが必要です。
正しく動作させるには、特定の設定を適用する必要があります。そのため、ユーザーがスムーズにインストールできるよう、Kubernetesの.yaml設定ファイルとともにこのイメージを配布しなければなりません。さらに、プロダクトのオンボーディングをできる限り簡単にしたいという考えから、Helmチャートと「バニラ」.yamlファイルの両方でのインストールをサポートすることになり、どちらもテストして公開する必要があります。
これはクライアントサイドのアプリケーションです。公開した瞬間に制御権を手放すことになり、取り消すことはできません。マイクロサービスのホットフィックスなら数分で公開できますが、ユーザーに何度もインストールのアップグレードをお願いするのは、オンボーディングを複雑にするだけです。
そこで、開発のペースを(大きく)落とすことなく、このようなプロダクトを安心してリリースできるCI/CDパイプラインの計画に、もう少し時間をかけることにしました。
Snykの既存のマイクロサービス向けCI/CDパイプラインは、開発者によるGitHubの操作を中心に構成されています。
プルリクエストを作成すると、テスト環境にサービスがインストールされ、実行およびテストされます。
プルリクエストをマージすると、同じプロセスから始まり、その後、サービスのセマンティックリリース、Dockerイメージのビルドと実行、本番環境のKubernetesクラスターへのデプロイが行われます。
プロセスができるだけ自然に感じられるよう、同様の操作を取り入れたいと考えました。
では、このプロジェクトにはどのように取り組んだのでしょうか。
Kindを使ったKubernetes統合テスト
Kubernetes APIに大きく依存するソフトウェアをどうテストするか。経験の浅い私たちがまず直面した疑問です。Kubernetes APIに関連するすべてのフローをモックするのは、実装も保守も非常に難しく、テストすべき重要なフローを見落とす可能性もあります。単純な方法としては、テストのたびに実際のKubernetesクラスターを用意するか、常時稼働する環境を使うことが考えられます。どちらも確実に機能するでしょうが、ソフトウェア開発の初期段階で対処するには、あまりにも多くの複雑さが伴います。
毎回新しいKubernetes環境を立ち上げるには開発に時間がかかり、テストのセットアップ時間も大幅に増えてしまいます。
常時稼働する環境を用意すると、クリーンアップが必要になり、テストが不安定になる可能性も高まります。
ありがたいことに、Kindプロジェクトを紹介してもらいました。Kindは、Kubernetes APIに準拠したローカルKubernetesクラスターを実行するためのDockerベースのツールです。テストごとにクリーンな環境を用意できるうえ、非常に素早くセットアップできるため、私たちのニーズにぴったりでした。
これにより、CI環境でもローカルでも、テストの段階でKindクラスターを作成し、新しく作成したイメージを読み込んで、コンテナが所定の処理を行うことを検証できるようになりました。
提供するものをテストし、テストしたものを提供する
次の課題は、コードだけでなく、実際に提供するプロダクト全体をテストすることでした。
コード(およびサービス)はDockerイメージにインストールされます。このDockerイメージは、Helmチャートまたは「バニラ」.yamlファイルを使ってKubernetesクラスターにデプロイできます。また、Kubernetesクラスターによって、サポートするKubernetes APIのバージョンも異なります。
こうした幅広いシナリオをテストし、サポートすることを目指しています。
イメージをビルドした時点でタグを付け、テストの各段階を通じて識別できるようにします。そのイメージを、Helmチャートと「バニラ」.yamlファイルの両方を使って、異なるAPIバージョンのKubernetesクラスターにインストールし、テストしてから「approved」としてタグ付けします。
この段階で、semantic-releaseを使ってイメージにバージョンを付けます。Git SHAや手作業で割り当てる任意のバージョンでプロダクトを管理するのではなく、セマンティックバージョニングを採用しました。これにより、シンプルで分かりやすいバージョンを提供でき、リリースノートも自動的に作成されてGitHubに公開されます。最終的に、1.2.3のようなバージョンタグが付いたイメージが完成します。
この段階で必要なのは、デプロイ用の.yamlファイルを新しいバージョンに向けることだけです……
HelmチャートとGitHub Pagesで公開を簡単に
HelmはKubernetesのパッケージマネージャーです。少し調べてみると、Helmチャートの公開方法は主に2つあることが分かりました。
公開されている厳選済みHelmチャートリポジトリでホスティングする。
自分たちのリポジトリでGitHub Pagesを使って「セルフホスティング」する。
少なくとも当初は、自己完結していて他の組織に依存しない2つ目の方法が、私たちには適していました。プロダクトとCI/CDパイプラインが成熟すれば、いずれ公開リポジトリにもチャートを提供するかもしれません。
GitHubを使って全体をまとめる
これで主な課題のほとんどに対処できたようです。Kindを使った統合テストでさまざまなシナリオの実際のKubernetes環境を再現し、セマンティックバージョニングでプロダクトにタグを付け、シンプルに公開できるようGitHub Pagesに配布しています。
では、これらすべてを開発プロセスにどう組み込むのでしょうか。
Gitリポジトリには、主要なブランチを2つ設けることにしました。
stagingはデフォルトのブランチで、機能追加、バグ修正、雑務など、プロダクトへのすべての変更を分岐・マージする起点です。masterは、テスト済みのプロダクトを公開するかどうかを決めるブランチです。
CI/CDパイプラインは、この2つのブランチと、開発者がGitHubのブランチに対して行う2つの主な操作(プルリクエストの作成とマージ)の間に、合計4つの段階があります。各段階を通じて、構築したプロダクトへの信頼を高め、ユーザーへのリリースに近づけていきます。
オーケストレーションにはTravis CIを使っています(CircleCIへの移行を進めています)が、目的を果たすだけなら、短い社内用Pythonスクリプトでも実現できたでしょう。
次の表は、4つの段階の概要と、それぞれで実施する内容をまとめたものです。
トリガー | 処理 | 目的 |
|---|---|---|
機能ブランチから | Lint、コンパイル、単体テストを実行する。 | クリーンな環境でテストする。 |
機能ブランチから | Dockerイメージをビルドする。 | マージするブランチ間に競合がないことを確認する。クリーンな環境でテストとビルドを行う。公開予定のプロダクトを一度だけビルドする。 |
| なし。 プロダクトを公開する前に、ドッグフーディングなど、手動で行いたい作業を実施するための一時停止ポイントです。 | 公開前にフィードバックを得る最後の機会です。 必要に応じて、特定のテスト環境を手動で確認するためのリマインダーです。 |
| イメージに公開用の | すでにビルドとテストが完了したプロダクトを公開します。何を公開するか、把握できています。 |
ここでよくある退屈なまとめはしません
冗談です。完全に退屈な内容ですが、手短にまとめます。
優れたCI/CDパイプラインを構築し、その過程で多くのことを学びました。自動化と、私たちにとって自然に感じられる適度な手動操作とのバランスを取っています。各段階を通じて、提供するものへの信頼が高まり、最後の段階で実際に公開されます。
ほかのソフトウェアプロジェクトと同様、まだ改善の余地はあります。公開Helmチャートリポジトリへの公開を始めることもできます。アップグレード可能性をテストするため、テスト基盤に長期稼働環境を追加することもできます。テストに十分な信頼が持てるようになれば、リリース前の手動作業を一部省略できるかもしれません。どうなるかは分かりません。
開発者ファーストのコンテナセキュリティ
Snykは、コンテナイメージとKubernetesワークロードの脆弱性を検出し、自動的に修正します。
