Skip to content
This repository has been archived by the owner on Nov 4, 2021. It is now read-only.

Commit

Permalink
Build healthcheck into index.js (#455)
Browse files Browse the repository at this point in the history
* Build healthcheck into index.js

* Add "Alternative modes" to README

* Fix uncaught reject in Sentry flush
  • Loading branch information
Twixes committed Jun 4, 2021
1 parent ee88e2d commit 13ed91b
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 16 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ FROM node:14 AS runner
WORKDIR /code/
COPY --from=builder /code/ ./

HEALTHCHECK --start-period=10s CMD [ "node", "dist/healthcheck.js" ]
HEALTHCHECK --start-period=10s CMD [ "node", "dist/index.js", "--healthcheck" ]

CMD [ "node", "dist/index.js" ]
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ Let's get you developing the plugin server in no time:

1. Run Postgres pipeline tests with `yarn test:postgres:{1,2}`. Run ClickHouse pipeline tests with `yarn test:clickhouse:{1,2}`. Run benchmarks with `yarn benchmark`.

## Alternative modes

This program's main mode of operation is processing PostHog events, but there are also a few alternative utility ones.
Each one does a single thing. They are listed in the table below, in order of precedence.

| Name | Description | CLI flags |
| ----------- | ---------------------------------------------------------- | ----------------- |
| Help | Show plugin server [configuration options](#configuration) | `-h`, `--help` |
| Version | Only show currently running plugin server version | `-v`, `--version` |
| Healthcheck | Check plugin server health and exit with 0 or 1 | `--healthcheck` |
| Migrate | Migrate Graphile job queue | `--migrate` |
| Idle | Start server in a completely idle, non-processing mode | `--idle` |

## Configuration

There's a multitude of settings you can use to control the plugin server. Use them as environment variables.
Expand Down
34 changes: 22 additions & 12 deletions src/healthcheck.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import { defaultConfig } from './config/config'
import { Status } from './utils/status'
import { status } from './utils/status'
import { createRedis } from './utils/utils'

const healthStatus = new Status('HLTH')

void createRedis(defaultConfig).then(async (redis) => {
const ping = await redis.get('@posthog-plugin-server/ping')
if (ping) {
healthStatus.info('💚', `Redis key @posthog-plugin-server/ping found with value ${ping}`)
process.exit(0)
} else {
healthStatus.error('💔', 'Redis key @posthog-plugin-server/ping not found! Plugin server seems to be offline')
process.exit(1)
export async function healthcheck(): Promise<boolean> {
const redis = await createRedis(defaultConfig)
try {
const ping = await redis.get('@posthog-plugin-server/ping')
if (ping) {
status.info('💚', `Redis key @posthog-plugin-server/ping found with value ${ping}`)
return true
} else {
status.error('💔', 'Redis key @posthog-plugin-server/ping not found! Plugin server seems to be offline')
return false
}
} catch (error) {
status.error('💥', 'An unexpected error occurred:', error)
return false
} finally {
redis.disconnect()
}
})
}

export async function healthcheckWithExit(): Promise<never> {
process.exit((await healthcheck()) ? 0 : 1)
}
9 changes: 8 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { defaultConfig, formatConfigHelp } from './config/config'
import { healthcheckWithExit } from './healthcheck'
import { initApp } from './init'
import { GraphileQueue } from './main/job-queues/concurrent/graphile-queue'
import { startPluginsServer } from './main/pluginsServer'
Expand All @@ -11,15 +12,18 @@ const { argv } = process
enum AlternativeMode {
Help = 'HELP',
Version = 'VRSN',
Healthcheck = 'HLTH',
Idle = 'IDLE',
Migrate = 'MIGRATE',
Migrate = 'MGRT',
}

let alternativeMode: AlternativeMode | undefined
if (argv.includes('--help') || argv.includes('-h')) {
alternativeMode = AlternativeMode.Help
} else if (argv.includes('--version') || argv.includes('-v')) {
alternativeMode = AlternativeMode.Version
} else if (argv.includes('--healthcheck')) {
alternativeMode = AlternativeMode.Healthcheck
} else if (argv.includes('--migrate')) {
alternativeMode = AlternativeMode.Migrate
} else if (defaultConfig.PLUGIN_SERVER_IDLE) {
Expand All @@ -36,6 +40,9 @@ switch (alternativeMode) {
case AlternativeMode.Help:
status.info('⚙️', `Supported configuration environment variables:\n${formatConfigHelp(7)}`)
break
case AlternativeMode.Healthcheck:
void healthcheckWithExit()
break
case AlternativeMode.Idle:
status.info('💤', `Disengaging this plugin server instance due to the PLUGIN_SERVER_IDLE env var...`)
setInterval(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/main/pluginsServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ export async function startPluginsServer(
} catch (error) {
Sentry.captureException(error)
status.error('💥', 'Launchpad failure!', error)
void Sentry.flush() // flush in the background
void Sentry.flush().catch(() => null) // Flush Sentry in the background
await closeJobs()
process.exit(1)
}
Expand Down
11 changes: 10 additions & 1 deletion src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { status } from './status'

/** Time until autoexit (due to error) gives up on graceful exit and kills the process right away. */
const GRACEFUL_EXIT_PERIOD_SECONDS = 5
/** Number of Redis error events until the server is killed gracefully. */
const REDIS_ERROR_COUNTER_LIMIT = 10

export function killGracefully(): void {
status.error('⏲', 'Shutting plugin server down gracefully with SIGTERM...')
Expand Down Expand Up @@ -386,10 +388,17 @@ export async function createRedis(serverConfig: PluginsServerConfig): Promise<Re
...credentials,
maxRetriesPerRequest: -1,
})
let errorCounter = 0
redis
.on('error', (error) => {
errorCounter++
Sentry.captureException(error)
status.error('🔴', 'Redis error encountered! Trying to reconnect...\n', error)
if (errorCounter > REDIS_ERROR_COUNTER_LIMIT) {
status.error('😡', 'Redis error encountered! Enough of this, I quit!\n', error)
killGracefully()
} else {
status.error('🔴', 'Redis error encountered! Trying to reconnect...\n', error)
}
})
.on('ready', () => {
if (process.env.NODE_ENV !== 'test') {
Expand Down

0 comments on commit 13ed91b

Please sign in to comment.