Condividi tramite


Eseguire la migrazione di un'app da Heroku ad App Azure Container

Questo articolo illustra come eseguire la migrazione di un'applicazione Heroku ad App Azure Container. Si esporta la configurazione di Heroku, si distribuisce l'app, si esegue la migrazione dei servizi dati, si configura CI/CD e si configurano domini personalizzati.

Per una panoramica concettuale del concetto di heroku-to-Azure, degli equivalenti dei servizi e delle insidie comuni, vedere Panoramica della migrazione di Heroku alle app contenitore di Azure.

Obiettivi di apprendimento

In questo articolo vengono illustrate le operazioni seguenti:

  • Esportare la configurazione dell'app Heroku e distribuirla in App Azure Container
  • Eseguire la migrazione dei dati PostgreSQL e Redis ai servizi gestiti di Azure
  • Configurare una pipeline CI/CD con GitHub Actions
  • Configurare domini personalizzati con certificati TLS gestiti
  • Configurare le regole di scalabilità automatica per l'app migrata

Prerequisiti

  • Account Azure con una sottoscrizione attiva. Creane uno gratis.

  • Azure CLI (versione 2.53.0 o successiva) con l'estensione Container Apps installata.

    az extension add --name containerapp --upgrade
    az provider register --namespace Microsoft.App
    
  • Interfaccia della riga di comando heroku installata e autenticata (usata per esportare dati e configurazione).

  • Docker (facoltativo: necessario solo se si compilano immagini in locale).

  • Il codice sorgente dell'app nel repository Git.

  • Familiarità con: gestione delle app Heroku, comandi di base dell'interfaccia della riga di comando di Azure e concetti relativi ai contenitori.

1 - Esportare la configurazione di Heroku

Per iniziare, esportare le variabili di configurazione dell'app Heroku. Usare questo file come riferimento quando si impostano le variabili di ambiente in Azure.

heroku config -a <HEROKU_APP_NAME> --json > heroku-config.json

Annotazioni

Sostituire <HEROKU_APP_NAME> con il nome dell'app Heroku. In questo articolo sostituire i valori tra parentesi angolari (< >) con i propri valori.

2 - Creare risorse di Azure

Definire le variabili della shell usate in questa procedura. Creare quindi un gruppo di risorse e un ambiente di applicazioni container.

# Define variables used throughout this migration.
# Replace the placeholder values with your own.
RESOURCE_GROUP="<RESOURCE_GROUP>"
LOCATION="eastus"
ENVIRONMENT="<ENVIRONMENT_NAME>"
APP_NAME="<APP_NAME>"

Registrare i provider di risorse necessari. Creare il gruppo di risorse e l'ambiente.

# Register resource providers (required once per subscription)
az provider register --namespace Microsoft.App
az provider register --namespace Microsoft.OperationalInsights

# Create a resource group to hold all migration resources
az group create \
  --name $RESOURCE_GROUP \
  --location $LOCATION

# Create a Container Apps environment, which automatically
# provisions a Log Analytics workspace for logging
az containerapp env create \
  --name $ENVIRONMENT \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION

Annotazioni

La creazione dell'ambiente effettua automaticamente il provisioning di un'area di lavoro Log Analytics. Questo passaggio può richiedere da uno a due minuti.

Verifica: conferma che l'ambiente sia in esecuzione.

az containerapp env show \
  --name $ENVIRONMENT \
  --resource-group $RESOURCE_GROUP \
  --query "properties.provisioningState" -o tsv

L'output dovrebbe visualizzare Succeeded.

3 - Implementare l'app

Scegliere una delle opzioni di distribuzione seguenti in base alla configurazione dell'app.

Opzione A: Eseguire la distribuzione dall'origine (nessun Dockerfile necessario)

Questo comando utilizza Cloud Native Buildpacks per rilevare la lingua, la compilazione e la distribuzione automaticamente, in modo analogo all'esperienza Heroku git push.

az containerapp up \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --environment $ENVIRONMENT \
  --source . \
  --ingress external \
  --target-port 3000

Annotazioni

Il --source flag utilizza Container Apps Cloud Build, che potrebbe non essere disponibile in tutte le regioni o per tutti gli stack di linguaggi. Se ha esito negativo, usare invece l'opzione B.

Se l'app non ha già un Dockerfile, crearne una. L'esempio seguente mostra un dockerfile di Node.js minimo che installa le dipendenze di produzione, copia il codice dell'applicazione e avvia il server.

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

Compilare l'immagine tramite Azure Container Registry e distribuirla su Container Apps. Questa sequenza crea un registro, compila l'immagine nel cloud, registra il registro con App contenitore e aggiorna l'app contenitore per l'uso della nuova immagine.

# Create an Azure Container Registry
az acr create \
  --name <REGISTRY_NAME> \
  --resource-group $RESOURCE_GROUP \
  --sku Basic \
  --admin-enabled true

# Build the image in ACR (no local Docker required)
az acr build \
  --registry <REGISTRY_NAME> \
  --image $APP_NAME:v1 .

# Retrieve the ACR password for registry authentication
ACR_PASSWORD=$(az acr credential show \
  --name <REGISTRY_NAME> \
  --query "passwords[0].value" -o tsv)

# Register ACR with the container app
az containerapp registry set \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --server <REGISTRY_NAME>.azurecr.io \
  --username <REGISTRY_NAME> \
  --password $ACR_PASSWORD

# Deploy the image to the container app
az containerapp update \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --image <REGISTRY_NAME>.azurecr.io/$APP_NAME:v1

Suggerimento

az acr build compila l'immagine Docker nel cloud. Non è necessario che Docker sia installato in locale. Questo approccio è il percorso di distribuzione più affidabile.

Verificare: verificare che l'app contenitore sia in esecuzione.

az containerapp show \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --query "properties.runningStatus" -o tsv

4 - Impostare le variabili di ambiente

App contenitore usa segreti per valori sensibili, ad esempio stringhe di connessione e chiavi API. È necessario impostare i segreti prima di farvi riferimento nelle variabili di ambiente.

Importante

Impostare i segreti prima di farvi riferimento come variabili di ambiente. L'ordine è importante: fare riferimento a un segreto che non esiste ancora causa un errore.

I comandi seguenti creano segreti in App contenitore e quindi impostano le variabili di ambiente che fanno riferimento a tali segreti. L'aggiornamento dei segreti da solo non riavvia l'app: il az containerapp update comando crea una nuova revisione che preleva i nuovi valori.

# Set secrets (connection strings, API keys)
az containerapp secret set \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --secrets "redis-url=<AZURE_REDIS_CONNECTION_STRING>" \
            "api-key=<YOUR_API_KEY>"

# Set environment variables that reference the secrets
az containerapp update \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --set-env-vars "REDIS_URL=secretref:redis-url" \
                 "API_KEY=secretref:api-key"

Suggerimento

Usare uno script per convertire i comandi heroku-config.json in az containerapp update per la migrazione di massa delle variabili di ambiente.

5 - Verificare la distribuzione

Recuperare l'URL dell'applicazione, quindi testare l'app in un browser o usando curl. Usare il flusso di log per monitorare gli errori di avvio.

# Get the app URL
az containerapp show \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --query "properties.configuration.ingress.fqdn" -o tsv

# Stream live console logs to check for errors
az containerapp logs show \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --type console \
  --follow

6 - Eseguire la migrazione dei servizi dati

Eseguire la migrazione di PostgreSQL

La sequenza seguente esporta un backup da Heroku Postgres, crea un server flessibile di Database di Azure per PostgreSQL, ripristina i dati e aggiorna la stringa di connessione nell'app contenitore.

# Export a backup from Heroku Postgres
heroku pg:backups:capture -a <HEROKU_APP_NAME>
heroku pg:backups:download -a <HEROKU_APP_NAME>

Creare il server flessibile e il database di Azure Database for PostgreSQL.

# Create the PostgreSQL Flexible Server instance
az postgres flexible-server create \
  --resource-group $RESOURCE_GROUP \
  --name <PG_SERVER_NAME> \
  --location $LOCATION \
  --admin-user <ADMIN_USER> \
  --admin-password '<STRONG_PASSWORD>' \
  --sku-name Standard_B1ms \
  --tier Burstable

# Create the application database
az postgres flexible-server db create \
  --resource-group $RESOURCE_GROUP \
  --server-name <PG_SERVER_NAME> \
  --database-name <DATABASE_NAME>

Ripristinare il backup heroku nel nuovo database di Azure e aggiornare la stringa di connessione.

# Restore the Heroku backup to Azure PostgreSQL
pg_restore \
  --host=<PG_SERVER_NAME>.postgres.database.azure.com \
  --port=5432 \
  --username=<ADMIN_USER> \
  --dbname=<DATABASE_NAME> \
  --no-owner --no-acl \
  latest.dump

# Update the Container App with the new connection string
az containerapp secret set \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --secrets "database-url=postgresql://<ADMIN_USER>:<PASSWORD>@<PG_SERVER_NAME>.postgres.database.azure.com:5432/<DATABASE_NAME>?sslmode=require"

Verificare: connettersi al database di Azure e verificare che le tabelle e i conteggi delle righe corrispondano all'origine Heroku.

Eseguire la migrazione di Redis

Creare un'istanza di Azure Cache per Redis e collegarla all'app contenitore. I comandi seguenti effettuano il provisioning della cache, recuperano la chiave di accesso e impostano la stringa di connessione come segreto.

# Create Azure Cache for Redis (provisioning takes 10–20 minutes)
az redis create \
  --resource-group $RESOURCE_GROUP \
  --name <REDIS_NAME> \
  --location $LOCATION \
  --sku Basic \
  --vm-size c0

# Retrieve the primary access key
az redis list-keys \
  --resource-group $RESOURCE_GROUP \
  --name <REDIS_NAME> \
  --query "primaryKey" -o tsv

# Store the connection string as a Container App secret
az containerapp secret set \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --secrets "redis-url=rediss://:<ACCESS_KEY>@<REDIS_NAME>.redis.cache.windows.net:6380"

# Set the environment variable referencing the secret
az containerapp update \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --set-env-vars "REDIS_URL=secretref:redis-url"

Annotazioni

Il provisioning di Cache Redis di Azure può richiedere da 10 a 20 minuti. Redis viene in genere usato come cache, quindi, a meno che non venga usato come archivio dati primario, non sono presenti dati di cui eseguire la migrazione. Indirizzare l'app alla nuova istanza.

Verifica: Conferma la connessione Redis controllando lo stato di salute dell'app o i log dopo l'aggiornamento.

Altri componenti aggiuntivi

Per altri componenti aggiuntivi di Heroku, vedere la tabella Equivalenti del servizio nella panoramica della migrazione.

Per ogni componente aggiuntivo: effettuare il provisioning del servizio equivalente di Azure, aggiornare i dettagli di connessione nelle variabili di ambiente dell'app contenitore, convalidare l'integrazione e quindi rimuovere il componente aggiuntivo Heroku.

7 - Configurare CI/CD

GitHub Actions

Creare un flusso di lavoro di GitHub Actions che compila l'immagine Docker, esegue il push su ACR e la distribuisce sulle App contenitori ad ogni push nel branch main. Salvare il file seguente come .github/workflows/deploy.yml nel repository.

Questo flusso di lavoro esegue i passaggi seguenti:

  1. Recupera il codice sorgente.
  2. Accede ad Azure usando un'entità servizio archiviata come segreto GitHub.
  3. Compila ed esegue il push di un'immagine Docker nel Azure Container Registry (ACR), contrassegnata con il Git commit SHA.
  4. Aggiorna l'app contenitore per usare la nuova immagine, che attiva una nuova revisione.
name: Deploy to Azure Container Apps

on:
  push:
    branches: [main]

# Environment variables shared across all jobs.
# Update these values to match your Azure resource names.
env:
  AZURE_CONTAINER_REGISTRY: <REGISTRY_NAME>.azurecr.io
  IMAGE_NAME: <APP_NAME>
  RESOURCE_GROUP: <RESOURCE_GROUP>
  CONTAINER_APP_NAME: <APP_NAME>

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      # Check out the repository source code
      - uses: actions/checkout@v4

      # Authenticate to Azure using the service principal credentials
      - uses: azure/login@v2
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      # Build the Docker image and push it to ACR
      - name: Build and push image
        run: |
          az acr login --name <REGISTRY_NAME>
          docker build -t ${{ env.AZURE_CONTAINER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} .
          docker push ${{ env.AZURE_CONTAINER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}

      # Deploy the new image to the container app
      - name: Deploy to Container Apps
        run: |
          az containerapp update \
            --name ${{ env.CONTAINER_APP_NAME }} \
            --resource-group ${{ env.RESOURCE_GROUP }} \
            --image ${{ env.AZURE_CONTAINER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}

Crea l'entità servizio: esegui il seguente comando per creare un'entità servizio con accesso limitato al tuo gruppo di risorse. Archiviare l'output JSON come AZURE_CREDENTIALS segreto nelle impostazioni del repository GitHub.

az ad sp create-for-rbac \
  --name "github-deploy" \
  --role contributor \
  --scopes /subscriptions/<SUBSCRIPTION_ID>/resourceGroups/$RESOURCE_GROUP \
  --json-auth

Azure DevOps

Usare Azure Container Apps Deploy task nella pipeline di Azure DevOps. Il flusso di lavoro è simile: costruire l'immagine, eseguire il push in ACR e aggiornare l'app contenitore.

8 - Configurare domini personalizzati e TLS

I comandi seguenti aggiungono un dominio personalizzato all'app contenitore, recuperano i record di verifica DNS e associano un certificato TLS gestito gratuito. È necessario aggiungere record DNS nel provider di dominio tra i passaggi 2 e 4.

# Add your custom domain to the container app
az containerapp hostname add \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --hostname <YOUR_DOMAIN>

# List hostnames to get the required DNS verification records
az containerapp hostname list \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  -o table

Nel provider DNS aggiungere i record seguenti:

  • Record TXT: per la verifica del dominio (valore visualizzato nell'output del comando precedente).
  • Record CNAME: punta <YOUR_DOMAIN> a <APP_NAME>.<REGION>.azurecontainerapps.io.

Dopo la propagazione DNS, associare il certificato gestito.

# Bind a free managed TLS certificate (auto-renews)
az containerapp hostname bind \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --hostname <YOUR_DOMAIN> \
  --environment $ENVIRONMENT \
  --validation-method CNAME

Verifica: verificare che il certificato sia associato.

az containerapp hostname list \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  -o table

I certificati gestiti sono gratuiti e si rinnovano automaticamente, equivalenti al sistema Automated Certificate Management di Heroku.

9 - Configurare il ridimensionamento

Configurare le regole di scalabilità automatica per sostituire il ridimensionamento manuale dei dyno di Heroku. Il comando seguente configura la scalabilità automatica basata su HTTP che viene ridimensionata tra 0 e 10 repliche in base al caricamento simultaneo delle richieste, con una nuova replica aggiunta ogni volta che una singola istanza supera 50 richieste simultanee.

az containerapp update \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --min-replicas 0 \
  --max-replicas 10 \
  --scale-rule-name http-rule \
  --scale-rule-type http \
  --scale-rule-http-concurrency 50

Per i processi di lavoro, distribuire un'app contenitore separata con scalabilità basata su coda. Il comando seguente crea un'applicazione worker container che viene ridimensionata da 0 a 5 repliche in base al numero di messaggi in una coda di archiviazione Azure.

az containerapp create \
  --name <APP_NAME>-worker \
  --resource-group $RESOURCE_GROUP \
  --environment $ENVIRONMENT \
  --image <REGISTRY_NAME>.azurecr.io/<APP_NAME>-worker:latest \
  --min-replicas 0 \
  --max-replicas 5 \
  --scale-rule-name queue-rule \
  --scale-rule-type azure-queue \
  --scale-rule-metadata "queueName=jobs" "queueLength=10" \
  --scale-rule-auth "connection=queue-connection-string"

Suggerimento

Per le app di produzione che devono rispondere immediatamente, impostare --min-replicas 1. Per gli ambienti di sviluppo e gestione temporanea, usare --min-replicas 0 per sfruttare i vantaggi della scalabilità fino a zero ed eliminare i costi di inattività.

Risoluzione dei problemi

Problema Motivo Risoluzione
L'app non risponde alle richieste HTTP dopo la distribuzione Container Apps prevede che l'app sia in ascolto sulla porta specificata dalla variabile di ambiente PORT (di default 80). L'app potrebbe essere in ascolto su una porta diversa. Impostare --target-port sulla porta in cui l'app è in ascolto durante la creazione o l'aggiornamento dell'app contenitore.
az containerapp up --source ha esito negativo con errori del generatore La compilazione cloud non è disponibile in tutte le aree o per tutti gli stack di linguaggi. Usare l'approccio basato su Dockerfile: compilare con az acr build e distribuire l'immagine. Vedere Opzione B: Distribuire con un Dockerfile.
Le variabili di ambiente che fanno riferimento ai segreti sono vuote I segreti devono esistere prima di farvi riferimento nelle variabili di ambiente. Eseguire prima az containerapp secret set, quindi az containerapp update per impostare le variabili d'ambiente. Vedere Passaggio 4.
Il provisioning dei servizi di Azure richiede più tempo del previsto I servizi gestiti di Azure hanno tempi di provisioning più lunghi rispetto ai componenti aggiuntivi Heroku. Cache di Azure per Redis: 10-20 minuti. Server flessibile PostgreSQL: 5-10 minuti. Effettuare il provisioning dei servizi in parallelo durante la distribuzione dell'app.
I file scritti in fase di esecuzione scompaiono dopo il riavvio App contenitore di Azure usa un file system temporaneo, simile a Heroku. Montare una condivisione di File di Azure per l'archiviazione permanente.

Pulire le risorse

Se hai creato risorse specifiche per questa procedura dettagliata di migrazione, elimina il gruppo di risorse per rimuovere tutte le risorse associate ed evitare ulteriori addebiti.

az group delete --name $RESOURCE_GROUP --yes --no-wait

Attenzione

Questo comando elimina il gruppo di risorse e tutte le risorse al suo interno, inclusi database, registri contenitori e app contenitore. Non è possibile annullare questa azione.