こんにちは。開発部門 開発部 Data AI Strategyセクション データ基盤 Unitの小野です。
2020年8月に入社してから早3年。SREエンジニアとして、日々業務改善に励んでいます。
ここ一年ほど、DAOという組織改善プロジェクトを推進していく中で、Google Kubernetes Engine (GKE)を使ったGKE共通デプロイ基盤の整備も進めてきました。
※ DAOについての詳細はSREエンジニアが組織改善プロジェクトを立ち上げてみたを参照ください
SREエンジニアの責務の一つは、プロダクトのリリースサイクルを極限まで短くし、次々と新しいサービスを世の中にリリースすることです。ChatGPTのような誰でも簡単に扱えるAIモデルが誕生したことで、プロダクト開発競争は今後ますます激しくなっていくと予想しており、SREエンジニアの責務の重要性をヒシヒシと感じています。
そういった背景もあり、アプリケーションの実行基盤である「共通デプロイ基盤」のニーズもあるかと思い、私がこれまで取り組んできたGKE共通デプロイ基盤構築・運用についての道のりを記事にしました。
<目次>
GKE共通デプロイ基盤の立ち上げ
真っ先にお伝えしたいことは、「GKE共通デプロイ基盤とはなにか?」についてです。これは、社内向けのアプリケーションを「誰でも」「自由に」「簡単に」デプロイできることを目指したデプロイ基盤です。背景から順に説明させてください。
基盤立ち上げの背景
SREプラクティスの一つに「IaC(Infrastructure as Code)推進」があります。 IaCは、TerraformやAWS CDKを使ってインフラをコード化する取り組みのことです。
以下は、AWSのEC2をコード化したTerraformのサンプルです。
IaC化には、次のメリットがあります。
<メリット>
- インフラ構成をプログラミングできる
- CI/CDや第三者レビューの組み込みなど、品質向上や属人化防止につながる
- 人為的なミス、ドキュメント作成、構築コストを削減できる
初期開発コストはかかるものの、再利用開発時は60%〜90%のコスト削減が見込めます。
クラウド活用が当たり前になってきた現代において、IaCスキルの習得はSREエンジニアにとって必須であると考えています。特にTerraformは、AWS/GCP/Azureなどのパブリッククラウドはもちろん、SnowflakeやNew RelicなどのSaaSプロダクトもコード化できるため、習得しておいて損はない技術だと考えています。
しかし、このIaCにもデメリットがあります。
<デメリット>
- プログラミングスキルが必要になる
- AWSのEC2など、コード化する対象の知識(インフラ)が必要になる
- コード(IaC)や構築後のインフラに対して保守コストがかかる
これらのデメリットは「インフラの標準化」を名目に、Terraform Cloudを使って解決しようと全社的に働きかけてはいますが、まだまだ道半ばです。 ※ 詳しくは、「TerraformとTerraform Cloud Businessを導入してインフラ環境の構築・運用を改善してみた」をご一読ください
また、データサイエンス系のエンジニアはデータ分析やAI開発が主戦場のため、IaCに馴染みがなく、IaCの活用があまり進まないというジレンマがありました。 ※ 私はデータサイエンス系のグループに所属しています
彼らの依頼にあわせて私がインフラ環境を構築してもよいのですが、SREエンジニアとしてもっと簡単にアプリケーションをデプロイできる環境が今後必要になるかと思い、別の手段を考えることにしました。
標準化されたデプロイ環境を提供する
GKE共通デプロイ基盤を立ち上げる前、私はIaCやCI/CD、SLI/SLOなどさまざまなSREプラクティスを学び、実践投入していました。Kubernetesを学習したのもそんな時期です。
Kubernetesは、Dockerなどから作られたコンテナの運用管理を自動化するオープンソースのツールです。IaC化の推進も軌道にのりそのメリットを感じる一方で問題点も見えていたため、このツールを使って共通デプロイ基盤を作りたいと考えました。
そもそも利用者にとって「アプリケーションをただデプロイできる環境があればいい」というケースも多いと感じていたため、上長に相談して社内の人が利用するデプロイ基盤を立ち上げることにしました。
使い方のイメージとしては、以下を想定しました。
■ 利用者側
Dockerイメージを用意してもらい、この基盤にデプロイしてもらう。
■ SREエンジニア側
共通デプロイ基盤(インフラ)の運用・管理に徹し、デプロイを補助する。
これなら一からインフラ環境を構築する必要がない上、開発の効率性もあがるなど色々メリットがありそうです。ただ、その道のりは果てしない困難の連続だったわけですが、詳しくは後述します。
GKEを選択した理由
Kubernetesはオープンソースであるため、オンプレサーバーにインストールして自前で運用することもできます。しかし運用維持費の関係上、それは絶対にやりたくありませんでした。
自前で構築する以外の選択肢としては、AWSのEKSや、GCPのGKEなどのマネージドサービスが候補に上がりました。これらのサービスは、Kubernetesの難しい運用をマネージドし、簡単に使えるようにしたサービスです。 ※ それでもKubernetesの知識は必須ですが...
この2つの中で、今回は「GKE」を選択しました。先述の通り、データサイエンス系のグループに所属しており、GCPをメインに使っていたためです。
GKEには、「GKE Standard」と「GKE Autopilot」の2つのモードが存在します。
大きく違う点は「Nodes」の管理です。ここに利用者がデプロイしたDockerイメージがPodとしてデプロイされるわけですが、GKE AutopilotではそのNodesの管理をマネージドしてくれます。
これにより、クラスタとワークロードの設定、スケーラビリティの管理、セキュリティに関するGKEのベストプラクティ導入など運用上で発生するさまざまな負担を軽減できます。ただし、GKEがNodesを管理するため、細かなカスタマイズや特定の3rd-partyツールが導入できませんでした。
ユースケースによってはGKE Standardの方が適しているかもしれませんが、社内利用想定のデプロイ基盤であったため、運用コストをおさえられるGKE Autopilotを選択しました。
GKE共通デプロイ基盤をリリースするまでの道のり
結論からいうと、GKE共通デプロイ基盤は1年以上前(2022年8月頃)に構築し、現在まで運用しています。
完成系はこちらになります。
<技術>
- Docker - コンテナ型のアプリケーション実行ツール
- Kubernetes - Dockerコンテナのオーケストレーションツール
- GKE Autopilot - Kubernetesのマネージドサービス
- Argo CD(アルゴ) - デプロイに特化したKubernetes用のデプロイメントツール
- Helm - Kubernetes用のパッケージマネージャー
- Terraform Cloud Business(TFCB) - インフラ構成管理ツール
- GitHub - バージョン管理ツール
- Artifact Registry - Docker Imageを格納するツール(GCPのサービス)
この構成に至るまでの改善譚をぜひ聞いてください。
Helmを導入してKubernetesファイルの作成コストを下げる
GKE Autopilot(インフラ)の構築はTerraformでコード化し、デプロイするだけだったため簡単でした。これは利用者側は意識する必要はなく、SREエンジニアが対応します。
しかし、問題はKubernetesの設定ファイルを作成する方法です。この設定ファイルは利用者側に用意してもらう必要があります。
Kubernetesでは1つ以上のDockerコンテナの集合体であるPodをGKEのNodesにデプロイします。このPodを作るためには、Kubernetesの設定ファイルが必要になります。
さらに、Podに乗せてデプロイするだけではアプリケーションを使えるようにはなりません。
具体的には、以下のKubernetesリソースが必要です。
- Pod - 1つ以上のコンテナからなる集合体(Kubernetes上でコンテナを管理する最小単位)
- Deployment - Podをまとめて管理するためのオブジェクト
- Servic - Podの集合体であるDeploymentにアクセスするためのエンドポイントを提供するオブジェクト
- Ingress - GKEクラスタ外からServiceへアクセスするためのオブジェクト(URL設定など行う)
ユースケースによっては他にも必要なリソースはありますが、上記は最低限必要なもの*3です。
そして、以下の構成を取ることでアプリケーションはようやく使えるようになります。
では、これらの設定ファイルを利用者に頑張って用意してもらうのか?と考えた時、それは現実的ではないと考えました。IaC化の課題と同じく利用者側の負担が大きいからです。
そこで、Helmテンプレートを導入しました。
Helmは、Kuberentes用のパッケージマネージャーであり、以下の特徴を持っています。
- KubernetesのYAMLリソース(pod, deployment, service)から変数を除外し、 values.yamlで一括管理
- 複数のKubernetesのYAMLリソース(pod, deployment, service)をバンドル化し、1つのArtifactであるChartとして管理
このHelm Chartを使って必要なリソースをテンプレート化し、値の書き換えのみでリソースを作成できる仕組みを作りました。
テンプレートで対応できないリソースに関してはSREエンジニアがサポートする形にしているので、今のところ大きな問題は発生していません。
Argo CDを導入してアプリケーションのデプロイコストを下げる
GKE共通デプロイ基盤へのデプロイ方法には、正直悩みました。
Kubernetesには「kubectl」、Helmには「helm」によるCLIベースのコマンドラインツールが提供されています。それを使えば、ローカルPCからKubernetesリソースをデプロイできます。しかし、「ローカルから誰でも簡単にデプロイできる共通デプロイ基盤」というコンセプトにはしたくありませんでした。
<理由>
- 利用者にkubectlやhelmをインストールしてもらう必要がある
- 利用者に自由な操作を与えたくない
- セキュリティ的にも不安
そこで、Argo CDを導入することにしました。このツールはデプロイメントに特化したツールで、GitHubと連携しコードの変更を検知して自動的にPodをデプロイできる仕組みを提供してくれます。
Argo CDにはいくつかのカスタムリソースが提供されていますが、その中でも「AppProject」と「Application」は特に重要です。
■ AppProject
対象となるApplicationのマニフェストリポジトリやデプロイ先を制限する。
■ Application
Argo CDが展開するデプロイメントの単位。
そして便利なことに、Argo CDはWebインタフェースを提供しています。
上記のようにデプロイしたKubernetesのリソースがUI上で確認できるので大変便利です。
また先述の通り、Argo CDはGitHubと連携ができるため以下のGitOpsを構築しています。
Gitリポジトリ構成の話になりますが、GKE共通デプロイ基盤では以下の2つのリポジトリに分けてソースコード(Kubernetesの設定ファイルやアプリケーションのソースコード)を管理しています。
- Application Repository
アプリケーションとしてデプロイするソースコードを管理するリポジトリ。
アプリケーションの数だけ存在し、利用者側で用意してもらう。 - K8S Manifest Repository
Kubernetesの設定ファイルを管理するリポジトリ。
SREエンジニアが一括集中管理する。
利用者には、K8S Manifest Repositoryに自分用に作成したhelmテンプレートを配置してもらいます。
利用の流れとしては、以下の通りです。
<利用の流れ>
- アプリケーションコードをApplication RepositoryにPush(GitHub Actions実行)
- UnitTestを実行
- Docker Imageをビルド
- Artifact Registry へDocker ImageをPush
- K8S Manifest Repositoryから利用者が配置したHelmテンプレートをクローン
- Helmテンプレートの内容を書き換える
- K8S Manifest RepositoryへソースコードをPushする
- Argo CDが自動検知 → 自動デプロイする
「6. Helmテンプレートの内容を書き換える」についてですが、これはGitHub Actionsで、K8S Manifest Repositoryから更新したいHelmテンプレートをCloneして一部の設定を書き換えることを指しています。
# GitHub Actionsのコード --- promote: runs-on: ubuntu-latest steps: - run: | # clone git clone https://oauth2:${{ secrets.GH_PAT }}@github.com/${{ github.repository_owner }}/$CONFIG_REPO_NAME.git cd $CONFIG_REPO_NAME git checkout main # Helmテンプレートの一部を書き換え sed -i "s,repository:.*,repository:\ ${{ env.REPOSITORY }}," ${{ env.VALUES_PATH }} sed -i "s,tag:.*,tag:\ ${{ github.sha }}," ${{ env.VALUES_PATH }} git add . && git commit -m "update image tag" git push
ここでは、Helmテンプレートで用意したvalues.yamlに設定されている「tag」を新しいtagに書き換える処理を実行しています。
GitHub Actionsの中でアプリケーションのDocker Imageをビルドし、GCPのArtifact Registry へPushします。その際、新しいタグを生成するので、それを使ってvalues.yamlの値を書き換えます。書き換えた後は、K8S Manifest Repositoryに再びPushし、Argo CDがそれを検知して新しいアプリケーションをデプロイする、といった流れになります。
この方法のメリットは、デプロイ方法を標準化できる点にあります。GitHub ActionsおよびArgo CDの設定ファイルはそれぞれテンプレート化しているので、利用者側には設定方法をレクチャーする必要はありますが、誰でもデプロイできる状態になります。
オンボーディング資料を用意してレクチャーする
設定ファイルをテンプレート化し、簡単なレクチャーを実施することで誰でもデプロイ可能な状態にはなりました。しかし、それだけだとGKE共通デプロイ基盤の利用は進みませんでした。
GKE共通デプロイ基盤を使うには、Kubernetesをはじめとするたくさんの知識が必要になるので、利用者側にとってハードルが高いためです。
そこで、SREエンジニアとしてオンボーディング資料を用意することにしました。
一つ一つの用語をわかりやすく丁寧に解説した資料を作成しました。この資料はハンズオン形式になっており、GKE共通デプロイ基盤の使い方が誰でもわかるように心がけています。
この資料のおかげで、デプロイ方法を教えられる人が2名->12名に増やすことができました。
利用拡大に向けた取り組み
先ほどまではリリースまでに実践した取り組みですが、ここからはGKE共通デプロイ基盤の利用拡大に向けた取り組みについても触れていきます。
Blue-Greenデプロイメントを取り入れてみる
どうしたらGKE共通デプロイ基盤の利用を拡大できるか考えてみた時、最初に思いついたのがBlue-Greenデプロイメントでした。
これは、アプリケーションやサービスを新しいバージョンにアップグレードする際のデプロイメント戦略の一つです。従来のバージョン(Blue)と新しいバージョン(Green)の2つの環境を同時に用意し、トラフィックを切り替えることでスムーズにアップグレードを行います。
Argo CDではカスタムリソースのArgo RolloutsでBlue-Greenデプロイメントを実現できます。所感ですが、簡単に実装できました。
以下はArgo Rolloutsのサンプルコードです。
##############Service############## apiVersion: v1 kind: Service metadata: labels: argocd.argoproj.io/instance: demo name: api-cluster-ip-service-active namespace: demo spec: ports: - port: 8080 targetPort: 8080 selector: component: api type: ClusterIP --- apiVersion: v1 kind: Service metadata: labels: argocd.argoproj.io/instance: demo name: api-cluster-ip-service-preview namespace: demo spec: ports: - port: 8080 targetPort: 8080 selector: component: api type: ClusterIP ##############Deployment############## # apiVersion: apps/v1 # kind: Deployment apiVersion: argoproj.io/v1alpha1 # apps/v1から書き換え kind: Rollout # Deploymentから書き換え metadata: name: hoge namespace: hoge spec: replicas: 2 revisionHistoryLimit: 1 selector: matchLabels: component: hoge template: metadata: labels: component: api spec: containers: - name: hoge image: "hoge" imagePullPolicy: Always ports: - containerPort: 8080 strategy: blueGreen: # 本番アクセス用のService名 activeService: api-cluster-ip-service-active # Previewアクセス用のService名 previewService: api-cluster-ip-service-preview # 手動昇格の有無(falseなら手動で昇格) autoPromotionEnabled: false ##############Ingress############## apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: spec: ingressClassName: nginx rules: - host: XX.XX.XX.XX.nip.io http: paths: - backend: service: name: api-cluster-ip-service-active port: number: 8080 path: /sample-argo-demo/?(.*) pathType: Prefix - backend: service: name: api-cluster-ip-service-preview port: number: 8080 path: /sample-argo-demo-preview/?(.*) pathType: Prefix status: loadBalancer: ingress: - ip: XX.XXX.XXX.XXX
Deploymentエリアが肝で、ここにBlue-Greenの設定を書いています。
この設定を行うとArgo CD上では以下のようにBlue-Green用の経路ができます。
Argo Rollouts側でもUIを提供しており、ここでBlue-Greenの切り替え作業を行えます。
新しいKubernetesリソースがデプロイされたら以下のようになります。
Revision8が新しいリソース、Revision6が古いリソースです。この状態の時、Argo CDのUIからはSVCからPodに対して2つの経路(Blue-Green)ができていることがわかります。
問題がないようであれば、Argo RolloutsのUIから「PROMOTE」を押下してBlue-Greenデプロイメントを完了させます。
Argo RolloutsでのBlue-Greenデプロイメントは、実装前(Blue)と実装後(Green)の比較が可能になるため、本番リリース前に関係者限定でプレリリースするなど、導入前と比べてデプロイの柔軟性が格段に上りました。
値の共有化の仕組みを作る
Helmテンプレートで作成したリソースには共通の設定値がいくつかあります。例えばホワイトリスト(IPアドレス)です。
これまでは利用者側にそれぞれ設定してもらっていましたが、負担になりますし、値が書きかわった際に全てのHelm Chartを修正する必要が生じるので共通値の設定ルールを作ることにしました。
方法はいくつかありますが、今回は一番簡単なSubChart(サブチャート)手法で対処しました。
これは、Helm Chart内に別のChartを含めるための仕組みです。 以下の特徴があります。
<特徴>
- 複数のChartを単一のディレクトリ構造で管理し、再利用性を高めることができる
- 特定のアプリケーションやサービスが複数の関連するコンポーネントから構成される場合、SubChartは便利である
SubChartの利用はものすごく簡単です。まずは、SubChartとするHelm Chartを作ります。
mkdir subcharts/valueLibrary cd subcharts/valueLibrary touch Chart.yaml touch values.yaml
Chart.yamlおよびvalues.yamlにはそれぞれ以下の設定を書きます。
########Chart.yaml######## apiVersion: v2 name: valueLibrary description: A Helm chart for Kubernetes type: application version: 0.1.0 appVersion: "1.16.0" ########Values.yaml######## whiteList: ipAddrs: <ホワイトリストのIPアドレス>
作成したSubChartは、各Helmチャート内のChart.yaml内で次のように呼び出します。
apiVersion: v2 ... dependencies: - name: valueLibrary # SubChartの名前 version: "0.1.0" # SubChartのChart.yamlに記載のVersion repository: "file://../../subcharts/valueLibrary/" # SubChartの格納先 SubChartの値は以下のように呼び出せます。 apiVersion: networking.k8s.io/v1 kind: Ingress metadata: ... nginx.ingress.kubernetes.io/whitelist-source-range: {{ .Values.valueLibrary.whiteList.ipAddrs }} # SubChartの値呼び出し
この手法であれば利用者側の負担を減らせますし、テンプレートの抽象度も上がります。
New RelicとArgo CDを連携させてみる
弊社では、監視ツールとしてNew Relicを導入しています。これを使ってArgo CDの監視設定を行いました。
<前提条件>
- Argo CD V2.5.0
- Helmがインストールされていること
- GKE Autopilotで以下のコマンドが実行されていること
# Namespace作成 $ kubectl create ns argocd # Argo CD導入 $ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v2.5.0/manifests/install.yaml # 確認 $ kubectl get deployment -n argocd NAME READY UP-TO-DATE AVAILABLE AGE argocd-applicationset-controller 1/1 1 1 5m3s argocd-dex-server 1/1 1 1 5m3s argocd-notifications-controller 1/1 1 1 5m3s argocd-redis 1/1 1 1 5m2s argocd-repo-server 1/1 1 1 5m2s argocd-server 1/1 1 1 5m2s
連携するためのステップは、以下になります。
<ステップ>
- Argo CD integrationの導入
- IntegrationをGKEクラスタに導入し、Argo CDのメトリクスを収集できるようにする
- インストールする際は、Install the Kubernetes integrationの「In GKE Autopilot」からHelmコマンドでインストールする
- パッチを当てる
- メトリクスを収集するために、Argo CDの設定を変更する
- Argo CD ダッシュボード構築
- メトリクスの可視化のため、Argo CDのダッシュボードを構築する
上記のステップのとおり、Install the Kubernetes integrationの「In GKE Autopilot」からHelmコマンドをコピーし、GKE共通デプロイ基盤上に展開しました。
なお、以下のコマンドを付与しない場合、Autopilotモードではエラーが出るため注意が必要です。
--set newrelic-infrastructure.privileged=false --set newrelic-infrastructure.controlPlane.enabled=false --set newrelic-infrastructure.kubelet.config.scheme=http --set newrelic-infrastructure.kubelet.config.port=10255 --set logging.enabled=false --set newrelic-pixie.enabled=false
次に、Integrationを導入しただけではArgo CDからメトリクスを収集できなかったため、以下のパッチをArgo CDに当てました。
kubectl patch deployment argocd-server -n argocd -p '{"spec": {"template":{"metadata":{"annotations":{"prometheus.io/scrape":"true","prometheus.io/port": "8083","prometheus.io/path": "metrics"}}}} }' kubectl patch statefulset argocd-application-controller -n argocd -p '{"spec": {"template":{"metadata":{"annotations":{"prometheus.io/scrape":"true","prometheus.io/port": "8082","prometheus.io/path": "metrics"}}}} }' kubectl patch deployment argocd-repo-server -n argocd -p '{"spec": {"template":{"metadata":{"annotations":{"prometheus.io/scrape":"true","prometheus.io/port": "8084","prometheus.io/path": "metrics"}}}} }'
最後に、Argo CDのダッシュボードを作成してメトリクスを収集できるようになったか確認します。クイックスタートの「Install now」からあっという間に構築できました。
AIを取り入れてみる(検証レベル)
昨今のSREエンジニアはAIの導入検証もします。現在はOpenAIやVertex AI SearchのPoC検証を実施しており、これらの技術を使ってHelmテンプレートを自動生成できないか試行錯誤しています。 ※ 最終的にはVertex AI Searchは使いませんでしたが...
結論から言うと割と簡単にHelmテンプレートの自動生成ができました。以下は、OpenAIを組み込んだStreamlit(Pythonのフレームワーク)の画像です。
画像の通り、指示した通りのHelm Chartを生成してくれました。
まだ完全なものではないのでこれからも実験を重ねる必要はありますが、これを実運用化できればGKE共通デプロイ基盤の利用拡大を狙えるかと思います。
おわりに
GKE共通デプロイ基盤は、社内向けサービスとしてまだまだこれからです。運用にはたくさんのハードルがあり、それを越えるために日々頭を悩ましています。とはいえ、色々と技術検証ができるので楽しくはあります。
GKE共通デプロイ基盤がより整備されていけば、社内の開発サイクルがより早くなり、様々なアウトプットが生まれ、知見が蓄積されていくと思います。そして、最終的にはぐるなびサービスの利用者に還元されていくと信じています。 SREエンジニアとしてより洗礼した基盤を提供できるようにこれからも精進していきたいと思います。
ここまでお読みいただき、ありがとうございました。
*1:出典:Terraformドキュメント|aws_instance(2024.02.08アクセス)
*2:出典:Google Kubernetes Engine(GKE)ドキュメント|GKE クラスタのアーキテクチャ(2024.02.08アクセス)
*3:バッチ実行など、特定用途で使用する場合はPodのみで問題ない場合があります