
Mettere ordine in reti e apparati con l'IP Address Management (IPAM)
Negli ultimi tempi sempre più clienti mi segnalano problemi di networking, in particolare sul peering, dovuti a collisioni tra range di indirizzi IP. Un chiaro segnale che è arrivato il momento di pianificare e gestire gli indirizzi IP in tutta l'organizzazione.
Tenere traccia degli IP in un foglio di calcolo condiviso è possibile, ma esistono anche strumenti software dedicati. In questo articolo vediamo come eseguire Netbox, un noto tool open source per l'IP address management (IPAM), in modalità cloud-native su Google Cloud Platform (GCP).
Lo stack tradizionale
Storicamente Netbox gira su una o più macchine virtuali, con davanti un web server. Esiste un'immagine Docker mantenuta dalla community, ma le uniche istruzioni disponibili prevedono l'avvio tramite docker compose. Si tratta di un'architettura simile a molte applicazioni che le aziende sviluppano o gestiscono in casa: un ottimo caso di studio anche per illustrare la migrazione verso il cloud pubblico.

Fonte: Netbox — installazione standard di Netbox
Un design cloud-native
Ho deciso di approfondire il funzionamento dell'immagine Docker, le sue dipendenze e i parametri di configurazione, per poi distribuirla su GCP utilizzando esclusivamente servizi gestiti. Questo esempio può fare da riferimento per chi sta valutando un approccio "move and improve" oppure "rip and replace" nella migrazione delle proprie applicazioni al cloud pubblico.

Installazione di Netbox su GCP rivisitata con i servizi gestiti
Componenti dell'applicazione
- Applicazione Netbox (app Python basata sul framework Django)
- Database PostgreSQL (Cloud SQL)
- Redis (Cloud Memorystore)
Scelte progettuali
- Database e cache gestiti (Cloud SQL, Cloud Memorystore)
- IP esclusivamente privati per database e cache (Private Service Access)
- DNS privato per gli hostname dei database (Cloud DNS)
- Secret archiviati nel secret manager (Secret Manager)
- Runtime serverless per i container (Cloud Run, Artifact Registry)
- Load balancer globale con TLS (HTTP(S) Load Balancing, Managed Certificate)
- Firewall WAF (Cloud Armor)
Una difficoltà che vedo affrontare a molte organizzazioni è collegare servizi gestiti e applicazioni serverless tramite indirizzi IP privati. Questo esempio mostra come riservare range privati nella propria rete VPC per poi assegnarli ai servizi gestiti, creando di fatto un ponte di connettività.
Cloud DNS viene usato per definire hostname privati con cui le app si collegano ai database. Si guadagna così flessibilità in futuro: se dovessi cambiare database o gestire un failover, basterà aggiornare i record DNS e le applicazioni continueranno a puntare allo stesso dominio. In teoria avrei potuto sfruttare il forwarding DNS e collegare il tutto al mio dominio pubblico, ma a livello interno non serviva, quindi ho usato example.com.
La VM bastion (o jump host) non era strettamente necessaria, ma ne ho creata una per testare le connessioni durante il setup. Di solito un bastion viene distribuito in un managed instance group (MIG) di dimensione 1 e senza indirizzi IP esterni.
Web application sicura e in load balancing

Per mostrare al meglio come tutto si incastra, ho usato un dominio personale, registrando un "record A" per l'indirizzo IP statico assegnato al Global Load Balancer; il certificato gestito è stato provisionato in automatico.

Per maggiore sicurezza ho applicato al load balancer una policy Cloud Armor (firewall WAF) e ho ristretto i range IP (vedi sotto).
Codice di implementazione
Il codice qui sotto illustra, passo dopo passo, i comandi che ho utilizzato per configurare l'intero ambiente: networking, variabili d'ambiente e secret, database, artifact registry e immagini Docker, Cloud Run, load balancing e firewall 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 \ --globalComplessità aggiuntiva e considerazioni
Uno dei motivi per cui ho scelto Netbox per illustrare come modernizzare e distribuire applicazioni in modalità cloud-native è proprio la sua complessità tecnica. L'applicazione comprende file system, sessioni, worker e perfino processi cron giornalieri di pulizia.

Estratto del Docker Compose dell'applicazione Netbox (notare netbox-worker e netbox-housekeeping)
Lo snippet del file docker-compose.yaml qui sopra mostra una funzionalità di YAML chiamata anchors, che non è specifica di docker-compose.

Permette di duplicare le configurazioni in modo sintetico per poi sovrascrivere i comandi ed eseguire script diversi a runtime.
Ricreare i worker su Cloud Run
Per riprodurre questo tipo di funzionalità su Cloud Run sono disponibili i flag --cmd e --args. Basta duplicare i comandi usati per il deploy dell'applicazione principale, cambiare il nome e sovrascrivere il CMD per eseguire un entrypoint script differente, come nell'esempio seguente:
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"Job di housekeeping giornaliero con Cloud Run e Cloud Scheduler
Il job di housekeeping giornaliero si può eseguire creando un servizio duplicato su Cloud Run e schedulandone l'invocazione quotidiana tramite 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"Una volta effettuato il deploy del servizio di housekeeping, abilitiamo l'API di Cloud Scheduler:
gcloud services enable cloudscheduler.googleapis.comA questo punto creiamo un service account, gli assegniamo i permessi di invoker e creiamo il job pianificato:
# 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_URLFile system
Il mio obiettivo era dimostrare che si può scomporre un'applicazione complessa come Netbox e distribuirla nel cloud usando Cloud Run e altri servizi gestiti. Forse non è la soluzione ottimale per questa specifica applicazione, ma è fattibile.
Se hai bisogno di sfruttare il file system, attualmente le piattaforme serverless presentano alcuni limiti: meglio orientarsi su Kubernetes Engine, oppure anche solo su una VM di Compute Engine. Puoi eseguire una VM come container, soluzione molto elegante, e poi collegare dischi/volumi all'occorrenza.
Un trucco per ottenere un "file system" basilare in Cloud Run, però, è sfruttare Secret Manager, come ho fatto nel codice di esempio e nello snippet seguente.
# 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: service account separati
Anche se negli esempi mostrati ho usato un service account dedicato solo per l'add-on Cloud Scheduler, la best practice è creare service account separati per ciascun servizio e per il bastion (VM), assegnando a ognuno solo i ruoli IAM strettamente necessari. Si rispetta così il principio del minimo privilegio.
Per il servizio Cloud Run conviene creare un service account dedicato "netbox-runner" e concedergli solo i ruoli effettivamente necessari, ad esempio:
- Cloud SQL Client
- Redis Viewer
- Secret Accessor
- Artifact Registry Reader
- Service Account User
- Storage Object Viewer
Spero che questo esempio dimostri come si possano modernizzare le applicazioni esistenti sfruttando i servizi gestiti del cloud pubblico. Se ti serve solo mettere in piedi Netbox, gli snippet di codice qui sopra dovrebbero bastare, ma puoi anche valutare l'esecuzione su VM o K8s.
Potresti inoltre convertire l'esempio in Terraform usando soluzioni di terze parti come Terraformer, oppure i tool di bulk export di GCP, che fanno reverse engineering dell'infrastruttura esistente generando codice Terraform.
Se anche la tua organizzazione si trova ad affrontare problemi di collisioni IP nella configurazione delle reti, forse è il momento di adottare l'IPAM, che sia con un foglio di calcolo condiviso o con uno strumento diffuso come Netbox.
Buon clouding!