初めに
こんにちは! 筋肉エンジニアの渡邉です。最近はGCP/GKEについて勉強しています。
今回はGitHubへのPushをトリガーにCloudBuildを起動し、プライベートエンドポイントのみのGKE(Google Kubernetes Engine)へデプロイする基盤を作りましたので、共有したいと思います。
GCPリソースはTerraformで作成しています。CloudBuildとGitHubの連携は一部画面による紐づけが必要になるので、手動でCloudBuildを作成した後、terraform importでコード管理するようにしました。
デプロイフロー
デプロイフローは以下の流れになります。
- ローカルでアプリケーションコードの修正
- ローカルで修正をCommit、GitHubへPush
- GitHubへPushされたことをトリガーにCloudBuildが起動
- CloudBuildでコンテナをビルド、Artifact RegistoryにコンテナイメージをPush
- CloudBuildからGKEへコンテナイメージをデプロイ
アプリケーションコード
Goで記述されたアプリケーションを書きました。
リクエストを投げると、Hello world!とVersionとHostnameをレスポンスします。
こちらはディレクトリ構成です。
├── src |
package main |
GKEにデプロイするアプリケーションはコンテナのため、Dockerfileを作成します。
Dockerfileは以下の通りです。
# goバージョン |
GKEのアーキテクチャ
クラスタ構成
- クラスタバージョン:1.23.13-gke.900(リリースチャンネルをSTABLEで構築した時のデフォルトバージョン)
- リージョンクラスタ:本番環境で利用することを考慮して可用性を高くしたいため
- VPCネイティブクラスタ:GKE バージョン 1.21.0-gke.1500 以降のすべてのクラスタはVPCネイティブクラスタがデフォルトのネットワークモードのため
- Standardクラスタ:インフラチームがGKEクラスタを管理することを想定
- 限定公開クラスタ:Control Planeへのアクセスがプライベートエンドポイントのみの「パブリックエンドポイントアクセスが無効」で構成しています。ノードには内部IPアドレスしか付与されず、Control Planeへのアクセスは内部ネットワークからのみしかアクセスできません。
※この構成は制限が厳しいのでセキュリティ要件的に問題なければ、「パブリック エンドポイント アクセスが有効、承認済みネットワークが有効」で構築してもよいです。
Manifest
GoのアプリケーションコンテナをGKE上にPodとして建てたいので、deploymentを作成します。containers.imageにはArtifact Registryに保存されているイメージ名を指定します。containerPortにはDockerfileのEXPOSEで指定した8080を指定します。
apiVersion: apps/v1 |
deploymentを作成するだけでは、podに対して外部からアクセスできないのでServiceとIngressを作成します。
Ingressを使用するためには、ServiceのtypeをNodePortにしなければならないのでNodePortで構築します。ports.portに80を指定し、ports.targetPortにdeploymentのports.containerPortで指定した8080を指定します。
80番ポートでServiceにアクセスされ、8080番ポートのdeploymentの各Podにルーティングされる仕組みです。
apiVersion: v1 |
Ingressのmanifestファイルのannotationsには以下を記します。
- kubernetes.io/ingress.class: “gce”:外部ロードバランサの作成します
- kubernetes.io/ingress.allow-http: “false”:クライアントとHTTP(S)ロードバランサ間のすべての通信をHTTPSに強制します
- kubernetes.io/ingress.global-static-ip-name: “loadbalancer-external-ip-address”:事前にterraformで構築していた静的外部IPアドレスを外部ロードバランサに設定します
- networking.gke.io/managed-certificates: “hello-go”:クライアントとからのHTTPS通信を実現するためGoogleマネージド証明書をIngressに適用します(後述のmanaged-certificate.yamlで作成)
Ingressのbackendには、先ほど作成したServiceを指定し、port.numberはserviceのポート80番を指定して紐づけます。
apiVersion: networking.k8s.io/v1 |
Ingressに適用させるGoogleマネージド証明書を作成します。
ドメインはterraformで作成済みの静的外部IPアドレスにフリーなワイルドカードDNSのnip.ioを設定しました。
apiVersion: networking.gke.io/v1 |
manifestファイルの適用自体は踏み台サーバから実行しています。
ここまで構築するとドメインに対してアクセスすると以下のキャプチャのように
- Hello, world!
- Version:1.0.0
- Hostname: hello-go-deployment-c58cf7b66-fwgbb
がブラウザ上に返却されます。
CloudBuildの作成
現状の状態だと
- アプリケーションコードの変更を行う(ローカル)。
- GitHubへ変更をPushする(ローカル)。
- docker buildコマンドの実行を行いイメージを作成する(ローカル)
- docker pushコマンドを実行し、Artifact RegistoryへイメージをPushする(ローカル)
- 踏み台サーバにログインして
kubectl apply -f deployment.yaml
コマンドを実行してデプロイ
となるため、デプロイまでに非常に手間がかかります。
GitHubへのPushをトリガーに上記の手順の3~5を自動化したいため、CloudBuildを利用します。
CloudBuildトリガーの作成
GitHubとの連携
CloudBuildとGitHub(プライベートリポジトリ)を連携するためには画面での認証手続きが生じるため、一旦Terraformでは作成せず手動で設定を行いました。
手動で設定が完了した後、terraform importコマンドを利用してコード管理するようにします。
Google Cloudコンソール画面から「Cloud Build」をクリック→「トリガー」をクリック→「トリガーを作成」をクリックします。
トリガーの作成画面で- 名前:sample-build
- リージョン:asia-northeast1
- イベント:ブランチにpushする
を入力し、ソース:「新しいリポジトリに接続」をクリックします。
リポジトリに接続画面で
- ソースを選択:GitHub (Cloud Build GitHubアプリ)
を選択し、「続行」をクリックする。
Sign in to GitHub to continue to Google Cloud Buildの画面で
- Username or email address:自身のGitHubアカウントのユーザ名
- Password:自身のGitHubアカウントのパスワード
を入力し、「Sign in」をクリックします。
Google Cloud Build by Google Cloud Build would like permission toの画面の「Authorize Google Cloud Build」をクリックします。
リポジトリを選択画面の「GOOGLE CLOUD BUILDのインストール」をクリックします。
Install Google Cloud Buildの画面から
- Only Select repositories:Cloud Buildと連携したいリポジトリ
を入力し、「Install」をクリックします。
- GitHubアカウント:自身のGitHubアカウント
- リポジトリ:Cloud Buildと連携したいリポジトリ
を入力し、チェックボックスにチェックを入れて「接続」をクリックします。
ここまでの設定で、Cloud Buildと自身のGitHubリポジトリを連携させることができます。
terraform importの実行
作成したトリガーの「実行」の隣をクリックし、リソースパスをコピーをクリックします(terraform importで利用します)。。
terraform実行環境にて、terraform importを実行し、手動で作成したCloud Buildトリガーをコード管理できるように設定します。
xxxxxxxx@xxxxxxxx:~/cloud-provider/gcp/gke$ terraform import google_cloudbuild_trigger.trigger projects/xxxxxxxxx/locations/asia-northeast1/triggers/f51e2b94-2be8-4ec6-a983-72ded1f69bb7 |
terraform import後
- サービスアカウントの設定
- ビルド実行時に必要なビルド構成ファイルのパスを設定
- ビルド構成ファイルに必要な環境変数
を指定してterraform applyをして適用します。
CloudBuildからGKE Control Planeへの接続
今回、Control Planeへのアクセスにプライベートエンドポイントのみの「パブリックエンドポイントアクセスが無効」でGKEを構成しているため、CloudBuildからGKE Control Planeへの接続も内部ネットワーク経由でプライベートエンドポイントに対して行わなければいけません。
(GKEをパブリック エンドポイント アクセスが有効、承認済みネットワークが有効で構成している場合は、CloudBuildからGKE Control Planeへのアクセスもパブリックエンドポイントに対して行う必要がありますが、CloudBuildの外部IPはユーザで指定できずビルド環境ごとに変わってしまい、承認済みネットワークが定義できないので、少しトリッキーなやり方をしないとアクセスができないです)。。
CloudBuildからGKE Control Planeのプライベートエンドポイント接続を内部ネットワークを経由するようにしたいので、CloudBuildをPrivate Poolを利用するように作成します。
Cloud Build プライベート プールを使用した限定公開 Google Kubernetes Engine クラスタへのアクセスはGoogle Cloudのアーキテクチャセンターにも記載されているので、詳しくはこちらの記事をご覧ください。
ネットワークアーキテクチャ
CloudBuildからGKEへデプロイするためのネットワークアーキテクチャの完成図になります。
それぞれ詳細を見ていきましょう。
CloudBuild Private Poolとsample-build-vpc間
Private Poolは、サービスプロデューサーネットワークと呼ばれる Google 所有の Virtual Private Cloud ネットワークでホストされます。サービスプロデューサーネットワークだけでは、GKE Control Planeへアクセスするルートがないので、Private Poolとプライベート接続する用のVPC(sample-build-vpc)を別途作成します。
Private Poolとプライベート接続する用のVPCには、名前付きIP範囲を指定できるので、192.168.3.0/24を設定します。private poolが、このIPアドレス範囲からGKEのControl Planeにトラフィックを送信できるので、こちらのIP範囲をGKEの承認済みネットワークに定義します。
名前付きIP範囲には以下のIP範囲は避けるように公式ドキュメントに記載されているので、注意しましょう。
※Cloud Build は、Docker ブリッジ ネットワークの IP 範囲 192.168.10.0/24 を予約します。プロジェクト内のリソースに IP 範囲を割り当てる際、Cloud Build ビルダーがこれらのリソースにアクセスする場合は、192.168.10.0/24 以外の範囲を選択することをおすすめします。
この時サービスのプライベート接続でカスタムルートのエクスポートは「有効」に設定してください。
この設定により、のちにPrivate PoolにGKE Control PlaneのCIDR(192.168.64.0/28)が広報されます。
GKE Control Planeとmy-stg-environment-vpc間
GKE Control Planeとmy-stg-environment-vpcを接続しているVPC Peeringのカスタムルートのエクスポートを有効化します。
これにより、のちにHA VPN Gatewayを通じて広報されてきたPrivate PoolのCIDR(192.168.3.0/24)をGKE Control Plane側に広報できます。
HA VPNの作成
CloudBuildのprivate poolのCIDR(192.168.3.0/24)をmy-stg-environment-vpcに、GKE Control PlaneのCIDR(192.168.64.0/28)をsample-build-vpcにそれぞれ広報したいので、my-stg-environment-vpcとsample-build-vpcをHA VPNで接続します。
VPC PeeringでそれぞれのVPCを接続することもできますが、VPC Peeringは推移的ピアリングをサポートしていないため、CloudBuildのprivate poolのCIDR(192.168.3.0/24)とGKE Control PlaneのCIDR(192.168.64.0/28)をそれぞれのVPCへ広報できません。
まず、HA VPN Gatewayを作成します。
my-stg-environment-vpcに「ha-vpn-my-stg-environment-tky-gw」、sample-build-vpcに「ha-vpn-sample-build-vpc-tky-gw」を作成します。
次に、それぞれのHA VPN Gatewayに対応するVPN Tunnelを作成します。
ha-vpn-my-stg-environment-tky-gwに
- 「ha-vpn-my-stg-environment-tky-tunnel-0」
- 「ha-vpn-my-stg-environment-tky-tunnel-1」
を
ha-vpn-sample-build-vpc-tky-gwに
- 「ha-vpn-sample-build-vpc-tky-tunnel-0」
- 「ha-vpn-sample-build-vpc-tky-tunnel-1」
を作成します。
次に、それぞれのHA VPN Tunnelに対応するCloud Routerを作成します。
- ha-vpn-my-stg-environment-tky-rt
sample-build-vpcにGKE Control PlaneのCIDR(192.168.64.0/28)を広報したいので、アドバタイズされたIP範囲に192.168.64.0/28を設定します。
- ha-vpn-sample-build-vpc-tky-rt
my-stg-environmentにCloudBuild Private PoolのCIDR(192.168.3.0/24)を広報したいので、アドバタイズされたIP範囲に192.168.3.0/24を設定します。
ここまでの設定で、CloudBuildからGKEへデプロイするためのネットワークアーキテクチャの完成になります。
デプロイの実施
上記でCloudBuildから内部ネットワークを経由してGKE Contorl Planeへ通信できるルートができたので、実際にデプロイを実施してみましょう。
CloudBuild.yaml
CloudBuildでビルドを実行するためには、ビルド構成ファイルを作成する必要があります。
ビルド構成ファイルには、各ビルドSTEPごとに実行したい処理を記述します。
ビルド構成ファイルに記載しているプロパティの内容は以下の通りです。
キー | 内容 |
---|---|
name | タスクを実行するコンテナイメージを指すように指定します |
id | ビルドステップの識別子を指定します |
args | nameに指定したイメージ実行時に渡す引数を記述します |
dir | ビルド実行時の作業ディレクトリを指定します。 |
env | ビルド実行時に使用される環境変数を指定します。 |
以下のビルド構成ファイルの内容に従ってビルドが実行されていきます。
steps: |
こちらのcloudbuild.yamlをGitHub上のルートディレクトリ内に保存します。
├── api_service.tf |
アプリケーションコードの修正
Version: 1.0.0 → Version: 2.0.0へ修正してmainブランチにPushします。
xxxxxx@xxxxxx:~/cloud-provider/gcp/gke$ git diff src/cmd/main.go |
CloudBuildのビルド画面
GitHubにPushされたことをトリガーにCloudBuildのビルドが実行されます(過去にビルドに苦戦したビルド履歴が残っていますね(笑))
最新のビルド履歴(9ee5d0a6)をクリックすると、詳細が確認できます。cloudbuild.yamlに記述したビルドステップごとにビルドが進行していきます。各ビルドステップごとのログも「ビルドログ」から確認できます。
正常終了するとすべてのステップでグリーンになります。
CloudBuildのビルドが正常終了したので、再度ドメインに対してアクセスをします。
すると、変更を加えたVersion:2.0.0の状態でレスポンスが返却され、デプロイが正常に完了したことを確認できました。
Podのライフサイクル
最後のビルドステップでGKEへのデプロイが行われます。
踏み台サーバからkubectl get pods -w
を実行することでGKE上のPodの状態を確認できます。
ビルドしたイメージをPullしてデプロイされることで、もともと存在していたPodが次々と終了し、新しいPodが作成されていることがわかります。
xxxxxx@xxxxxx@tky-bastion:~$ kubectl get pod -w |
最後に
今回はCloudBuildを利用したGKEへの継続デプロイ基盤について記載しました。GKEは限定公開クラスタでControl Planeへのアクセスがプライベートエンドポイントのみの「パブリックエンドポイントアクセスが無効」構築しているため、Cloud BuildからGKEのControl Planeへのアクセスを成功させるためのネットワーク構成が複雑になってしまいましたが、限定公開クラスタで「パブリック エンドポイント アクセスが有効、承認済みネットワークが無効」で構築すればCloudBuildでPrivate PoolやHA VPNで作成することもなくパブリックエンドポイント経由でControl Planeへアクセスができます。セキュリティ要件次第でデプロイフローやアーキテクチャなどは変更してください。
まだまだGKEの知らない機能がたくさんあるので、引き続きインプットとアウトプットをしていきたいと思います。