GithubHelp home page GithubHelp logo

npm / ndm Goto Github PK

View Code? Open in Web Editor NEW
180.0 27.0 22.0 325 KB

ndm allows you to deploy OS-specific service-wrappers directly from npm-packages.

License: ISC License

JavaScript 95.95% HTML 4.05%

ndm's Introduction

ndm

Build Status

ndm makes it easy to deploy a complex service-oriented-architecture by allowing you to deploy OS-specific service-wrappers directly from an npm package.

ndm currently supports Centos, OS X, and Ubuntu.

Table of Contents:

Installing

  • npm install ndm -g

You might need to run that as root with sudo.

Quick Start

How to build an ndm-ready package:

  1. Install ndm: sudo npm install ndm -g
  2. Create a project directory with a package.json: npm init
  3. Add service dependencies to your package.json: npm install my-service-module --save
  4. Generate your service.json: ndm init.
  5. Edit service.json to add appropriate args and envs for your server.
  6. When you're ready, generate service wrappers (upstart, initctl, etc): ndm install.
  7. Start the service wrappers you've just generated: ndm start.

Anatomy of an ndm service

ndm can run a single services or a collection of services. It's structured like an npm package, with a package.json file listing its dependencies. Each service you want to run with ndm should be packaged as its own separate npm module that the ndm wrapper depends on. Then a service.json file describes how to run each service.

An ndm wrapper package looks like this:

wrapper/
  package.json
  service.json
  logs/
  node_modules/

Service dependencies

A node-packaged service built for ndm can provide some hints in its package.json about how to run itself. Here's an example ndm-ready package.json:

{
  "name": "be-awesome",
  "version": "0.0.0",
  "description": "a service designed to be run with ndm.",
  "main": "index.js",
  "author": "",
  "license": "ISC",
  "dependencies": {
    "ssh2": "^0.2.25"
  },
  "devDependencies": {
    "mocha": "^1.20.0"
  },
  "script": {
    "start": "node ./bin/awesome.js"
  },
  "service": {
    "args": {
      "--verbose": "false"
    },
    "env": {
      "PORT": "5000"
    }
  },
}

Note the environment field and its subfields. environment.args is a map of arguments that should be passed to the ndm service. ndm.env is a map of environment variables that should be passed to your ndm service.

The service.json file

The ndm wrapper must also have a service.json file, which describes how to run the services. Run ndm init to generate service.json from your installed npm dependencies. The init script will copy default values from the environment stanza in each service's package.json. You can then edit the defaults if you need to change anything.

Here's an example:

{
  "baby-animals": {
    "description": "baby animal thumbnailing service",
    "scripts": {
      "start": "./baby-animal.js"
    },
    "env": {
      "PORT": "8000",
      "USER": "bcoe"
    },
    "args": {
      "--kitten": "cute"
    }
  },
  "ndm-test2": {
    "description": "the awesome service",
    "processes": 3,
    "module": "be-awesome",
    "scripts": {
      "start": "./bin/foo.js"
    },
    "env": {
      "PORT": "5000"
    },
    "args": {
      "--verbose": "false"
    }
  },
  "env": {
    "APP": "my-test-app-%i",
    "NODE_ENV": "production"
  },
  "args": {
    "--batman": "greatest-detective"
  }
}
  • module: the name of the npm module that should be the working directory for the service. If no module is specified, the key of the service will be used as the module name to look for.
  • description: description of the service.
  • scripts: scripts that can be executed by ndm. When generating service wrappers the start script is used.
  • env: string environment variables available within the script executed by the ndm wrapper.
  • args: command-line-arguments available to the script executed by the ndm wrapper.
  • processes: how many copies of the service should be run.
    • useful for taking advantage of multiple cpus, defaults to 1 process.
  • %i: %i is a place-holder for the process # if you're running multiple processes.
    • this can be useful if you want each process to bind to a different port, e.g., 800%i.

Defaults for all services are in the top-level env and args fields. Each services can override and extend the defaults in its own options stanza.

Updating dependencies

To add new dependencies:

  • Add a new dependency to the wrapper's package.json the way you would for any npm package:
    npm install <new-service> --save
  • Add the new service to service.json:
    ndm update.

Installing the services

To install your ndm-wrapped services, copy the package directory to your host system using whatever means you prefer. Then from inside the directory, run ndm install.

On systems like Ubuntu, you'll need to run this as root so ndm has permission to add the upstart config file to /etc/init. On OS X, you can run it as any user to create a local launch control script.

Command line arguments can be passed to the service wrapper at generation time, by appending them after --:

ndm install -- --verbose true

Starting and stopping

You can start and stop the services manually using your host's native daemon control: upstart, launchctl, or initctl. Or you can use ndm start and ndm stop from inside an ndm wrapper directory to start & stop all the wrapped services.

Tailing Logs

All console.log and console.error output is recorded in the logs/ directory, in files named <service-name>.log. This is separate from whatever internally-managed logging the service might do.

Interviewing the User

Rather than providing set-in-stone default values, you can opt to interview your user. To interview a user for important variables, write your default values in this form:

"env": {
  "HOST": {
    "default": "localhost",
    "description": "what host should I bind to?"
  }
}

By running ndm interview, a user will then be asked to fill in these values in an interactive manner:

Benjamins-MacBook-Air:ndm benjamincoe$ node ./bin/ndm.js interview
starting interview:
[?] url of front facing server: www.example.com
[?] what environment should we run the app in: test
[?] what do you think of dogs? I like 'em.

.ndmrc

Add an .ndmrc file to your home directory, to override ndm's default settings.

Variable names should be camel-case. As an example, the following .ndmrc would change the default logging location:

; override ndm CLI variables by adding
; them to a .ndmrc file. Variables should be
; cammel case.
logsDirectory=/foo/bar/logs

The ndm API

Rather than using the ndm bin to manage services, you can use the ndm API to create a self-installable service:

  1. add ndm to your package.json dependencies.
  2. add a service.json to the root of your module with a scripts stanza which includes:
  • a start script, which is what ndm will run by default.
  • any other scripts that you'd like to expose via runScript.

service.json example:

{
  "ndm-test": {
    "description": "ndm test service",
    "scripts": {
      "start": "node ./test.js",
      "echo": "echo hello",
      "node-echo": "node ./test2.js"
    },
    "env": {
      "PORT": 8000,
      "USER": {
        "description": "enter a username."
      }
    }
  }
}
  1. update your package's bin to look something like this (the argument passed to ndm's require is the name of the module in the sevice.json that you'd like to run):
#!/usr/bin/env node

var argv = require('yargs').argv,
  ndm = require('ndm')('ndm-test');

switch(argv._[0]) {
  case 'install':
    ndm.install();
    break;
  case 'remove':
    ndm.remove();
    break;
  case 'start':
    ndm.start();
    break;
  case 'restart':
    ndm.restart();
    break;
  case 'stop':
    ndm.stop();
    break;
  case 'list-scripts':
    ndm.listScripts();
    break;
  case 'run-script':
    ndm.runScript();
    break;
}

ndm-test is published to npm, try it out:

npm install ndm-test -g
ndm-test install
ndm-test start

Setting Node Flags

For each service in your service.json file, you can optionally set the following flags.

  • maxOldSpaceSize: sets the --max-old-space-size flag to to N megabytes.
{
  "foo": {
    "maxOldSpaceSize": "4096"
  }
}

Disclaimer

ndm is an experiment, based on ops challenges we've been facing at npm. This is a dot release. I'll be moving things around a lot in this library, as we use it for our own deployments.

The ndm stanza is not officially supported by npm.

LICENSE

ISC

ndm's People

Contributors

aredridel avatar bcoe avatar piranna avatar seanewest avatar simonexmachina avatar waldyrious avatar watilde avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ndm's Issues

Support uid on Centos

This is easy on ubuntu:

setuid ubuntu.

Setting uid does not seem to work as well on Centos, I'm thinking perhaps we could put:

su user in the script clause on Centos.

Fallback to package.json

Currently we require that a service.json file is present in modules which are installed using ndm. I think we should fallback to using the package.json if none is found. I suggest that we, by convention, look for the following fields in package.json:

{
  "name": "package-name",
  "description": "description of services",
  "scripts": {
    "start": "the-service-script"
  },
  "env": {
       "PORT": 8000,
       "uid": {
          "description": "user to run as"
        }
   },
   "args": {
        "--foo": "args-instead-of-envs"
   }
}

This reuses a lot of fields from package.json, which I think is awesome, and fits well with the discussion around service manifests started here: Service Descriptions

CC: @piranna, @seanewest, @groundwater

Refactor Logging Class

  • It would be nice to have different log levels.
  • It would be nice to abstract the coloring logic out of the various classes that log.
  • the number of new-lines I use are all over the place.

environment variables should be quoted in upstart scripts

To reproduce:

Create a service.json with an env stanza that looks like this:

"env": { "MY_VAR": "i am a string with a lot of spaces in it" }

Generate upstart configs in an ubuntu environment. Observe that the service is invoked as something like this:

PORT=8080 MY_VAR=i am a string with a lot of spaces in it /usr/bin/nodejs server.js

Sadness ensues.

Refactor Configuration Class

  • I'd like command-line argument generation to be automatic.
  • there's some repeated code when setting up configuration for different OS' I'd like to get rid of.

systemd support

As common as Upstart@~0.6.5 (which lacks useful features from 1.4) support is, systemd looks like it is or will be taking over the role of "most common non-sysv-init" since Ubuntu and Debian are moving to it and almost every other distro already supports it.

More/less religious logging

It would be nice if ndm's generated Upstart jobs allowed Upstart's logging to be used. Using the console log stanza directs the process's stdout/stderr to /var/logs/upstart/.log with automatic log rotation.

Disclaimer: I wrote a rather opinionated blog post on how Node apps should log to stdout instead of managing log files.

.ndmrc

It would be nice to have a .ndmrc file, so that users can place their service.json, logs, etc, in different locations.

API for writing self-installing modules

Continuing tangential discussions from #39 and #48.

Given an app, allow it to require('ndm') and use it to implement its own service installer. The app could use a combination of it's own services.json for the interview phase and custom code for additional setup like creating necessary files/directories/users or whatever else such an installer might need to do.

Use ndm to manage the containing module

It would be great to be able to use ndm on the containing module. I want to be able to use ndm to create service wrappers for the module itself, rather than modules in node_modules. Obviously I can use a wrapper module but this isn't a good solution for my use case.

support for forever-monitor

I've done some commits to help forever to work as service manager for NodeOS, only keeps ndm to export services to its format :-) This would need to have render functions instead of templates, since the idea is to have all that config in a json file, with a list of objects one for each installed service, so an API is necesary. Where could we start looking for?

SmartOS Support + Thoughts about Service Generation

It would be awesome to support SmartOS, we've had a few people request it.

As we add more service wrappers, It would probably be smart to abstract things a bit, so that an integration can be written as a self contained module. Here's where a new service is added currently:

  • OS-specific default settings are defined in config.js (e.g., logs) this would be easy to pull out into another module:

https://github.com/npm/ndm/blob/master/lib/config.js#L53

  • The actual service generation step is performed here, and simply involves rendering a different template depending on the OS:

https://github.com/npm/ndm/blob/master/lib/config.js#L53

A couple thoughts about this approach:

  • It would be better to pull the rendering into its own JS class for each service, this could be where we also place the OS default settings.
    • we could have a base class that template-based service wrappers inherit from.
  • this abstraction would let us support an OS like NodeOS, where service generation is done through an API rather than through plopping a file somewhere on disk.
    • as part of this we should probably make the generation step async, and make it so you can reference an external module for service generation.

CC: @mmalecki, @rmg

Discuss usefulness of Upstart for service coordination

Carrying from a tangent conversation on #48.

Upstart (and systemd) has support for connecting services as triggers. This could be used to model in Upstart the service dependency implied by listing multiple services inside services.json. Foreman does this based on a Procfile, but ndm's services manifest looks like it is even more suited to this task.

Infer module, description, and scripts.start from package.json

Things would be a bit more DRY in service.json if ndm would read from package.json in the absence of certain properties. Instead of this:

{
  "newww": {
    "module": "newww",
    "description": "A total rewrite of npm-www using hapijs",
    "scripts": {
      "start": "node server.js"
    },
    "env": {
      "PORT": "8081"
    },
    "args": {}
  },
  "newww2": {
    "module": "newww",
    "description": "A total rewrite of npm-www using hapijs",
    "scripts": {
      "start": "node server.js"
    },
    "env": {
      "PORT": "8082"
    },
    "args": {}
  },
  "newww3": {
    "module": "newww",
    "description": "A total rewrite of npm-www using hapijs",
    "scripts": {
      "start": "node server.js"
    },
    "env": {
      "PORT": "8083"
    },
    "args": {}
  },
  "newww4": {
    "module": "newww",
    "description": "A total rewrite of npm-www using hapijs",
    "scripts": {
      "start": "node server.js"
    },
    "env": {
      "PORT": "8084"
    },
    "args": {}
  }
}

I would prefer something like this:

{
  "newww": {
    "env": {
      "PORT": "8081",
      "NODE_ENV": "PRODUCTION"
    }
  },
  "newww2": {
    "env": {
      "PORT": "8082",
      "NODE_ENV": "PRODUCTION"
    }
  },
  "newww3": {
    "env": {
      "PORT": "8083",
      "NODE_ENV": "PRODUCTION"
    }
  },
  "newww4": {
    "env": {
      "PORT": "8084",
      "NODE_ENV": "PRODUCTION"
    }
  }
}

For personal reference, these spots in the code are related:

bad exit codes

When an error happens starting/stopping/generating a script, we should exit with a non 0 code.

Using self-install with private npme modules

Following is a workaround to make it possible for npme private modules to be self-installed.

Assuming private module is @private/myapp

  1. On the "bin" script, Include the private repo prefix, after the require statement:

var argv = require('yargs').argv
, ndm = require('ndm')('@private/myapp');

The docs say that you should use the name of the module inside the service.json, but without the prefix, the service.json is not found.

  1. On the service.json, the "module" property has to be included for the service instantiation path to be set correctly.

"myapp": {
"description": "myapp",
"module": "@private/myapp",
"scripts": {
"start": "node ./lib/myapp.js"
},
........

Without the "module" property the script change-directory (cd) command changes to an incorrect path.

ideas for collaboration

Hey @bcoe, nice to meet you. I ran across this project and it looks like we are solving very similar problems with npm. For the past few months, I've been building this: https://github.com/xtuple/xtuple-server.

I ended up building a semi-generic task-based deployment system. The one piece I've struggled to do well is the management of the deployed processes. If you have a minute, I'd be interested to see what you think. I can see some opportunity for combining forces, if that interests you.

Windows support?

Are there any plans (wishlist) for Windows support? Sorry if this was mentioned somewhere else, searched issues and didn't find much. Thanks.

accept full set of node-flags

It would be good to accept a full set of node flags, perhaps we could have a special suffix in the package.json for indicating flags that should be passed to the node bin?

ndm <cmd> <npm-package>

I would love to be able to ship an npm package (with a service.json) that can be installed as a service, right away, like so:

npm install -g <my-package>
ndm generate <my-package>

Same goes for start/stop etc.

Currently one has to create a wrapper around services that are dependencies. I can understand this setup in a devops environment where you want to package together and distribute services, and in addition with specific args for ports etc.

But I think it would also be cool if in addition there was a more basic use case where you could just start an individual service directly from the npm package, without creating a wrapper at all.

If I'm not mistaken to pull this off you would need to allow top-level scripts in the service.json, right next to the top-level args and env.

I'd be more than willing to send a PR if this sounds like something that could get merged.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.