graphile / crystal

🔮 Graphile's Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!
https://graphile.org/
Other
12.56k stars 568 forks source link

For production, do I need PM2 or what? #82

Closed meglio closed 6 years ago

meglio commented 8 years ago

Hi. To make the server run in background and restart on failure and start automatically on server reboot, what would be the solution?

I read about PM2 - is it suitable, or is there an easier way?

calebmer commented 8 years ago

I would definitely recommend PM2, it’s a solid product that may be a little tricky to set up, but it does everything you may want (and may not know you want) from a process manager. That said, I’m always looking for new and better process management solutions. I wouldn’t be opposed to a daemon mode for PostGraphQL in the future if there is enough demand.

You could also look at Ubuntu Upstart which I’ve used before, or similar platform specific solutions.

meglio commented 8 years ago

With PM2, the quickest and simplest version that worked for me is:

pm2 start postgraphql -- postgres://pool_user:PA$$WORD@localhost:5432/dbname --route=/graphql --secret=$ECRET --development

Is there a better way to pass db password and secret token without revealing it in e.g. logs?

Also, an offtopic question, but still about starting postgraphql, maybe you can easily see where the error is in this pm2_postgraphql.json file?


{
  "name":               "PostgrqphQL",
  "script":             "postgraphql",
  "args":               "postgres://pool_user:PA$$WORD@localhost:5432/dbname --route=/graphql --secret=$ECRET --development"
}

When configuring a pm2 service through a .json file, it does not seem to understand the arguments and instead tries to connect to the database as root (which fails).

calebmer commented 8 years ago

Use environment variables to store secrets. Here’s a quick crash course on how to set environment variables: https://www.digitalocean.com/community/tutorials/how-to-read-and-set-environmental-and-shell-variables-on-a-linux-vps

Here’s the documentation I always use for PM2: http://pm2.keymetrics.io/docs/usage/application-declaration/

So a sample configuration may be:

{
  "apps": [{
    "name": "postgraphql",
    "script": "./node_modules/.bin/postgraphql",
    "args": "postgres://$DB_USER:$DB_PASS@localhost:5432/database --route graphql --secret $SECRET --development",
    "instances": 1,
    "exec_mode": "cluster",
    "env": {
      "DB_USER": "user",
      "DB_PASS": "password",
      "SECRET": "secret"
    }
  }]
}
ryanblakeley commented 7 years ago

I'll show what I've done:

The built file for production has private variables baked in. PM2 runs, restarts, etc. the built file.

/ecosystem.json

"apps": [
    {
      "name": "api",
      "script": "./build/dist/server.js"
    }
  ],

Gulp is used as the CLI for running the build. Gulp has access to /.env, which is git-ignored and used for private variables.

package.json

"scripts": {
  "setup": "cp -n .env.example .env || true",
  "dev": "gulp",
  "eslint": "eslint ./src",
  "test": "JWT_PRIVATE_KEY=foo NODE_ENV=test ./node_modules/mocha/bin/_mocha --opts ./spec/mocha.opts",
  "build": "rimraf build && gulp build",
  "start": "pm2 start ecosystem.json --env production",
  "restart": "pm2 restart ecosystem.json",
  "stop": "pm2 stop api",
 },

/gulpfile.babel.js

import config from './webpack.config';

function onBuild (done, logLevel) {
  return (err, stats) => {
    if (err) {
      console.log('Build threw error:', err);
    } else if (logLevel === 'debug') {
      console.log('[build stats]:', stats.toString());
    }

    if (done) {
      done();
    }
  };
}

gulp.task('build-server', (done) => {
  webpack(config).run(onBuild(done));
});

gulp.task('build', ['build-server']);

Webpack bundles the app src file. Webpack has an API for passing in env vars.

/webpack.config.js

import env from 'gulp-env';

if (!process.env.PRIVATE_IP) {
  env({file: './.env', type: 'ini'});
}

const {
  NODE_ENV,
  PRIVATE_IP,
  REVERSE_PROXY_PRIVATE_IP,
  PORT,
  DB_PORT,
  JWT_PRIVATE_KEY,
} = process.env;

...

plugins: [
  new webpack.DefinePlugin({
    'process.env': {
      NODE_ENV: JSON.stringify(NODE_ENV),
      PRIVATE_IP: JSON.stringify(PRIVATE_IP),
      REVERSE_PROXY_PRIVATE_IP: JSON.stringify(REVERSE_PROXY_PRIVATE_IP),
      PORT: Number(PORT),
      DB_PORT: Number(DB_PORT),
      JWT_PRIVATE_KEY: JSON.stringify(JWT_PRIVATE_KEY),
    },
  }),
  ...
],

Postgraphql middleware in an Express.js app that references private vars on process.env.

/src/index.js

import postgraphql from 'postgraphql';

const {
  NODE_ENV,
  PRIVATE_IP,
  REVERSE_PROXY_PRIVATE_IP,
  PORT,
  DB_PORT,
  JWT_PRIVATE_KEY,
} = process.env;

api.use(postgraphql(`postgres://${REVERSE_PROXY_PRIVATE_IP}:${DB_PORT}`, ['terrafarm'], {
  graphiql: NODE_ENV !== 'production',
  jwtSecret: JWT_PRIVATE_KEY,
});

api.listen(PORT, () => {
  console.log(`API listening at http://${PRIVATE_IP}:${PORT} 🌲`);
});
sourabhkheterpal commented 7 years ago

@calebmer : I want to start postgraphql server in background. so i am using this code in ecosystem.config .js as you suggested:

module.exports = {
   "apps": [
    {
      "name": "postgraphql",
      "script": "./node_modules/.bin/postgraphql",
      "args": "postgres://$DB_USER:$DB_PASS@localhost:5432/database --route graphql --schema canny --default-role canny_anonymous --secret $SECRET --token canny.jwt_token --cors",
      "instances": 1,
      "exec_mode": "cluster",
      "env": {
        "DB_USER": "NAME",
        "DB_PASS": "PASSWORD",
        "SECRET": "secret"
      }
    }
  ]
}

In above code i have extra added --token canny.jwt_token --cors . i have schema name canny and using JWT Token canny.jwt_token for this but it give me error while i start it with pm2. Error:

Error: Postgres token type "canny"."jwt_token" does not exist in your Postgres schema subset. Perhaps try adding schema "canny" to your list of introspected queries.
    at Object.getPgTokenTypeFromIdentifier [as default] (/home/sourabh/canny/node_modules/postgraphql/build/postgraphql/schema/auth/getPgTokenTypeFromIdentifier.js:13:15)
    at Object.<anonymous> (/home/sourabh/canny/node_modules/postgraphql/build/postgraphql/schema/createPostGraphQLSchema.js:49:65)
    at step (/home/sourabh/canny/node_modules/tslib/tslib.js:121:27)
    at Object.next (/home/sourabh/canny/node_modules/tslib/tslib.js:102:57)
    at fulfilled (/home/sourabh/canny/node_modules/tslib/tslib.js:92:62)
    at propagateAslWrapper (/usr/local/lib/node_modules/pm2/node_modules/async-listener/index.js:421:23)
    at /usr/local/lib/node_modules/pm2/node_modules/async-listener/index.js:458:70
    at process._tickDomainCallback [as _tickCallback] (internal/process/next_tick.js:129:7)

but when i run below command its works fine but i have to run this in background: postgraphql -c \"postgres://USERNAME:PASSWORD@localhost/canny_dev\" --schema canny --default-role canny_anonymous --secret SECRET --token canny.jwt_token --cors

Please look into this. Thanks.

benjie commented 7 years ago

In args above you have postgres://$DB_USER:$DB_PASS@localhost:5432/database; but in the command line you replace database with canny_dev

sourabhkheterpal commented 7 years ago

@benjie: I just give reference. I have replaced database with canny_dev while trying this code . canny_dev is my DB name.

benjie commented 7 years ago

Well, not sure how much I can help if you don't provide the actually config; but it might be that you're lacking -c before the database URL right at the beginning of args

sourabhkheterpal commented 7 years ago

@benjie : Thanks. yes i have skipped -c with this option its works. but for this i have to pass direct DB_USER and DB_PASS in above url. Its not take it from env config. Error: error: role "$DB_USER" does not exist

Thanks again.

omatrot commented 6 years ago

For those interested, I just blogged about using pm2 and postgraphile.

sjmcdowall commented 6 years ago

@omatrot -- I would love to read this but I am clicking on the link and it just gets me to a blank page? Not sure what I am doing wrong..

omatrot commented 6 years ago

@sjmcdowall Yeah sorry the link was incorrect. Now it should work.

sjmcdowall commented 6 years ago

Nice! ALthough this seems a bit off topic -- but have you tried instances > 1 and how does that perform? In fact, how performant is postgraphile in general? Any gut feel for V4 with all the improvements?

benjie commented 6 years ago

You can read about performance here: https://www.graphile.org/postgraphile/performance/

benjie commented 6 years ago

(I only added that page a few days ago)

barbalex commented 6 years ago

The solution using ecosystem.config does not work for me when using a plugin, see: https://github.com/graphile-contrib/postgraphile-plugin-connection-filter/issues/38

benjie commented 6 years ago

Does anyone fancy writing up a guide for the website on how to use PM2 in production?

benjie commented 6 years ago

[semi-automated message] We try and keep the open issues to actual issues (bugs, etc); this seems like more of a discussion right now, so I'm closing it but please feel free to keep discussing it below 👍