こんにちは。AI・機械学習チーム(以下AIチーム)チームリーダー、兼、エンジニアリンググループゼネラルマネージャーの横本(@yokomotod)です。
AIチームでは開発したMLプロダクトの実行基盤としてGoogle Kubernetes Engine(以下GKE)を採用しています。デプロイ関連コードのテンプレートも整備され、新しいプロダクトをスタートさせるときはGKE初心者でも簡単にデプロイまで持っていけるようになっています。
しかし自動生成は一方で、なにがどうなってデプロイされているのかブラックボックスのままになってしまいがちという問題もあります。
そこで今回は、チームメンバーへのオンボーディング、そしてKubernetesを触ってみたい人の参考になればと期待して、 ゼロからGKEにデプロイするハンズオン形式のチュートリアルを作ってみました。
What's this
このチュートリアルでは
- GKEでクラスタを作成
- サンプルアプリケーションのDockerイメージをbuild、push
- サンプルアプリケーションのクラスタへのデプロイ
- dev/prodの2環境にデプロイするための改良
を行います。
チュートリアル中で使うファイルなどは以下のgithubレポジトリでも公開しています
前提条件
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が提供するサンプルアプリケーションをビルドします。
実はこのサンプルのビルド済みイメージも公開されていて、公式ドキュメントではそちらを使っています。が、ここではビルドしてプッシュする部分もやってみましょう。
ソースコードを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
ログも見てみましょう。
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チームでロケットスタートできますね!
もしご興味ありましたらこちらのページからどうぞ。 カジュアル面談・ご応募お待ちしております!