
Netzwerke und Geräte mit IP Address Management (IPAM) im Griff behalten
In letzter Zeit melden immer mehr Kunden Probleme in ihren Netzwerken – insbesondere beim Peering – wegen kollidierender IP-Adressbereiche. Ein klares Signal, dass IP-Adressen organisationsweit geplant und verwaltet werden müssen.
Sie können Ihre IPs zwar in einer gemeinsamen Tabelle pflegen, doch es gibt auch dedizierte Software-Tools. Dieser Beitrag zeigt, wie Sie das beliebte Open-Source-Tool zur IP-Adressverwaltung (IPAM) Netbox cloud-native auf der Google Cloud Platform (GCP) betreiben.
Klassischer Stack
Klassischerweise läuft Netbox auf einer oder mehreren virtuellen Maschinen, denen ein Webserver vorgeschaltet ist. Es gibt zwar ein von der Community gepflegtes Docker-Image, doch die Anleitungen beschreiben ausschließlich den Betrieb über docker compose. Diese Architektur ähnelt vielen Anwendungen, die Unternehmen entwickeln oder betreiben – und eignet sich deshalb hervorragend, um eine Migration in die Public Cloud zu zeigen.

Quelle: Netbox – Standardinstallation von Netbox
Cloud-native Architektur
Ich wollte herausfinden, wie das Docker-Image funktioniert, welche Abhängigkeiten und Konfigurationsparameter es mitbringt – und es dann ausschließlich über Managed Services auf GCP bereitstellen. Dieses Beispiel zeigt, wie Sie Anwendungen bei der Migration in die Public Cloud entweder per "Move and Improve" oder "Rip and Replace" überführen.

Überarbeitete Netbox-Installation auf GCP mit Managed Services
Anwendungskomponenten
- Netbox-Anwendung (Python-App auf Basis des Django-Frameworks)
- PostgreSQL-Datenbank (Cloud SQL)
- Redis (Cloud Memorystore)
Architekturentscheidungen
- Managed Database und Cache (Cloud SQL, Cloud Memorystore)
- Ausschließlich private IPs für Datenbanken und Cache (Private Service Access)
- Privates DNS für Datenbank-Hostnamen (Cloud DNS)
- Secrets im Secret Manager (Secret Manager)
- Serverlose Container-Runtime (Cloud Run, Artifact Registry)
- Globaler Load Balancer mit TLS (HTTP(S) Load Balancing, Managed Certificate)
- WAF-Firewall (Cloud Armor)
Viele Organisationen tun sich schwer damit, Managed Services und serverlose Anwendungen über private IP-Adressen zu verbinden. Dieses Beispiel zeigt, wie Sie private Bereiche in Ihrem VPC-Netzwerk reservieren und sie dann den Managed Services zuweisen, um eine Konnektivitätsbrücke zu schaffen.
Cloud DNS dient dazu, private Hostnamen einzurichten, über die die Anwendungen die Datenbanken erreichen. Das verschafft Ihnen Flexibilität für die Zukunft – etwa beim Datenbankwechsel oder bei einem Failover –, denn Sie aktualisieren einfach Ihre DNS-Einträge, und die Anwendungen verweisen weiterhin auf dieselbe Domain. Theoretisch hätte ich auch DNS-Forwarding nutzen und alles an meine öffentliche Domain anbinden können; intern ist das aber nicht nötig, daher habe ich example.com verwendet.
Die Bastion-VM (bzw. der Jump-Host) wäre nicht zwingend nötig gewesen, doch ich habe eine eingerichtet, um die Verbindungen während des Aufbaus zu testen. Im Regelbetrieb würde eine Bastion in einer Managed Instance Group (MIG) der Größe 1 und ohne externe IP-Adresse bereitgestellt.
Sichere, lastverteilte Webanwendung

Um zu verdeutlichen, wie alles zusammenspielt, habe ich eine private Domain genutzt und einen "A-Record" für die statische IP-Adresse registriert, die ich dem globalen Load Balancer zugewiesen hatte. Ein Managed Certificate wurde automatisch ausgestellt.

Für zusätzliche Sicherheit habe ich am Load Balancer eine Cloud-Armor-Richtlinie (WAF-Firewall) angewendet und die zulässigen IP-Bereiche eingeschränkt (siehe unten).
Implementierungs-Code
Der folgende Code zeigt Schritt für Schritt die Befehle, mit denen ich die gesamte Umgebung aufgesetzt habe – inklusive Networking, Umgebungsvariablen und Secrets, Datenbanken, Artifact Registry und Docker-Images, Cloud Run, Load Balancing und WAF-Firewall.
#!/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 \ --globalZusätzliche Komplexität und Überlegungen
Einer der Gründe, warum ich gerade Netbox gewählt habe, um cloud-native Modernisierung und Bereitstellung zu illustrieren, ist die technische Komplexität der Anwendung. Sie umfasst Dateisystem, Sessions, Worker und sogar einen täglichen Cron-Job zur Bereinigung.

Docker-Compose-Snippet für die Netbox-Anwendung (beachten Sie netbox-worker und netbox-housekeeping)
Das obige docker-compose.yaml-Snippet zeigt ein YAML-Feature namens Anchors, das nicht docker-compose-spezifisch ist.

Sie können Konfigurationen kompakt duplizieren und dann Befehle überschreiben, um zur Laufzeit unterschiedliche Skripte auszuführen.
Worker auf Cloud Run nachbauen
Um diese Funktionalität auf Cloud Run nachzubilden, gibt es die Flags --cmd und --args. Ich würde einfach die Befehle für das Deployment der Hauptanwendung duplizieren, den Namen ändern und das CMD überschreiben, um wie unten ein anderes Entrypoint-Skript auszuführen:
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"Täglicher Housekeeping-Job mit Cloud Run und Cloud Scheduler
Den täglichen Housekeeping-Job lassen Sie laufen, indem Sie einen duplizierten Service auf Cloud Run anlegen und ihn täglich per Cloud Scheduler aufrufen.
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"Nach dem Deployment des Housekeeping-Service aktivieren wir die Cloud Scheduler API:
gcloud services enable cloudscheduler.googleapis.comAnschließend legen wir einen Service Account an, weisen ihm die Invoker-Berechtigung zu und erstellen den geplanten Job:
# 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_URLDateisysteme
Mein Ziel war zu zeigen, dass sich auch eine komplexe Anwendung wie Netbox aufteilen und mit Cloud Run und weiteren Managed Services in der Cloud betreiben lässt. Vielleicht ist das nicht die beste Lösung gerade für diese Anwendung – machbar ist es aber.
Wenn Sie auf das Dateisystem angewiesen sind, sind die serverlosen Plattformen aktuell eingeschränkt. Dann bietet sich der Betrieb auf Kubernetes Engine oder einfach auf einer Compute-Engine-VM an. Sie können sogar eine VM als Container betreiben – sehr elegant – und bei Bedarf Disks bzw. Volumes anhängen.
Ein Trick, um in Cloud Run trotzdem ein einfaches "Dateisystem" zur Hand zu haben, ist die Nutzung des Secret Managers, wie ich es im Beispielcode und im folgenden Snippet gemacht habe.
# 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_VERSIONBest Practice: getrennte Service Accounts
Auch wenn meine Beispiele nur einen separaten Service Account für das Cloud-Scheduler-Add-on gezeigt haben: Best Practice ist es, für jeden Service und für die Bastion-VM eigene Service Accounts anzulegen und ihnen jeweils nur die minimal nötigen IAM-Rollen zuzuweisen. Das entspricht dem Prinzip der minimalen Rechtevergabe (Least Privilege).
Für den Cloud-Run-Service sollten wir einen separaten Service Account "netbox-runner" anlegen und ihm nur die nötigen Rollen zuweisen, etwa:
- Cloud SQL Client
- Redis Viewer
- Secret Accessor
- Artifact Registry Reader
- Service Account User
- Storage Object Viewer
Ich hoffe, dieses Beispiel zeigt anschaulich, wie sich bestehende Anwendungen modernisieren und Managed Services in der Public Cloud sinnvoll nutzen lassen. Wenn Sie Netbox einfach nur ans Laufen bringen wollen, sollten die obigen Code-Snippets genügen – alternativ kommen aber auch ein VM-Betrieb oder K8s in Frage.
Sie könnten das funktionierende Beispiel auch nach Terraform überführen – etwa mit Drittanbieter-Lösungen wie Terraformer oder mit GCPs eigenen Bulk-Export-Tools, die Ihre bestehende Infrastruktur reverse-engineeren und passenden Terraform-Code generieren.
Falls Ihr Unternehmen beim Konfigurieren von Netzwerken vor ähnlichen Herausforderungen mit IP-Kollisionen steht, ist es vielleicht an der Zeit, IPAM ernsthaft anzugehen – sei es mit einer gemeinsamen Tabelle oder einem etablierten Tool wie Netbox.
Happy clouding!