mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Add an option to disable preboot
phase (#179057)
## Summary Fix https://github.com/elastic/kibana/issues/178180 - Add a new `core.lifecycle.disablePreboot` (internal) config option to forcefully disable Core's `preboot` phase. - Enable the option in the serverless configuration file Gain is around 150/200ms on local developer machine, which translates to ~300/500ms on serverless environment
This commit is contained in:
parent
ca9c4bd3ea
commit
dcba4d08f1
8 changed files with 231 additions and 58 deletions
|
@ -11,6 +11,9 @@ xpack.fleet.internal.retrySetupOnBoot: true
|
|||
# Cloud links
|
||||
xpack.cloud.base_url: 'https://cloud.elastic.co'
|
||||
|
||||
# Disable preboot phase for serverless
|
||||
core.lifecycle.disablePreboot: true
|
||||
|
||||
# Enable ZDT migration algorithm
|
||||
migrations.algorithm: zdt
|
||||
|
||||
|
|
|
@ -95,20 +95,24 @@ export async function bootstrap({ configs, cliArgs, applyConfigOverrides }: Boot
|
|||
}
|
||||
|
||||
try {
|
||||
const { preboot } = await root.preboot();
|
||||
const prebootContract = await root.preboot();
|
||||
let isSetupOnHold = false;
|
||||
|
||||
// If setup is on hold then preboot server is supposed to serve user requests and we can let
|
||||
// dev parent process know that we are ready for dev mode.
|
||||
const isSetupOnHold = preboot.isSetupOnHold();
|
||||
if (process.send && isSetupOnHold) {
|
||||
process.send(['SERVER_LISTENING']);
|
||||
}
|
||||
if (prebootContract) {
|
||||
const { preboot } = prebootContract;
|
||||
// If setup is on hold then preboot server is supposed to serve user requests and we can let
|
||||
// dev parent process know that we are ready for dev mode.
|
||||
isSetupOnHold = preboot.isSetupOnHold();
|
||||
if (process.send && isSetupOnHold) {
|
||||
process.send(['SERVER_LISTENING']);
|
||||
}
|
||||
|
||||
if (isSetupOnHold) {
|
||||
rootLogger.info('Holding setup until preboot stage is completed.');
|
||||
const { shouldReloadConfig } = await preboot.waitUntilCanSetup();
|
||||
if (shouldReloadConfig) {
|
||||
await reloadConfiguration('configuration might have changed during preboot stage');
|
||||
if (isSetupOnHold) {
|
||||
rootLogger.info('Holding setup until preboot stage is completed.');
|
||||
const { shouldReloadConfig } = await preboot.waitUntilCanSetup();
|
||||
if (shouldReloadConfig) {
|
||||
await reloadConfiguration('configuration might have changed during preboot stage');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { schema, type TypeOf } from '@kbn/config-schema';
|
||||
import type { ServiceConfigDescriptor } from '@kbn/core-base-server-internal';
|
||||
|
||||
const coreConfigSchema = schema.object({
|
||||
lifecycle: schema.object({
|
||||
disablePreboot: schema.boolean({ defaultValue: false }),
|
||||
}),
|
||||
});
|
||||
|
||||
export type CoreConfigType = TypeOf<typeof coreConfigSchema>;
|
||||
|
||||
export const coreConfig: ServiceConfigDescriptor<CoreConfigType> = {
|
||||
path: 'core',
|
||||
schema: coreConfigSchema,
|
||||
};
|
|
@ -26,15 +26,16 @@ import { config as i18nConfig } from '@kbn/core-i18n-server-internal';
|
|||
import { config as deprecationConfig } from '@kbn/core-deprecations-server-internal';
|
||||
import { statusConfig } from '@kbn/core-status-server-internal';
|
||||
import { uiSettingsConfig } from '@kbn/core-ui-settings-server-internal';
|
||||
|
||||
import { config as pluginsConfig } from '@kbn/core-plugins-server-internal';
|
||||
import { elasticApmConfig } from './root/elastic_config';
|
||||
import { serverlessConfig } from './root/serverless_config';
|
||||
import { coreConfig } from './core_config';
|
||||
|
||||
const rootConfigPath = '';
|
||||
|
||||
export function registerServiceConfig(configService: ConfigService) {
|
||||
const configDescriptors: Array<ServiceConfigDescriptor<unknown>> = [
|
||||
coreConfig,
|
||||
cspConfig,
|
||||
deprecationConfig,
|
||||
elasticsearchConfig,
|
||||
|
|
|
@ -46,7 +46,10 @@ const logger = loggingSystemMock.create();
|
|||
const rawConfigService = rawConfigServiceMock.create({});
|
||||
|
||||
beforeEach(() => {
|
||||
mockConfigService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: true }));
|
||||
mockConfigService.atPath.mockReturnValue(
|
||||
// config for `core` path, only one used with all the services being mocked
|
||||
new BehaviorSubject({ lifecycle: { disablePreboot: false } })
|
||||
);
|
||||
mockPluginsService.discover.mockResolvedValue({
|
||||
preboot: {
|
||||
pluginTree: { asOpaqueIds: new Map(), asNames: new Map() },
|
||||
|
@ -298,3 +301,32 @@ test('migrator-only node throws exception during start', async () => {
|
|||
expect(migrationException!.processExitCode).toBe(0);
|
||||
expect(migrationException!.cause).toBeUndefined();
|
||||
});
|
||||
|
||||
describe('When preboot is disabled', () => {
|
||||
beforeEach(() => {
|
||||
mockConfigService.atPath.mockReturnValue(
|
||||
// config for `core` path, only one used with all the services being mocked
|
||||
new BehaviorSubject({ lifecycle: { disablePreboot: true } })
|
||||
);
|
||||
});
|
||||
|
||||
test('only preboots the mandatory services', async () => {
|
||||
const server = new Server(rawConfigService, env, logger);
|
||||
|
||||
await server.preboot();
|
||||
|
||||
expect(mockNodeService.preboot).toHaveBeenCalledTimes(1);
|
||||
expect(mockEnvironmentService.preboot).toHaveBeenCalledTimes(1);
|
||||
expect(mockUiSettingsService.preboot).toHaveBeenCalledTimes(1);
|
||||
expect(mockLoggingService.preboot).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(mockContextService.preboot).not.toHaveBeenCalled();
|
||||
expect(mockHttpService.preboot).not.toHaveBeenCalled();
|
||||
expect(mockI18nService.preboot).not.toHaveBeenCalled();
|
||||
expect(mockElasticsearchService.preboot).not.toHaveBeenCalled();
|
||||
expect(mockRenderingService.preboot).not.toHaveBeenCalled();
|
||||
expect(mockPluginsService.preboot).not.toHaveBeenCalled();
|
||||
expect(mockPrebootService.preboot).not.toHaveBeenCalled();
|
||||
expect(mockStatusService.preboot).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import apm from 'elastic-apm-node';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
import type { Logger, LoggerFactory } from '@kbn/logging';
|
||||
import type { NodeRoles } from '@kbn/core-node-server';
|
||||
|
@ -55,6 +56,7 @@ import { CoreAppsService } from '@kbn/core-apps-server-internal';
|
|||
import { SecurityService } from '@kbn/core-security-server-internal';
|
||||
import { registerServiceConfig } from './register_service_config';
|
||||
import { MIGRATION_EXCEPTION_CODE } from './constants';
|
||||
import { coreConfig, type CoreConfigType } from './core_config';
|
||||
|
||||
const coreId = Symbol('core');
|
||||
const KIBANA_STARTED_EVENT = 'kibana_started';
|
||||
|
@ -158,16 +160,22 @@ export class Server {
|
|||
this.uptimePerStep.constructor = { start: constructorStartUptime, end: performance.now() };
|
||||
}
|
||||
|
||||
public async preboot() {
|
||||
public async preboot(): Promise<InternalCorePreboot | undefined> {
|
||||
this.log.debug('prebooting server');
|
||||
|
||||
const config = await firstValueFrom(this.configService.atPath<CoreConfigType>(coreConfig.path));
|
||||
const { disablePreboot } = config.lifecycle;
|
||||
if (disablePreboot) {
|
||||
this.log.info('preboot phase is disabled - skipping');
|
||||
}
|
||||
|
||||
const prebootStartUptime = performance.now();
|
||||
const prebootTransaction = apm.startTransaction('server-preboot', 'kibana-platform');
|
||||
|
||||
// service required for plugin discovery
|
||||
const analyticsPreboot = this.analytics.preboot();
|
||||
|
||||
const environmentPreboot = await this.environment.preboot({ analytics: analyticsPreboot });
|
||||
const nodePreboot = await this.node.preboot({ loggingSystem: this.loggingSystem });
|
||||
|
||||
this.nodeRoles = nodePreboot.roles;
|
||||
|
||||
// Discover any plugins before continuing. This allows other systems to utilize the plugin dependency graph.
|
||||
|
@ -176,57 +184,70 @@ export class Server {
|
|||
node: nodePreboot,
|
||||
});
|
||||
|
||||
// Immediately terminate in case of invalid configuration. This needs to be done after plugin discovery. We also
|
||||
// silent deprecation warnings until `setup` stage where we'll validate config once again.
|
||||
await ensureValidConfiguration(this.configService, { logDeprecations: false });
|
||||
if (!disablePreboot) {
|
||||
// Immediately terminate in case of invalid configuration. This needs to be done after plugin discovery. We also
|
||||
// silent deprecation warnings until `setup` stage where we'll validate config once again.
|
||||
await ensureValidConfiguration(this.configService, { logDeprecations: false });
|
||||
}
|
||||
|
||||
const { uiPlugins, pluginTree, pluginPaths } = this.discoveredPlugins.preboot;
|
||||
const contextServicePreboot = this.context.preboot({
|
||||
pluginDependencies: new Map([...pluginTree.asOpaqueIds]),
|
||||
});
|
||||
const httpPreboot = await this.http.preboot({ context: contextServicePreboot });
|
||||
|
||||
// setup i18n prior to any other service, to have translations ready
|
||||
await this.i18n.preboot({ http: httpPreboot, pluginPaths });
|
||||
|
||||
this.capabilities.preboot({ http: httpPreboot });
|
||||
const elasticsearchServicePreboot = await this.elasticsearch.preboot();
|
||||
// services we need to preboot even when preboot is disabled
|
||||
const uiSettingsPreboot = await this.uiSettings.preboot();
|
||||
await this.status.preboot({ http: httpPreboot });
|
||||
|
||||
const renderingPreboot = await this.rendering.preboot({ http: httpPreboot, uiPlugins });
|
||||
const httpResourcesPreboot = this.httpResources.preboot({
|
||||
http: httpPreboot,
|
||||
rendering: renderingPreboot,
|
||||
});
|
||||
|
||||
const loggingPreboot = this.logging.preboot({ loggingSystem: this.loggingSystem });
|
||||
|
||||
const corePreboot: InternalCorePreboot = {
|
||||
analytics: analyticsPreboot,
|
||||
context: contextServicePreboot,
|
||||
elasticsearch: elasticsearchServicePreboot,
|
||||
http: httpPreboot,
|
||||
uiSettings: uiSettingsPreboot,
|
||||
httpResources: httpResourcesPreboot,
|
||||
logging: loggingPreboot,
|
||||
preboot: this.prebootService.preboot(),
|
||||
};
|
||||
let corePreboot: InternalCorePreboot | undefined;
|
||||
|
||||
await this.plugins.preboot(corePreboot);
|
||||
if (!disablePreboot) {
|
||||
const { uiPlugins, pluginTree, pluginPaths } = this.discoveredPlugins.preboot;
|
||||
|
||||
httpPreboot.registerRouteHandlerContext<PrebootRequestHandlerContext, 'core'>(
|
||||
coreId,
|
||||
'core',
|
||||
() => {
|
||||
return new PrebootCoreRouteHandlerContext(corePreboot);
|
||||
}
|
||||
);
|
||||
const contextServicePreboot = this.context.preboot({
|
||||
pluginDependencies: new Map([...pluginTree.asOpaqueIds]),
|
||||
});
|
||||
|
||||
this.coreApp.preboot(corePreboot, uiPlugins);
|
||||
const httpPreboot = await this.http.preboot({ context: contextServicePreboot });
|
||||
|
||||
// setup i18n prior to any other service, to have translations ready
|
||||
await this.i18n.preboot({ http: httpPreboot, pluginPaths });
|
||||
|
||||
this.capabilities.preboot({ http: httpPreboot });
|
||||
|
||||
const elasticsearchServicePreboot = await this.elasticsearch.preboot();
|
||||
|
||||
await this.status.preboot({ http: httpPreboot });
|
||||
|
||||
const renderingPreboot = await this.rendering.preboot({ http: httpPreboot, uiPlugins });
|
||||
|
||||
const httpResourcesPreboot = this.httpResources.preboot({
|
||||
http: httpPreboot,
|
||||
rendering: renderingPreboot,
|
||||
});
|
||||
|
||||
corePreboot = {
|
||||
analytics: analyticsPreboot,
|
||||
context: contextServicePreboot,
|
||||
elasticsearch: elasticsearchServicePreboot,
|
||||
http: httpPreboot,
|
||||
uiSettings: uiSettingsPreboot,
|
||||
httpResources: httpResourcesPreboot,
|
||||
logging: loggingPreboot,
|
||||
preboot: this.prebootService.preboot(),
|
||||
};
|
||||
|
||||
await this.plugins.preboot(corePreboot);
|
||||
|
||||
httpPreboot.registerRouteHandlerContext<PrebootRequestHandlerContext, 'core'>(
|
||||
coreId,
|
||||
'core',
|
||||
() => {
|
||||
return new PrebootCoreRouteHandlerContext(corePreboot!);
|
||||
}
|
||||
);
|
||||
|
||||
this.coreApp.preboot(corePreboot, uiPlugins);
|
||||
}
|
||||
|
||||
prebootTransaction.end();
|
||||
this.uptimePerStep.preboot = { start: prebootStartUptime, end: performance.now() };
|
||||
|
||||
return corePreboot;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import {
|
||||
createRootWithCorePlugins,
|
||||
createTestServers,
|
||||
TestElasticsearchUtils,
|
||||
} from '@kbn/core-test-helpers-kbn-server';
|
||||
|
||||
function createRootWithDisabledPreboot() {
|
||||
return createRootWithCorePlugins({
|
||||
core: {
|
||||
lifecycle: {
|
||||
disablePreboot: true,
|
||||
},
|
||||
},
|
||||
logging: {
|
||||
appenders: {
|
||||
'test-console': {
|
||||
type: 'console',
|
||||
layout: {
|
||||
type: 'json',
|
||||
},
|
||||
},
|
||||
},
|
||||
root: {
|
||||
appenders: ['test-console'],
|
||||
level: 'info',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe('Starting root with disabled preboot', () => {
|
||||
let esServer: TestElasticsearchUtils;
|
||||
let root: ReturnType<typeof createRootWithDisabledPreboot>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { startES } = createTestServers({
|
||||
adjustTimeout: (t: number) => jest.setTimeout(t),
|
||||
settings: {
|
||||
es: {
|
||||
license: 'basic',
|
||||
},
|
||||
},
|
||||
});
|
||||
esServer = await startES();
|
||||
|
||||
root = createRootWithDisabledPreboot();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await root?.shutdown();
|
||||
await esServer?.stop();
|
||||
});
|
||||
|
||||
it('successfully boots', async () => {
|
||||
const preboot = await root.preboot();
|
||||
await root.setup();
|
||||
await root.start();
|
||||
|
||||
// preboot contract is not returned when preboot is disabled
|
||||
expect(preboot).toBeUndefined();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
// TODO replace the line below with
|
||||
// preset: '@kbn/test/jest_integration_node
|
||||
// to do so, we must fix all integration tests first
|
||||
// see https://github.com/elastic/kibana/pull/130255/
|
||||
preset: '@kbn/test/jest_integration',
|
||||
rootDir: '../../../../..',
|
||||
roots: ['<rootDir>/src/core/server/integration_tests/root'],
|
||||
// must override to match all test given there is no `integration_tests` subfolder
|
||||
testMatch: ['**/*.test.{js,mjs,ts,tsx}'],
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue