Desplegar Drupal 8 en un contenedor en Azure App Service

Pues hoy tocaba Drupal 8 ūüôā y he estado buscando la mejor forma, tanto de despliegue en App Service como de automatizaci√≥n con Azure DevOps, para montar este escenario. No soy ninguna experta en Drupal, por lo que si hay alg√ļn punto que se pueda hacer mejor el feedback siempre es bien recibido. En este art√≠culo te cuento c√≥mo montar un entorno en local que luego migrar√© a Microsoft Azure.

Entorno en local con ddev

Existen diferentes formas de crear tu entorno para Drupal en local, pero el más sencillo con el que me he encontrado ha sido con la herramienta ddev. He seguido las instrucciones de la documentación oficial de Drupal. Por lo que básicamente he hecho lo siguiente:

He instalado ddev, que me ayuda a montarme un entorno de desarrollo de forma r√°pida, apoy√°ndose en Docker:

curl -L https://raw.githubusercontent.com/drud/ddev/master/scripts/install_ddev.sh | bash

Después he creado un sitio con Drupal 8 con la ayuda de Composer.

# Replace my_site_name!
export SITE_NAME=drupal-src
composer create-project drupal-composer/drupal-project:8.x-dev --stability dev --no-interaction $SITE_NAME
cd $SITE_NAME

Dentro de mi proyecto he configurado ddev:

ddev config --docroot web --projectname $SITE_NAME --projecttype drupal8

y lo he arrancado para que me genere los contenedores pertinentes y me de la URL por la que acceder a mi nuevo sitio:

ddev start

Para darle un poco de emoción al asunto, he elegido la plantilla de demo durante el asistente.

Plantilla de ejemplo de Drupal para hacer las pruebas

Exportación de la base de datos a Azure Database for MySQL

Ahora que todo funciona correctamente en local, lo siguiente que he hecho ha sido crearme una base de datos en Azure Database for MySQL. La versión de MySQL que he desplegado es la 5.7 , ya que la versión 8 parece que todavía no anda muy bien con Drupal 8.

Ahora lo que necesito es hacer un backup de la base de datos asociada a mi entorno de desarrollo a través del siguiente comando con ddev:

ddev export-db --gzip=false >/tmp/db.sql

Como todavía no tengo una base de datos en Azure a la que volcar esta información, solo un servidor de base de datos, a través del cliente de MySQL me he conectado al servidor que acabo de crear y me he creado una base de datos vacía:

mysql -h YOUR_SERVER.mysql.database.azure.com -u [email protected]_SERVER -p YOUR_PASSWORD
CREATE DATABASE drupaldb;

Nota: si tienes instalado MySQL Workbench puedes usar el cliente de MySQL exportando la siguiente ruta:

export PATH=$PATH:/Applications/MySQLWorkbench.app/Contents/MacOS

Por √ļltimo he volcado el backup en la nueva base de datos:

mysql -h YOUR_SERVER.mysql.database.azure.com -u [email protected]_SERVER -p YOUR_PASSWORD drupaldb < /tmp/db.sql 

Asegurate de que has permitido en tu servidor de MySQL en Azure la conexión desde otros servicios de la nube (y tu IP por si quieres probar en local). También he deshabilitado el SSL para este artículo.

Ahora debes a√Īadir la configuraci√≥n de la base de datos a web/sites/default/settings.php (recuerda que hay mejores formas de hacer esto. Esto es solo un piloto):

$databases['default']['default'] = [
  'database' => 'drupaldb',
  'username' => '[email protected]_SERVER',
  'password' => 'YOUR_PASSWORD',
  'host' => 'YOUR_SERVER.mysql.database.azure.com',
  'port' => '3306',
  'driver' => 'mysql',
  'prefix' => '',
  'collation' => 'utf8mb4_general_ci',
];

Tambi√©n necesitas a√Īadir un hash_salt a este archivo:

$settings['hash_salt'] = 'CHANGE_THIS';

Para evitar problemas a la hora de migrar al nuevo entorno he deshabilitado el preprocesamiento de los archivos javascript y css:

/**
 * Disable CSS and JS aggregation.
 * https://stackoverflow.com/questions/57027471/javascript-css-not-loading-after-drupal-8-migration
 */
$config['system.performance']['css']['preprocess'] = FALSE;
$config['system.performance']['js']['preprocess'] = FALSE;

Lo ideal es habilitarlo de nuevo una vez que haya finalizado el despliegue.

He eliminado el archivo .gitignore del proyecto para que todos los cambios que realice en local vayan a mi nueva imagen. Obviamente habr√° que afinar el mismo para que solo se suban los directorios y archivos que corresponda.

DockerFile

Para poder desplegar mi solución en un contenedor en App Service necesito un archivo Dockerfile. El equipo de App Service tiene diferentes imágenes subidas en Docker Hub, con el código de las mismas en GitHub, por lo que voy a utilizar esta como base. La estructura de mi proyecto quedaría de esta forma, estando en drupal-src mi proyecto:

Estructura de mi proyecto de Drupal 8 para Web App for Containers

Hay algunos ficheros que no necesitas, como drupal-database-install-tasks.php o incluso parte del script de entrypoint.sh, donde inicialmente recupera el código fuente de la aplicación de un repositorio git. Por otro lado he hecho los siguientes cambios:

Docker file

En este archivo solo he a√Īadido la sentencia ADD . /drupal-src ${DRUPAL_PRJ} para que todo el contenido de mi c√≥digo fuente se copie dentro de la imagen.

# 1. Drupal
# ====================
RUN mkdir -p $DOCKER_BUILD_HOME

#Add your code to /home/drupal_prj
ADD ./drupal-src ${DRUPAL_PRJ}

entrypoint.sh

En este archivo, la función setup_drupal estaba recuperando el código fuente de un repositorio git, entre otras tareas. Lo he modificado para que simplemente recupere el código que copié en el archivo Dockerfile, ajuste los permisos (me volví loca con las imágenes) y utilice drush rebuild para reconstruir la cache y que no pasen cosas raras (me volví loca con los estilos).

#Get drupal from your code
setup_drupal(){    
    cd $DRUPAL_PRJ    
    chmod a+w "$DRUPAL_PRJ/web/sites/default" 
    if [ -e "$DRUPAL_PRJ/web/sites/default/settings.php" ]; then 
        #Test this time, if application settings are set to a personal git, myabe drupal has already installed in repo.
        echo "INFO: Settings.php is exist..."    
    else
        echo "INFO: Settings.php isn't exist..."    
        mkdir -p "$DRUPAL_PRJ/web/sites/default/files"
        cp "$DRUPAL_PRJ/web/sites/default/default.settings.php" "$DRUPAL_PRJ/web/sites/default/settings.php"
    fi
    chmod a+w "$DRUPAL_PRJ/web/sites/default/files"
    chmod a+w "$DRUPAL_PRJ/web/sites/default/settings.php"
    while test -d "$DRUPAL_HOME"  
    do
        echo "INFO: $DRUPAL_HOME is exist, clean it ..."        
        chmod 777 -R $DRUPAL_HOME 
        rm -Rf $DRUPAL_HOME
    done
    
    echo "Composer install --no-dev"
    composer install -o --no-dev --no-interaction
    echo "Giving write permissions to $DRUPAL_PRJ/web/sites/default/files/"
    chmod -R 777 "$DRUPAL_PRJ/web/sites/default/files/" 
    echo "Rebuild cache (drush rebuild)..."
    drush rebuild
    echo "Clean generated images (drush "
    ln -s $DRUPAL_PRJ/web  $DRUPAL_HOME           	
}

El resto de archivos se quedan igual que en el ejemplo de GitHub.

Despliegue con Azure DevOps

Lo siguiente que he hecho ha sido subir a GitHub mi código fuente y me he creado un proyecto dentro de Azure DevOps para poder gestionar la integración continua y el despliegue continuo:

Pipeline para la build

La pipeline de build sería como la siguiente:

Build pipeline

Como estoy trabajando con contenedores de Docker, necesito como mínimo estas dos tareas. A esta pipeline le he asociado el repositorio de GitHub y he utilizado un agente de Linux hosteado por Azure DevOps.

Build an image

Generación de la imagen de docker

La imagen se genera con el archivo Dockerfile del c√≥digo fuente y he elegido un Container Registry en Azure como repositorio para mis im√°genes. La raz√≥n por la cual necesito especificarlo ahora es para que me etiquete la imagen de manera correcta, y autom√°tica ūüôā

Push an image

Publicar la imagen en Azure Container Registry

Una vez que la imagen se ha creado correctamente en el agente, el siguiente paso es subirla al ACR, donde utilizo el mismo que en el paso anterior y simplemente hago push.

Pipeline para la release

En este ejemplo he creado una pipeline con un √ļnico stage:

Tareas en la pipeline de release

Bash Script

Es interesante que sepas que puedes tener ciertas tareas en Azure DevOps que luego puedes desactivar. En este caso, la primera solamente la estaba usando para comprobar que la variable Build.BuildId está disponible, ya que no quiero sobrescribir la imagen una y otra vez sino que prefiero tener una nueva imagen por cada build y así tenerla versionada por si necesito dar un paso atrás.

Deploy in staging slot to warm up

Esta tarea va a desplegar mi nueva imagen, con mi proyecto de Drupal 8, en el slot llamado staging, para que pueda hacer un precalentamiento del sitio y cambiar después al slot de producción. En ella debo personalizar los siguientes valores:

Por otro lado, en el apartado Application and Configuration Settings debes almacenar los siguientes valores para poder conectarte a tu ACR:

  • DOCKER_REGISTRY_SERVER_URL: puedes encontrar la URL de tu ACR en el apartado Overview del mismo.
  • DOCKER_REGISTRY_SERVER_USERNAME: se encuentra en Access Keys.
  • DOCKER_REGISTRY_SERVER_PASSWORD: tambi√©n en el apartado Access Keys.
Application settings necesarias para el acceso a ACR

En esta tarea también he configurado la variable de salida AppServiceApplicationUrl para poder recuperarla en la siguiente:

Configuración de la variable de salida AppServiceApplicationUrl

Web Url

Lo mismo que en anterior bash, s√≥lo lo he usado para comprobar que Staging.AppServiceApplicationUrl tiene el valor de la URL de staging,que siempre viene bien un poco de debugging ūüėÄ

Wait for 2 minutes

Tarea que espera el tiempo que le indiques

Me descargué esta tarea desde el marketplace, en la que simplemente espero 2 minutos para que le dé tiempo al contenedor a arrancar.

Warmup staging slot

Tarea Warmup staging slot

Otra tarea m√°s del marketplace que me ayudar√° en el proceso de precalentamiento del sitio, pas√°ndole como variable la URL del entorno de staging y los paths a los que quiero que haga una llamada.

Swap to production

Por √ļltimo, una vez que el entorno de staging est√° listo, hago el swap con producci√≥n. Cuando finalice todo el proceso, si accedes a la URL de tu App Service deber√≠as de ver tu Drupal funcionando.

Cómo ver los logs

Te recomiendo que habilites los logs de App Service en el apartado Monitoring > App Service logs.

Monitoring > App Service logs

Después, haz clic en Go en la sección Advanced Tools:

App Service – Advanced Tools

Acceder√°s al portal Kudu donde podr√°s ver tus logs en el apartado Log Stream:

App Service – Kudu – Log stream

¬°Saludos!