エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

AIチームGoogle Kubernetes Engineオンボーディングチュートリアル

こんにちは。AI・機械学習チーム(以下AIチーム)チームリーダー、兼、エンジニアリンググループゼネラルマネージャーの横本(@yokomotod)です。

AIチームでは開発したMLプロダクトの実行基盤としてGoogle Kubernetes Engine(以下GKE)を採用しています。デプロイ関連コードのテンプレートも整備され、新しいプロダクトをスタートさせるときはGKE初心者でも簡単にデプロイまで持っていけるようになっています。

しかし自動生成は一方で、なにがどうなってデプロイされているのかブラックボックスのままになってしまいがちという問題もあります。

そこで今回は、チームメンバーへのオンボーディング、そしてKubernetesを触ってみたい人の参考になればと期待して、 ゼロからGKEにデプロイするハンズオン形式のチュートリアルを作ってみました。

What's this

このチュートリアルでは

  1. GKEでクラスタを作成
  2. サンプルアプリケーションのDockerイメージをbuild、push
  3. サンプルアプリケーションのクラスタへのデプロイ
  4. dev/prodの2環境にデプロイするための改良

を行います。

チュートリアル中で使うファイルなどは以下のgithubレポジトリでも公開しています

github.com

前提条件

Google Cloudのアカウントの作成や gcloud CLIのインストールまでは完了している前提としています。

GKEクラスタの準備

まずはチュートリアルを行うためのGKEクラスタから準備します。

公式ドキュメント: https://cloud.google.com/kubernetes-engine/docs/how-to/creating-an-autopilot-cluster

プロジェクトの作成

クラスタを配置するプロジェクトですが、最後にまとめて掃除できるように、また他のリソースと干渉しないように、今回のチュートリアル専用のプロジェクトを作成しましょう。

例えば m3-ai-team-k8s-tutorial-(ランダムな数字) という名前でプロジェクトを作成します。

PROJECT_ID=m3-ai-team-k8s-tutorial-$RANDOM

gcloud projects create $PROJECT_ID

Billing Account の設定

GKEを使用するためには、プロジェクトにBilling Accountが設定されている必要があります。

有効になっているか確認

gcloud billing projects describe $PROJECT_ID

出力

billingAccountName: ''
billingEnabled: false
name: projects/m3-ai-team-k8s-tutorial-xxxxx/billingInfo
projectId: m3-ai-team-k8s-tutorial-xxxxx

作成直後であれば billingEnabled: false になっているはずなので、Billing Account を設定します。

Billing Account の一覧を取得して、設定したいACCOUNT_IDを確認。

gcloud billing accounts list

プロジェクトに紐づけます。

ACCOUNT_ID=xxxxxx-xxxxxx-xxxxxx

gcloud billing projects link $PROJECT_ID --billing-account=$ACCOUNT_ID

これで billingEnabled: true になっているはずです。

gcloud billing projects describe $PROJECT_ID

出力

billingAccountName: billingAccounts/xxxxxx-xxxxxx-xxxxxx
billingEnabled: true
name: projects/m3-ai-team-k8s-tutorial-xxxxx/billingInfo
projectId: m3-ai-team-k8s-tutorial-xxxxx

GKEサービスのAPIを有効化

GKEを利用するためにAPIを有効化する必要があります。まずはAPIが有効になっているか確認します。

gcloud services list --project=$PROJECT_ID \
    | grep container.googleapis.com

プロジェクト作成直後では container.googleapis.com が表示されない = API無効状態のはずなので、次のようにして有効化します。

gcloud services enable container.googleapis.com --project=$PROJECT_ID

有効になったか、再度確認

gcloud services list --project=$PROJECT_ID \
    | grep container.googleapis.com

出力

container.googleapis.com            Kubernetes Engine API

GKEクラスタの作成

いよいよGKEクラスタを作成します。今回はAutopilotモードで作成します。

クラスタ名など

export CLUSTER_NAME=tutorial-cluster
export LOCATION=asia-northeast1

いざ作成!

gcloud container clusters create-auto $CLUSTER_NAME \
    --location=$LOCATION \
    --project=$PROJECT_ID

しばらく時間がかかりますが、以下のように出力されれば構築完了です。

NAME              LOCATION         MASTER_VERSION      MASTER_IP     MACHINE_TYPE  NODE_VERSION        NUM_NODES  STATUS
tutorial-cluster  asia-northeast1  1.30.6-gke.1125000  34.146.50.13  e2-small      1.30.6-gke.1125000  3          RUNNING

※ 次のような警告が出ますが、後ほどインストールするので今は気にしなくて大丈夫です。

CRITICAL: ACTION REQUIRED: gke-gcloud-auth-plugin, which is needed for continued use of kubectl, was not found or is not executable. Install gke-gcloud-auth-plugin for use with kubectl by following https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl#install_plugin

Webコンソール

https://console.cloud.google.com/kubernetes/list/overview

作成されたクラスタ

クラスタへの接続

デプロイなどの操作するためのCLI kubectl と、警告に出ていた認証用のプラグインも必要なのでインストールします。

gcloud components install kubectl gke-gcloud-auth-plugin

作成したクラスタに対して認証を通します。

gcloud container clusters get-credentials $CLUSTER_NAME \
    --location=$LOCATION \
    --project=$PROJECT_ID

これでクラスタにアクセスできるようになりました。試しにNamespace 一覧を表示してみましょう。

kubectl get namespaces

出力

NAME                       STATUS   AGE
default                    Active   9m47s
gke-gmp-system             Active   8m36s
gke-managed-cim            Active   9m19s
gke-managed-filestorecsi   Active   9m9s
gke-managed-system         Active   8m54s
gmp-public                 Active   8m36s
kube-node-lease            Active   9m48s
kube-public                Active   9m48s
kube-system                Active   9m48s

Namespaceが表示されていれば成功です!

デプロイするDockerイメージの準備

クラスタの準備が出来たので、次はデプロイするサンプルアプリケーションのDockerイメージを準備します。

公式ドキュメント: https://cloud.google.com/kubernetes-engine/docs/deploy-app-cluster

サンプルアプリケーションをビルドする

Google Cloudが提供するサンプルアプリケーションをビルドします。

ソースコード: https://github.com/GoogleCloudPlatform/kubernetes-engine-samples/blob/main/quickstarts/hello-app/main.go

実はこのサンプルのビルド済みイメージも公開されていて、公式ドキュメントではそちらを使っています。が、ここではビルドしてプッシュする部分もやってみましょう。

ソースコードをclone。

git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples.git
cd kubernetes-engine-samples/quickstarts/hello-app/

ビルド。イメージタグは後ほど作成するArtifact Registryの設定に合わせて指定します。

docker build -t $LOCATION-docker.pkg.dev/$PROJECT_ID/tutorial/hello-app:latest .

ローカルで動作確認してみましょう

docker run --rm -p 8080:8080 $LOCATION-docker.pkg.dev/$PROJECT_ID/tutorial/hello-app

起動ログ

2024/12/26 18:52:16 Server listening on port 8080

curlやブラウザでアクセスできればビルド成功です。

curl localhost:8080

出力

Hello, world!
Version: 1.0.0
Hostname: 748b9d88aa6e

Artifact Registryにプッシュする

無事ビルドできたので、Artifact Registryにプッシュします。Artifact Registry は、Dockerイメージ(や各種パッケージも)を保存・配信するGoogle Cloudのサービスです。

プッシュ先のリポジトリが必要なので作成しましょう。

gcloud artifacts repositories create tutorial --repository-format=docker \
    --location=$LOCATION \
    --project=$PROJECT_ID

プッシュできるように認証を設定します。

gcloud auth configure-docker $LOCATION-docker.pkg.dev

push!

docker push $LOCATION-docker.pkg.dev/$PROJECT_ID/tutorial/hello-app:latest

いざデプロイ

お疲れ様でした。クラスタとアプリケーションのイメージが出来たので、いよいよデプロイしていきます!

プレーンなYAMLファイルでのデプロイ

Kubernetesにデプロイする構成は基本的にYAMLファイルで記述します。

まずは普通にYAMLファイルを書いてデプロイしてみましょう。

Namespaceの作成

Namespaceはデプロイするリソースを分けるためのものです。

今回は tutorial-dev という名前で作成します。

apiVersion: v1
kind: Namespace
metadata:
  name: tutorial-dev

kubectl apply コマンドでファイルの内容を適用します。

kubectl apply -f namespace.yaml

Deployment の作成

DeploymentはPodのデプロイを管理するリソースです。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloweb
  namespace: tutorial-dev
  labels:
    app: hello
spec:
  selector:
    matchLabels:
      app: hello
  replicas: 3
  template:
    metadata:
      labels:
        app: hello
    spec:
      containers:
        - name: hello-app
          image: asia-northeast1-docker.pkg.dev/m3-ai-team-k8s-tutorial-xxxxx/tutorial/hello-app:latest
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: 200m

少し長いですが、使用するDockerイメージやportなど docker run -p 8080:8080 IMAGE と同様の設定や、使用するCPU量などを書いています。

kubectl apply -f deployment.yaml

デプロイすると replicas: 3 に従ってPodが3つ作成されているはずです。

kubectl -n tutorial-dev get po
NAME                        READY   STATUS    RESTARTS   AGE
helloweb-5b6b6cc799-wq6n6   1/1     Running   0          14s
helloweb-5b6b6cc799-sl38s   1/1     Running   0          14s
helloweb-5b6b6cc799-t6n8h   1/1     Running   0          14s

Webコンソール

https://console.cloud.google.com/kubernetes/workload/overview

デプロイされたDeployment

ログも見てみましょう。

kubectl -n tutorial-dev logs helloweb-5b6b6cc799-t6n8h
2024/12/26 19:02:32 Server listening on port 8080

無事コンテナが起動しました。が、まだ外部からアクセスできるようにはなっていません。

Service、Ingress の作成

Global Load Balancerを使って外部からアクセスできるようにします。

Global Load BalancerはGoogle Cloudのリソースですが、GKEではIngressリソースを使うことでk8sから構築できます。

Serviceの作成

apiVersion: v1
kind: Service
metadata:
  name: helloweb
  namespace: tutorial-dev
spec:
  selector:
    app: hello
  ports:
    - port: 80
      targetPort: 8080

Serviceのapply

kubectl apply -f service.yaml

Ingreesの作成

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: helloweb
  namespace: tutorial-dev
spec:
  defaultBackend:
    service:
      name: helloweb
      port:
        number: 80

Ingressのapply

kubectl apply -f ingress.yaml

applyしてしばらくすると ADDRESS が割り当てられ、さらにまたしばらくするとアクセスできるようになります。

Webコンソールでも作成されたGlobal Load Balancerが確認できます。

(見た目構築が完了したように見えてもアクセスが出来ない時間がしばらくあるので、なにか構築を間違えたのかと心配になるのですが、焦らずしばらく待ってみましょう)

kubectl -n tutorial-dev get ingress
NAME       CLASS    HOSTS   ADDRESS         PORTS   AGE
helloweb   <none>   *       34.54.192.203   80      5m28s
curl 34.54.192.203
Hello, world!
Version: 1.0.0
Hostname: helloweb-5b6b6cc799-t6n8h

おめでとうございます!デプロイしたアプリケーションにアクセスが出来るようになりました!!

改善

プレーンなYAMLを使ってデプロイに成功しましたが、実際の運用ではベタ書きのYAMLでは管理が難しくなりがちです。

例えばdev環境とprod環境のようにデプロイ先が複数あって設定が少しだけ異なる、ということはよくあります。

ほとんど同じYAMLをコピペ状態で複数メンテナンスすることは変更漏れやミスが起こりやすく管理が大変なので、いい感じにYAMLを合成する方法が欲しくなります。

ここからは、構成ファイルをもう少し管理しやすくするツールを導入して改善してみましょう。

Kustomize の導入

Kustomizeを使うことでYAMLを合成することができます。

例えば、以下のようにbaseとなるYAMLを作成し、そこに対してdevとprodの環境ごとに差分を適用するといったことが可能です*1。

base/kustomization.yaml:

namespace: TO_BE_SPECIFIED

resources:
  - deployment.yaml
  - service.yaml
  - ingress.yaml

(- namespace.yaml の行が無いことに気づいた鋭い方はしばしお待ち下さい。)

今回はNamespace名だけを変更するような差分を適用してみます。

dev/kustomization.yaml:

namespace: tutorial-dev

resources:
  - ../base

prod/kustomization.yaml:

namespace: tutorial-prod

resources:
  - ../base

デプロイ済みのdev環境は、Kustomize化しただけで合成結果のYAMLは変化していないはずなので、差分がないことを確認します。

kubectl diff -k dev/

デプロイする場合は

kubectl apply -k dev/

のようにデプロイできます。

未作成のprod環境も同様にデプロイ出来ますが、その前にもう1つツールを導入しましょう。

Skaffold の導入

Kustomizeを導入したことでYAMLの管理が楽になりましたが、まだDockerイメージのビルドとデプロイを個別に行う必要があります。

Skaffoldを使うことで、Dockerイメージのビルドとデプロイといったパイプラインを一括で行えるようにしてみましょう。

まずskaffoldをインストールします。

gcloud components install skaffold

skaffold.yaml という構成ファイルを記述します。

apiVersion: skaffold/v4beta11
kind: Config
build:
  artifacts:
    - image: asia-northeast1-docker.pkg.dev/m3-ai-team-k8s-tutorial-xxxxx/tutorial/hello-app
      context: ../kubernetes-engine-samples/quickstarts/hello-app/
      docker:
        dockerfile: Dockerfile
  local:
    useBuildkit: true

manifests:
  kustomize:
    paths:
      - dev

profiles:
  - name: dev

  - name: prod
    manifests:
      kustomize:
        paths:
          - prod

これで、コマンド1つでDockerイメージのビルドとデプロイが一括で行えるようになります。

skaffold run -p dev

prod環境も構築しましょう。

ここで一つAIチーム流で、NamespaceはKustomizeに含めず、外部で作成しています*2。こうすることで、なんらかの間違いで接続先の環境が違っていた場合、namespaceが無いのでデプロイが安全に失敗するようにしています。

というわけで、prod環境のNamespaceを作成

kubectl create namespace tutorial-prod

skaffold runで一括デプロイ!

skaffold run -p prod

これで、dev, prodの2環境の構成ファイルをDRYに保ち、簡単にデプロイ出来るようになりました。

お片付け

チュートリアルは以上です!

無駄な料金を発生させないために、namespaceごと削除しておきましょう。

kubectl delete namespace tutorial-dev tutorial-prod

プロジェクトも削除。

gcloud projects delete $PROJECT_ID

おわり

如何でしたでしょうか。入門用チュートリアルはネット上にも多くありますが、GKEやKubernetesの世界は広く、ちょうど自分たちが欲しいスコープ、欲しい手順のハンズオンがあってもいいかなと思って書いてみました。

お役に立てば幸いです。

We are hiring !!

ここまで読み進めていただきありがとうございます。これでいつでもAIチームでロケットスタートできますね!

もしご興味ありましたらこちらのページからどうぞ。 カジュアル面談・ご応募お待ちしております!

エンジニア採用ページはこちら

jobs.m3.com

カジュアル面談もお気軽にどうぞ

jobs.m3.com

インターンも常時募集しています

open.talentio.com

*1:今回は簡単のために同一クラスタ上でnamespaceだけで環境を分離しています。実際にはdev環境とprod環境はプロジェクトレベルで分離されて別クラスタにします

*2:実際にはterraformを使って他のk8s外のリソースと一緒に作成しています。おかげでterraform destroyでk8sリソースも含めてきれいに消すことが出来たりもします。