Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Node.js Production Checklist - SFNode

Node.js Production Checklist - SFNode

Gergely Nemeth

February 01, 2018
Tweet

More Decks by Gergely Nemeth

Other Decks in Programming

Transcript

  1. Hi, I am Gergely. ▪ Works at GoDaddy here ▪

    Previously RisingStack ▪ To chat with me: – @nthgergo – [email protected] ▪ Read my stuff: – nemethgergely.com
  2. Why do you need a production checklist? ▪ Providing a

    “three nines” (99,9%) service, this is your error budget: – 43 minutes of downtime in a month – 2.2 hours of downtime in a quarter – 8.8 hours of downtime in a year
  3. Why do you need a production checklist? ▪ Providing a

    “five nines” (99,999%) service – 26 seconds of downtime in a month – 78 seconds of downtime in a quarter – 5 minutes of downtime in a year
  4. Why do you need a production checklist? ▪ These are

    called SLAs (service level agreement) ▪ SLAs consist of an SLO (service level objectives) and penalties if you don’t meet them, like the 99% of the requests finish under 200 ms
  5. Why do you need a production checklist? If you are

    operating a service, you should define SLOs for your customers – no matter if they are internal or external
  6. Today, you will learn about: ▪ Error handling in Node.js

    ▪ How to secure your Node.js applications ▪ How to get full visibility into production systems – Best practices for logging – Monitoring your Node.js applications ▪ What to do after hell broke loose
  7. Error handling - Express app.get('/users/:id', (req, res) => { const

    userId = req.params.id if (!userId) { return res.sendStatus(400).json({ error: 'Missing id' }) } Users.get(userId, (err, user) => { if (err) { return res.sendStatus(500).json(err) } res.send(users) }) })
  8. Error handling - Express - Errors are handled differently across

    the codebase - > Use Express error handlers instead - Migrate from callbacks to async-await
  9. Error handling - Express app.get('/users/:id', (req, res, next) => {

    const userId = req.params.id if (!userId) { const missingIdError = new Error('Missing id') missingIdError.httpStatusCode = 400 return next(missingIdError) } Users.get(userId, (err, user) => { if (err) { err.httpStatusCode = 500 return next(err) } res.send(Users) }) })
  10. Error handling - Express app.use((err, req, res, next) => {

    // log the error... res.sendStatus(err.httpStatusCode).json(err) }) Add an error handler middleware as the last one
  11. Error handling - Express const missingIdError = new Error('Missing id')

    missingIdError.httpStatusCode = 400 next(missingIdError) $
  12. Error handling - Express const boom = require('boom') app.get('/users/:id', (req,

    res, next) => { const userId = req.params.id if (!userId) { return next(boom.badRequest('missing id')) } Users.get(userId, (err, user) => { if (err) { return next(boom.badImplementation(err)) } res.send(Users) }) })
  13. Error handling - Express const asyncMiddleware = fn => (req,

    res, next) => { Promise.resolve(fn(req, res, next)).catch(next) } module.exports = exports = asyncMiddleware Wrapping the Express route handlers for proper error propagation
  14. Error handling - Express app.get('/users/:id', asyncMw((req, res) => { const

    userId = req.params.id if (!userId) { throw boom.badRequest('missing id') } const users = await getUserAsync(userId) res.json(users) }))
  15. Error handling – Express The modified error handler app.use((err, req,

    res, next) => { if (err.isServer) { // log the error... // probably you don't want to log unauthorized access // or do you? } return res.status(err.output.statusCode).json(err.output.payload) })
  16. Error handling - Express app.get('/users/:id', asyncMw((req, res) => { const

    userId = req.params.id if (!userId) { throw boom.badRequest('missing id') } const users = await getUserAsync(userId) res.json(users) })) app.get('/users/:id', (req, res) => { const userId = req.params.id if (!userId) { return res.sendStatus(400).json({ error: 'Missing id' }) } Users.get(userId, (err, user) => { if (err) { return res.sendStatus(500).json(err) } res.send(users) }) })
  17. Securing you application Security HTTP header • X-Frame-Options to mitigates

    clickjacking attacks, • Strict-Transport-Security to keep your users on HTTPS, • X-XSS-Protection to prevent reflected XSS attacks, • X-DNS-Prefetch-Control to disable browsers’ DNS prefetching.
  18. Securing you application Security HTTP header const express = require('express')

    const helmet = require('helmet') const app = express() app.use(helmet())
  19. Securing you application Validating user input const Joi = require('joi');

    const schema = Joi.object().keys({ username: Joi.string().alphanum().min(3).max(30).required(), access_token: [Joi.string(), Joi.number()], birthyear: Joi.number().integer().min(1900).max(2017), email: Joi.string().email() }).with('username', 'birthyear') // Return result const result = Joi.validate({ username: 'abc', birthyear: 1994 }, schema) // result.error === null -> valid
  20. Logging best practices Requirements • Timestamps to know when a

    given event happened, • Format to keep log lines readable for both humans and machines, • Destination should be the standard output and error only, • Support for log levels
  21. Logging best practices Using log levels • Error • General

    errors, always reported • Used whenever an unexpected error happens which prevents further processing • The app may try to recover (like on database connection lost) or forcefully terminate
  22. Logging best practices Using log levels • Warn • For

    events indicating irregular circumstances, with clearly defined recovery strategy • It has no impact on system availability or performance • These events should be reported too
  23. Logging best practices Using log levels • Info • These

    events indicate major state changes in the application, like the startup of the HTTP server • Each component should log: • When it starts and when it became operational • When it started shutdown, and just before it stopped
  24. Logging best practices Using log levels • Debug • Diagnostical

    level events, for internal state changes • These events are usually not reported, just for troubleshooting • At the discretion of the engineer developing the system component
  25. Logging best practices Example loggel.info('server started initializing ') const app

    = express() app.get('/', (req, res) => { res.send('ok!') }) app.listen(PORT, (err) => { if (err) { return logger.error(err) } logger.info(`server started listening on ${PORT}`) })
  26. Logging best practices Log the original URL of the request

    for errors app.use((err, req, res, next) => { if (err.isServer) { logger.error(err.message, { stack: err.stack, originalUrl: req.originalUrl }) } return res.status(err.output.statusCode).json(err.output.payload); })
  27. Graceful shutdown • When deploying new versions of your application,

    you will replace old versions • Listen on SIGTERM • Stop accepting new requests • Serve ongoing requests • Clean up the resources your app used
  28. Graceful shutdown Meet Terminus • Provides • Graceful shutdown, •

    Health checks for HTTP applications • Works with any Node.js HTTP servers • Built on stoppable • github.com/godaddy/terminus
  29. Monitoring your applications Most important metrics to watch • Error

    rate, as they directly affect customer satisfaction; • Latency, as the slower the service, the most likely your customers close your application; • Throughput, to put error rate and latency in context; • Saturation, to tell if you can handle more traffic.
  30. Monitoring your applications You want to have route-level metrics This

    way of monitoring is called white box monitoring Tools like Prometheus, New Relic, Opbeat or Dynatrace can help to implement it.
  31. Monitoring your applications Can users access it? This way of

    monitoring is called black box monitoring Tools like Pingdom or ping.apex.sh provides these services.
  32. Incident handling You build it, you run it • Teams

    will think about how their software is going to run in production • Encourages ownership and accountability which leads to more independent, responsible teammates • Leads to operational excellence • Which leads to more satisfied customers
  33. Disaster recovery • Disasters can happen from natural or human-induced

    causes • Involves tools, policies and procedures to enable recovery from disasters
  34. Disaster recovery Meet Ark (by Heptio) • Utility for managing

    disaster recovery for Kubernetes cluster resources and persistent volumes • Helps with • Disaster recovery • Cloud provider migration • Clone the production environment for development / testing