Migra apps de Node.js desde Heroku hacia Cloud Run

En este instructivo, se describe cómo migrar apps web de Node.js que se ejecutan en Heroku a Cloud Run en Google Cloud. Este instructivo está dirigido a los arquitectos y propietarios de productos que quieran migrar sus apps de Heroku a servicios administrados en Google Cloud.

Cloud Run es una plataforma de procesamiento administrada que te permite ejecutar contenedores sin estado que se pueden invocar a través de solicitudes HTTP. Se compila en Knative de código abierto, que permite la portabilidad entre plataformas y admite flujos de trabajo de contenedores y estándares para la entrega continua. La plataforma de Cloud Run está integrada en el paquete de productos de Google Cloud y facilita el diseño y desarrollo de apps portátiles, escalables y resilientes.

En este instructivo, aprenderás a migrar una app a Google Cloud que está escrita en Node.js y usa Heroku Postgres como un servicio de apoyo en Heroku. La app web está en contenedores y alojada en Cloud Run, y usa Cloud SQL para PostgreSQL como su capa de persistencia.

En el instructivo, usarás una app simple llamada Tasks que te permite ver y crear tareas. Estas tareas se almacenan en Heroku Postgres en la implementación actual de la app en Heroku.

En este instructivo, se supone que estás familiarizado con la funcionalidad básica de Heroku y que tienes una cuenta de Heroku (o acceso a una). También se supone que estás familiarizado con Cloud Run, Cloud SQL, Docker y Node.js.

Objetivos

  • Compilar una imagen de Docker para implementar la app en Cloud Run
  • Crear una instancia de Cloud SQL para PostgreSQL que funcione como backend después de la migración a Google Cloud
  • Revisar el código de Node.js a fin de comprender cómo Cloud Run se conecta a Cloud SQL y ver los cambios de código necesarios (si los hay) para migrar a Cloud Run desde Heroku
  • Migrar datos de Heroku Postgres a Cloud SQL para PostgreSQL
  • Implementar la app en Cloud Run
  • Probar la app implementada

Costos

En este instructivo, se usan los siguientes componentes facturables de Google Cloud:

Para generar una estimación de costos en función del uso previsto, usa la calculadora de precios. Es posible que los usuarios nuevos de Google Cloud sean aptos para obtener una prueba gratuita.

También es posible que se te cobre por los recursos que uses en Heroku.

Antes de comenzar

  1. Accede a tu Cuenta de Google.

    Si todavía no tienes una cuenta, regístrate para obtener una nueva.

  2. En la página del selector de proyectos de Google Cloud Console, selecciona o crea un proyecto de Google Cloud.

    Ir a la página del selector de proyectos

  3. Comprueba que la facturación esté habilitada en tu proyecto.

    Descubre cómo puedes habilitar la facturación

  4. Habilita las API de Cloud SQL and Cloud Run .

    Habilita las API

Configura tu entorno

  1. Abre Cloud Shell

    Abrir Cloud Shell

  2. En Cloud Shell, asigna la configuración predeterminada para los valores que se usan en todo el instructivo, como región y zona. En este instructivo, usarás us-central1 como la región predeterminada y us-central1-a como la zona predeterminada.

    gcloud config set compute/region us-central1
    gcloud config set compute/zone us-central1-a
    
  3. Configura la herramienta de línea de comandos de gcloud a fin de usar us-central1 como la región predeterminada para Cloud Run:

    gcloud config set run/region us-central1
    
  4. Crea una variable de entorno a fin de contener un nombre de app predeterminado para este instructivo:

    export APP_NAME=tasks-web-app
    

Arquitectura

En las siguientes figuras, se describe la arquitectura de la app web en Heroku (tal como está) y su diseño arquitectónico en Google Cloud (que compilarás).

Arquitectura tal como está en Heroku.
Figura 1. Arquitectura tal como está en Heroku.

La app de Tasks que se implementa en la actualidad en Heroku consta de uno o más dynos web. Los dynos web pueden recibir y responder al tráfico HTTP, a diferencia de los dynos de trabajador, que son más adecuados para trabajos en segundo plano y tareas temporizadas. La app entrega una página de índice que muestra las tareas almacenadas en una base de datos de Postgres con la biblioteca de plantillas Mustache para Node.js.

Puedes acceder a la app mediante una URL HTTPS. Una ruta /tasks en esa URL te permite crear tareas nuevas.

Arquitectura tal como está en Heroku.
Figura 2. Arquitectura que compilas en Google Cloud

En Google Cloud, Cloud Run se usa como la plataforma sin servidores para implementar la app de Tasks. Cloud Run está diseñado con el fin de ejecutar contenedores sin estado basados en solicitudes. Es adecuado para cuando necesitas que tu servicio administrado admita apps en contenedores que realizan un ajuste de escala automático y escalan a cero cuando no entregan tráfico.

Asigna componentes que se usan en Heroku a Google Cloud

En la siguiente tabla, se asignan componentes de la plataforma de Heroku a Google Cloud. Esta asignación te ayuda a traducir la arquitectura descrita en este instructivo de Heroku a Google Cloud.

Componente Plataforma de Heroku Google Cloud
Contenedores Dynos: Heroku usa el modelo de contenedor para compilar y escalar apps de Heroku. Estos contenedores de Linux se denominan dynos y pueden escalar a un número que especifiques para admitir las demandas de recursos de tu app de Heroku. Puedes seleccionar entre un rango de tipos de dynos en función de los requisitos de CPU y memoria de la app. Contenedores de Cloud Run: Google Cloud admite la ejecución de cargas de trabajo en contenedores sin estado que se pueden ejecutar en un entorno completamente administrado o en clústeres de Google Kubernetes Engine (GKE).
App web App de Heroku: Los dynos son las piezas fundamentales de las apps de Heroku. Por lo general, las apps constan de uno o más tipos de dynos, que suelen ser una combinación de dynos web y de trabajador. Servicio de Cloud Run: Una app web se puede modelar como un servicio de Cloud Run. Cada servicio obtiene su propio extremo HTTPS y puede aumentar o reducir la escala de forma automática de 0 a N según el tráfico al extremo del servicio.
Base de datos Heroku Postgres es la base de datos como servicio (DaaS) de Heroku basada en PostgreSQL. Cloud SQL es un servicio de base de datos administrado para bases de datos relacionales en Google Cloud. Ofrece dos variantes: PostgreSQL y MySQL.

Implementa la app web de Tasks de muestra en Heroku

En las siguientes secciones, se muestra cómo configurar la interfaz de línea de comandos (CLI) para Heroku, clonar el repositorio de código fuente de GitHub e implementar la app en Heroku.

Configura la interfaz de línea de comandos para Heroku

  1. En Cloud Shell, ejecuta el siguiente comando para configurar la CLI de Heroku:

    curl https://cli-assets.heroku.com/install-ubuntu.sh | sh
    
  2. Accede a tu cuenta de Heroku:

    heroku login --interactive
    

Clona el repositorio de código fuente

  1. En Cloud Shell, clona el repositorio de GitHub de la app de Tasks de muestra:

    git clone https://github.com/GoogleCloudPlatform/migrate-webapp-heroku-to-cloudrun-node.git
    
  2. Cambia los directorios al directorio que se creó cuando se clonó el repositorio:

    cd migrate-webapp-heroku-to-cloudrun-node
    

    El directorio contiene los siguientes archivos:

    • Una secuencia de comandos de Node.js llamada index.js con el código para las rutas que entrega la app web
    • Archivos package.json y package-lock.json que describen las dependencias de la app web. Debes instalar estas dependencias para que la app se ejecute
    • Un archivo Procfile que especifica el comando que ejecuta la app en el inicio. Debes crear un archivo Procfile para implementar la app en Heroku
    • Un directorio views, con el contenido HTML que entrega la app web en la ruta “/”
    • Un archivo .gitignore

Implementa una app en Heroku

  1. En Cloud Shell, crea una app de Heroku:

    heroku create
    
  2. Agrega el complemento de Heroku Postgres para aprovisionar una base de datos de PostgreSQL:

    heroku addons:create heroku-postgresql:hobby-dev
    
  3. Asegúrate de que el complemento se haya agregado de forma correcta:

    heroku addons
    

    Si el complemento de Postgres se agregó de forma correcta, verás un mensaje similar al siguiente:

    Owning-App               Add-on                      Plan                          Price    State
    ----------------------   ------------------------    --------------------------    -----    -----
    sample-nodejs-todo-app   postgresql-adjacent-95585   heroku-postgresql:hobby-dev   free     created
    
  4. Implementa la app en Heroku:

    git push heroku master
    
  5. Recupera el URI de Heroku Postgres desde la consola de Heroku. Como alternativa, ejecuta el siguiente comando para recuperar el URI:

    heroku config
    

    Toma nota del URI recuperado. La necesitarás en el próximo paso.

  6. Ejecuta un contenedor de Docker. Reemplaza database-uri por el URI de Heroku Postgres que anotaste en el paso anterior.

    docker run -it --rm postgres psql "database-uri"
    
  7. En el contenedor de Docker, crea la tabla TASKS con el siguiente comando:

    CREATE TABLE TASKS
    (DESCRIPTION TEXT NOT NULL);
    
  8. Sal del contenedor:

    exit
    
  9. En Cloud Shell, obtén la URL de la app de Heroku:

    heroku info
    
  10. Abre la URL de la app en una ventana del navegador. La app se ve de la siguiente manera (aunque tu versión no tendrá las tareas enumeradas):

    App de tareas pendientes en el navegador web.

  11. Crea tareas de muestra en tu app desde el navegador. Asegúrate de que las tareas se recuperen de la base de datos y se puedan ver en la IU.

Prepara el código de la app web para la migración a Cloud Run

En esta sección, se detallan los pasos que debes completar para preparar tu app web a fin de implementarla en Cloud Run.

Compila y publica tu contenedor de Docker en Container Registry

Necesitas una imagen de Docker para compilar el contenedor de la app a fin de que pueda ejecutarse en Cloud Run. Puedes compilar el contenedor de forma manual o mediante Buildpacks.

Compila el contenedor de forma manual

  1. En Cloud Shell, crea un Dockerfile en el directorio que se creó cuando se clonó el repositorio para este instructivo:

    cat <<EOF > Dockerfile
    # Use the official Node image.
    # https://hub.docker.com/_/node
    FROM node:10-alpine
    
    # Create and change to the app directory.
    WORKDIR /app
    
    # Copying this separately prevents re-running npm install on every code change.
    COPY package*.json ./
    RUN npm install
    
    # Copy local code to the container image.
    COPY . /app
    
    # Configure and document the service HTTP port.
    ENV PORT 8080
    EXPOSE $PORT
    
    # Run the web service on container startup.
    CMD ["npm", "start"]
    EOF
    
  2. Compila el contenedor con Cloud Build y publica la imagen en Container Registry:

    gcloud builds submit --tag gcr.io/${DEVSHELL_PROJECT_ID}/$APP_NAME:1
    
  3. Crea una variable de entorno para guardar el nombre de la imagen de Docker que creaste:

    export IMAGE_NAME="gcr.io/${DEVSHELL_PROJECT_ID}/$APP_NAME:1"
    

Compila el contenedor con Buildpacks

  1. En Cloud Shell, instala la CLI pack:

    wget https://github.com/buildpack/pack/releases/download/v0.2.1/pack-v0.2.1-linux.tgz
    tar xvf pack-v0.2.1-linux.tgz
    rm pack-v0.2.1-linux.tgz
    sudo mv pack /usr/local/bin/
    
  2. Configura la CLI pack para usar el compilador de Heroku de forma predeterminada:

    pack set-default-builder heroku/buildpacks
    
  3. Crea una variable de entorno para guardar el nombre de la imagen de Docker:

    export IMAGE_NAME=gcr.io/${DEVSHELL_PROJECT_ID}/$APP_NAME:1
    
  4. Compila la imagen con el comando pack y envía o publica la imagen en Container Registry:

    pack build --publish $IMAGE_NAME
    

Crea una instancia de Cloud SQL para PostgreSQL

Debes crear una instancia de Cloud SQL para PostgreSQL a fin de que funcione como backend de la app web. En este instructivo, PostgreSQL es más adecuada como la app de muestra implementada en Heroku, que usa una base de datos de Postgres como su backend. Para los fines de esta app, la migración a Cloud SQL para PostgreSQL desde un servicio administrado de Postgres no requiere cambios de esquema.

gcloud

  1. Crea una variable de entorno llamada CLOUDSQL_DB_NAME para guardar el nombre de la instancia de base de datos que crearás en el siguiente paso:

    export CLOUDSQL_DB_NAME=tasks-db
    
  2. Crea la base de datos:

    gcloud sql instances create $CLOUDSQL_DB_NAME  \
        --cpu=1 \
        --memory=4352Mib \
        --database-version=POSTGRES_9_6 \
        --region=us-central1
    

    La instancia puede tomar unos minutos en inicializarse.

  3. Establece una contraseña para el usuario de Postgres:

    gcloud sql users set-password postgres \
        --instance=$CLOUDSQL_DB_NAME  \
        --password=postgres-password
    

Console

  1. En Cloud Console, ve a la página Instancias de Cloud SQL:

    IR A LA PÁGINA INSTANCIAS DE CLOUD SQL

  2. Haz clic en Crear instancia.

  3. Selecciona PostgreSQL y haz clic en Siguiente.

  4. Ingresa tasks-db como el nombre de la instancia.

  5. Crea una contraseña para el usuario postgres:

  6. En la región, selecciona us-central1.

  7. No modifiques otros valores predeterminados.

Importa datos a Cloud SQL desde Heroku Postgres

Existen varios patrones de migración que puedes usar para migrar datos a Cloud SQL. En general, el mejor enfoque que requiere poco o ningún tiempo de inactividad es configurar Cloud SQL como una réplica de la base de datos que se migrará y hacer que Cloud SQL sea la instancia principal luego de la migración. Heroku Postgres no admite réplicas externas (seguidores), por lo que en este instructivo, usarás herramientas de código abierto para migrar el esquema de la app.

Para la app de Tasks en este instructivo, usarás la utilidad pg_dump a fin de exportar datos de Heroku Postgres a un bucket de Cloud Storage y, luego, importarlos en Cloud SQL. Esta utilidad puede transferir datos entre versiones homogéneas o cuando la versión de la base de datos de destino es más reciente que la base de datos de origen.

  1. En Cloud Shell, obtén las credenciales de la base de datos de Heroku Postgres que está conectada a la app de muestra. Necesitarás estas credenciales en el próximo paso.

    heroku pg:credentials:url
    

    Con este comando, se muestra la string de información de conexión y la URL de conexión de tu aplicación. La string de información de conexión tiene el siguiente formato:

    "dbname=database-name host=FQDN port=5432 user=user-name password=password-string sslmode=require"
    

    Para ver un ejemplo de un valor de FQDN (nombre de dominio completamente calificado) en una string de información de conexión, consulta la documentación de Heroku.

  2. Configura las variables de entorno para conservar los valores de Heroku que usarás en los pasos posteriores. Reemplaza los marcadores de posición FQDN, user-name, password-string y database-name por sus valores correspondientes en la string de información de conexión.

    export HEROKU_PG_HOST=FQDN
    export HEROKU_PG_USER=user-name
    export HEROKU_PG_PASSWORD=password-string
    export HEROKU_PG_DBNAME=database-name
    
  3. Crea una copia de seguridad en formato SQL de tu base de datos de Heroku Postgres:

    docker run \
      -it --rm \
      -e PGPASSWORD=$HEROKU_PG_PASSWORD \
      -v $(pwd):/tmp \
      --entrypoint "pg_dump" \
      postgres \
      -Fp \
      --no-acl \
      --no-owner \
      -h $HEROKU_PG_HOST \
      -U $HEROKU_PG_USER \
      $HEROKU_PG_DBNAME > herokudump.sql
    
  4. Crea una variable de entorno para guardar el nombre de tu bucket de Cloud Storage. Este nombre debe ser único. La variable de entorno debe tener el formato gs://bucket-name.

    export PG_BACKUP_BUCKET=gs://bucket-name
    
  5. Crea un bucket de Cloud Storage mediante la herramienta de línea de comandos de gsutil:

    gsutil mb -c regional -l us-central1 $PG_BACKUP_BUCKET
    
  6. Sube el archivo SQL a este bucket:

    gsutil cp herokudump.sql $PG_BACKUP_BUCKET
    
  7. En Cloud Console, ve a la página Instancias de Cloud SQL:

    IR A LA PÁGINA INSTANCIAS DE CLOUD SQL

  8. Selecciona la instancia para abrir la página Detalles de la instancia (Instance details).

  9. En la barra de botones, haz clic en Importar (Import).

    Importa el archivo de volcado de SQL desde Cloud Storage.

  10. En Archivo de Cloud Storage, ingresa la ruta de acceso al archivo de volcado de SQL que subiste a Cloud Storage.

  11. En Format of import, selecciona SQL.

  12. En Base de datos, selecciona postgres.

  13. Expande Opciones avanzadas y, en Usuario, selecciona postgres.

  14. Haz clic en Importar para comenzar la importación.

  15. Revisa las actualizaciones en la pestaña Operaciones de la instancia para validar que la importación se realizó de forma correcta.

Cómo Cloud Run accede a la base de datos de Cloud SQL

Así como la app web implementada en Heroku necesita conectarse a la instancia administrada de Heroku Postgres, Cloud Run requiere acceso a Cloud SQL para poder leer y escribir datos.

Cloud Run se comunica con Cloud SQL mediante el proxy de Cloud SQL que se activa y configura de forma automática cuando implementas el contenedor en Cloud Run. La base de datos no necesita tener direcciones IP externas aprobadas porque toda la comunicación que recibe proviene del proxy con TCP seguro.

Tu código debe invocar operaciones de la base de datos (como recuperar sus datos o escribir en ella) mediante la invocación del proxy en un socket UNIX.

Debido a que esta app web está escrita en Node.js, usarás la biblioteca pg-connection-string para analizar una URL de base de datos y crear un objeto config. La ventaja de este enfoque es que la conexión a la base de datos del backend en Heroku y Cloud Run se realiza sin problemas.

En el siguiente paso, debes pasar la URL de la base de datos como una variable de entorno cuando implementas la app web.

Implementa la app de muestra en Cloud Run

  1. En Cloud Shell, crea una variable de entorno que contenga el nombre de la conexión de la instancia de Cloud SQL que creaste:

    export DB_CONN_NAME=$(gcloud sql instances describe $CLOUDSQL_DB_NAME --format='value(connectionName)')
    
  2. Crea una variable de entorno llamada DATABASE_URL para conservar la string de conexión a fin de conectarte al proxy de Cloud SQL a través de un puerto UNIX. Reemplaza your-db-password por la contraseña que creaste para tu instancia de base de datos.

    export DATABASE_URL="socket:/cloudsql/${DB_CONN_NAME}?db=postgres&user=postgres&password=your-db-password"
    
  3. Implementa la app web en Cloud Run.

    gcloud beta run deploy tasksapp-$DEVSHELL_PROJECT_ID \
        --image=$IMAGE_NAME \
        --set-env-vars=DATABASE_URL=$DATABASE_URL \
        --add-cloudsql-instances $DB_CONN_NAME \
        --allow-unauthenticated \
        --platform managed
    

    Mediante el comando anterior, también se vincula tu contenedor de Cloud Run a la instancia de base de datos de Cloud SQL que creaste. Con el comando, se establece una variable de entorno para que Cloud Run apunte a la string DATABASE_URL que creaste en el paso anterior.

Prueba la aplicación

  1. En Cloud Shell, obtén la URL en la que Cloud Run entrega el tráfico:

    gcloud beta run services list --platform managed
    

    También puedes revisar el servicio de Cloud Run en Cloud Console.

  2. Navega a la URL del servicio de Cloud Run para asegurarte de que tu app web acepte solicitudes HTTP.

Cloud Run crea o inicia un contenedor nuevo cuando se envía una solicitud HTTP al extremo de entrega y si un contenedor aún no está en ejecución. Esto significa que la solicitud que hace que un contenedor nuevo se inicie puede tomar un poco más de tiempo en entregarse. Debido a ese tiempo adicional, ten en cuenta la cantidad de solicitudes simultáneas que tu app puede admitir y cualquier requisito de memoria específico que pueda tener.

Para esta app, usarás la configuración de simultaneidad predeterminada, que permite que un servicio de Cloud Run entregue 80 solicitudes en simultáneo desde un solo contenedor.

Realiza una limpieza

Para evitar que se generen costos en tu cuenta de Google Cloud por los recursos que se usaron en este instructivo, sigue estos pasos. También se recomienda borrar los recursos que se crearon en Heroku para este instructivo.

Borra el proyecto de Google Cloud

  1. En Cloud Console, ve a la página Administrar recursos.

    Ir a la página Administrar recursos

  2. En la lista de proyectos, selecciona el proyecto que deseas borrar y haz clic en Borrar .
  3. En el cuadro de diálogo, escribe el ID del proyecto y haz clic en Cerrar para borrar el proyecto.

Borra la app de Heroku

Para borrar la app de muestra que implementaste en Heroku y el complemento de PostgreSQL asociado, ejecuta el siguiente comando:

heroku apps:destroy -a $APP_NAME

¿Qué sigue?