はじめに
筑波大学 大学院 修士1年の石黒湧大です。2023年8月より6週間の就業型インターンシップに参加させていただきました。インターンシップでは、LINE Platform Developmentセンター1/CSI室 DevBチームに配属され、「HelmによるKubernetesのマニフェストのバージョン管理」に関する検証および導入に取り組みました。
本ブログではその内容とインターンシップの感想について紹介させていただきます。
背景
ショップサービスの問題
CSI室はCommunication & Service Integration室の略称であり、LINEサービスのスタンプ・絵文字・着せかえなどのコンテンツの機能開発、関連サービスとの連携、およびその信頼性の向上を担当しています。「LINE」アプリのユーザーは私たちが運用する「スタンプショップ」や「着せかえショップ」といったサービスを利用して、上記のコンテンツを購入・ダウンロードして利用しています。
このショップサービスは複数のアプリケーションで構成されており、そのうちの一部はプライベートクラウドのサービスの一つであるVerda Kubernetes Service (VKS)上で動作しています。ショップアプリケーションのKubernetes (k8s) マニフェストには共通部分が多いものの、各アプリケーションが独自のマニフェストを持っているため、アプリケーションで共通の設定を変更する際に手間がかかるといった問題がありました。
現状のk8sマニフェストの管理方法
現状、ショップサービスではk8sマニフェストの管理にkustomizeというツールを利用しています。開発環境、ステージング環境、本番環境などの各環境ごとにk8sマニフェストを作成すると、内容に差異が出ることがよくあります(例: Podのレプリカ数やリソースの割り当て量など)。そうした状況で、ベースのk8sマニフェストを共通として維持しつつ、各環境の差分だけを別のk8sマニフェストで管理できるのがkustomizeです。
具体的には、ベースとなるk8sマニフェストと各環境用のファイルを以下のように構成することで利用できます。
├── bases │ ├── deployment.yaml │ ├── service.yaml │ └── kustomization.yaml └── overlays ├── dev │ ├── deployment-patch.yaml │ └── kustomization.yaml └── prod ├── deployment-patch.yaml └── kustomization.yaml |
bases
ディレクトリには、各環境共通のdeploymentとserviceの定義があります。一方、環境ごとの差分は、overlays
ディレクトリ内のパッチファイルを通じて反映され、これらを組み合わせることで最終的なマニフェストを完成させる形になっています。ショップサービスは複数のアプリケーションで構成されているため、それぞれのアプリケーションに対して上記のような構成のk8sマニフェストが必要となります。そのため、現在のk8sマニフェストでは、全てのアプリケーションで共通設定を変更する際は、大量のコピー&ペーストのような作業が必要になってしまいます。
解決策
上記のk8sマニフェストの管理に関する問題を解決するための解決策は以下の2つが考えられます。
-
解決案1: 全てのアプリケーションで共通のbasesファイルを持つ
-
解決案2: Helmによるバージョン管理
解決案1: 全てのアプリケーションで共通のbasesファイルを持つ
以下のようにkustomizeを利用して、各アプリケーションの共通部分をbasesディレクトリに集約させることで、問題を解決することができます。
├── bases │ ├── deployment.yaml │ ├── kustomization.yaml │ └── service.yaml ├── app-a │ ├── dev │ │ ├── deployment.yaml │ │ └── kustomization.yaml │ └── prod │ ├── deployment.yaml │ └── kustomization.yaml ├── app-b │ ├── dev │ └── prod └── app-c ├── dev └── prod |
しかし、この構成には問題があります。ショップサービスはKubernetesのデプロイ戦略としてArgoCDを活用したGitOpsを採用しています。この戦略では、ArgoCDがGitHubリポジトリの変更を監視し、GitHubリポジトリ内のk8sマニフェストに変更が加えられた場合、その変更を自動的にKubernetesに適用します。そのため、上記の構成でベースのk8sマニフェストに変更を加えると、ベースのk8sマニフェストを利用している全てのアプリケーションが新しい設定に基づき、即時にデプロイされることになります。障害影響を小さくするためにも、リリースを小さく行いたいため、このような構成は避けたいです。
解決案2: Helmによるバージョン管理
HelmはKubernetesのためのパッケージマネージャーです。Helmを使用すると、複数の関連するKubernetesリソースの定義や設定をまとめて、再利用可能なパッケージとして管理できます。Helmではこのパッケージを「Chart」と呼びます。Helm Chartには変更可能なフィールドが設定されており、その多くはデフォルト値があらかじめ設定されています。そのため、特別な設定をすることなくHelm Chartをすぐに利用することができます。もし特定の値を変更したい場合は、それを記述したyamlファイルとHelm Chartを併用します。このyamlファイルは慣習的に「values.yaml」という名前にすることが多いです。またHelmでは特定のChartのバージョンを指定できるため、たとえ新しいバージョンのChartが公開されても、そのChartを利用してデプロイされたKubernetesのリソースが自動的に変更されることがありません。
今回はこちらの方法を採用し、実装を行いました。
動作フロー
今回の実装の動作フローを紹介します。動作フローは、Helm Chartの作成とデプロイの2つのステップに分かれています。それぞれのステップは以下のように動作します。
Step 1: Helm Chartの作成
- Helm Chartを作成し、GitHubリポジトリにPush
- 特定のブランチへのPushでGitHub Actionが起動
- GitHub Actionが以下の処理を行う
- Helm ChartのLint
- Helm Chartのパッケージング
- パッケージングされたHelm ChartをHarbor*にPush
*Harborは一般的にコンテナレジストリとして知られていますが、Helm Chartを管理するHelmリポジトリの役割も果たすことができます。また、HarborでHelm Chartsを利用する場合は、Helm ChartをOCIというコンテナイメージの規格に変換(パッケージ化)する必要があります。(参考:Harbor: Managing Helm Charts)
Step 2: デプロイ
- values.yamlを作成し、GitHubリポジトリにPush
- 特定のブランチへのPushでArgo CDが起動
- Argo CDが以下の処理を行う
- GitHubリポジトリからvalues.yamlを取得
- HarborからHelm Chartを取得
- 取得したHelm Chartとvalues.yamlを元にk8sマニフェストを作成し、それをKubernetesにデプロイ
実装
具体的な仕組みやコードを以下の順に紹介します。
- GitHub Action
- Helm Chart
- ArgoCD Application
GitHub Action
GitHub Actionsでは、2つのワークフローが実行されます。一つ目はHelm Chartを構成するyamlファイルのLintを行うもので、二つ目はHelm Chartを実際に作成するワークフローです。これらのワークフローは次のように動作します。
Helm ChartのLint
- 変更のあったHelm Chartを検出
- Helm ChartのLint
Helm Chartの作成
- 変更のあったHelm Chartを検出
- Harborの認証
- Helm Chartのパッケージング
- パッケージングされたHelm ChartをHarborにPush
今回は複数のHelm Chartを同じGitHubリポジトリで管理することを想定しています。Helm Chartは基本的にそれぞれ独立しているので、変更があったHelm ChartのみLintやパッケージングの処理が行われます。また、Helm ChartのLintには、Chart Testingというコマンドラインツールを利用しています。
Helm Chart
ディレクトリ構成
Helm Chartを管理するリポジトリ
Helm Chartを管理しているリポジトリは以下のようなディレクトリ構成になっています。charts/line-shop-base-appディレクトリ配下には、ショップサービスのアプリケーションのテンプレートとなるHelm Chartが定義されています。Helm Chart内のvalues.yamlにはそのチャートで変更可能なフィールドやそのデフォルト値が設定されています。ci-configsディレクトには、Helm Chartに対してLintを行うための設定ファイルがあります。
├── README.md ├── charts │ └── line-shop-base-app │ ├── Chart.yaml │ ├── templates │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ ├── deployment.yaml │ │ ├── ingress.yaml │ │ ├── pod-disruption-budget.yaml │ │ ├── service.yaml │ │ └── tests │ │ └── test-connection.yaml │ └── values.yaml └── ci-configs ├── chart_schema.yaml └── lintconf.yaml |
また、templatesディレクトリ配下のファイルでは、以下の4つのKubernetesリソースを定義しています。
- Deployment
- Service
- Ingress
- Pod Disruption Budget
環境差分を吸収するためのvalues.yamlを管理するリポジトリ
アプリケーションや環境ごとの差分を吸収するためのvalues.yamlを管理しているリポジトリは以下のようなディレクトリ構成になっています。また、環境名-values.yamlは環境ごとの値を設定しているyamlファイルです。
├── app-a │ ├── dev-values.yaml │ └── prod-values.yaml └── app-b ├── dev-values.yaml └── prod-values.yaml |
テンプレートとオーバーライド
次に、Helm Chart内のdeployment.yamlとvalues.yamlの内容を簡略化して紹介いたします。deployment.yamlにはKubernetesのDeploymentリソースが定義されており、Podのレプリカ数やコンテナイメージなどの複数のパラメータが変更可能なフィールドとして定義されています。これらのフィールドのデフォルト値がHelm Chart内のvalues.yamlで設定されいます。また、環境差分を吸収するためのvalues.yamlでも、Helm Chart内のvalues.yamlと同様のフォーマットで、フィールドの値の設定が行われています。
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
service: line-shop
spec:
replicas: {{ .Values.replicaCount }}
省略
spec:
containers:
- name: application
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
values.yaml
replicaCount: 2
image:
repository: DOCKER_IMAGES_URL # Docker imageを指定
tag: latest
ArgoCD Application
ArgoCDのApplicationについて紹介します。ArgoCDのApplicationは、Gitリポジトリの内容をKubernetesクラスタにデプロイする際の設定やポリシーを持っています。以下は、ArgoCDのApplicationの定義を含むyamlファイルです。一般的なk8sマニフェストのみを利用する場合、一つのソース(GitHubリポジトリ)の指定で十分です。しかし、今回はHarborで管理されているHelm Chartと、GitHubリポジトリにあるアプリケーションや環境の差分を吸収するためのvalues.yamlの両方が必要となるため、二つのソースを指定しています。また、Helm Chartのバージョンやハッシュ値を指定することで、Helm Chartが更新されても、新しい設定に基づく自動的なデプロイは実行されません。
argocd-application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: line-shop-app
spec:
project: line-shop
destination:
server: KUBERNETES_CLUSTER_URL # デプロイを行うKubernetesクラスタを指定
namespace: line-shop-app
sources:
- repoURL: HARBOR_URL
chart: PROJECT_NAME/CHART_NAME # Helm Chartの指定
targetRevision: 0.1.0
helm:
releaseName: line-app
valueFiles:
- $values/service-a/dev-values.yaml # 環境別のvalues.yamlの指定
- repoURL: GitHub_REPO_URL
targetRevision: HEAD
ref: values
まとめ
今回のインターンシップでは、ショップサービスを構成する複数のアプリケーションのk8sマニフェストの管理のためにHelmを導入しました。Helm導入により、共通のHelm Chartを利用しつつ、各アプリケーションやデプロイ環境で異なる設定を個別に変更することが可能になりました。HelmのChartを作成する際、Chartの汎用性とシンプルさを保ちつつ、少ない設定でChartを使用できるようにすることが非常に難しく、またやりがいのあるタスクでした。ショップサービスを構成する全てのアプリケーションの詳細や、今後の変更予定を一人で完全に把握するのは困難でした。そのためメンターや開発チームのメンバーからの聞き取りや議論を通じて、Helm Chartの仕様を決めていきました。
また今回初めて、多国籍のメンバーと共に英語を主体とした開発を体験しました。チャットや直接の会話での議論の際、細かい意図を正確に伝えることが難しい場面でも、日本語が堪能なマネージャーとメンターのサポートのおかげで、タスクを進めることができました。この経験を通して今後、さらなる英語力の向上が必要であることを痛感しました。
最後になりますが、マネージャーやメンターの方、開発チームのメンバーをはじめとする社員の方々、大変お世話になりました。皆様の協力とサポートのおかげで、このような貴重な経験と学びを得ることができました。この6週間で得た貴重な経験と学びを今後活かしていきたいと思います。本当にありがとうございました。