Implementación de una aplicación Express con clústeres en Elastic Beanstalk - AWS Elastic Beanstalk

Implementación de una aplicación Express con clústeres en Elastic Beanstalk

En este tutorial se describe el proceso de implementar una aplicación de ejemplo en Elastic Beanstalk mediante la interfaz de línea de comandos de Elastic Beanstalk (CLI de EB) y, a continuación, actualizar la aplicación para que use el marco de Express, Amazon ElastiCache y la agrupación en clústeres. La agrupación en clústeres mejora la disponibilidad, el desempeño y la seguridad de su aplicación web. Para obtener más información sobre Amazon ElastiCache, vaya a ¿Qué es Amazon ElastiCache for Memcached? en la Guía del usuario de Amazon ElastiCache for Memcached.

nota

En este ejemplo se crean recursos de AWS, los cuales podrían incurrir gastos. Para obtener más información sobre los precios de AWS, consulte http://aws.amazon.com/pricing/. Algunos servicios forman parte del nivel de uso gratuito de AWS. Si es un cliente nuevo, puede probar estos servicios de forma gratuita. Para obtener más información, consulte http://aws.amazon.com/free/.

Requisitos previos

Este tutorial tiene siguientes los requisitos previos:

  • Los tiempos de ejecución de Node.js

  • El software de gestión de paquetes de Node.js predeterminado, npm

  • El generador de línea de comandos Express

  • La interfaz de línea de comando de Elastic Beanstalk (CLI de EB)

Para obtener más detalles acerca de la instalación de los primeros tres componentes y la configuración de su entorno de desarrollo local, consulte Configuración del entorno de desarrollo de Node.js. En este tutorial, no es necesario que instale el AWS SDK para Node.js, que también se menciona en el tema de referencia.

Para obtener detalles sobre la instalación y configuración de la CLI de EB, consulte Instalación de la CLI de EB y Configuración de la CLI de EB.

Cree un entorno de Elastic Beanstalk

Su directorio de aplicaciones

Este tutorial usa un directorio llamado nodejs-example-express-elasticache para el paquete de origen de la aplicación. Cree el directorio nodejs-example-express-elasticache para este tutorial.

~$ mkdir nodejs-example-express-elasticache
nota

Cada tutorial de este capítulo usa su propio directorio para el paquete de origen de la aplicación. El nombre del directorio coincide con el nombre de la aplicación de ejemplo utilizada en el tutorial.

Cambie su directorio de trabajo actual a nodejs-example-express-elasticache.

~$ cd nodejs-example-express-elasticache

Ahora configuremos un entorno de Elastic Beanstalk que ejecute la plataforma Node.js y la aplicación de ejemplo. Se usará la interfaz de línea de comandos de Elastic Beanstalk (CLI de EB)

Para configurar un repositorio en la CLI de EB para la aplicación y crear un entorno de Elastic Beanstalk que ejecute la plataforma Node.js
  1. Cree un repositorio con el comando eb init.

    ~/nodejs-example-express-elasticache$ eb init --platform node.js --region <region>

    Este comando crea un archivo de configuración en una carpeta llamada .elasticbeanstalk que especifica los ajustes para crear los entornos de la aplicación y crea una aplicación de Elastic Beanstalk con el nombre de la carpeta actual.

  2. Cree un entorno que ejecute una aplicación de ejemplo con el comando eb create.

    ~/nodejs-example-express-elasticache$ eb create --sample nodejs-example-express-elasticache

    Este comando crea un entorno con balanceador de carga utilizando la configuración predeterminada de la plataforma de Node.js y los siguientes recursos:

    • Instancia de EC2: una máquina virtual de Amazon Elastic Compute Cloud (Amazon EC2) configurada para ejecutar aplicaciones web en la plataforma que elija.

      Cada plataforma ejecuta un conjunto específico de software, archivos de configuración y scripts compatibles con una determinada versión de lenguaje, marco de trabajo y contenedor web (o una combinación de estos). La mayoría de las plataformas utilizan Apache o nginx como un proxy inverso que se sitúa delante de la aplicación web, reenvía las solicitudes a esta, administra los recursos estáticos y genera registros de acceso y errores.

    • Instance security group (Grupo de seguridad de la instancia): grupo de seguridad de Amazon EC2 configurado para permitir el tráfico entrante en el puerto 80. Este recurso permite que el tráfico HTTP procedente del balanceador de carga llegue a la instancia EC2 en la que se ejecuta la aplicación web. De forma predeterminada, el tráfico no está permitido en otros puertos.

    • Balanceador de carga: balanceador de carga de Elastic Load Balancing configurado para distribuir solicitudes a las instancias que se ejecutan en la aplicación. Los balanceadores de carga también permiten que las instancias no estén expuestas directamente a Internet.

    • Grupo de seguridad del balanceador de carga: grupo de seguridad de Amazon EC2 configurado para permitir el tráfico entrante en el puerto 80. Este recurso permite que el tráfico HTTP procedente de Internet llegue al balanceador de carga. De forma predeterminada, el tráfico no está permitido en otros puertos.

    • Grupo de Auto Scaling: grupo de Auto Scaling configurado para reemplazar una instancia si termina o deja de estar disponible.

    • Bucket de Amazon S3: ubicación de almacenamiento para el código fuente, los registros y otros artefactos que se crean al utilizar Elastic Beanstalk.

    • Alarmas de Amazon CloudWatch: dos alarmas de CloudWatch que supervisan la carga de las instancias del entorno y que se activan si la carga es demasiado alta o demasiado baja. Cuando se activa una alarma, en respuesta, el grupo de Auto Scaling aumenta o reduce los recursos.

    • Pila de AWS CloudFormation. Elastic Beanstalk utiliza AWS CloudFormation para lanzar los recursos en su entorno y propagar los cambios de configuración. Los recursos se definen en una plantilla que puede verse en la consola de AWS CloudFormation.

    • Nombre de dominio: nombre de dominio que direcciona el tráfico a la aplicación web con el formato subdominio.región.elasticbeanstalk.com.

  3. Cuando se complete la creación del entorno, utilice el comando eb open para abrir la URL del entorno en el navegador predeterminado.

    ~/nodejs-example-express-elasticache$ eb open

Ahora ha creado un entorno de Elastic Beanstalk para Node.js con una aplicación de ejemplo. Puede actualizarlo con su propia aplicación. Luego, actualizamos la aplicación de ejemplo para que use el marco de Express.

Actualizar la aplicación para que use Express

Actualice la aplicación de ejemplo en el entorno de Elastic Beanstalk para que utilice el marco de Express.

Puede descargar el código fuente final en nodejs-example-express-elasticache.zip.

Si desea actualizar la aplicación para que use Express

Una vez que haya creado el entorno con una aplicación de ejemplo, puede actualizarlo con su propia aplicación. En este procedimiento, ejecutamos primero los comandos express y npm install para configurar el marco de Express en el directorio de la aplicación.

  1. Ejecute el comando express. Esto genera package.json, app.js y unos directorios.

    ~/nodejs-example-express-elasticache$ express

    Cuando se le pregunte, escriba y si desea continuar.

    nota

    Si el comando express no funciona, es posible que no haya instalado el generador de línea de comandos Express tal y como se describe en la sección Requisitos previos anterior. O bien, puede que sea necesario configurar la ruta del directorio de su máquina local para ejecutar el comando express. Consulte la sección Requisitos previos para conocer los pasos detallados sobre la configuración del entorno de desarrollo, de modo que pueda continuar con este tutorial.

  2. Configure las dependencias locales.

    ~/nodejs-example-express-elasticache$ npm install
  3. (Opcional) Compruebe que el servidor de aplicaciones web se inicie.

    ~/nodejs-example-express-elasticache$ npm start

    Debería ver un resultado similar a este:

    > nodejs@0.0.0 start /home/local/user/node-express > node ./bin/www

    El servidor se ejecuta en el puerto 3000 de forma predeterminada. Para probarlo, ejecute curl http://localhost:3000 en otro terminal o abra un navegador en el equipo local e ingrese el http://localhost:3000 de la dirección URL.

    Presione Ctrl+C para detener el servidor.

  4. Cambie el nombre de nodejs-example-express-elasticache/app.js a nodejs-example-express-elasticache/express-app.js.

    ~/nodejs-example-express-elasticache$ mv app.js express-app.js
  5. Actualice la línea var app = express(); en nodejs-example-express-elasticache/express-app.js a la siguiente:

    var app = module.exports = express();
  6. En el equipo local, cree un archivo llamado nodejs-example-express-elasticache/app.js con el siguiente código.

    /** * Module dependencies. */ const express = require('express'), session = require('express-session'), bodyParser = require('body-parser'), methodOverride = require('method-override'), cookieParser = require('cookie-parser'), fs = require('fs'), filename = '/var/nodelist', app = express(); let MemcachedStore = require('connect-memcached')(session); function setup(cacheNodes) { app.use(bodyParser.raw()); app.use(methodOverride()); if (cacheNodes.length > 0) { app.use(cookieParser()); console.log('Using memcached store nodes:'); console.log(cacheNodes); app.use(session({ secret: 'your secret here', resave: false, saveUninitialized: false, store: new MemcachedStore({ 'hosts': cacheNodes }) })); } else { console.log('Not using memcached store.'); app.use(session({ resave: false, saveUninitialized: false, secret: 'your secret here' })); } app.get('/', function (req, resp) { if (req.session.views) { req.session.views++ resp.setHeader('Content-Type', 'text/html') resp.send(`You are session: ${req.session.id}. Views: ${req.session.views}`) } else { req.session.views = 1 resp.send(`You are session: ${req.session.id}. No views yet, refresh the page!`) } }); if (!module.parent) { console.log('Running express without cluster. Listening on port %d', process.env.PORT || 5000) app.listen(process.env.PORT || 5000) } } console.log("Reading elastic cache configuration") // Load elasticache configuration. fs.readFile(filename, 'UTF8', function (err, data) { if (err) throw err; let cacheNodes = [] if (data) { let lines = data.split('\n'); for (let i = 0; i < lines.length; i++) { if (lines[i].length > 0) { cacheNodes.push(lines[i]) } } } setup(cacheNodes) }); module.exports = app;
  7. Reemplace el contenido del archivo nodejs-example-express-elasticache/bin/www por lo siguiente:

    #!/usr/bin/env node /** * Module dependencies. */ const app = require('../app'); const cluster = require('cluster'); const debug = require('debug')('nodejs-example-express-elasticache:server'); const http = require('http'); const workers = {}, count = require('os').cpus().length; function spawn() { const worker = cluster.fork(); workers[worker.pid] = worker; return worker; } /** * Get port from environment and store in Express. */ const port = normalizePort(process.env.PORT || '3000'); app.set('port', port); if (cluster.isMaster) { for (let i = 0; i < count; i++) { spawn(); } // If a worker dies, log it to the console and start another worker. cluster.on('exit', function (worker, code, signal) { console.log('Worker ' + worker.process.pid + ' died.'); cluster.fork(); }); // Log when a worker starts listening cluster.on('listening', function (worker, address) { console.log('Worker started with PID ' + worker.process.pid + '.'); }); } else { /** * Create HTTP server. */ let server = http.createServer(app); /** * Event listener for HTTP server "error" event. */ function onError(error) { if (error.syscall !== 'listen') { throw error; } const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; // handle specific listen errors with friendly messages switch (error.code) { case 'EACCES': console.error(bind + ' requires elevated privileges'); process.exit(1); break; case 'EADDRINUSE': console.error(bind + ' is already in use'); process.exit(1); break; default: throw error; } } /** * Event listener for HTTP server "listening" event. */ function onListening() { const addr = server.address(); const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; debug('Listening on ' + bind); } /** * Listen on provided port, on all network interfaces. */ server.listen(port); server.on('error', onError); server.on('listening', onListening); } /** * Normalize a port into a number, string, or false. */ function normalizePort(val) { const port = parseInt(val, 10); if (isNaN(port)) { // named pipe return val; } if (port >= 0) { // port number return port; } return false; }
  8. Implemente los cambios en su entorno Elastic Beanstalk con el comando eb deploy.

    ~/nodejs-example-express-elasticache$ eb deploy
  9. Su entorno se actualizará al cabo de unos minutos. Una vez que el entorno esté listo, actualice la dirección URL para verificar que ha funcionado. Debería aparecer una página web que diga “Welcome to Express”.

Puede obtener acceso a los logs de las instancias EC2 que ejecutan la aplicación. Para obtener instrucciones acerca del acceso a los logs, consulte Visualización de registros de instancias de Amazon EC2 en su entorno de Elastic Beanstalk.

A continuación, vamos a actualizar la aplicación de Express para que utilice Amazon ElastiCache.

Para actualizar la aplicación de Express para que utilice Amazon ElastiCache
  1. En su equipo local, cree un directorio .ebextensions en el directorio de nivel superior del paquete de código fuente. En este ejemplo, usaremos nodejs-example-express-elasticache/.ebextensions.

  2. Cree un archivo de configuración nodejs-example-express-elasticache/.ebextensions/elasticache-iam-with-script.config con el siguiente fragmento. Para obtener más información sobre el archivo de configuración, consulte Espacio de nombres de configuración de Node.js. Se crea un usuario de IAM con los permisos necesarios para descubrir los nodos de elasticache y se escribe en un archivo siempre que cambia la caché. También puede copiar el archivo de nodejs-example-express-elasticache.zip. Para obtener más información sobre las propiedades de ElastiCache consulte Ejemplo: ElastiCache.

    nota

    YAML usa la sangría uniforme. Utilice el mismo nivel de sangría cuando sustituya el contenido en el archivo de configuración de ejemplo y asegúrese de que el editor de texto utiliza espacios para la sangría, no tabuladores.

    Resources: MyCacheSecurityGroup: Type: 'AWS::EC2::SecurityGroup' Properties: GroupDescription: "Lock cache down to webserver access only" SecurityGroupIngress: - IpProtocol: tcp FromPort: Fn::GetOptionSetting: OptionName: CachePort DefaultValue: 11211 ToPort: Fn::GetOptionSetting: OptionName: CachePort DefaultValue: 11211 SourceSecurityGroupName: Ref: AWSEBSecurityGroup MyElastiCache: Type: 'AWS::ElastiCache::CacheCluster' Properties: CacheNodeType: Fn::GetOptionSetting: OptionName: CacheNodeType DefaultValue: cache.t2.micro NumCacheNodes: Fn::GetOptionSetting: OptionName: NumCacheNodes DefaultValue: 1 Engine: Fn::GetOptionSetting: OptionName: Engine DefaultValue: redis VpcSecurityGroupIds: - Fn::GetAtt: - MyCacheSecurityGroup - GroupId AWSEBAutoScalingGroup : Metadata : ElastiCacheConfig : CacheName : Ref : MyElastiCache CacheSize : Fn::GetOptionSetting: OptionName : NumCacheNodes DefaultValue: 1 WebServerUser : Type : AWS::IAM::User Properties : Path : "/" Policies: - PolicyName: root PolicyDocument : Statement : - Effect : Allow Action : - cloudformation:DescribeStackResource - cloudformation:ListStackResources - elasticache:DescribeCacheClusters Resource : "*" WebServerKeys : Type : AWS::IAM::AccessKey Properties : UserName : Ref: WebServerUser Outputs: WebsiteURL: Description: sample output only here to show inline string function parsing Value: | http://`{ "Fn::GetAtt" : [ "AWSEBLoadBalancer", "DNSName" ] }` MyElastiCacheName: Description: Name of the elasticache Value: Ref : MyElastiCache NumCacheNodes: Description: Number of cache nodes in MyElastiCache Value: Fn::GetOptionSetting: OptionName : NumCacheNodes DefaultValue: 1 files: "/etc/cfn/cfn-credentials" : content : | AWSAccessKeyId=`{ "Ref" : "WebServerKeys" }` AWSSecretKey=`{ "Fn::GetAtt" : ["WebServerKeys", "SecretAccessKey"] }` mode : "000400" owner : root group : root "/etc/cfn/get-cache-nodes" : content : | # Define environment variables for command line tools export AWS_ELASTICACHE_HOME="/home/ec2-user/elasticache/$(ls /home/ec2-user/elasticache/)" export AWS_CLOUDFORMATION_HOME=/opt/aws/apitools/cfn export PATH=$AWS_CLOUDFORMATION_HOME/bin:$AWS_ELASTICACHE_HOME/bin:$PATH export AWS_CREDENTIAL_FILE=/etc/cfn/cfn-credentials export JAVA_HOME=/usr/lib/jvm/jre # Grab the Cache node names and configure the PHP page aws cloudformation list-stack-resources --stack `{ "Ref" : "AWS::StackName" }` --region `{ "Ref" : "AWS::Region" }` --output text | grep MyElastiCache | awk '{print $4}' | xargs -I {} aws elasticache describe-cache-clusters --cache-cluster-id {} --region `{ "Ref" : "AWS::Region" }` --show-cache-node-info --output text | grep '^ENDPOINT' | awk '{print $2 ":" $3}' > `{ "Fn::GetOptionSetting" : { "OptionName" : "NodeListPath", "DefaultValue" : "/var/www/html/nodelist" } }` mode : "000500" owner : root group : root "/etc/cfn/hooks.d/cfn-cache-change.conf" : "content": | [cfn-cache-size-change] triggers=post.update path=Resources.AWSEBAutoScalingGroup.Metadata.ElastiCacheConfig action=/etc/cfn/get-cache-nodes runas=root sources : "/home/ec2-user/elasticache" : "https://elasticache-downloads.s3.amazonaws.com/AmazonElastiCacheCli-latest.zip" commands: make-elasticache-executable: command: chmod -R ugo+x /home/ec2-user/elasticache/*/bin/* packages : "yum" : "aws-apitools-cfn" : [] container_commands: initial_cache_nodes: command: /etc/cfn/get-cache-nodes
  3. En el equipo local, cree un archivo de configuración nodejs-example-express-elasticache/.ebextensions/elasticache_settings.config con el siguiente fragmento para configurar ElastiCache.

    option_settings: "aws:elasticbeanstalk:customoption": CacheNodeType: cache.t2.micro NumCacheNodes: 1 Engine: memcached NodeListPath: /var/nodelist
  4. En el equipo local,sustituya nodejs-example-express-elasticache/express-app.js con el siguiente fragmento. Este archivo lee la lista de nodos del disco (/var/nodelist) y configura Express para que utilice memcached como un almacén de sesiones si los nodos están presentes. Su archivo debería tener el siguiente aspecto.

    /** * Module dependencies. */ var express = require('express'), session = require('express-session'), bodyParser = require('body-parser'), methodOverride = require('method-override'), cookieParser = require('cookie-parser'), fs = require('fs'), filename = '/var/nodelist', app = module.exports = express(); var MemcachedStore = require('connect-memcached')(session); function setup(cacheNodes) { app.use(bodyParser.raw()); app.use(methodOverride()); if (cacheNodes) { app.use(cookieParser()); console.log('Using memcached store nodes:'); console.log(cacheNodes); app.use(session({ secret: 'your secret here', resave: false, saveUninitialized: false, store: new MemcachedStore({'hosts': cacheNodes}) })); } else { console.log('Not using memcached store.'); app.use(cookieParser('your secret here')); app.use(session()); } app.get('/', function(req, resp){ if (req.session.views) { req.session.views++ resp.setHeader('Content-Type', 'text/html') resp.write('Views: ' + req.session.views) resp.end() } else { req.session.views = 1 resp.end('Refresh the page!') } }); if (!module.parent) { console.log('Running express without cluster.'); app.listen(process.env.PORT || 5000); } } // Load elasticache configuration. fs.readFile(filename, 'UTF8', function(err, data) { if (err) throw err; var cacheNodes = []; if (data) { var lines = data.split('\n'); for (var i = 0 ; i < lines.length ; i++) { if (lines[i].length > 0) { cacheNodes.push(lines[i]); } } } setup(cacheNodes); });
  5. En el equipo local, actualice package.json con el siguiente contenido:

    "dependencies": { "cookie-parser": "~1.4.4", "debug": "~2.6.9", "express": "~4.16.1", "http-errors": "~1.6.3", "jade": "~1.11.0", "morgan": "~1.9.1", "connect-memcached": "*", "express-session": "*", "body-parser": "*", "method-override": "*" }
  6. Ejecute npm install.

    ~/nodejs-example-express-elasticache$ npm install
  7. Implemente la aplicación actualizada.

    ~/nodejs-example-express-elasticache$ eb deploy
  8. Su entorno se actualizará al cabo de unos minutos. Una vez que su entorno esté listo, verifique que el código ha funcionado.

    1. Consulte la consola de Amazon CloudWatch para ver las métricas de ElastiCache. Para ver las métricas de ElastiCache, seleccione Metrics (Métricas) en el panel izquierdo y, a continuación, busque CurrItems. Seleccione ElastiCache > Cache Node Metrics (Métricas del nodo de caché) y, a continuación, seleccione el nodo de caché para ver el número de elementos en la caché.

      nota

      Asegúrese de buscar en la misma región en la que ha implementado su aplicación.

      Si copia y pega la URL de la aplicación en otro navegador web y actualiza la página, debería ver que el recuento CurrItem aumenta después de 5 minutos.

    2. Tome una instantánea de los registros.. Para obtener más información acerca de la recuperación de registros, consulte Visualización de registros de instancias de Amazon EC2 en su entorno de Elastic Beanstalk.

    3. Compruebe el archivo /var/log/nodejs/nodejs.log en el paquete del registro. Debería ver algo similar a lo siguiente:

      Using memcached store nodes: [ 'aws-my-1oys9co8zt1uo.1iwtrn.0001.use1.cache.amazonaws.com:11211' ]

Eliminar recursos

Si ya no desea ejecutar la aplicación, puede limpiar los recursos terminando el entorno y eliminando la aplicación.

Utilice el comando eb terminate para finalizar el entorno y el comando eb delete para eliminar la aplicación.

Para terminar su entorno

En el directorio en el que creó el repositorio local, ejecute eb terminate.

$ eb terminate

Este proceso puede tardar unos minutos. Elastic Beanstalk muestra un mensaje cuando el entorno termina correctamente.