diff --git a/packages/backend/src/boot/entry.ts b/packages/backend/src/boot/entry.ts index 6b8e83d4f9..04c6ca9723 100644 --- a/packages/backend/src/boot/entry.ts +++ b/packages/backend/src/boot/entry.ts @@ -15,6 +15,7 @@ import Logger from '@/logger.js'; import { envOption } from '../env.js'; import { masterMain } from './master.js'; import { workerMain } from './worker.js'; +import { readyRef } from './ready.js'; import 'reflect-metadata'; @@ -79,6 +80,8 @@ if (cluster.isWorker || envOption.disableClustering) { await workerMain(); } +readyRef.value = true; + // ユニットテスト時にMisskeyが子プロセスで起動された時のため // それ以外のときは process.send は使えないので弾く if (process.send) { diff --git a/packages/backend/src/boot/ready.ts b/packages/backend/src/boot/ready.ts new file mode 100644 index 0000000000..2339fa935a --- /dev/null +++ b/packages/backend/src/boot/ready.ts @@ -0,0 +1 @@ +export const readyRef = { value: false }; diff --git a/packages/backend/src/server/HealthServerService.ts b/packages/backend/src/server/HealthServerService.ts new file mode 100644 index 0000000000..13a3d04ffc --- /dev/null +++ b/packages/backend/src/server/HealthServerService.ts @@ -0,0 +1,43 @@ +import { Inject, Injectable } from '@nestjs/common'; +import * as Redis from 'ioredis'; +import { DataSource } from 'typeorm'; +import { bindThis } from '@/decorators.js'; +import { DI } from '@/di-symbols.js'; +import { readyRef } from '@/boot/ready.js'; +import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; + +@Injectable() +export class HealthServerService { + constructor( + @Inject(DI.redis) + private redis: Redis.Redis, + + @Inject(DI.redisForPub) + private redisForPub: Redis.Redis, + + @Inject(DI.redisForSub) + private redisForSub: Redis.Redis, + + @Inject(DI.redisForTimelines) + private redisForTimelines: Redis.Redis, + + @Inject(DI.db) + private db: DataSource, + ) {} + + @bindThis + public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { + fastify.get('/', async (request, reply) => { + reply.code(await Promise.all([ + new Promise((resolve, reject) => readyRef.value ? resolve() : reject()), + this.redis.ping(), + this.redisForPub.ping(), + this.redisForSub.ping(), + this.redisForTimelines.ping(), + this.db.query('SELECT 1'), + ]).then(() => 200, () => 503)); + }); + + done(); + } +} diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts index f43968d236..12d5061985 100644 --- a/packages/backend/src/server/ServerModule.ts +++ b/packages/backend/src/server/ServerModule.ts @@ -8,6 +8,7 @@ import { EndpointsModule } from '@/server/api/EndpointsModule.js'; import { CoreModule } from '@/core/CoreModule.js'; import { ApiCallService } from './api/ApiCallService.js'; import { FileServerService } from './FileServerService.js'; +import { HealthServerService } from './HealthServerService.js'; import { NodeinfoServerService } from './NodeinfoServerService.js'; import { ServerService } from './ServerService.js'; import { WellKnownServerService } from './WellKnownServerService.js'; @@ -55,6 +56,7 @@ import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js ClientServerService, ClientLoggerService, FeedService, + HealthServerService, UrlPreviewService, ActivityPubServerService, FileServerService, diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index da17a88e03..3572f16627 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -28,6 +28,7 @@ import { ApiServerService } from './api/ApiServerService.js'; import { StreamingApiServerService } from './api/StreamingApiServerService.js'; import { WellKnownServerService } from './WellKnownServerService.js'; import { FileServerService } from './FileServerService.js'; +import { HealthServerService } from './HealthServerService.js'; import { ClientServerService } from './web/ClientServerService.js'; import { OpenApiServerService } from './api/openapi/OpenApiServerService.js'; import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js'; @@ -61,6 +62,7 @@ export class ServerService implements OnApplicationShutdown { private wellKnownServerService: WellKnownServerService, private nodeinfoServerService: NodeinfoServerService, private fileServerService: FileServerService, + private healthServerService: HealthServerService, private clientServerService: ClientServerService, private globalEventService: GlobalEventService, private loggerService: LoggerService, @@ -108,6 +110,7 @@ export class ServerService implements OnApplicationShutdown { fastify.register(this.wellKnownServerService.createServer); fastify.register(this.oauth2ProviderService.createServer, { prefix: '/oauth' }); fastify.register(this.oauth2ProviderService.createTokenServer, { prefix: '/oauth/token' }); + fastify.register(this.healthServerService.createServer, { prefix: '/healthz' }); fastify.get<{ Params: { path: string }; Querystring: { static?: any; badge?: any; }; }>('/emoji/:path(.*)', async (request, reply) => { const path = request.params.path;