Avoid rebuilding node_modules in the elastic beanstalk

We have a fairly simple node.js application, but due to the AWS Elastic Beanstalk deployment mechanism, it takes about 5 minutes to deploy the new version (via git aws.push ) even after git aws.push single file.

those. commit itself (and loading) is fast (only one file for push), but then Elastic Beanstalk extracts the entire package from S3, unzips it and runs npm install , which forces node -gyp to compile some modules. After installation / completion of construction, elastic bean wipes /var/app/current and replace it with a new version of the application.

Needless to say, the node_modules constant is not needed, and the rebuild, which takes 30 seconds on my old Macbook Air, takes> 5 minutes on the ec2.micro instance, and not in weight.

Here I see two approaches:

  • tweak /opt/containerfiles/ebnode.py and play with node_modules to avoid deleting it and recovering from deployment.
  • configure the git repository on the Elastic instance of ECC Beanstalk and basically re-record the deployment procedure yourself, so / var / app / current gets clicked and runs npm install only if necessary (which makes the elastic beanstalk look like OpsWorks ..)

Both options have no grace and are prone to problems when Amazon updates its hooks and architecture from an elastic beanstalk.

Can someone better think about how to avoid the constant rebuilding of node_modules that are already in the application directory? Thank.

+42
git npm amazon-web-services elastic-beanstalk
Jan 18 '14 at 5:06
source share
5 answers

Thanks Cyril, it was really useful!

I am just sharing my configuration file for people who are just looking at a simple solution for npm install . This file must be placed in the project .ebextensions folder, it is easier because it does not include the latest version of the node installation and is ready to use.

It also dynamically checks the version of node, so there is no need to include it in the env.vars file.

.ebextensions/00_deploy_npm.config

 files: "/opt/elasticbeanstalk/env.vars" : mode: "000775" owner: root group: users content: | export NPM_CONFIG_LOGLEVEL=error export NODE_PATH=`ls -td /opt/elasticbeanstalk/node-install/node-* | head -1`/bin "/opt/elasticbeanstalk/hooks/appdeploy/pre/50npm.sh" : mode: "000775" owner: root group: users content: | #!/bin/bash . /opt/elasticbeanstalk/env.vars function error_exit { eventHelper.py --msg "$1" --severity ERROR exit $2 } #install not-installed yet app node_modules if [ ! -d "/var/node_modules" ]; then mkdir /var/node_modules ; fi if [ -d /tmp/deployment/application ]; then ln -s /var/node_modules /tmp/deployment/application/ fi OUT=$([ -d "/tmp/deployment/application" ] && cd /tmp/deployment/application && $NODE_PATH/npm install 2>&1) || error_exit "Failed to run npm install. $OUT" $? echo $OUT "/opt/elasticbeanstalk/hooks/configdeploy/pre/50npm.sh" : mode: "000666" owner: root group: users content: | #no need to run npm install during configdeploy 
+35
Apr 23 '14 at 11:15
source share

25/01/13 NOTE: updated scripts to start updating the npm -g version (only once, when deploying or restoring the original instance) and to avoid NPM actions during EB configuration changes (when the application is absent, to avoid the error and speed up the configuration update )

Well, Elastic Beanstalk is dodgy with recent node.js lines (including the supposedly supported v.0.10.10), so I decided to go and fine-tune EB to do the following:

  • to install ANY version of node.js according to your env.config (including the most recent ones that are not yet supported by AWS EB)
  • to avoid restoring existing node modules, including in-app node_modules dir
  • install node.js globally (and any module you want).

Basically, I use env.config to replace & config config with interceptors with custom ones (see below). In addition, in the default configuration of the EB container, some env variables are missing (for example, $HOME ), and node-gyp sometimes fails during recovery due to this (I needed 2 hours to find and reinstall libxmljs to solve this problem) .

Below are the files that will be included with your build. You can enter them through env.config as inline code or through source: URL (as in this example)

env.vars (the desired version of the node and arch are included here and in env.config, see below)

 export HOME=/root export NPM_CONFIG_LOGLEVEL=error export NODE_VER=0.10.24 export ARCH=x86 export PATH="$PATH:/opt/elasticbeanstalk/node-install/node-v$NODE_VER-linux-$ARCH/bin/:/root/.npm" 

40install_node.sh (fetching and deploying the desired version of node.js, creating global symbolic links, updating the global version of npm)

 #!/bin/bash #source env variables including node version . /opt/elasticbeanstalk/env.vars function error_exit { eventHelper.py --msg "$1" --severity ERROR exit $2 } #UNCOMMENT to update npm, otherwise will be updated on instance init or rebuild #rm -f /opt/elasticbeanstalk/node-install/npm_updated #download and extract desired node.js version OUT=$( [ ! -d "/opt/elasticbeanstalk/node-install" ] && mkdir /opt/elasticbeanstalk/node-install ; cd /opt/elasticbeanstalk/node-install/ && wget -nc http://nodejs.org/dist/v$NODE_VER/node-v$NODE_VER-linux-$ARCH.tar.gz && tar --skip-old-files -xzpf node-v$NODE_VER-linux-$ARCH.tar.gz) || error_exit "Failed to UPDATE node version. $OUT" $?. echo $OUT #make sure node binaries can be found globally if [ ! -L /usr/bin/node ]; then ln -s /opt/elasticbeanstalk/node-install/node-v$NODE_VER-linux-$ARCH/bin/node /usr/bin/node fi if [ ! -L /usr/bin/npm ]; then ln -s /opt/elasticbeanstalk/node-install/node-v$NODE_VER-linux-$ARCH/bin/npm /usr/bin/npm fi if [ ! -f "/opt/elasticbeanstalk/node-install/npm_updated" ]; then /opt/elasticbeanstalk/node-install/node-v$NODE_VER-linux-$ARCH/bin/ && /opt/elasticbeanstalk/node-install/node-v$NODE_VER-linux-$ARCH/bin/npm update npm -g touch /opt/elasticbeanstalk/node-install/npm_updated echo "YAY! Updated global NPM version to `npm -v`" else echo "Skipping NPM -g version update. To update, please uncomment 40install_node.sh:12" fi 

50npm.sh (creates / var / node_modules, symbolizes it in the dir application and runs npm install. You can install any module around the world here, they land in /root/.npm)

 #!/bin/bash . /opt/elasticbeanstalk/env.vars function error_exit { eventHelper.py --msg "$1" --severity ERROR exit $2 } #install not-installed yet app node_modules if [ ! -d "/var/node_modules" ]; then mkdir /var/node_modules ; fi if [ -d /tmp/deployment/application ]; then ln -s /var/node_modules /tmp/deployment/application/ fi OUT=$([ -d "/tmp/deployment/application" ] && cd /tmp/deployment/application && /opt/elasticbeanstalk/node-install/node-v$NODE_VER-linux-$ARCH/bin/npm install 2>&1) || error_exit "Failed to run npm install. $OUT" $? echo $OUT 

env.config (also note the version of node) and, to be safe, put the desired version of node in the env configuration in the AWS console. I'm not sure which of these settings will take precedence.)

 packages: yum: git: [] gcc: [] make: [] openssl-devel: [] option_settings: - option_name: NODE_ENV value: production - option_name: RDS_HOSTNAME value: fill_me_in - option_name: RDS_PASSWORD value: fill_me_in - option_name: RDS_USERNAME value: fill_me_in - namespace: aws:elasticbeanstalk:container:nodejs option_name: NodeVersion value: 0.10.24 files: "/opt/elasticbeanstalk/env.vars" : mode: "000775" owner: root group: users source: https://dl.dropbox.com/.... "/opt/elasticbeanstalk/hooks/configdeploy/pre/40install_node.sh" : mode: "000775" owner: root group: users source: https://raw.github.com/.... "/opt/elasticbeanstalk/hooks/appdeploy/pre/50npm.sh" : mode: "000775" owner: root group: users source: https://raw.github.com/.... "/opt/elasticbeanstalk/hooks/configdeploy/pre/50npm.sh" : mode: "000666" owner: root group: users content: | #no need to run npm install during configdeploy "/opt/elasticbeanstalk/hooks/appdeploy/pre/40install_node.sh" : mode: "000775" owner: root group: users source: https://raw.github.com/.... 

There you have it: on t1.micro, deploying an instance now takes 20-30 seconds instead of 10-15 minutes! If you deploy 10 times a day, this setting will save you 3 (three) weeks per year. Hope this helps and special thanks to AWS EB staff for my lost weekend :)

+35
Jan 21 '14 at 14:20
source share

There's an npm package that overwrites the default EB behavior for the npm install command, trimming the following files:

  • /opt/elasticbeanstalk/hooks/appdeploy/pre/50npm.sh
  • /opt/elasticbeanstalk/hooks/configdeploy/pre/50npm.sh

https://www.npmjs.com/package/eb-disable-npm

It may be better than just copying the script from SO, as this package is supported and will probably be updated when EB behavior changes.

+2
Jul 26 '17 at 13:27
source share

I found a quick solution. I looked at the build scripts that Amazon uses and they only run npm install if package.json is present. So after your initial deployment, you can change it to _package.json , and npm install will no longer work! This is not the best solution, but it is a quick solution if you need it!

0
Dec 17 '15 at 16:32
source share

I had 10+ minute builds when I deployed. The solution was much simpler than others came up with ... Just check node_modules on git! See http://www.futurealoof.com/posts/nodemodules-in-git.html for a rationale

-6
Jun 27 '14 at 0:21
source share



All Articles