Condividi tramite


Panoramica di JavaScript sulle Azure Container Apps

App Contenitore di Azure può eseguire qualsiasi applicazione JavaScript in contenitori nel cloud offrendo opzioni flessibili per la distribuzione delle applicazioni.

Impostazione

Le app contenitore di Azure consentono di ottimizzare la distribuzione delle applicazioni JavaScript tramite una containerizzazione efficace, inclusa la configurazione di variabili di ambiente, la progettazione di dockerfile efficienti e l'organizzazione del processo di compilazione dell'applicazione.

Variabili di ambiente

Le variabili di ambiente sono fondamentali per la configurazione dell'applicazione. Usare un .env file per gestire queste variabili in locale e assicurarsi che siano gestite in modo sicuro nell'ambiente di produzione con un servizio come Azure Key Vault.

L'esempio seguente illustra come creare variabili per l'applicazione.

# .env
NODE_ENV=production
PORT=3000
AZURE_COSMOS_DB_ENDPOINT=https://<YOUR_COSMOSDB_RESOURCE_NAME>.documents.azure.com:443/

Contenitori

Un Dockerfile ben configurato è essenziale per la containerizzazione dell'applicazione:

  • Usare un Dockerfile di base: se più progetti condividono una configurazione comune, è possibile creare un Dockerfile di base che includa questi passaggi comuni. Il Dockerfile di ogni progetto può quindi iniziare con FROM questa immagine di base e aggiungere configurazioni specifiche del progetto.

  • Parametrizzazione degli argomenti di compilazione: è possibile usare gli argomenti di compilazione (ARG) nel Dockerfile per renderlo più flessibile. In questo modo, è possibile passare valori diversi per questi argomenti durante la compilazione per lo sviluppo, la gestione temporanea o la produzione.

  • Immagine di base ottimizzata Node.js: assicurarsi di usare un'immagine di baseNode.js appropriata. Prendere in considerazione l'uso di immagini più piccole e ottimizzate, ad esempio le varianti Alpine, per ridurre il sovraccarico.

  • File minimi: copiare solo informazioni di base: concentrarsi sulla copia solo dei file necessari nel contenitore. Creare un .dockerignore file per assicurarsi che i file di sviluppo non vengano copiati in, ad esempio .env e node_modules. Questo file aiuta a velocizzare le compilazioni nei casi in cui gli sviluppatori hanno incluso file non necessari.

  • Compilazione e runtime separati con compilazioni a più fasi: usare compilazioni a più fasi per creare un'immagine finale snella separando l'ambiente di compilazione dall'ambiente di runtime.

  • Precompilare gli artefatti compilando e creando bundle: la precompilazione degli artefatti dell'applicazione (ad esempio la compilazione di TypeScript o la creazione di bundle in JavaScript) prima di copiarli nella fase di runtime può ridurre al minimo le dimensioni dell'immagine, velocizzare la distribuzione dei contenitori e migliorare le prestazioni di avvio a freddo. Un'attenta ordinamento delle istruzioni nel Dockerfile ottimizza anche i tempi di memorizzazione nella cache e ricompilazione.

  • Docker Compose per gli ambienti di sviluppo: Docker Compose consente di definire ed eseguire applicazioni Docker multi-contenitore. Questo approccio multi-contenitore è utile per configurare gli ambienti di sviluppo. È possibile includere il contesto di compilazione e il Dockerfile nel file compose. Questo livello di incapsulamento consente di usare dockerfile diversi per servizi diversi, quando necessario.

Base Dockerfile

Questo file funge da punto di partenza comune per le immagini Node.js. È possibile usarlo con una FROM direttiva nei Dockerfile che fanno riferimento a questa immagine di base. Usare un numero di versione o un commit per supportare la versione recente e sicura dell'immagine.

# Dockerfile.base

FROM node:22-alpine

# Set the working directory
WORKDIR /usr/src/app

# Define build arguments with default values
ARG PORT_DEFAULT=3000
ARG ENABLE_DEBUG_DEFAULT=false

# Set environment variables using the build arguments
ENV PORT=${PORT_DEFAULT}
ENV ENABLE_DEBUG=${ENABLE_DEBUG_DEFAULT}

# Copy package manifests and install dependencies
COPY package*.json ./
RUN npm install

# Expose the application and debugging ports
EXPOSE $PORT
EXPOSE 9229

# This image focuses on common steps; project-specific Dockerfiles can extend this.

Quando si passano valori utilizzando il flag --build-arg durante il processo di build, i valori passati sostituiscono quelli predefiniti fissati nel codice nel Dockerfile.

Per esempio:

docker build \
  --build-arg PORT_DEFAULT=4000 \
  --build-arg ENABLE_DEBUG_DEFAULT=true \
  --tag <IMAGE>:<TAG> \
  --file Dockerfile.base .

In questo esempio, le variabili PORT di ambiente e ENABLE_DEBUG sono impostate su valori espliciti, anziché sui valori predefiniti.

Le convenzioni di assegnazione di tag alle immagini del contenitore, ad esempio l'uso di latest sono una convenzione. Altre informazioni sulle raccomandazioni per l'assegnazione di tag e il controllo delle versioni delle immagini del contenitore.

Configurare l'ambiente di sviluppo con Docker Compose

La configurazione di esempio seguente utilizza un Dockerfile di sviluppo dedicato (Dockerfile.dev) insieme al montaggio dei volumi per l'aggiornamento live e la sincronizzazione dell'origine locale.

version: "3.8"
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.base
      args:
        PORT_DEFAULT: ${PORT:-3000}
        ENABLE_DEBUG_DEFAULT: ${ENABLE_DEBUG:-false}
    ports:
      - "${PORT:-3000}:3000"
      - "9229:9229"  # Expose debug port if needed
    volumes:
      - .:/usr/src/app
      - /usr/src/app/node_modules
    environment:
      - NODE_ENV=development
      - PORT=${PORT:-3000}
      - ENABLE_DEBUG=${ENABLE_DEBUG:-false}

Per avviare Docker Compose con valori personalizzati, è possibile esportare le variabili di ambiente nella riga di comando. Per esempio:

PORT=4000 ENABLE_DEBUG=true docker compose up

Dockerfile di produzione

Questo Dockerfile a più fasi compila l'applicazione e produce un'immagine di runtime snella. Assicurarsi di avere già il .dockerignore file nel codice sorgente in modo che il COPY . . comando non venga copiato in alcun file specifico dell'ambiente di sviluppo non necessario nell'ambiente di produzione.

# Stage 1: Builder
FROM node:22 AS build

WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .

# Build your project (e.g., compile TypeScript or bundle JavaScript)
RUN npm run build

# Stage 2: Runtime
FROM my-base-image:latest AS runtime

WORKDIR /usr/src/app

# Copy only the compiled output and essential files from the build stage
COPY --from=build /usr/src/app/dist ./dist
COPY --from=build /usr/src/app/package*.json ./

# Install only production dependencies
RUN npm ci --omit=dev

# Copy the entrypoint script for remote debugging
COPY entrypoint.sh /usr/src/app/entrypoint.sh
RUN chmod +x /usr/src/app/entrypoint.sh

# Expose the application port (using the PORT environment variable) and the debug port (9229)
EXPOSE $PORT
EXPOSE 9229

# Use the entrypoint script to conditionally enable debugging
ENTRYPOINT ["sh", "/usr/src/app/entrypoint.sh"]

Lo script del punto di ingresso consente di connettersi all'app contenitore per il debug remoto.

Per eseguire un contenitore dall'immagine di produzione compilata con variabili di ambiente personalizzate, eseguire:

docker run \
  --env PORT=4000 \
  --env ENABLE_DEBUG=true \
  --publish 4000:4000 \
  --publish 9229:9229 \
  <IMAGE>:<TAG>

Per le build di produzione, assicurarsi di usare il tag di versione corretto, che potrebbe non essere latest. Le convenzioni di assegnazione di tag alle immagini del contenitore, ad esempio l'uso di latest sono una convenzione. Altre informazioni sulle raccomandazioni per l'assegnazione di tag e il controllo delle versioni delle immagini del contenitore.

Distribuzione

Per supportare l'integrazione continua/la distribuzione continua (CI/CD), configurare una pipeline CI/CD usando GitHub Actions, Azure DevOps o un altro strumento CI/CD per automatizzare il processo di distribuzione.

# .github/workflows/deploy.yml
name: Deploy to Azure

on:
push:
    branches:
    - main

jobs:
build-and-deploy:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4

    - name: Set up Node.js
      uses: actions/setup-node@v4
      with:
          node-version: '22'

    - name: Install dependencies
      run: npm ci

    - name: Build the app
      run: npm run build

    - name: Log in to Azure
      uses: azure/login@v2
      with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

    - name: Deploy to Azure Container Apps
      run: |
          az containerapp up \
          --name my-container-app \
          --resource-group my-resource-group \
          --image my-image:my_tag \
          --environment my-environment \
          --cpu 1 --memory 2Gi \
          --env-vars NODE_ENV=production PORT=3000

Quando si usa Registro Docker, accedere al registro e quindi eseguire il push delle immagini Docker in un registro contenitori, ad esempio Registro Azure Container (ACR) o Docker Hub.

# Tag the image
docker tag \
  <IMAGE>:<TAG> \
  <AZURE_REGISTRY>.azurecr.io/<IMAGE>:<TAG>

# Push the image
docker push <AZURE_REGISTRY>.azurecr.io/<IMAGE>:<TAG>

Avvio a freddo

Ottimizza la build di produzione includendo solo il codice e le dipendenze essenziali. Per assicurarti che il payload sia il più snello possibile, usa uno degli approcci seguenti:

  • Compilazioni o bundler di Docker a più fasi: usare strumenti di compilazione e creazione di bundle come Webpack o Rollup per creare il payload più piccolo possibile per il contenitore. Quando si compila e si aggrega solo ciò che è necessario per la produzione, è possibile ridurre al minimo le dimensioni del contenitore e contribuire a migliorare i tempi di avvio a freddo.

  • Gestire attentamente le dipendenze:node_modules Mantenere la cartella snella includendo solo i pacchetti necessari per l'esecuzione del codice di produzione. Non elencare le dipendenze di sviluppo o di test nella sezione dependencies del tuo package.json. Assicurati di rimuovere tutte le dipendenze inutilizzate e che il tuo file package.json e il file di blocco rimangano coerenti.

Sicurezza

Le considerazioni sulla sicurezza per gli sviluppatori JavaScript che usano App Azure Container includono la protezione delle variabili di ambiente ,ad esempio l'uso di Azure Key Vault, la garanzia di HTTPS con una corretta gestione dei certificati, la gestione delle dipendenze up-to-date con controlli regolari e l'implementazione di registrazione e monitoraggio affidabili per rilevare e rispondere rapidamente alle minacce.

Proteggere le variabili di ambiente

Assicurarsi che le informazioni riservate, ad esempio le stringhe di connessione del database e le chiavi API, vengano archiviate in modo sicuro. Usare Azure Key Vault per gestire i segreti e le variabili di ambiente in modo sicuro.

Prima di eseguire questo comando, assicurarsi di sostituire i segnaposto racchiusi tra <> con i propri valori.

az keyvault secret set \
  --vault-name <KEY_VAULT_APP> \
  --name "<SECRET_NAME>" \
  --value "<CONNECTION_STRING>"

HTTPS e certificati

Assicurarsi che l'applicazione venga servita tramite HTTPS. Le app contenitore di Azure possono gestire automaticamente i certificati . Configurare il dominio personalizzato e il certificato nel portale di Azure.

Gestione delle dipendenze

Aggiornare regolarmente le dipendenze per evitare vulnerabilità di sicurezza. Usare strumenti come npm audit per verificare la presenza di vulnerabilità.

npm audit

Gestione degli errori

Implementare una gestione degli errori affidabile nell'applicazione Node.js. Usare il middleware in Express o Fastify per gestire correttamente gli errori.

// src/middleware/errorHandler.ts
import { Request, Response, NextFunction } from 'express';

export function errorHandler(err: any, req: Request, res: Response, next: NextFunction) {
  console.error(err.stack);
  res.status(500).send('Something broke!');
}

Arresti graziosi

L'arresto corretto dell'applicazione è fondamentale per garantire che le richieste in corso vengano completate e che le risorse vengano rilasciate correttamente. Ciò consente di evitare la perdita di dati e di mantenere un'esperienza utente uniforme durante le distribuzioni o gli eventi con scalabilità orizzontale. L'esempio seguente illustra un approccio che usa Node.js ed Express per gestire correttamente i segnali di arresto.

import express from 'express';
import healthRouter from './health.js';

const app = express();

app.use(healthRouter);

const server = app.listen(process.env.PORT || 3000);

// Graceful shutdown
process.on('SIGTERM', () => {
  console.log('SIGTERM received, shutting down...');
  server.close(() => {
    console.log('Server closed');
    process.exit(0);
  });

  // Force close after 30s
  setTimeout(() => {
    console.error('Could not close connections in time, forcing shutdown');
    process.exit(1);
  }, 30000);
});

Registrazione

Nelle Azure Container Apps, sia le chiamate console.log che console.error vengono acquisite e registrate automaticamente. Azure Container Apps acquisisce i flussi di output standard (stdout) e di errore standard (stderr) dalla tua applicazione e li rende disponibili in Azure Monitor e Log Analytics.

Configurazione della registrazione nelle app Azure Container

Per assicurarsi che i log siano acquisiti e accessibili correttamente, è necessario configurare le impostazioni di diagnostica per l'app Azure Container. Il programma di installazione è un processo in due passaggi.

  1. Abilita impostazioni di diagnostica: usare l'interfaccia della riga di comando di Azure per abilitare le impostazioni di diagnostica per l'app Azure Container.

    Prima di eseguire questo comando, assicurarsi di sostituire i segnaposto racchiusi tra <> con i propri valori.

    az monitor diagnostic-settings create \
    --resource /subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP>/providers/Microsoft.Web/containerApps/<CONTAINER_APP_NAME> \
    --name "containerapp-logs" \
    --workspace <LOG_ANALYTICS_WORKSPACE_ID> \
    --logs '[{"category": "ContainerAppConsoleLogs","enabled": true}]'
    
  2. Accedere ai log nel portale passando all'area di lavoro Log Analytics ed eseguendo query sui log.

Uso delle librerie di registrazione

Mentre console.log e console.error vengono acquisiti automaticamente, l'uso di una libreria di registrazione come Winston offre maggiore flessibilità e controllo sulla registrazione. Questa flessibilità consente di formattare i log, impostare i livelli di log e i log di output su più destinazioni, ad esempio file o servizi di registrazione esterni.

L'esempio seguente illustra come configurare Winston per archiviare log ad alta fedeltà.

// src/logger.ts
import { createLogger, transports, format } from 'winston';

const logger = createLogger({
  level: 'info',
  format: format.combine(
    format.timestamp(),
    format.json()
  ),
  transports: [
    new transports.Console(),
    new transports.File({ filename: 'app.log' })
  ]
});

export default logger;

Per usare il logger, usare la sintassi seguente nell'applicazione:

import logger from './logger';

logger.info('This is an info message');
logger.error('This is an error message');

Debug remoto

Per abilitare il debug remoto, è possibile usare il controllo predefinito di Node. Invece di inserire staticamente le impostazioni di debug nel Dockerfile, CMD è possibile abilitare dinamicamente il debug remoto usando uno script di shell come entrypoint del contenitore.

Lo script seguente controlla una variabile di ambiente ,ad esempio , ENABLE_DEBUGall'avvio del contenitore. Se la variabile è impostata su true, lo script avvia Node.js in modalità di debug (usando --inspect o --inspect-brk). In caso contrario, il contenitore avvia l'applicazione normalmente.

È possibile implementare il debug remoto con la procedura seguente:

  1. Creare uno script del punto di ingresso in un file denominato entrypoint.sh nella radice del progetto con il contenuto seguente:

    #!/bin/sh
    # If ENABLE_DEBUG is set to "true", start Node with debugging enabled
    if [ "$ENABLE_DEBUG" = "true" ]; then
      echo "Debug mode enabled: starting Node with inspector"
      exec node --inspect=0.0.0.0:9229 dist/index.js
    else
      echo "Starting Node without debug mode"
      exec node dist/index.js
    fi
    
  2. Modificare il Dockerfile per copiare lo entrypoint.sh script nel contenitore e impostarlo come punto di ingresso. Esporre anche la porta di debug, se necessario:

    # Copy the entrypoint script to the container
    COPY entrypoint.sh /usr/src/app/entrypoint.sh
    
    # Ensure the script is executable
    RUN chmod +x /usr/src/app/entrypoint.sh
    
    # Expose the debugging port (if using debug mode)
    EXPOSE 9229
    
    # Set the shell script as the container’s entrypoint
    ENTRYPOINT ["sh", "/usr/src/app/entrypoint.sh"]
    
  3. Attivare la modalità di debug impostando la variabile ENABLE_DEBUG di ambiente su true. Ad esempio, usando l'interfaccia della riga di comando di Azure:

    az containerapp update \
      --name <CONTAINER_APP> \
      --env-vars ENABLE_DEBUG=true
    

Prima di eseguire questo comando, assicurarsi di sostituire i segnaposto racchiusi tra <> con i propri valori.

Questo approccio offre una soluzione flessibile che consente di riavviare il contenitore in modalità di debug aggiornando una variabile di ambiente all'avvio. Evita la necessità di creare una nuova revisione con impostazioni diverse CMD ogni volta che è necessario eseguire il debug dell'applicazione.

Considerazioni sulla manutenzione e sulle prestazioni

Per mantenere e ottimizzare le prestazioni dell'applicazione nel tempo, assicurarsi di gestire in modo efficiente le modifiche delle variabili di ambiente, monitorare le risorse, mantenere le dipendenze up-to-date, configurare correttamente il ridimensionamento e configurare gli avvisi di monitoraggio.

Modifiche delle variabili di ambiente

Poiché ogni modifica alle variabili di ambiente richiede una nuova revisione distribuita, apportare tutte le modifiche ai segreti dell'app contemporaneamente. Al termine delle modifiche, collegare i segreti alle variabili di ambiente della revisione. Questo approccio riduce al minimo il numero di revisioni e consente di mantenere una cronologia di distribuzione pulita.

Allocazione delle risorse

Monitorare e regolare l'allocazione di CPU e memoria per i contenitori in base ai modelli di utilizzo e prestazioni dell'applicazione. Il provisioning eccessivo può comportare costi non necessari, mentre il sottoprovisioning può causare problemi di prestazioni.

Sono state aggiornate le dipendenze

Aggiorna regolarmente le dipendenze per trarre vantaggio dai miglioramenti delle prestazioni e dalle patch di sicurezza. Usare strumenti come npm-check-updates per automatizzare questo processo.

npm install -g npm-check-updates
ncu -u
npm install

Scaling

Configurare la scalabilità automatica in base al carico dell'applicazione. App Azure Container supporta il ridimensionamento orizzontale, che regola automaticamente il numero di istanze del contenitore in base all'utilizzo della CPU o della memoria.

L'esempio seguente illustra come impostare una regola di scalabilità basata sulla CPU. Prima di eseguire questo comando, assicurarsi di sostituire i segnaposto racchiusi tra <> con i propri valori.

az containerapp revision set-scale \
  --name <CONTAINER_APP> \
  --resource-group <RESOURCE_GROUP> \
  --min-replicas 1 \
  --max-replicas 10 \
  --cpu 80

Monitoraggio degli avvisi

Configurare il monitoraggio e gli avvisi per tenere traccia delle prestazioni e dell'integrità dell'applicazione. Usare Monitoraggio di Azure per creare avvisi per metriche specifiche, ad esempio l'utilizzo della CPU, l'utilizzo della memoria e i tempi di risposta.

Prima di eseguire questo comando, assicurarsi di sostituire i segnaposto racchiusi tra <> con i propri valori.

az monitor metrics alert create \
  --name "HighCPUUsage" \
  --resource-group <RESOURCE_GROUP> \
  --scopes /subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP>/providers/Microsoft.ContainerInstance/containerGroups/<CONTAINER_GROUP> \
  --condition "avg Percentage CPU > 80" \
  --description "Alert when CPU usage is above 80%"

Gestione delle risorse

Usare l'estensione App Azure Container per Visual Studio Code per creare, modificare e distribuire rapidamente app in contenitori direttamente da Visual Studio Code.

Risoluzione dei problemi

Quando la tua applicazione incontra problemi di runtime su Azure Container Apps, puoi utilizzare la registrazione, il debug remoto e gli avvisi di controllo dell'integrità per individuare e risolvere il problema.

Registrazione

Abilitare e configurare la registrazione per acquisire i log applicazioni. Usare Monitoraggio di Azure e Log Analytics per raccogliere e analizzare i log. Prima di eseguire questi comandi, assicurati di sostituire i segnaposto racchiusi tra <> con i tuoi valori.

  1. Creare una nuova area di lavoro.

    az monitor log-analytics workspace create \
        --resource-group <RESOURCE_GROUP> \
        --workspace-name <WORKSPACE_NAME>
    
  2. Creare quindi una nuova impostazione dell'area di lavoro.

    az monitor diagnostic-settings create \
        --resource <CONTAINER_APP> \
        --workspace <WORKSPACE_NAME> \
        --logs '[{"category": "ContainerAppConsoleLogs","enabled": true}]'
    

Risoluzione dei problemi

Si utilizzano gli strumenti di debug remoto per connettersi al contenitore in esecuzione. Assicurarsi che il Dockerfile esponga le porte necessarie per il debug.

# Expose the debugging port
EXPOSE 9229

Controlli di salute

Configurare i controlli di integrità per monitorare l'integrità dell'applicazione. Questa funzionalità garantisce che le app di Azure Container possano riavviare il contenitore se non risponde.

# Azure Container Apps YAML configuration
properties:
configuration:
    livenessProbe:
    httpGet:
        path: /health
        port: 3000
    initialDelaySeconds: 30
    periodSeconds: 10