Kibana7.6源码阅读

2,991 阅读4分钟

最近项目要升级原有的kibana,于是学习了一下kibana的源码。kibana的功能是真的又多又强,代码量也是非常多。

前言

kibana7.6的实现包含node层以及web层。node层用的hapi框架,web层涉及多种框架react、angular以及rxjs的混用,编码语言typescript与javascript并存。打开package.json收获n多插件。

代码结构

kibana web相关实现基本归纳在public文件夹下,node端实现在server下

  • config 配置文件 kibana.yml 配置kibana的访问方式以及与es相关的配置 大多数问题都可以在配置中找到解决方案
#server.port: 5601

#server.host: "localhost"

# basePath可自定义访问前缀,当rewriteBasePath为true时会被去掉
#server.basePath: ""

# 该项定义决定kibana请求是否带basePath的前缀,以及在做反向代理时是否要重写地址
# 在6.3版本之前默认值是false,7.0之后默认为true
#server.rewriteBasePath: false

#server.maxPayloadBytes: 1048576

#server.name: "your-hostname"

# kibana连接es的地址
#elasticsearch.hosts: ["http://localhost:9200"]

# es集群索引
# dashboards. Kibana creates a new index if the index doesn't already exist.
#kibana.index: ".kibana"

#kibana.defaultAppId: "home"

# 用户名密码用于权限校验
#elasticsearch.username: "kibana_system"
#elasticsearch.password: "pass"

# Enables SSL and paths to the PEM-format SSL certificate and SSL key files, respectively.
# These settings enable SSL for outgoing requests from the Kibana server to the browser.
#server.ssl.enabled: false
#server.ssl.certificate: /path/to/your/server.crt
#server.ssl.key: /path/to/your/server.key

# Optional settings that provide the paths to the PEM-format SSL certificate and key files.
# These files are used to verify the identity of Kibana to Elasticsearch and are required when
# xpack.security.http.ssl.client_authentication in Elasticsearch is set to required.
#elasticsearch.ssl.certificate: /path/to/your/client.crt
#elasticsearch.ssl.key: /path/to/your/client.key

# Optional setting that enables you to specify a path to the PEM file for the certificate
# authority for your Elasticsearch instance.
#elasticsearch.ssl.certificateAuthorities: [ "/path/to/your/CA.pem" ]

# To disregard the validity of SSL certificates, change this setting's value to 'none'.
#elasticsearch.ssl.verificationMode: full

# Time in milliseconds to wait for Elasticsearch to respond to pings. Defaults to the value of
# the elasticsearch.requestTimeout setting.
#elasticsearch.pingTimeout: 1500

# Time in milliseconds to wait for responses from the back end or Elasticsearch. This value
# must be a positive integer.
#elasticsearch.requestTimeout: 30000

# 可以传递到es的请求头白名单
#elasticsearch.requestHeadersWhitelist: [ authorization ]

# 可以自定义传递到es的请求头
#elasticsearch.customHeaders: {}

# Time in milliseconds for Elasticsearch to wait for responses from shards. Set to 0 to disable.
#elasticsearch.shardTimeout: 30000

# Logs queries sent to Elasticsearch. Requires logging.verbose set to true.
#elasticsearch.logQueries: false

# Specifies the path where Kibana creates the process ID file.
#pid.file: /var/run/kibana.pid

# Enables you to specify a file where Kibana stores log output.
#logging.dest: stdout

# Set the value of this setting to true to suppress all logging output.
#logging.silent: false

# Set the value of this setting to true to suppress all logging output other than error messages.
#logging.quiet: false

# Set the value of this setting to true to log all events, including system usage information
# and all requests.
#logging.verbose: false

# Set the interval in milliseconds to sample system and process performance
# metrics. Minimum is 100ms. Defaults to 5000.
#ops.interval: 5000

# 国际化 中文对应值为zh-CN.
#i18n.locale: "en"

  • packages 提供了一些es及kbn的工具包
  • scripts 一些启动脚本
  • x-pack kibana提供的插件用于权限控制,提供登录、space分离等
  • src 核心代码实现

kibana运行流程

  • 入口src/cli/index.js 开始node环境的安装校验以及apm监控的注册以及命令参数的初始化
  • 调用src/core/server/bootstrap.ts中bootstrap方法启动服务。其中的 root.setup()以及root.start()则是注册及启动服务的核心实现。
/**
 *
 * @internal
 * @param param0 - options
 */
export async function bootstrap({
  configs,
  cliArgs,
  applyConfigOverrides,
  features,
}: BootstrapArgs) {
  if (cliArgs.repl && !features.isReplModeSupported) {
    onRootShutdown('Kibana REPL mode can only be run in development mode.');
  }

  const env = Env.createDefault({
    configs,
    cliArgs,
    isDevClusterMaster: isMaster && cliArgs.dev && features.isClusterModeSupported,
  });

  const rawConfigService = new RawConfigService(env.configs, applyConfigOverrides);
  rawConfigService.loadConfig();

  const root = new Root(rawConfigService, env, onRootShutdown);

  process.on('SIGHUP', () => reloadLoggingConfig());

  // This is only used by the LogRotator service
  // in order to be able to reload the log configuration
  // under the cluster mode
  process.on('message', msg => {
    if (!msg || msg.reloadLoggingConfig !== true) {
      return;
    }

    reloadLoggingConfig();
  });

  function reloadLoggingConfig() {
    const cliLogger = root.logger.get('cli');
    cliLogger.info('Reloading logging configuration due to SIGHUP.', { tags: ['config'] });

    try {
      rawConfigService.reloadConfig();
    } catch (err) {
      return shutdown(err);
    }

    cliLogger.info('Reloaded logging configuration due to SIGHUP.', { tags: ['config'] });
  }

  process.on('SIGINT', () => shutdown());
  process.on('SIGTERM', () => shutdown());

  function shutdown(reason?: Error) {
    rawConfigService.stop();
    return root.shutdown(reason);
  }

  try {
    await root.setup();
    await root.start();
  } catch (err) {
    await shutdown(err);
  }

  if (cliArgs.optimize) {
    const cliLogger = root.logger.get('cli');
    cliLogger.info('Optimization done.');
    await shutdown();
  }
}
  • src/core/server/server.ts 列出了kibana服务所依赖的对象实例
    • 其中ElasticsearchService初始化得到esClusterClient实例,所有kibana到es的请求都是通过该client
    • LegacyService创建kbnServer,该文件引入了src/legacy/server/kibana_service.ts
    • RenderingService setup中实现了渲染方法,render模板在 src/core/server/rendering/views/template
  public readonly configService: ConfigService;
  private readonly capabilities: CapabilitiesService;
  private readonly context: ContextService;
  private readonly elasticsearch: ElasticsearchService;
  private readonly http: HttpService;
  private readonly rendering: RenderingService;
  private readonly legacy: LegacyService;
  private readonly log: Logger;
  private readonly plugins: PluginsService;
  private readonly savedObjects: SavedObjectsService;
  private readonly uiSettings: UiSettingsService;
  private readonly uuid: UuidService;
  
  constructor(
    rawConfigProvider: RawConfigurationProvider,
    public readonly env: Env,
    private readonly logger: LoggerFactory
  ) {
    this.log = this.logger.get('server');
    this.configService = new ConfigService(rawConfigProvider, env, logger);

    const core = { coreId, configService: this.configService, env, logger };
    this.context = new ContextService(core);
    this.http = new HttpService(core);
    this.rendering = new RenderingService(core);
    this.plugins = new PluginsService(core);
    this.legacy = new LegacyService(core);
    this.elasticsearch = new ElasticsearchService(core);
    this.savedObjects = new SavedObjectsService(core);
    this.uiSettings = new UiSettingsService(core);
    this.capabilities = new CapabilitiesService(core);
    this.uuid = new UuidService(core);
  }
  • src/legacy/server/kibana_service.ts -> src/legacy/ui/ui_mixin.js 整合了ui层的一些实现
    • uiRenderMixin 中实现了renderApp服务的入口
 export async function uiMixin(kbnServer) {
  await kbnServer.mixin(uiAppsMixin);
  await kbnServer.mixin(uiBundlesMixin);
  await kbnServer.mixin(uiSettingsMixin);
  await kbnServer.mixin(fieldFormatsMixin);
  await kbnServer.mixin(tutorialsMixin);
  await kbnServer.mixin(uiRenderMixin);
}

kibana页面资源加载

找到模板文件 template.js.hbs(src/legacy/ui/ui/ui_render/bootstrap)可以看到页面加载的资源

 window.onload = function () {
    var files = [
      '{{dllBundlePath}}/vendors_runtime.bundle.dll.js',
      {{#each dllJsChunks}}
        '{{this}}',
      {{/each}}
      '{{regularBundlePath}}/kbn-ui-shared-deps/{{sharedDepsFilename}}',
      '{{regularBundlePath}}/commons.bundle.js',
      '{{regularBundlePath}}/{{appId}}.bundle.js'
    ];

    var failure = function () {
      // make subsequent calls to failure() noop
      failure = function () {};

      var err = document.createElement('h1');
      err.style['color'] = 'white';
      err.style['font-family'] = 'monospace';
      err.style['text-align'] = 'center';
      err.style['background'] = '#F44336';
      err.style['padding'] = '25px';
      err.innerText = document.querySelector('[data-error-message]').dataset.errorMessage;

      document.body.innerHTML = err.outerHTML;
    }

    function loadStyleSheet(path) {
      var dom = document.createElement('link');

      dom.addEventListener('error', failure);
      dom.setAttribute('rel', 'stylesheet');
      dom.setAttribute('type', 'text/css');
      dom.setAttribute('href', path);
      document.head.appendChild(dom);
    }

    function createJavascriptElement(path) {
      var dom = document.createElement('script');

      dom.setAttribute('defer', 'defer');
      dom.addEventListener('error', failure);
      dom.setAttribute('src', file);
      dom.addEventListener('load', next);
      document.head.appendChild(dom);
    }

    {{#each styleSheetPaths}}
      loadStyleSheet('{{this}}');
    {{/each}}

    (function next() {
      var file = files.shift();
      if (!file) return;

      var dom = document.createElement('script');

      dom.setAttribute('async', '');
      dom.addEventListener('error', failure);
      dom.setAttribute('src', file);
      dom.addEventListener('load', next);
      document.head.appendChild(dom);
    }());
  };
}