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
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/
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
-
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. -
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.
-
-
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.
-
Ejecute el comando
express
. Esto generapackage.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.
-
Configure las dependencias locales.
~/nodejs-example-express-elasticache$
npm install
-
(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 elhttp://localhost:3000
de la dirección URL.Presione Ctrl+C para detener el servidor.
-
Cambie el nombre de
nodejs-example-express-elasticache/app.js
anodejs-example-express-elasticache/express-app.js
.~/nodejs-example-express-elasticache$
mv
app.js express-app.js
-
Actualice la línea
var app = express();
ennodejs-example-express-elasticache/express-app.js
a la siguiente:var app = module.exports = express();
-
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;
-
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; }
-
Implemente los cambios en su entorno Elastic Beanstalk con el comando eb deploy.
~/nodejs-example-express-elasticache$
eb deploy
-
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
-
En su equipo local, cree un directorio
.ebextensions
en el directorio de nivel superior del paquete de código fuente. En este ejemplo, usaremosnodejs-example-express-elasticache/.ebextensions
. -
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
-
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
-
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 utilicememcached
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); });
-
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": "*" }
-
Ejecute npm install.
~/nodejs-example-express-elasticache$
npm install
-
Implemente la aplicación actualizada.
~/nodejs-example-express-elasticache$
eb deploy
-
Su entorno se actualizará al cabo de unos minutos. Una vez que su entorno esté listo, verifique que el código ha funcionado.
-
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.
-
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.
-
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.