こんにちは、もーすけです。
前回のブログでは「Kubernetesで実践するクラウドネイティブDevOps」の書籍を紹介しました。
こちらの反響もよかったこともあり、KubernetesにおけるCI/CD(継続的インテグレーションと継続的デリバリー)に焦点を絞って、いままでの経験も含めて大事なポイントをいくつかの切り口でまとめることにしました。 一部は書籍の内容とかぶる部分もあるのですが、わたしが普段Kubernetesでのアプリケーション運用に携わる中で大事だと思うことなど含めてご紹介します。 KubernetesにおけるCI/CDとしていますが、項目は必ずしもKubernetesに限った話ではありませんのでご了承ください。
また、もっといろんなトピックを書きたいのですが、すべてのトピックを揃えると記事自体のボリュームも大きくなり公開も遅くなりそうだったので、基本的な内容のみ選んでみました。随時追加されていく(あるいは別記事?)可能性があります。
ブランチ戦略
CI/CDの実践にまず根本的に関わるポイントとして、ブランチ戦略があります。
CI/CDパイプラインのトリガーはさまざまですが、Gitレポジトリのアクション(主にPushやMerge)をトリガーとすることが多いです。つまり、Gitレポジトリの運用の仕方とCI/CDは切っても切り離せない関係です。
特に環境が複数面ある場合(例えば、テスト環境とプロダクション環境がある場合)は、どのタイミングでどの環境にデプロイさせるべきかはよく考えないといけないです。
CI/CDを実践したいと思っている人は、まず自分たちのチームのブランチの運用方法を見直すところから行ってみるといいでしょう。
以下は、モノレポジトリ(アプリケーションとマニフェストを同一レポジトリで管理)でデプロイ先のステージが複数ある場合の一例です。
宣言的な設定
Kubernetesのマニフェストによる「宣言的な設定」は最も強力なポイントの1つです。
宣言的な設定が可能とはどういうことかというと、アプリケーションを実行するワークロードの「あるべき姿を記述」できるということです。
アプリケーション単体だけではなく、他のコンポーネント(例えばDB)や必要とするストレージ、ネットワーク、ジョブなどといったアプリケーションを動かす上で必要になることをまとめてコードとして管理できます。
もっと平たく言うと、マニフェストはYAML形式で記述することができ、Gitを通じてバージョン管理が可能ということです。アプリケーションの動作する環境を、バージョン管理できるということです。
いままでもクラウドインフラの上で、TerraformやCloudFormationなどといったツールでの宣言的な記述は行われてきました。 Kubernetesには、Pod Lifecycle Event Generator(PLEG)を使用したKubernetes コントロールプレーンが機能することで、クラスターの現在の状態を望ましい状態に一致させるように動きます。これにより、この宣言的な設定がより強力なものになったと感じています。
Kubernetesを利用する以上、マニフェストの管理は非常に重要なポイントになるのです。 いまの運用・これからやろうとしている運用が、上のメリットを崩してしまわないように常に注意していくといいでしょう。
マニフェストのテンプレート化
上でアプリケーションを動かす上で必要になることをマニフェストという形で記述できる、記述して管理しようと書いたわけですが、そんなに現実世界をきれいにマニフェストで表せるものでしょうか?
たとえば、テスト環境と本番環境のKubernetesクラスターがあったとして、まったく同じマニフェストが適応できるでしょうか?
CI/CDの世界では、基本的に環境ごとのデプロイの方法は同一のほうが望ましいです。
「テスト環境はテスト環境用にマニフェストを用意して、本番環境は本番環境用のマニフェストを用意して、本番環境にデプロイするときには追加でこの作業をして、、、」
こういった状況はわれわれの目指したい姿ではないはずです。
理由は簡単です。環境毎に用意したマニフェストの記述の多くは重複があり、変更の追従が大変だからです。
変更の追従が困難であると、環境ごとの環境差分が生まれやすく、そのうちデプロイミスにもつながるでしょう。
ではこういった問題にどう立ち向かえばいいのでしょうか?
マニフェストのテンプレート化がひとつの解決策になります。
大本のマニフェストは同一で使いまわしつつ、環境によってパラメータや一部の変更ができるという状態を作る必要があります。
マニフェストをテンプレート化するツールはたくさんありますが代表的なものに下記があります。
- Helm
- Kustomize
- Ansible
個人的にはKustomizeがお気に入りです。Kustomizeの使用感や必要性については下記をぜひご覧ください。
テスト
アプリケーションのテストはKubernetes環境でもそれほど変わりはないです。 どのCIツールを使うかにもよりますが、たとえばJenkinsでは、Jenkins自身とCI/CDパイプラインの実行環境をKubernetes上で実行できます。そうなると、テスト環境は、コンテナ上になり一時的な環境でのテストを行うことになります。特定の環境依存せずポータブルに動作可能なように、アプリケーションはTwelve factor appなどに基づいておくことが重要です。
アプリケーションのテストに、外部コンポーネント(RDBなど)が必要になる場合は、テスト用に常時稼働のDBを用意するのではなく、パイプライン毎に個別のDBを起動できるようにしておくといいでしょう。
Jenkinsを使う場合、テストを実行するJenkins SlaveのPodのサイドカーとしてMySQLなどのDBを起動することなどができます。
テストを実行するPod内にMySQLなどのDBを起動することで、アプリケーションからはローカルホストへの接続でDBを利用できるようになります。(下記はJenkinsfileの例)
podTemplate(
cloud: 'kubernetes',
label: 'jenkins-slave-ruby',
serviceAccount: 'jenkins',
containers: [
// メインのコンテナでアプリケーションのテストなどを行う
containerTemplate(
name: 'jnlp',
image: 'xxxx/jenkins-slave-ruby',
args: '${computer.jnlpmac} ${computer.name}',
workingDir: '/tmp',
envVars: [
envVar(key: 'RAILS_ENV', value: 'test')
]
),
// サイドカーとしてアプリケーションのテストに利用するDBを起動する
containerTemplate(
name: 'mysql',
image: 'xxxx/mysql:5.7',
envVars: [
envVar(key: 'MYSQL_USER', value: 'xxxx'),
envVar(key: 'MYSQL_PASSWORD', value: 'xxxx'),
envVar(key: 'MYSQL_DATABASE', value: 'xxxx')
]
)
])
モノレポジトリ、マルチレポジトリ
Kubernetesのマニフェストをどこのレポジトリで管理するかはひとつの選択です。
アプリケーションレポジトリで管理するもよし、マニフェスト用のレポジトリで管理するのもよしです。
ただし、それぞれの方法で向き不向きがあるので、それを理解した上で選択するといいでしょう。
- モノレポジトリ
- アプリケーションとマニフェストをひとつのレポジトリで管理する
- アプリケーションの変更に対してトリガーできる
- アプリケーション開発者がマニフェストやアプリケーション運用も面倒みれる体制では向いている
- 単一のアプリケーションが独立してデプロイできる場合などは向いている
- マルチレポジトリ
- アプリケーションとマニフェストを異なるレポジトリで管理する
- アプリケーションの変更とは異なるトリガーで実行できる
- 運用担当者との役割分担が別れている場合は向いている
- 複数のアプリケーションのデプロイを管理したい場合などにも向いている
GitOpsという考え方
マニフェストをどう管理するかの観点でシングルレポジトリかマルチレポジトリかがでてきました。
この議論は、デプロイとも関連してくるのでここで「GitOps」という考え方と密接に関係があることを理解しておくといいです。
GitOpsとは、Kubernetesクラスタの管理やアプリケーションデプロイの手法のひとつで、Gitレポジトリを唯一信頼できる情報源(Single source of truth)と考え、クラスタ上の状態とGitレポジトリ上の差分を埋めるやりかたと認識すると良さそうです。
Kubernetesのコントローラの発想と非常に近しいかなと思います。 Kubernetesでは、API Serverの管理する期待する状態(マニフェスト)と、クラスタ上の実際の状態との差分をReconciliation loopが解消する、というのが大きなコンセプトです。そう考えると、Gitレポジトリを期待する状態とし、クラスタ上の実態との差分をGitOpsツール(Reconciliation loop)が解消すると置き換えて読むこともできますよね。
また、Kubernetesにおいてこの考え方が重要な理由は、ソースコードとそれが動く状態としたコンテナイメージ、そしてオーケストレーションとしてKubernetesマニフェストこれらの関係性にあると考えます。 コンテナイメージは、ソースコードとDockerfileから作ることができます。Kubernetesのマニフェストは、コンテナイメージとその他の設定記述で表すことができます。つまり、アプリケーション成果物としてのコンテナイメージと、それをどうコンフィギュレーションして動かすか(オーケストレーションするか)は切り離して考えることができるということです。
GitOpsを実現するツールの代表格としてArgo CDやFluxがあります。
また、個人的な意見ですが、Argo CDといったGitOpsツールを採用するかどうかは別にしても、GitOpsの発想は非常にこのKubernetesの環境と相性がいいと思っています。デプロイ手法は、マニフェストレポジトリの変更をトリガーにしたCIパイプラインからだったとしても、Gitレポジトリを唯一信頼できる情報源と捉えてデプロイする手法はぜひとりいれることを検討してほしいです。
イメージ管理
Kubernetesではアプリケーションはコンテナイメージに同梱して管理することがメジャーです。
コンテナイメージにはタグをつけることが可能で、タグを指定してデプロイするイメージを決定できます。
代表的なタグの付け方に以下2つの方法があります。
- gitのコミットのハッシュ値を使用する
- コンテナイメージのタグにgitのコミットのハッシュをつける
- Git上のバージョンとコンテナイメージのバージョンの紐づけが簡単
- セマンティクバージョンを使用する
- gitのTagやアプリケーションコード内に記述のバージョンなどを活用して、セマンティクバージョンを使用する
- セマンティクスバージョンに加えて、ビルドナンバーなどを付与して一意性を確保することもある
また、コンテナイメージはどこで作成してレポジトリに保存していますか。
だれかメンバーのローカルPCでdocker build
した結果をイメージレポジトリへ保存しているのであれば避けましょう。
イメージのビルド環境を統一化し、自動化のパイプラインに含められるように検討が必要です。
コンテナイメージのビルドの属人化を避けるとともに、ビルド環境を共通化することでビルドした環境の差分をなくすことが重要です。
イメージスキャニング
コンテナ、Kubernetesの利用にといてセキュリティの考慮レイヤーは多岐にわたります。 その中で、CI/CDのスコープで関連する部分にイメージスキャニングがあります。 パイプラインの中で生成したコンテナイメージに脆弱性がないかどうかの検査できます。 コンテナイメージの脆弱性といっても、いくつかの観点がありますが、代表的なものとしてイメージ内で利用されているパッケージに脆弱性がないか確認するという点があります。 脆弱性のあるイメージを不用意にデプロイしないよう、デプロイする前に気づくことができるようになります。
イメージスキャニングをする方法の代表的なツールとしてClairやTrivyがあります。 Clairはクライアント・サーバ方式で構成されるため、利用するためにDBの用意などが必要になります。一方、Trivyはバイナリさえあれば動くので気軽に実行できます。パイプラインの実装ではTrivyのタイプのほうが実装しやすいです。
注意事項
イメージスキャニングをパイプラインに実装する上ではいくつか注意するといい点があります。
ひとつはキャッシュです。脆弱性のスキャンには、当然ながら脆弱性情報を利用します。コンテナ環境においてもキャッシュできるようにしておくと、実行時間を短くできるので工夫が必要です。
次に、脆弱性の検知ができる範囲のスコープです。
上で紹介したものはイメージの中で利用されているパッケージに対する脆弱性の検査です。
コンテナイメージに対する脆弱性はパッケージだけで判断できるものではありません。あくまでパッケージに対する脆弱性の検査であることを理解して利用してください。
最後に、検査結果に対する対応です。おそらく検査をすると影響の大小にかかわらず多くの脆弱性が指摘されると思います。
利用するチーム・組織の中でどのレベルの脆弱性の場合に対応するべきかなどの指針を決めておくことが重要になります。
セキュリティに関するDeepDive
コンテナ、Kubernetesに関するセキュリティはイメージスキャニングだけではなく多くの観点があります。 網羅的に勉強したい人は下記の書籍で勉強することを強くオススメします。Kubernetesセキュリティについては圧倒的に詳しく解説しています。
シークレット管理
運用環境へのデプロイを考えると必ず問題になるのが、シークレット管理です。
本番環境のDBへのパスワードをどう管理するか?みたいなのはいつどのプラットフォームになってもつきまとう問題です。
Excelにパスワードかけて保存しておいて、そのExcelのパスワードを忘れるという経験をしたければまあそれはそれでいいでしょう。
それぞれメリットデメリットはあるのですが、代表的な方法は下記のようなものがあります。
- バージョン管理を通じた機密情報の暗号化
- 機密情報のリモート保存
- 専用の機密情報管理ツールの使用
Gitレポジトリに機密情報を保存する方法は、よくアンチパターンとよくいわれたりします。
機密情報をGit管理する際には、なんといっても平文でコミットしてしまったときのリスクがあります。
しかし、シークレット含めたデプロイの自動化を考えると、Gitレポジトリに保存する方法も一概に悪いとは思えません。
適切に暗号化して保存することが必要になるわけですが、具体的なツールとしてSOPSが結構使えます。
SOPSは、ファイル全体を暗号化するのではなく、JsonやYAMLのvalueだけを暗号化できます。
そのためプルリクエストでも内容をみやすく非常に強力なツールです。
暗号化のキーにクラウドサービスのKey Management Serviceなどを利用できる点もよいです。
以前にAnsibleのansible-vaultを使って同様のことを行いましたが、ファイル単位での暗号化のため苦労した覚えもありました。
デプロイ
アプリケーションを環境にデプロイする際の方式(strategy)ですが、kubernetsのネイティブの機能だと、recreateかrollingの選択が可能です。 それ以外にblue greenデプロイメントなどを選択したい場合には、CI/CDパイプライン内での実装が必要になるでしょう。
デプロイについて、そのスコープについて述べられることは多くないと感じているのですが、 デプロイの対象や範囲を明確にしておくことも非常に大事と感じています。 アプリケーションのみの変更を期待するのか、他のKubernetesのリソースの変更も期待とするのか、またはAWSなどのクラウド側のリソースのデプロイも含むかということです。 この問題は、上のモノレポジトリかマルチレポジトリかの選択と本質的には一緒と言えるかもしれません。 アプリケーション以外のコンポーネントの管理をどのチームが責務をもっているか、そことの連携をどうするかを確認して決定する必要があります。
例として、Kubernetesでアプリケーションを実行しつつも、AWSのマネージドサービスも併用して活用しているとします(AWSのリソース管理をTerraformで行っていると仮定)。もしアプリケーションチームで他のコンポーネントにも責務をもっているのであれば、アプリケーションコードと一緒にTerraformコードも管理し、KuberntesのデプロイとともにTerraformコードの適応も検討することができます。 マルチレポジトリで、KubernetesのマニフェストファイルとTerraformのコードを"インフラコード"としてまとめて管理することもできるでしょう。
大事なことは、アプリケーションの変更は必ずしもアプリケーションの入れ替えだけでは済まないことがあるため、 影響範囲の責務をどこでどのように持つかを考えていかなければいけないということです。 アプリケーション以外の部分は、他チームなので手動でやっています、という体制はそれはそれでいいですが、アプリケーションのデプロイまでの全体のバリューストリームの改善にならないことがあるので一緒に考えましょうというはなしです。
CIツール
CIツールに何を使ったらいいのか?ということもよく聞かれるので記述します。このブログでは具体的にこれがいい!というのは示しませんが、CIの実行環境もコンテナで実現できるかどうか? ここを重要視するといいのではないかと思います。(現代的なCIツールであれば当然だろうと思われるかもしれませんが)
CIとコンテナは非常に相性がいいです。なぜならば、テストや自動化作業は毎度クリーンな環境で行うほうが効率的だからです。前に行ったテストデータが残っていたのでその準備から行うや、CIで実行したい対象のアプリケーションが必要なバージョンのソフトウェアを利用できないといったことがなくなります。それでいて、仮想サーバとことなり起動もはやいので効率的です。
まずは、最近のSaaS製品であれば、多くがコンテナとして動かすことができますのでそちらを探してみましょう。
CIツール自身もKubernetes上で動かすことも検討できます。以下、代表例です。
- Jenkinsであれば、Kubernetes Plugin for Jenkinsを使えば、Jenkins agentをKubernetesのPodとして動作可能です。Jenkins本体も当然Kubernetes上に構築することもできますが、既存にJenkinsを持っている場合は、agentのみでもKubernetesで動かすことを検討してみましょう。
- Gitlabであれば、Gitlab runnerをKubernetesで実行する方法があります。Gitlabを利用されている企業の方はすごく多くなってきていると感じています。余計なツールを増やすことなく、Kubernetes上でCIを実行できるので非常に有力な候補となりえます。
- もっとクラウドネイティブなCIツールとしてTektonがあります。まだまだ発展途上のプロダクトではありますが、Kubernetes Nativeにつくられたツールであり非常に楽しみです。もしTektonについて学びたい人は、Tekton学習シリーズ(全11回)もやっていますので学んでみてください。
チェックリスト
上で出てきた項目を、一言でチェックリスト形式にまとめました。
- どんなブランチ戦略を採用しているか?ブランチとパイプラインのトリガーの関係性を整理できたか?
- マニフェストの宣言的な設定のメリットを活かしてあるか?
- 環境ごとにマニフェストを個別に作っていないか?テンプレート化などをして再利用性を高めているか?
- テストは一時的な環境で実施できるようになっているか?テスト時の他のコンポーネントをどう扱うか?
- マニフェストの管理はどこのレポジトリで行っているか?その管理が体制に向いているか考えたか?
- コンテナイメージはどこでどのように作成しているか?タグ付けの方法は適切か?
- シークレット情報の管理の方法について検討したか?採用した方法のリスクを検討したか?
- アプリケーションのデプロイ方式を検討したか?
- デプロイのスコープを明確にしたか?
- 利用するCIツールは決まったか?なぜそれを選んだか説明できるか?
さいごに
KubernetesにおけるCI/CDで重要になるポイントを紹介してきました。
ほかにも紹介したいことがいくつかあるので、別記事ないしは内容の更新を随時おこなっていこうと思います。
またこの手の内容については「Kubernetesで実践するクラウドネイティブDevOps」という書籍でより広範囲に紹介されています。
是非手にとって学んでみてください。