この記事は、NTT Communications Advent Calendar 2024 7日目の記事です。
こんにちは!イノベーションセンターの外村です。 日頃は twada 塾 と呼ばれる社内向けソフトウェア開発研修を運営しています。最近チームを異動し、 ノーコード・ローコードで 時系列分析 AI を作ることができる Node-AI の開発にもジョインしました。
本記事ではフルマネージド開発環境である Google Cloud の Cloud Workstations についてその特徴と構築方法を述べたいと思います。
はじめに
みなさん、新しい部署やプロジェクトに参加したらまず何をしますか? そう、開発環境の構築ですよね。 でも、環境構築はなかなか大変です。 まずは開発用の端末を購入するところから始まります。 そして、端末が届いて意気込んでセットアップを始めても、手順がやたら複雑だったり、 最新バージョンではうまく動かないことがあります。結果、構築に1週間近くかかることも…。 さらに、「やっと動いた!」と思って開発を始めても、数週間や数ヶ月後に先輩社員の環境と動作が違うことに気づく…なんてこと、 経験ありませんか?そのうえ、当時は快適だった端末も数年後には性能不足や故障で交換が必要になったりするんですよね。
このような課題を解決してくれるフルマネージド開発環境がここ数年注目を集めています。
フルマネージド開発環境
フルマネージド開発環境とは、開発者が開発に専念できるように 開発環境の設定や管理をクラウドプロバイダーがサポートするサービスです。 これにより環境構築の手間から解放され、端末管理の負担が軽減されます。 フルマネージド開発環境は以下の特徴を持ちます。
- 即時利用可能: 開発環境を即座にセットアップできる。
- 端末性能に依存しない: 開発環境はクラウド上で動作するため、低スペックの端末でも快適に利用可能。
- スケーラブル: 必要な分だけの性能を拡張・縮小可能。
- 一貫性のある環境: チームメンバー全員が同じ設定の環境で作業できる。
今回は私が日頃利用している Google Cloud の Cloud Workstations について、特徴やその構築方法をお伝えします。
Cloud Workstations の概要
Cloud Workstations は、Google Cloud が提供するフルマネージド開発環境です。 事前に開発環境の設定や構成を定義しておき、利用者に払い出すことができます。 ブラウザベースの IDE やローカルのコードエディタ(IntelliJ IDEA Ultimate、VS Code など)を通じてアクセスできます。 Google Cloud ならではの特徴として、Google Cloud の IAM を用いたアクセス制御や、 Gemini Code Assist など、他の Google サービスとの連携が円滑になっています。
Cloud Workstations の全体像は以下のようになっています。
- Workstation Cluster: 特定のリージョンに置く Workstation のグループです。(Cluster というと Kubernetes Cluster を想像しますが、関係はありません)
- Workstation Config: 「Workstations の構成」と表現されています。開発環境のテンプレート。開発環境の性能や利用するコンテナイメージなどを定義します。
- Workstation: 実際にアクセスする開発環境。実態は Google Compute Engine (GCE) のインスタンスです (1 Workstation につき 1 GCE インスタンスが立ち上がってきます)。
Cloud Workstations の構築
ここで構築する Workstation のポイントをあらかじめ決めておきます。
- ポイント1: Workstation の GCE インスタンスについて、IP 予約料金の削減やセキュリティを考慮してパブリック IP アドレスが付与されないようにする。
- ポイント2: Workstation 利用者にはそれぞれの Workstation を払い出す。払い出される Workstation は同一の環境とする。
- ポイント3: Workstation 利用者は、自分に払い出された Workstation のみ閲覧・利用できる (他者のものを閲覧・確認できない)。
- ポイント4: Workstation の開発環境は、カスタマイズしたコンテナイメージにより構築される。
ポイント1 については ドキュメント に記載があり、 ネットワークで限定公開の Google アクセス、もしくは Cloud NAT を構成する必要があります。今回は Cloud NAT を構成します。 また、ポイント4 については、Google Artifact Registory にプッシュしたコンテナイメージが利用されるように設定します。 この方法ではカスタマイズしたコンテナイメージだけでなく、プルするためのサービスアカウントも必要になります。 コンテナイメージのプッシュについては手動で行うこととします。 構成は以下のイメージですね。
これらの構成を効率よく管理するために、インフラストラクチャの設定をコードとして管理する Infrastructure as Code(IaC) の考え方を採用します。 そのためのツールとして、Terraform を用います。 Terraform は HashiCorp が提供するオープンソースのインフラストラクチャ管理ツールで、クラウドリソースの構成や管理を宣言的なコードで実現できます。 これを使えば、ネットワーク設定やコンテナイメージリポジトリなどを効率よく構築し、設定の再現性や変更管理が容易になります。
Terraform を用いた Google Cloud の設定
それでは Google Cloud の設定を Terraform で実施していきましょう。
構築にあたり、事前に自身のアカウントに対して IAM で オーナー
と サービス アカウント トークン作成者
のロールを付与しておきます。
また、対象プロジェクトに gcloud auth application-default login
でログインしておきましょう。
Terraform の Provider (特定のクラウドプロバイダーやサービスと連携するためのプラグイン) については、google
と google-beta
を用います。
手元のツールのバージョンは以下です。
Google Cloud SDK 495.0.0 Terraform v1.9.2 on darwin_arm64 + provider registry.terraform.io/hashicorp/google v6.11.2 + provider registry.terraform.io/hashicorp/google-beta v6.11.2
Cloud Storage に tfstate を保存するためのバケットを作成しましょう。
gcloud storage buckets create gs://workstations-tfstate-bucket \ --location=asia-northeast1 \ --default-storage-class=standard
バケットが作成されたことを確認しておきます。
$ gcloud storage buckets list --format="table(name, location)" NAME LOCATION workstations-tfstate-bucket ASIA-NORTHEAST1
ここからは Terraform 用のファイル作成をします。 Terraform 用のフォルダを用意して provider.tf、backend.tf、variables.tf を作成してください。 なお、variables.tf には各種パラメータ定義しています。必要に応じて変更してください。
# provider.tf terraform { required_providers { google = { source = "hashicorp/google" version = "~> 6.1" } } required_version = ">= 1.3.0" } provider "google" { project = var.project_id region = var.region } provider "google-beta" { project = var.project_id region = var.region }
# backend.tf terraform { backend "gcs" { bucket = "workstations-tfstate-bucket" prefix = "terraform/state" } }
# variables.tf variable "project_id" { default = "your project ID" # ご自身が利用するプロジェクト ID に変更してください。 } variable "region" { default = "asia-northeast1" # リージョンを変更したい場合は変更してください。 } variable "workstation_cluster_machine_type" { default = "e2-standard-4" # 利用者に払い出される Workstations のマシンスペック (GCE のスペックを参照してください) } variable "workstation_cluster_persistent_disk_size_gb" { default = 100 # 利用者に払い出される Workstations の SSD ディスクサイズ (GB) } variable "user_accounts" { description = "List of user email accounts that need Workstations" type = list(string) default = [ # 利用者の Google アカウントを列挙してください "ws.user01@gmail.com", "ws.user02@gmail.com", "ws.user03@gmail.com", ] }
肝である main.tf をステップに分けて作成します。 まずは main.tf に Workstations が立ち上げるネットワーク部分を記載していきましょう。
# main.tf # VPC ネットワーク resource "google_compute_network" "workstation_network" { name = "workstation-cluster" auto_create_subnetworks = false project = var.project_id } # サブネット resource "google_compute_subnetwork" "workstation_subnet" { name = "workstation-cluster" ip_cidr_range = "10.0.0.0/24" network = google_compute_network.workstation_network.name private_ip_google_access = true } # Cloud NAT 用 IP アドレス resource "google_compute_address" "workstation_nat_ip" { name = "workstation-nat-ip" } # Cloud Router resource "google_compute_router" "workstation_router" { name = "workstation-router" network = google_compute_network.workstation_network.name } # Cloud NAT resource "google_compute_router_nat" "workstation_nat" { name = "workstation-nat" router = google_compute_router.workstation_router.name nat_ip_allocate_option = "MANUAL_ONLY" source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES" nat_ips = [google_compute_address.workstation_nat_ip.self_link] min_ports_per_vm = 64 udp_idle_timeout_sec = 30 tcp_established_idle_timeout_sec = 1200 }
上記はポイント1 に関連する記述ですね。 この記述は、workstation-router というルータを作成し、同時に Cloud NAT が有効化される状態を定義しています。 ここではサブネットを 10.0.0.0/24 としています。 内部 IP アドレスは Workstation Cluster や Workstation に払い出されるので、 サブネットは余裕をもった大きさにしておくと良さそうです。
つづいて以下の記述を追加します。
# main.tf へ追記 # コンテナイメージを格納するためのリポジトリ resource "google_artifact_registry_repository" "workstation_image_repository" { repository_id = "workstation-image" format = "DOCKER" location = var.region description = "Artifact Registry for Cloud Workstations base images" } # コンテナイメージをプルするためのサービスアカウント resource "google_service_account" "workstation_cluster_service_account" { account_id = "workstation-cluster-sa" display_name = "Workstation Cluster Service Account" } # サービスアカウントへリポジトリ読み取り権限を付与 resource "google_project_iam_member" "workstation_cluster_service_account_roles" { project = var.project_id role = "roles/artifactregistry.reader" member = "serviceAccount:${google_service_account.workstation_cluster_service_account.email}" }
上記はポイント4 に関連する部分です。 Artifact Registory に workstation-image というリポジトリを作成しています。 加えて workstation-cluster-sa というサービスアカウントを作成し、Artifact Registory の読み取り権限を付与しています。
つづいての追記内容はこちら。
# main.tf へ追記 # Workstation Cluster resource "google_workstations_workstation_cluster" "workstation_cluster" { workstation_cluster_id = "workstation-cluster" provider = google-beta network = google_compute_network.workstation_network.id subnetwork = google_compute_subnetwork.workstation_subnet.id location = var.region labels = { environment = "development" } } # Workstation Config resource "google_workstations_workstation_config" "workstation_config" { provider = google-beta workstation_cluster_id = google_workstations_workstation_cluster.workstation_cluster.workstation_cluster_id workstation_config_id = "workstation-config" location = var.region idle_timeout = "3600s" # 1 hour running_timeout = "43200s" # 12 hours host { gce_instance { machine_type = var.workstation_cluster_machine_type disable_public_ip_addresses = true service_account = google_service_account.workstation_cluster_service_account.email # リポジトリ読み取り用サービスアカウント } } persistent_directories { mount_path = "/home" gce_pd { size_gb = var.workstation_cluster_persistent_disk_size_gb fs_type = "ext4" disk_type = "pd-ssd" reclaim_policy = "DELETE" } } container { image = "${var.region}-docker.pkg.dev/${var.project_id}/${google_artifact_registry_repository.workstation_image_repository.repository_id}/workstation-custom:latest" # Workstations コンテナイメージ (リージョン-docker.pkg.dev/プロジェクトID/workstation-image/workstation-custom:latest) } }
上記では Workstation Cluster と Workstation Config を定義しています。 とくに Workstation Config にはいくつかのパラメータを深掘りします。
idle_timeout
と running_timeout
どちらもリソース連続稼働によるコストを削減するための設定です。
idle_timeout
は Workstations が操作を受け付けなかったときに一時停止されるまでの時間、
running_timeout
は Workstations が一度立ち上がってから VM がシャットダウンされるまでの時間を定義できます(0にすることで機能をオフにもできます) 。
host
Workstations が立ち上がるための GCE インスタンスのスペックや設定を記述します。
この中の service_account
はポイント4 に関連しており、
先ほど記載したコンテナイメージプル用のサービスアカウントが参照されるように記載しています。
persistent_directories
GCE インスタンスにマウントされる永続化ディスクの定義です。/home
にマウントしています。
ここは注意点ですが、Workstations がシャットダウンされると GCE インスタンスは削除されます。
再起動時に GCE インスタンスは再作成されますが、以前に作成したファイルなどは消えてしまうのです。
/home
に永続化ディスクをマウントすることで、再起動時の GCE インスタンスでも /home
での作業を引き継ぐことができます。
ただし reclaim_policy
を DELETE
に設定しているため、
Workstation が削除されたときに永続化ディスクも削除される設定になっている部分は意識しておきましょう。
container
Workstation のコンテナイメージです。ポイント4 に関連しています。 以前のステップで記載した workstation-image リポジトリを参照しています。 このリポジトリの workstation-custom イメージの latest を使用する設定になっています。 (workstation-custom というイメージは Terraform の手順とは別でプッシュします)
それでは、最後の追記内容です。
# main.tf へ追記 # アカウント の Workstation resource "google_workstations_workstation" "workstations" { provider = google-beta for_each = toset(var.user_accounts) workstation_id = "workstation-${replace(replace(each.key, "@", "-"), ".", "-")}" workstation_cluster_id = google_workstations_workstation_cluster.workstation_cluster.workstation_cluster_id workstation_config_id = google_workstations_workstation_config.workstation_config.workstation_config_id location = var.region lifecycle { replace_triggered_by = [google_workstations_workstation_config.workstation_config.id] # Workstation Config } } # アカウントに対して workstations オペレータ閲覧者の権限を付与 resource "google_project_iam_member" "workstation_operation_viewer" { for_each = toset(var.user_accounts) project = var.project_id role = "roles/workstations.operationViewer" member = "user:${each.key}" } # アカウントごとに、自分の Workstations のみに対して workstations ユーザ権限を付与 resource "google_workstations_workstation_iam_member" "workstation_iam_user" { provider = google-beta for_each = google_workstations_workstation.workstations workstation_id = each.value.id workstation_cluster_id = google_workstations_workstation_cluster.workstation_cluster.workstation_cluster_id workstation_config_id = google_workstations_workstation_config.workstation_config.workstation_config_id location = var.region role = "roles/workstations.user" member = "user:${each.key}" }
上記では、Workstation 作成とアカウントへの権限付与を実施しています。ポイント2 および ポイント3 に関連する記述ですね。
for_each
を用いて variables.tf に定義していた user_accounts
のアカウントそれぞれに Workstation や権限付与の処理を行っています。
Workstation に対しては lifecycle
の replace_triggered_by
を設定しておきました。
Workstations にすでに参照されている Workstation Config を更新する際、依存関係が存在するせいで更新ができないと怒られる場合があります。
トリガーに Workstation Config を指定することで、Workstation Config の更新とともに Workstation の再作成され、更新が可能になります。
Workstation が一度削除されることになります (現状の設定だと永続化ディスクも一度削除されます) が、管理上都合がよいので設定しています。
ポイント3 について、あるユーザに自分の Workstation のみを使わせたい場合は以下の権限整理をすることで実現できます。
- プロジェクトに対して
roles/workstations.operationViewer
- Workstation に対して
roles/workstations.user
必要なファイルは完成しました!
最後に terraform apply
をしてあげれば Google Cloud の設定は完了です。
Workstation Cluster には 20分以上かかることもあるため、実行時間が長くても焦らないでください (自分は 30分かかりました)。
Workstation のためのコンテナイメージのビルドとプッシュ
Google Cloud の設定は完了しましたが、最後にもう一仕事残っています。 Workstation のためのコンテナイメージがまだでしたね。 公式ドキュメント を参考に進めていきましょう。
まずベースイメージですが、こちらも 公式 により公開されています。
ここでは VS Code ベースの us-central1-docker.pkg.dev/cloud-workstations-images/predefined/code-oss:latest
を使用します。
今回はこのイメージを拡張し、新たに Terraform がインストールされたイメージを作成します。
Dockerfile を以下のように作成します。
# Dockerfile FROM us-central1-docker.pkg.dev/cloud-workstations-images/predefined/code-oss:latest # 環境のアップデートと Terraform のインストール RUN apt-get update && \ apt-get install -y gnupg software-properties-common curl && \ curl -fsSL https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg && \ echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/hashicorp.list && \ apt-get update && \ apt-get install -y terraform && \ terraform -version
さらに拡張したい場合は、上記にならってレイヤを追加していけばよいです。
また、処理が多い場合はシェルスクリプトファイルに分けたくなってくるかもしれません。
その場合、コンテナの /etc/workstation-startup.d
に
シェルスクリプトを追加しておくことで 辞書順に実行を行ってくれる みたいですね。
それではコンテナイメージのビルドからプッシュまで行っていきます。
REGION
や PROJECT_ID
はご自身の環境に合わせて変更してください。
# 変数の定義 set REGION "asia-northeast1" # リージョン set PROJECT_ID "Your Porject ID" # プロジェクトID set REPOSITORY_NAME "workstation-image" # Artifact Registry リポジトリ名 set IMAGE_NAME "workstation-custom" # Docker イメージ名 set ADDITIONAL_TAG "latest" # 追加タグ # 小文字の UUID を生成 (ハイフンなし) set VERSION (uuidgen | tr -d '-' | tr 'A-Z' 'a-z') # イメージ タグの構築 set IMAGE_URI "$REGION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY_NAME/$IMAGE_NAME:$VERSION" set ADDITIONAL_URI "$REGION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY_NAME/$IMAGE_NAME:$ADDITIONAL_TAG" # Docker ビルド docker build --platform linux/amd64 -t "$IMAGE_URI" . # タグ追加 docker tag "$IMAGE_URI" "$ADDITIONAL_URI" # Docker Push docker push "$IMAGE_URI" docker push "$ADDITIONAL_URI"
latest のタグを持つイメージがプッシュされることを確認しておきましょう。
gcloud artifacts docker images list "$REGION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY_NAME/$IMAGE_NAME" \ --include-tags --format="table[createTime, tags](createTime, tags)" CREATE_TIME TAGS 2024-11-27T09:32:02 9f8f7c0d69c04be095d2c4ff5d4cfb1a,latest
構築が完了しました! ご自身の Workstation が起動することを確認してください。 また、ターミナルを用いるとインストールされた terraform を確認できると思います。
おわりに
フルマネージド開発環境として、Cloud Workstations を構築しました。 ここでまとめた構築方法をもとに、今後は twada 塾の受講生向け開発環境の払い出しを進めていく予定です。
それでは、明日の記事もお楽しみに!