Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Cloud-native Netbox auf Google Cloud Platform

By Mike SparrJan 24, 202312 min read

Diese Seite ist auch in English, Español, Français, Italiano, 日本語 und Português verfügbar.

netbox

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.

how to migrate to public cloud

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.

rip-and-replace-or-move-improve-public-web

Ü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

secure load-balanced web application

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.

automatically-provisioned

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 user
export 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 apis
gcloud 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 sdk
gcloud config set compute/region $GCP_REGION
gcloud 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 subnet
gcloud compute networks subnets create $SUBNET_BASTION_NAME \
--region=$GCP_REGION \
--network=$NETBOX_NETWORK_NAME \
--range=$SUBNET_BASTION_RANGE
# allocate private range
gcloud 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 services
gcloud 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 zone
gcloud dns managed-zones create $DNS_ZONE \
--description="internal zone" \
--dns-name=$DNS_SUFFIX \
--networks=$NETBOX_NETWORK_NAME \
--labels=$DNS_LABELS \
--visibility=private
# firewall allow ssh
gcloud 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=1
export SECRET_FILE=".env-local"
export ENV_FILE="netbox.env"
# individual
export 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 file
source $SECRET_FILE
# save file with injected values
cat > $ENV_FILE << EOF
# required
ALLOWED_HOSTS="*"
DB_HOST=$POSTGRES_INSTANCE.$DNS_SUFFIX
DB_PORT=$POSTGRES_PORT
DB_NAME=netbox
DB_USER=netbox
REDIS_CACHE_DATABASE=1
REDIS_CACHE_HOST=$REDIS_INSTANCE.$DNS_SUFFIX
REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY=false
REDIS_CACHE_SSL=false
REDIS_DATABASE=0
REDIS_HOST=$REDIS_INSTANCE.$DNS_SUFFIX
REDIS_INSECURE_SKIP_TLS_VERIFY=false
REDIS_SSL=false
EOF
# add config to env
source $ENV_FILE
# create secret for all vars
gcloud secrets create $SECRET_ID --replication-policy="automatic"
gcloud secrets versions add $SECRET_ID --data-file=${PWD}/$SECRET_FILE # version 1
# create env secrets
echo -n $DB_PASSWORD | gcloud secrets create $SECRET_DB_PASS \
--replication-policy="automatic" \
--data-file=-
# redis auth string after creation
# redis_cache auth string after creation
echo -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 IP
export POSTGRES_HOST=$(gcloud beta sql instances describe $POSTGRES_INSTANCE --format="value(ipAddresses.ipAddress)")
# add to private-zone DNS
gcloud dns record-sets transaction start --zone=$DNS_ZONE
gcloud dns record-sets transaction add $POSTGRES_HOST \
--name="$POSTGRES_INSTANCE.$DNS_SUFFIX" --ttl="3600" --type="A" --zone=$DNS_ZONE
gcloud 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 user
gcloud sql users create $DB_USER \
--instance=$POSTGRES_INSTANCE \
--password=$DB_PASSWORD
# create database
gcloud 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 IP
export 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 string
export REDIS_PASSWORD=$(gcloud beta redis instances get-auth-string $REDIS_INSTANCE --region $GCP_REGION --format="value(authString)")
# add to private-zone DNS
gcloud dns record-sets transaction start --zone=$DNS_ZONE
gcloud dns record-sets transaction add $REDIS_HOST \
--name="$REDIS_INSTANCE.$DNS_SUFFIX" --ttl="3600" --type="A" --zone=$DNS_ZONE
gcloud dns record-sets transaction execute --zone=$DNS_ZONE
# add secrets to secret manager
echo -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 ILB
gcloud compute instances create $BASTION_NAME \
--machine-type e2-micro \
--zone $GCP_ZONE \
--network $NETBOX_NETWORK_NAME \
--subnet $SUBNET_BASTION_NAME \
--tags allow-ssh
# install netcat
gcloud compute ssh $BASTION_NAME --zone $GCP_ZONE -- sudo apt-get update
gcloud 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 auth
gcloud auth configure-docker ${GCP_REGION}-docker.pkg.dev
# fetch latest community netbox image
docker pull $NETBOX_IMAGE
# tag image for artifact registry
docker tag $NETBOX_IMAGE \
$IMAGE_PATH
# push image to artifact registry
docker push $IMAGE_PATH
#############################################################
# NETBOX (CLOUD RUN)
#############################################################
export SERVICE_NAME="netbox"
export SECRET_PATH="env/$SECRET_FILE" # as config in docker-compose.yaml
export SA_EMAIL="[email protected]"
# add compute service account access to secrets
# - NOTE: best practice to create separate service account to run each workload
gcloud secrets add-iam-policy-binding $SECRET_ID \
--member="serviceAccount:$SA_EMAIL" \
--role="roles/secretmanager.secretAccessor"
# individual
gcloud 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 env
gcloud 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_NAME
export TLD="msparr.com" # OVERRIDE PRIOR - CHANGE ME TO DESIRED DOMAIN
export DOMAIN="$SERVICE_NAME.$TLD" # netbox.msparr.com
export EXT_IP_NAME="public-ip"
export BACKEND_SERVICE_NAME="$APP_NAME-service"
export SERVERLESS_NEG_NAME="$APP_NAME-neg"
# create static IP address
gcloud compute addresses create --global $EXT_IP_NAME
# wait 10 seconds and then set var
export 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 NEG
gcloud compute network-endpoint-groups create $SERVERLESS_NEG_NAME \
--region=$GCP_REGION \
--network-endpoint-type=serverless \
--cloud-run-service=$SERVICE_NAME
# create backend service
gcloud compute backend-services create $BACKEND_SERVICE_NAME \
--load-balancing-scheme=EXTERNAL \
--global
# add serverless NEG to backend service
gcloud compute backend-services add-backend $BACKEND_SERVICE_NAME \
--network-endpoint-group=$SERVERLESS_NEG_NAME \
--network-endpoint-group-region=$GCP_REGION \
--global
# create URL map
gcloud compute url-maps create $APP_NAME-url-map \
--default-service $BACKEND_SERVICE_NAME
# create managed SSL cert
gcloud beta compute ssl-certificates create $APP_NAME-cert \
--domains $DOMAIN
# create target HTTPS proxy
gcloud 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 policies
gcloud compute security-policies create $INTERNAL_POLICY_NAME \
--description "policy for internal test users"
# update default rules
gcloud compute security-policies rules update 2147483647 \
--security-policy $INTERNAL_POLICY_NAME \
--action "deny-502"
# restrict traffic to desired IP ranges
gcloud 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

Zusä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.

Netbox application to illustrate

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.

feature of yaml

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.com

Anschließend legen wir einen Service Account an, weisen ihm die Invoker-Berechtigung zu und erstellen den geplanten Job:

# fetch the service URL
export 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 account
gcloud iam service-accounts create $SA_NAME \
--display-name "${SA_NAME}"
# add sa binding to cloud run app
gcloud 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 AM
gcloud 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

Dateisysteme

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 vars
gcloud secrets create $SECRET_ID --replication-policy="automatic"
gcloud secrets versions add $SECRET_ID --data-file=${PWD}/$SECRET_FILE
# mount file path in cloud run
gcloud run deploy $SERVICE --image $IMAGE_URL \
--update-secrets="/env/netbox.env"=$SECRET_ID:$SECRET_VERSION

Best 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:

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!