
IPアドレス管理(IPAM)でネットワークと機器を可視化する
最近、IPアドレスレンジの衝突が原因でピアリングをはじめとするネットワーク関連のトラブルを訴えるお客様が増えています。これは、組織全体でIPアドレスを計画的に管理すべきだという明確なサインです。
共有スプレッドシートでIPを管理することもできますが、専用のソフトウェアツールも数多くあります。本記事では、人気のオープンソースIPアドレス管理(IPAM)ツールNetboxを、Google Cloud Platform(GCP)上でクラウドネイティブに動かす方法を紹介します。
従来のスタック
従来、Netboxは1台または複数台の仮想マシン上で動かし、その前段にWebサーバーを配置する構成が一般的でした。コミュニティが管理するDockerイメージもありますが、提供されている手順はdocker composeでの実行のみです。とはいえ、このアーキテクチャは多くの企業が構築・運用しているアプリケーションと共通点が多く、パブリッククラウドへの移行例としても格好の題材です。

出典:Netbox — 標準的なNetboxインストール構成
クラウドネイティブな設計
そこで、Dockerイメージの仕組みや依存関係、設定パラメータを調べ上げ、マネージドサービスのみでGCPにデプロイしてみることにしました。パブリッククラウドへ移行する際の「move and improve(移行して改善する)」あるいは「rip and replace(置き換える)」という二つのアプローチを示す例として参考にしていただければと思います。

マネージドサービスを活用したGCP上のNetbox構成(再設計版)
アプリケーションの構成要素
- Netboxアプリケーション(Djangoフレームワークを用いたPythonアプリ)
- PostgreSQLデータベース(Cloud SQL)
- Redis(Cloud Memorystore)
設計上の判断
- マネージドのデータベースとキャッシュ(Cloud SQL、Cloud Memorystore)
- データベースとキャッシュはプライベートIPのみで運用(Private Service Access)
- データベースのホスト名はプライベートDNSで解決(Cloud DNS)
- シークレットはSecret Managerに格納
- サーバーレスのコンテナランタイム(Cloud Run、Artifact Registry)
- TLS対応のグローバルロードバランサー(HTTP(S) Load Balancing、Managed Certificate)
- WAFファイアウォール(Cloud Armor)
多くの組織がつまずくのが、マネージドサービスやサーバーレスアプリをプライベートIPで接続する部分です。本例では、VPCネットワーク内でプライベートレンジを予約し、それをマネージドサービスに割り当てて接続のブリッジを作る方法を示しています。
Cloud DNSは、アプリがデータベースに接続するためのプライベートホスト名を提供する目的で使います。こうしておけば、将来データベースを差し替えたりフェイルオーバーが必要になったりした場合でも、DNSレコードを更新するだけで済み、アプリ側は同じドメインを参照し続けられます。理屈の上ではDNSフォワーディングを使ってパブリックドメインに接続することも可能ですが、内部利用では不要なのでexample.comを使用しました。
bastion(踏み台)VMは必須ではありませんが、構築中の接続テスト用に1台立ち上げました。本番運用であれば、外部IPを持たせず、サイズ1のマネージドインスタンスグループ(MIG)でデプロイするのが通例です。
セキュアでロードバランス対応のWebアプリケーション

全体の構成をわかりやすく示すため、個人ドメインを使い、グローバルロードバランサーに割り当てた静的IPアドレスに対して「Aレコード」を登録しました。マネージド証明書は自動でプロビジョニングされます。

セキュリティを強化するため、ロードバランサーにCloud Armor(WAFファイアウォール)ポリシーを適用し、IPレンジを制限しています(後述)。
実装コード
以下のコードは、ネットワーキング、環境変数とシークレット、データベース、Artifact RegistryとDockerイメージ、Cloud Run、ロードバランシング、WAFファイアウォールに至るまで、構築に使ったコマンドを順を追って示したものです。
#!/usr/bin/env bash
###################################################################### REFERENCES# - https://docs.netbox.dev/en/stable/installation/3-netbox/# - https://github.com/netbox-community/netbox-docker/wiki/# - https://hub.docker.com/r/netboxcommunity/netbox# - https://cloud.google.com/sql/docs/postgres/configure-private-ip# - https://cloud.google.com/sql/docs/postgres/create-instance# - https://cloud.google.com/sql/docs/postgres/create-manage-databases#gcloud# - https://cloud.google.com/sql/docs/postgres/create-manage-users#gcloud# - https://cloud.google.com/memorystore/docs/redis/create-manage-instances#creating_a_redis_instance_with_a_specific_ip_address_range# - https://cloud.google.com/artifact-registry/docs/docker/store-docker-container-images# - https://cloud.google.com/artifact-registry/docs/docker/pushing-and-pulling# - https://cloud.google.com/dns/docs/zones#create-private-zone# - https://cloud.google.com/dns/docs/records# - https://cloud.google.com/secret-manager/docs/configuring-secret-manager# - https://cloud.google.com/secret-manager/docs/create-secret# - https://cloud.google.com/run/docs/configuring/secrets#command-line# - https://cloud.google.com/run/docs/configuring/connecting-vpc#gcloud#####################################################################
export PROJECT_ID=$(gcloud config get-value project)export PROJECT_USER=$(gcloud config get-value core/account) # set current userexport PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")export IDNS=${PROJECT_ID}.svc.id.goog # workflow identity domain
export GCP_REGION="us-central1" # CHANGEME (OPT)export GCP_ZONE="us-central1-a" # CHANGEME (OPT)export NETWORK_NAME="default"
# enable apisgcloud services enable compute.googleapis.com \ servicenetworking.googleapis.com \ vpcaccess.googleapis.com \ secretmanager.googleapis.com \ sqladmin.googleapis.com \ redis.googleapis.com \ artifactregistry.googleapis.com \ dns.googleapis.com \ cloudbuild.googleapis.com \ storage.googleapis.com\ run.googleapis.com
# configure gcloud sdkgcloud config set compute/region $GCP_REGIONgcloud config set compute/zone $GCP_ZONE
############################################################## NETWORKING#############################################################export NETBOX_NETWORK_NAME="netbox"export NETBOX_RESERVED_RANGE_NAME="google-managed-services-netbox"export SUBNET_BASTION_NAME="bastion"export SUBNET_BASTION_RANGE="10.250.0.0/29"export CONNECTOR_NAME="serverless-connector"export CONNECTOR_RANGE="10.200.0.0/28"export DNS_ZONE="private-zone"export DNS_SUFFIX="example.com" # CHANGEME (OPT)export DNS_LABELS="dept=networking" # CHANGEME (OPT)
# create network (custom-mode)gcloud compute networks create $NETBOX_NETWORK_NAME \ --subnet-mode=custom
# create bastion subnetgcloud compute networks subnets create $SUBNET_BASTION_NAME \ --region=$GCP_REGION \ --network=$NETBOX_NETWORK_NAME \ --range=$SUBNET_BASTION_RANGE
# allocate private rangegcloud compute addresses create $NETBOX_RESERVED_RANGE_NAME \ --global \ --purpose=VPC_PEERING \ --addresses=10.100.0.0 \ --prefix-length=16 \ --network=projects/$PROJECT_ID/global/networks/$NETBOX_NETWORK_NAME
# create peering for managed servicesgcloud services vpc-peerings connect \ --service=servicenetworking.googleapis.com \ --ranges=$NETBOX_RESERVED_RANGE_NAME \ --network=$NETBOX_NETWORK_NAME
# create serverless vpc connector (for peering serverless to VPC)gcloud compute networks vpc-access connectors create $CONNECTOR_NAME \ --network $NETBOX_NETWORK_NAME \ --region $GCP_REGION \ --range $CONNECTOR_RANGE
# create private zonegcloud dns managed-zones create $DNS_ZONE \ --description="internal zone" \ --dns-name=$DNS_SUFFIX \ --networks=$NETBOX_NETWORK_NAME \ --labels=$DNS_LABELS \ --visibility=private
# firewall allow sshgcloud compute firewall-rules create fw-allow-ssh \ --network=$NETBOX_NETWORK_NAME \ --action=allow \ --direction=ingress \ --target-tags=allow-ssh \ --rules=tcp:22
############################################################## NETBOX (ENV/SECRETS)#############################################################export SECRET_ID="netbox-secrets"export SECRET_VERSION=1export SECRET_FILE=".env-local"export ENV_FILE="netbox.env"# individualexport SECRET_DB_PASS="db_password"export SECRET_REDIS_PASS="redis_password"export SECRET_REDIS_CACHE_PASS="redis_cache_password"export SECRET_SU_PASS="superuser_password"export SECRET_EMAIL_PASS="email_password"export SECRET_SECRET_KEY="secret_key"
# fetch secret values from local .env filesource $SECRET_FILE
# save file with injected valuescat > $ENV_FILE << EOF# requiredALLOWED_HOSTS="*"DB_HOST=$POSTGRES_INSTANCE.$DNS_SUFFIXDB_PORT=$POSTGRES_PORTDB_NAME=netboxDB_USER=netboxREDIS_CACHE_DATABASE=1REDIS_CACHE_HOST=$REDIS_INSTANCE.$DNS_SUFFIXREDIS_CACHE_INSECURE_SKIP_TLS_VERIFY=falseREDIS_CACHE_SSL=falseREDIS_DATABASE=0REDIS_HOST=$REDIS_INSTANCE.$DNS_SUFFIXREDIS_INSECURE_SKIP_TLS_VERIFY=falseREDIS_SSL=falseEOF
# add config to envsource $ENV_FILE
# create secret for all varsgcloud secrets create $SECRET_ID --replication-policy="automatic"gcloud secrets versions add $SECRET_ID --data-file=${PWD}/$SECRET_FILE # version 1
# create env secretsecho -n $DB_PASSWORD | gcloud secrets create $SECRET_DB_PASS \ --replication-policy="automatic" \ --data-file=-# redis auth string after creation# redis_cache auth string after creationecho -n $SUPERUSER_PASSWORD | gcloud secrets create $SECRET_SU_PASS \ --replication-policy="automatic" \ --data-file=-echo -n $EMAIL_PASSWORD | gcloud secrets create $SECRET_EMAIL_PASS \ --replication-policy="automatic" \ --data-file=-echo -n $SECRET_KEY | gcloud secrets create $SECRET_SECRET_KEY \ --replication-policy="automatic" \ --data-file=-
############################################################## DATABASE (POSTGRES)#############################################################export POSTGRES_INSTANCE="netbox-db"export POSTGRES_VERSION="POSTGRES_14"export POSTGRES_TIER="db-f1-micro"export POSTGRES_PORT=5432
gcloud beta sql instances create $POSTGRES_INSTANCE \ --database-version=$POSTGRES_VERSION \ --tier=$POSTGRES_TIER \ --network=projects/$PROJECT_ID/global/networks/$NETBOX_NETWORK_NAME \ --no-assign-ip \ --allocated-ip-range-name=$NETBOX_RESERVED_RANGE_NAME \ --region=$GCP_REGION
# get internal IPexport POSTGRES_HOST=$(gcloud beta sql instances describe $POSTGRES_INSTANCE --format="value(ipAddresses.ipAddress)")
# add to private-zone DNSgcloud dns record-sets transaction start --zone=$DNS_ZONEgcloud dns record-sets transaction add $POSTGRES_HOST \ --name="$POSTGRES_INSTANCE.$DNS_SUFFIX" --ttl="3600" --type="A" --zone=$DNS_ZONEgcloud dns record-sets transaction execute --zone=$DNS_ZONE
# lock down postgres (admin) user [manually input at prompt]gcloud sql users set-password postgres \ --instance=$POSTGRES_INSTANCE \ --prompt-for-password
# create netbox usergcloud sql users create $DB_USER \ --instance=$POSTGRES_INSTANCE \ --password=$DB_PASSWORD
# create databasegcloud sql databases create $DB_NAME \ --instance=$POSTGRES_INSTANCE
############################################################## CACHE (REDIS)#############################################################export REDIS_INSTANCE="netbox-cache"export REDIS_VERSION="redis_6_x"
gcloud redis instances create $REDIS_INSTANCE \ --size=1 \ --tier=STANDARD \ --region=$GCP_REGION \ --network=$NETBOX_NETWORK_NAME \ --reserved-ip-range=$NETBOX_RESERVED_RANGE_NAME \ --connect-mode=PRIVATE_SERVICE_ACCESS \ --redis-version=$REDIS_VERSION \ --enable-auth
# get internal IPexport REDIS_HOST=$(gcloud redis instances describe $REDIS_INSTANCE --region $GCP_REGION --format="value(host)")export REDIS_PORT=$(gcloud redis instances describe $REDIS_INSTANCE --region $GCP_REGION --format="value(port)")
# get auth stringexport REDIS_PASSWORD=$(gcloud beta redis instances get-auth-string $REDIS_INSTANCE --region $GCP_REGION --format="value(authString)")
# add to private-zone DNSgcloud dns record-sets transaction start --zone=$DNS_ZONEgcloud dns record-sets transaction add $REDIS_HOST \ --name="$REDIS_INSTANCE.$DNS_SUFFIX" --ttl="3600" --type="A" --zone=$DNS_ZONEgcloud dns record-sets transaction execute --zone=$DNS_ZONE
# add secrets to secret managerecho -n $REDIS_PASSWORD | gcloud secrets create $SECRET_REDIS_PASS \ --replication-policy="automatic" \ --data-file=-echo -n $REDIS_PASSWORD | gcloud secrets create $SECRET_REDIS_CACHE_PASS \ --replication-policy="automatic" \ --data-file=-
############################################################## COMPUTE (TEST BASTION)# - NOTE: if real bastion, create in managed instance group size=1# - NOTE: if real bastion, no external IP and use IAP tunnel only#############################################################export BASTION_NAME="bastion-1"
# create compute instance to test from proxy-only network to ILBgcloud compute instances create $BASTION_NAME \ --machine-type e2-micro \ --zone $GCP_ZONE \ --network $NETBOX_NETWORK_NAME \ --subnet $SUBNET_BASTION_NAME \ --tags allow-ssh
# install netcatgcloud compute ssh $BASTION_NAME --zone $GCP_ZONE -- sudo apt-get updategcloud compute ssh $BASTION_NAME --zone $GCP_ZONE -- sudo apt-get -y install netcat
# test internal DNS for database (IP may vary)gcloud compute ssh $BASTION_NAME --zone $GCP_ZONE -- nc -zv $POSTGRES_INSTANCE.$DNS_SUFFIX $POSTGRES_PORT# Connection to netbox-db.example.com (10.100.0.5) 5432 port [tcp/postgresql] succeeded!
# test internal DNS for cache (IP may vary)gcloud compute ssh $BASTION_NAME --zone $GCP_ZONE -- nc -zv $REDIS_INSTANCE.$DNS_SUFFIX $REDIS_PORT# Connection to netbox-cache.example.com (10.100.1.4) 6379 port [tcp/redis] succeeded!
############################################################## ARTIFACT REGISTRY# - WARNING: arm architecture on Mac will produce non-runnable image# run pull / tag / push commands from your temp bastion#############################################################export REPO_NAME="netbox-repo"export NETBOX_IMAGE="netboxcommunity/netbox:v3.4-beta1-2.3.0"export IMAGE_NAME="netbox"export TAG_NAME="v3.4-beta1-2.3.0"export IMAGE_PATH=$GCP_REGION-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/$IMAGE_NAME:$TAG_NAME
gcloud artifacts repositories create $REPO_NAME \ --repository-format=docker \ --location=$GCP_REGION \ --description="Docker repository"
# configure authgcloud auth configure-docker ${GCP_REGION}-docker.pkg.dev
# fetch latest community netbox imagedocker pull $NETBOX_IMAGE
# tag image for artifact registrydocker tag $NETBOX_IMAGE \ $IMAGE_PATH
# push image to artifact registrydocker push $IMAGE_PATH
############################################################## NETBOX (CLOUD RUN)#############################################################export SERVICE_NAME="netbox"export SECRET_PATH="env/$SECRET_FILE" # as config in docker-compose.yamlexport SA_EMAIL="[email protected]"
# add compute service account access to secrets# - NOTE: best practice to create separate service account to run each workloadgcloud secrets add-iam-policy-binding $SECRET_ID \ --member="serviceAccount:$SA_EMAIL" \ --role="roles/secretmanager.secretAccessor"# individualgcloud secrets add-iam-policy-binding $SECRET_DB_PASS \ --member="serviceAccount:$SA_EMAIL" \ --role="roles/secretmanager.secretAccessor"gcloud secrets add-iam-policy-binding $SECRET_REDIS_PASS \ --member="serviceAccount:$SA_EMAIL" \ --role="roles/secretmanager.secretAccessor"gcloud secrets add-iam-policy-binding $SECRET_REDIS_CACHE_PASS \ --member="serviceAccount:$SA_EMAIL" \ --role="roles/secretmanager.secretAccessor"gcloud secrets add-iam-policy-binding $SECRET_SU_PASS \ --member="serviceAccount:$SA_EMAIL" \ --role="roles/secretmanager.secretAccessor"gcloud secrets add-iam-policy-binding $SECRET_EMAIL_PASS \ --member="serviceAccount:$SA_EMAIL" \ --role="roles/secretmanager.secretAccessor"gcloud secrets add-iam-policy-binding $SECRET_SECRET_KEY \ --member="serviceAccount:$SA_EMAIL" \ --role="roles/secretmanager.secretAccessor"
# deploy cloud run service (default port 8080) config from envgcloud run deploy $SERVICE_NAME \ --platform managed \ --no-cpu-throttling \ --allow-unauthenticated \ --vpc-connector $CONNECTOR_NAME \ --ingress=internal-and-cloud-load-balancing \ --region $GCP_REGION \ --image $IMAGE_PATH \ --set-env-vars "ALLOWED_HOSTS=$ALLOWED_HOSTS" \ --set-env-vars "DB_HOST=$POSTGRES_INSTANCE.$DNS_SUFFIX" \ --set-env-vars "DB_PORT=$POSTGRES_PORT" \ --set-env-vars "DB_NAME=$DB_NAME" \ --set-env-vars "DB_USER=$DB_USER" \ --set-env-vars "REDIS_HOST=$REDIS_INSTANCE.$DNS_SUFFIX" \ --set-env-vars "REDIS_PORT=$REDIS_PORT" \ --set-env-vars "REDIS_DATABASE=$REDIS_DATABASE" \ --set-env-vars "REDIS_CACHE_HOST=$REDIS_INSTANCE.$DNS_SUFFIX" \ --set-env-vars "REDIS_CACHE_PORT=$REDIS_PORT" \ --set-env-vars "REDIS_CACHE_DATABASE=$REDIS_CACHE_DATABASE" \ --update-secrets=DB_PASSWORD=$SECRET_DB_PASS:$SECRET_VERSION \ --update-secrets=REDIS_PASSWORD=$SECRET_REDIS_PASS:$SECRET_VERSION \ --update-secrets=REDIS_CACHE_PASSWORD=$SECRET_REDIS_CACHE_PASS:$SECRET_VERSION \ --update-secrets=SUPERUSER_PASSWORD=$SECRET_SU_PASS:$SECRET_VERSION \ --update-secrets=EMAIL_PASSWORD=$SECRET_EMAIL_PASS:$SECRET_VERSION \ --update-secrets=SECRET_KEY=$SECRET_SECRET_KEY:$SECRET_VERSION \ --set-env-vars "DB_WAIT_DEBUG=1"
########################################################### Load Balancer##########################################################export APP_NAME=$SERVICE_NAMEexport TLD="msparr.com" # OVERRIDE PRIOR - CHANGE ME TO DESIRED DOMAINexport DOMAIN="$SERVICE_NAME.$TLD" # netbox.msparr.comexport EXT_IP_NAME="public-ip"export BACKEND_SERVICE_NAME="$APP_NAME-service"export SERVERLESS_NEG_NAME="$APP_NAME-neg"
# create static IP addressgcloud compute addresses create --global $EXT_IP_NAME
# wait 10 seconds and then set varexport EXT_IP=$(gcloud compute addresses describe $EXT_IP_NAME --global --format="value(address)")echo "Remember to add DNS 'A' record [$DOMAIN] for IP [$EXT_IP]"
# create serverless NEGgcloud compute network-endpoint-groups create $SERVERLESS_NEG_NAME \ --region=$GCP_REGION \ --network-endpoint-type=serverless \ --cloud-run-service=$SERVICE_NAME
# create backend servicegcloud compute backend-services create $BACKEND_SERVICE_NAME \ --load-balancing-scheme=EXTERNAL \ --global
# add serverless NEG to backend servicegcloud compute backend-services add-backend $BACKEND_SERVICE_NAME \ --network-endpoint-group=$SERVERLESS_NEG_NAME \ --network-endpoint-group-region=$GCP_REGION \ --global
# create URL mapgcloud compute url-maps create $APP_NAME-url-map \ --default-service $BACKEND_SERVICE_NAME
# create managed SSL certgcloud beta compute ssl-certificates create $APP_NAME-cert \ --domains $DOMAIN
# create target HTTPS proxygcloud compute target-https-proxies create $APP_NAME-https-proxy \ --ssl-certificates=$APP_NAME-cert \ --url-map=$APP_NAME-url-map
gcloud compute forwarding-rules create $APP_NAME-fwd-rule \ --target-https-proxy=$APP_NAME-https-proxy \ --global \ --ports=443 \ --address=$EXT_IP_NAME
# verify app is running (wait 10-15 minutes until cert provisions)curl -k "https://$DOMAIN" # Unauthorized request
########################################################### [OPTIONAL] Restrict Traffic with Cloud Armor security policy# - https://cloud.google.com/armor/docs/configure-security-policies#gcloud##########################################################export INTERNAL_POLICY_NAME="internal-users-policy"export ALLOWED_CIDR="192.168.0.0/24" # CHANGE ME TO DESIRED IP RANGE
# create policiesgcloud compute security-policies create $INTERNAL_POLICY_NAME \ --description "policy for internal test users"
# update default rulesgcloud compute security-policies rules update 2147483647 \ --security-policy $INTERNAL_POLICY_NAME \ --action "deny-502"
# restrict traffic to desired IP rangesgcloud compute security-policies rules create 1000 \ --security-policy $INTERNAL_POLICY_NAME \ --description "allow traffic from $ALLOWED_CIDR" \ --src-ip-ranges "$ALLOWED_CIDR" \ --action "allow"
# attach policy to backend service (one at a time)gcloud compute backend-services update $BACKEND_SERVICE_NAME \ --security-policy $INTERNAL_POLICY_NAME \ --global複雑さと注意したいポイント
クラウドネイティブな形でのモダナイゼーションとデプロイの題材としてNetboxを選んだ理由のひとつが、その技術的な複雑さにあります。このアプリケーションは、ファイルシステム、セッション、ワーカー、さらには日次のcronによるクリーンアップ処理まで備えています。

Netboxアプリケーション用のDocker Composeスニペット(netbox-workerとnetbox-housekeepingに注目)
上記のdocker-compose.yamlのスニペットでは、YAMLの機能であるアンカーが使われています。これはdocker-compose固有のものではありません。

これにより、設定をすっきりと再利用しつつ、コマンドをオーバーライドして実行時に異なるスクリプトを動かすことができます。
Cloud Runでワーカーを再現する
Cloud Run上で同等の機能を再現するには、--cmd_と--args_というフラグを使います。メインアプリケーションのデプロイコマンドをそのまま流用して名前だけ変更し、CMDをオーバーライドして以下のように別のエントリポイントスクリプトを実行させればOKです。
gcloud run deploy $WORKER_NAME \ --platform managed \ --allow-unauthenticated \ --vpc-connector $CONNECTOR_NAME \ --ingress=internal-and-cloud-load-balancing \ --region $GCP_REGION \ --image $IMAGE_PATH \ --set-env-vars "ALLOWED_HOSTS=$ALLOWED_HOSTS" \
...
--cmd "/opt/netbox/venv/bin/python" \ --args "/opt/netbox/netbox/manage.py" \ --args "rqworker"Cloud RunとCloud Schedulerによる日次ハウスキーピングジョブ
日次のハウスキーピングジョブは、Cloud Run上に同じサービスをもう1つ作成し、Cloud Schedulerでそのサービスを毎日呼び出すようにスケジューリングすれば実現できます。
gcloud run deploy $HOUSEKEEPING_NAME \ --platform managed \ --allow-unauthenticated \ --vpc-connector $CONNECTOR_NAME \ --ingress=internal-and-cloud-load-balancing \ --region $GCP_REGION \ --image $IMAGE_PATH \ --set-env-vars "ALLOWED_HOSTS=$ALLOWED_HOSTS" \
...
--cmd "/opt/netbox/housekeeping.sh"ハウスキーピングサービスをデプロイしたら、Cloud Scheduler APIを有効化します。
gcloud services enable cloudscheduler.googleapis.com続いてサービスアカウントを作成し、invoker権限を付与した上で、スケジュールジョブを作成します。
# fetch the service URLexport SVC_URL=$(gcloud run services describe $HOUSEKEEPING_NAME \ --platform managed --region $GCP_REGION --format="value(status.url)")
########################################################## create cloud scheduler job#########################################################export SA_NAME="cloud-scheduler-runner"export SA_EMAIL="${SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
# create service accountgcloud iam service-accounts create $SA_NAME \ --display-name "${SA_NAME}"
# add sa binding to cloud run appgcloud run services add-iam-policy-binding $HOUSEKEEPING_NAME \ --platform managed \ --region $GCP_REGION \ --member=serviceAccount:$SA_EMAIL \ --role=roles/run.invoker
# create the job to invoke service every day at 2:30 AMgcloud scheduler jobs create http housekeeping-job --schedule "30 2 * * *" \ --http-method=GET \ --uri=$SVC_URL \ --oidc-service-account-email=$SA_EMAIL \ --oidc-token-audience=$SVC_URLファイルシステム
本記事の狙いは、Netboxのような複雑なアプリケーションを切り分け、Cloud Runをはじめとするマネージドサービスでクラウドにデプロイできることを示すことでした。今回のアプリにとってこれが最適解とは言い切れませんが、十分に実現可能です。
ファイルシステムを使いたい場合、現状のサーバーレスプラットフォームには制約がありますので、代わりにKubernetes Engineや、シンプルにCompute EngineのVMで動かすという選択肢が現実的です。VMをコンテナとして動かす方法もスマートで、必要に応じてディスクやボリュームをアタッチできます。
ただし、Cloud Runで簡易的な「ファイルシステム」を実現する小ワザとして、サンプルコードや以下のスニペットのようにSecret Managerを活用する方法もあります。
# create secret for all varsgcloud secrets create $SECRET_ID --replication-policy="automatic"gcloud secrets versions add $SECRET_ID --data-file=${PWD}/$SECRET_FILE
# mount file path in cloud rungcloud run deploy $SERVICE --image $IMAGE_URL \ --update-secrets="/env/netbox.env"=$SECRET_ID:$SECRET_VERSIONベストプラクティス:サービスアカウントを分ける
本記事の例ではCloud Schedulerアドオン用に別サービスアカウントを用意していますが、ベストプラクティスは、各サービスとbastion(VM)ごとに専用のサービスアカウントを作成し、必要最小限のIAMロールだけを付与することです。これは最小権限の原則に沿った考え方です。
Cloud Runサービスについても、独立した「netbox-runner」サービスアカウントを作成し、以下のような必要なロールだけを付与すべきです。
- Cloud SQL Client
- Redis Viewer
- Secret Accessor
- Artifact Registry Reader
- Service Account User
- Storage Object Viewer
本記事の例から、既存アプリケーションをモダナイズし、パブリッククラウドのマネージドサービスを使いこなすイメージが伝われば幸いです。ひとまずNetboxを動かしたいだけなら、上記のコードスニペットで十分対応できますが、VMやK8Sでの運用も選択肢として検討する価値があります。
動作確認した構成は、Terraformerのようなサードパーティ製ソリューションや、GCP公式の一括エクスポートツールを使ってTerraformに変換することも可能です。これらは既存インフラをリバースエンジニアリングし、Terraformコードを生成してくれます。
もしネットワーク構築の現場でIPの衝突といった同様の課題に直面しているなら、共有スプレッドシートでもNetboxのような定番ツールでも構いませんので、ぜひIPAMに取り組んでみてください。
Happy clouding!