はじめに
この記事はNikkei Advent Calendar 2022の6日目の記事です。
こんにちは!バックエンドエンジニアの千葉です。 本記事では、日経で開発している負荷試験基盤「Cage」で抱えていた課題と、それを解決した「Cage v2」についてご紹介したいと思います。
そもそも Cage とは?
Cage は日経で開発している Kubernetes(GKE) + Locust + ArgoCD で構成される負荷試験基盤です。
Cage の全体像 (GKE と Locust.io でつくる負荷試験基盤より)
Cage を使うメリットとして以下が挙げられます。
- 負荷試験基盤の管理から開放
- SRE チームが Kubernetes, ArgoCD を含む負荷試験基盤全体を開発・運用します
- マニフェストの記述不要
- 便利なマニフェストテンプレートを SRE チームが用意しています
- 自動デプロイ
- テストシナリオとマニフェストを Git レポジトリにプッシュすれば、あとは ArgoCD がデプロイします
- WebUIから簡単操作
- GCP Console, ArgoCD WebUI, Locust WebUI から必要な操作が行えます
※ 詳細については、「GKE と Locust.io でつくる負荷試験基盤」の記事をご確認ください
SRE チームでは本基盤を通して、開発者が負荷試験シナリオの作成及び試験の実施に集中できる環境の実現を目指しています。
Cage の抱えていた課題
お陰様で様々なプロジェクトにおいて Cage が採用されていったため、前述した狙いをある程度を実現することができました。 一方で、改善すべき点も見えてきました。
1. ユーザ管理の手間
Cage を利用するためには、Cage がデプロイされている GCP Project のアクセス権が必要でした。 そのため、利用希望者は都度 SRE チームに依頼して権限を付与してもらう必要がありました。 この作業は SRE チームにとってもトイルとなる部分でした。
2. 各種 UI にアクセスするにはポートフォワードが必要
Kubernetes クラスター上には Ingress 等外部からアクセスするための仕組みはありませんでした。
そのため利用者は Locust クラスターを操作するために、都度 kubectl port-forward
を実行する必要がありました。
3. マニフェストテンプレートの Locust が古い/サポートしているユースケースが少ない
Cage としてサポートしている Locust のバージョンは 0.9.0
という非常に古いバージョンでした。
そのため利用できる機能や UI が古いという問題点がありました。
また、Python パッケージの追加ができなく、より大規模に/広範囲に展開するには少し心もとない状況でした。
Cage v2 について
前述した課題を解決するため、新しく「Cage v2」を開発しました。 以下に示す概要図を元に、課題への対処方法を説明させていただきます。
Cage v2 の全体像
1. 開発者向け共通認証基盤の採用
日経では多数の開発者向けサービスがあり、それらの多くが共通の認証基盤と統合されています。 Cage でもそれらの例に習い共通認証基盤を採用することにしました。 これにより、利用者・SRE チーム両方がユーザ管理の手間から開放されました。
2. Ingress の導入
Cage では GKE Ingress + Ingress Nginx 構成を採用しています。 2つの Ingress を使用している理由としては、
- 前述した認証基盤との統合方法として Ingress Nginx と組み合わせた事例が既にあり、容易に構成可能であったため
- GKE Ingress を利用した場合、Google マネージド SSL 証明書が利用できて管理コストを抑えられるため
という点があります。
認証基盤との統合はいたって簡単で、Ingress Nginx の global-auth-url
及び global-auth-signin
を設定すれば完了です。
また GKE Ingress と Google マネージド SSL 証明書との紐付けも簡単で、アノテーションを一つ書けば完了です。 同じ様に GKE Ingress と静的 IP アドレスもアノテーション一つで紐付けています(カスタムドメインを割り当てるため)。 マニフェストのイメージは以下の通りです。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress
annotations:
# 静的 IP アドレス及び Google マネージド SSL 証明書 は Terraform で作成・管理している
# ※ Google マネージド SSL 証明書はカスタムリソースでも作成できるが、クラウドリソースの
# 管理は Terraform にできる限り統一したかったので今回は利用していない
kubernetes.io/ingress.global-static-ip-name: "<静的 IP アドレスの名前>"
ingress.gcp.kubernetes.io/pre-shared-cert: "<Google マネージド SSL 証明書の名前>"
kubernetes.io/ingress.class: "gce"
kubernetes.io/ingress.allow-http: "false"
spec:
...
※ 上記も含む使用できるアノテーションの一覧はこちらをご確認ください
3. マニフェストテンプレートの刷新
負荷試験基盤の刷新及び Locust の更新に合わせて、マニフェストテンプレートも刷新しました。 構成は以下の通りです。
├── cookiecutter.json # テンプレートで使用するパラメータの定義
├── hooks
│ └── pre_gen_project.py # バリデーション用スクリプト
└── {{ cookiecutter.test_case_folder_name }}
├── application.yaml # ArgoCD Application
├── base
│ ├── kustomization.yaml # Locust の Helm を読み込み、パッチを当てる kustomization
│ ├── namespace.yaml
│ ├── patch_locust_deployments.yaml
│ └── values.yaml
├── configs # 設定ファイル用ディレクトリ
│ └── requirements.txt
├── kustomization.yaml # テストケース全体をまとめる kustomization
├── lib # Python モジュール用ディレクトリ
│ └── __init__.py
└── locustfile.py # テストシナリオ記述用ファイル
テンプレートの作成には cruft という Cookiecutter ベースのツールを使用しています。
実際には cruft を更にラップしたシェルスクリプトをユーザには提供しています。
hooks/pre_gen_project.py
は テンプレート展開前に実行されるスクリプトです( hooks の機能自体は Cookiecutter のものです)。
この中ではシェルスクリプトを介して cruft に渡されるテストケース名をバリデーションしています。
なぜなら、テストケース名はそのまま Kubernetes Namespace として使用されるので、 RFC1123 に準拠している必要があるからです。
シェルスクリプトを実行するとテストケースとして、{{ cookiecutter.test_case_folder_name }}
以下が展開されます。
base
ディレクトリ内では kustomize を利用して Locust Helm を読み込み、そして Helm が展開された後のマニフェストを Cage 用にカスタマイズしています。
例えば、patch_locust_deployments.yaml
を当てることで、任意の Python パッケージのインストールを実現しています。
具体的な処理は以下の通りです。
apiVersion: apps/v1
kind: Deployment
metadata:
name: cage-locust-worker
spec:
template:
# Python パッケージをインストールする Init Container
spec:
containers:
- name: locust
volumeMounts:
- mountPath: /home/locust/.local/lib
name: package-volume
initContainers:
- name: install-python-packages
image: REPLACE # kustomization を利用して、読み込んだ Locust Helm で使用されているイメージをパッチするようにしている
command: ["sh", "-c"]
args:
- pip install --user -r /home/locust/configs/requirements.txt; # Python パッケージをインストール
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /home/locust/configs # requirements.txt を ConfigMap としてマウント
name: cage-configs
- mountPath: /home/locust/.local/lib
name: package-volume
volumes:
- name: package-volume # インストールした Python パッケージを共有するための emptyDir
emptyDir: {}
{{ cookiecutter.test_case_folder_name }}/kustomization.yaml
では、
base
ファイルの読み込みlocustfile.py
,configs/requirements.txt
等を ConfigMap Generator で ConfigMap 化
を行っています。
ユーザが任意の設定ファイルを追加したい場合は、この kustomization.yaml
に一行ファイルパスを追加するだけで Cage 上でもその設定ファイルが読み込めるようになっています。
上記構成を見て、kustomization.yaml
は別に1つでも動くのでは( base
ディレクトリはいらないのでは?)と思った方もいるかと思います。
確かに1つでも問題はないのですが、前述の通りユーザが kustomization.yaml
を編集するユースケースがあり、その際ユーザが編集するべき部分とは関係ない設定が多くあるとユーザを混乱させるのでファイルを分けています。
上記の課題解決以外にもDXの向上を図りました。
4. 統一的な操作用 WebUI の導入
今回の刷新に合わせてユーザ向け WebUI として Kubernetes Dashboard を新たに導入しました。 導入した理由としては、
-
共通認証基盤を採用したことで、ユーザは GCP Console も
kubectl
も利用できなくなるため、Locust コンテナのリソース使用状況を知る術がなくなる。→ リソース情報を把握するための新たなツールが必要
-
ArgoCD でも Pod 数の調整など Kubernetes の操作が可能だが、ツールを行ったり来たりするのは DX を下げる。
→ ユーザには色々な機能が統合されたツールを提供する方向にしたい
- また、今後ユーザが増えてくる中で Kubernetes 初心者の人も必ず出てくる
- そのような方には ArgoCD 上でマニフェストを書き換えるより、ダッシュボード上でボタンをポチポチできる方がとっつきやすいのではと考えた
という点が挙げられます。
最終的に、
- テストシナリオの実行、負荷試験状況の確認 → Locust WebUI
- Locust コンテナ自体のスケールアップ・ダウン(テストの開始・終了)及びリソース使用状況の確認 → Kubernetes Dashboard
という形でユーザへ操作用 WebUI を提供しています。
最後に、インフラストラクチャ面では以下の取り組みを行いました。
5. GKE Autopilot の採用
以前の Cage では GKE Standard を採用していましたが、v2 では GKE Autopilot を使用しています。 理由としては、
使ってみたかったGKE Autopilot の知見を得たかった- 設計・管理コストを下げたかった
という点が挙げれます。
コストに関してもう少しお話しすると、今回は Spot Pod を利用しています。 Spot Pod は Google Cloud の予備コンピューティングリソースで実行される Pod で、もしクラウドリソースが不足するようであれば一定の猶予期間後に削除されます。 その代わり、通常料金の60~91%(!!)の割引が受けられる起動方式となっています(詳しくは、公式ブログをご参照ください)。 今回の場合、Cage 自体は日経のお客様向けのサービスではなく、多少可用性が落ちることが許容されるので導入しました。 (ただし、負荷試験中の Locust Pod は可能な限り落ちてほしくないので、Locust Pod 以外に導入しています。)
終わりに
今回は、負荷試験基盤「Cage」の抱えていた課題と、それらを解決した「Cage v2」についてご紹介しました。 この記事を通して日経の SRE チームの活動を少しでもご理解いただけたら幸いです。
日本経済新聞社では、Kubernetes 以外にも様々な技術を活用してメディアを作っていくことにチャレンジしています。 興味のある方はぜひ、以下のリンクからご連絡ください!
明日は尾崎さんによる「カスタム SF Symbols を作っていたら、 Apple のデザインの深さを垣間見た話」です!