Ember.js, DevOps, and You - NPM Camp 2016

Ember.js, DevOps, and You - NPM Camp 2016

My talk on Ember.js, DevOps, and You and lessons learned from building a Ember-CLI-Deploy plugin.

E02ac480d0b92ec00e660be464d35c8d?s=128

Iheanyi Ekechukwu

July 30, 2016
Tweet

Transcript

  1. Ember.js, DevOps, and You Iheanyi Ekechukwu NPM Camp 2016

  2. Iheanyi Ekechukwu Software Engineer @ DigitalOcean @kwuchu

  3. Defining DevOps What exactly is DevOps?

  4. DevOps?

  5. None
  6. DevOps.

  7. Development + Operations

  8. DevOps is deploying.

  9. DevOps is provisioning.

  10. DevOps is versioning.

  11. DevOps is complicated.

  12. Automation is

  13. Ember.js and DevOps What do Ember.js and DevOps have to

    do with one another?
  14. None
  15. Ember-CLI-Deploy Plugins

  16. An Ember-CLI-Deploy Plugin can implement up to 11 hooks

  17. None
  18. So, it doesn’t just deploy.

  19. Ember-CLI-Deploy can provision.

  20. Ember-CLI-Deploy can build.

  21. Ember-CLI-Deploy can deploy.

  22. Ember-CLI-Deploy can manage releases.

  23. Ember-CLI-Deploy

  24. Ember-CLI-DevOps

  25. Building a Plugin

  26. Create the Ember addon and install the Ember-CLI-Deploy base plugin.

    STEP ONE
  27. ember addon ember-cli-deploy-digitalocean

  28. cd ember-cli-deploy-digitalocean npm install ember-cli-deploy-plugin --save

  29. STEP TWO Manually deploy a Fastboot application, taking note of

    each action. (Provisioning, Deployment, etc.)
  30. Let’s go through the steps.

  31. Create a Droplet.

  32. SSH into the droplet.

  33. Install Nginx, Node, NPM, and Ember-Fastboot-Server on droplet

  34. On local machine, build the Fastboot application.

  35. SCP the built Ember application onto the droplet

  36. On droplet, go to the uploaded app and run the

    Fastboot application.
  37. Configure Nginx to proxy over the Fastboot’s server’s port and

    serve static assets.
  38. Reload Nginx.

  39. And you’re done.

  40. STEP THREE Translate each recorded step into code. (Automation)

  41. None
  42. Automation Problems and Solutions

  43. PROBLEM How do I create a droplet for the user?

  44. SOLUTION Use DigitalOcean’s API and create an Ember command that

    creates the droplet.
  45. None
  46. export DO_ACCESS_TOKEN=<token>

  47. ember do:provision

  48. None
  49. PROBLEM Installing all software dependencies on the droplet. (Provisioning)

  50. WITHOUT FASTBOOT One package dependency. Web Server (NGINX) serves the

    static assets.
  51. WITH FASTBOOT More dependencies. It’s not only NGINX. We now

    need Node, NPM, and Fastboot.
  52. SOLUTION DigitalOcean Images and execution of commands via Node SSH2.

  53. None
  54. this.conn = new SSHClient(); this.sshConfig = { host: config.ipAddress, port:

    22, username: config.dropletUsername, privateKey: require('fs').readFileSync(config.privateKeyPath), password: config.dropletPassword, passphrase: config.passphrase, };
  55. willUpload: function() { var conn = this.conn; conn.on('ready', () =>

    { conn.exec(‘sudo apt-get update -y; sudo apt-get upgrade -y; sudo apt-get install -y nginx gcc build- essential; rm /etc/nginx/sites-enabled/default', (err, stream) => { if (err) throw err; stream.on('data', (data) => { this.log('STDOUT: ' + data); }).on('end', (data) => { resolve(); }); }) }).connect(this.sshConfig); })
  56. PROBLEM How do I build my Ember application and get

    it onto the droplet?
  57. SOLUTION Use ember-cli-deploy-build to build the application, NPM install dependencies,

    then SCP files onto the droplet.
  58. None
  59. configure: function() { //… this.scpConfig = { host: config.ipAddress, port:

    22, username: process.env.DROPLET_USERNAME || 'root', privateKey: require('fs').readFileSync(process.env.PRIVATE_KEY_DIR), password: process.env.DROPLET_PASSWORD, passphrase: process.env.PASSPHRASE, path: '/etc/nginx/sites-enabled/ember-app' }; this.scpClient = SCPClient; },
  60. willUpload: function(context) { var npmInstallTask = new NPMInstallTask({ log: this.log.bind(this),

    distDir: context.distDir }); return npmInstallTask.run() // more code down here }
  61. upload: function(context) { this.log('Uploading assets to the droplet!'); var scpClient

    = this.scpClient; return new Promise((resolve, reject) => { scpClient.scp(context.distDir, this.scpConfig, (err) => { if (err) { throw err; } //… return resolve(); }); }); },
  62. context.distDir

  63. PROBLEM What if my droplet restarts and kills the Fastboot

    server?
  64. SOLUTION Create a Fastboot Upstart Service on the droplet.

  65. description "A job file for starting up the Fastboot service

    for Ember." author "Iheanyi Ekechukwu" start on filesystem or runlevel [2345] stop on shutdown pre-start script npm install -g ember-fastboot-server echo "Starting Fastboot server" >> /var/log/fastboot.log end script script export HOME echo $$ > /var/run/fastboot.pid exec ember-fastboot /etc/nginx/sites-enabled/ember-app >> /var/log/fastboot.log 2>&1 end script pre-stop script rm /var/run/fastboot.pid echo "Fastboot Server Stopping" >> /var/log/fastboot.log end script
  66. didUpload: function(context) { // other code…upload fast boot fileClient.upload('./node_modules/ember-cli- deploy-digitalocean/templates/fastboot.conf',

    '/ etc/init/fastboot.conf', err => { if (err) { this.log(err, {color: 'red'}); throw err; } // other code… });
  67. PROBLEM How do I get the application up and served

    up to the user?
  68. SOLUTION Upload a custom NGINX config and restart everything.

  69. http { include /etc/nginx/mime.types*; default_type application/octet-stream; sendfile on; tcp_nopush on;

    tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; server { listen 80; root /etc/nginx/sites-enabled/ember-app; index index.html index.html; server_name localhost; location / { index index.html; proxy_pass http://127.0.0.1:3000; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-NginX-Proxy true; } location ~ \.(ttf|ttc|otf|eot|woff|font.css|css)$ { add_header Access-Control-Allow-Origin "*"; } location /assets { autoindex on; } } }
  70. didUpload: function(context) { // upload nginx conf, other code left

    out fileClient.upload('./node_modules/ember-cli-deploy-digitalocean/ templates/nginx.conf', '/etc/nginx/nginx.conf', (err) => { if (err) { this.log(err); throw err; } return resolve(); }); // other code… }
  71. didUpload: function(context) { // restart nginx and fastboot service, other

    code left out conn.exec("sudo service nginx restart; sudo service fastboot restart;", (err, stream) => { if (err) throw err; stream.on('data', (data) => { this.log('STDOUT: ' + data); }).on('end', (data) => { this.log("We're in business!"); return resolve(); }).stderr.on('data', (data) => { this.log('STDERR: ' + data); }); }); }
  72. And we’re done automating.

  73. None
  74. One small NPM trick that makes building deploy plugins easier…

  75. None
  76. g

  77. None
  78. ember-cli-deploy-digitalocean (github.com/iheanyi/ember-cli-deploy- digitalocean)

  79. Plugin Limitations

  80. Deploys limited to one droplet.

  81. Provisioning tasks get re-run unnecessarily.

  82. No controlled release management.

  83. Old Fastboot Serving Logic

  84. Ideas for Future Enhancements

  85. Zero Downtime Deployments

  86. SSL Support?

  87. Semantic Versioning (semantic-release)

  88. The Future of DevOps?

  89. None
  90. + ?

  91. None
  92. Thanks to… @tomdale @lukemelia @davidpett @digitalocean and others!

  93. Thank you. @kwuchu