ceph分布式存储-日志和调试

文章介绍了如何在Ceph分布式存储系统中进行日志管理和调试,包括运行时和启动时调整调试选项,以及通过cephtell命令影响守护进程。还提到了日志滚动配置、子系统和调试选项的详细设置,以及如何使用logdump查看最近的日志。此外,文章强调了详尽日志对系统性能的影响和日志管理的重要性。
摘要由CSDN通过智能技术生成

ceph分布式存储-日志和调试

1. 介绍

Log 是每个项目必须的,他是跟踪问题的最直接的依据。Ceph 也设计自己的log机制。

一般来说,你应该在运行时增加调试选项来调试问题;也可以把调试选项添加到 Ceph 配置文件里来调试集群启动时的问题,然后查看 /var/log/ceph (默认位置)下的日志文件。

Tip: 调试输出会拖慢系统,这种延时有可能掩盖竞争条件。

日志记录是资源密集型任务。如果你碰到的问题在集群的某个特定区域,只启用那个区域对应的日志功能即可。例如,你的 OSD 运行良好、元数据服务器却有问题,这时应该先打开那个可疑元数据服务器实例的调试日志;如果不行再打开各子系统的日志。

重要: 详尽的日志每小时可能超过 1GB ,如果你的系统盘满了,这个节点就会停止工作。

如果你要打开或增加 Ceph 日志级别,确保有足够的系统盘空间。滚动日志文件的方法见下面的 加快日志更迭 小节。集群稳定运行后,可以关闭不必要的调试选项以优化运行。集群在运行中记录调试输出信息会拖慢系统、且浪费资源。

2. 运行时

要在运行时激活 Ceph 的调试输出(即 dout() ),用 ceph tell 命令把参数注入运行时配置:

    ceph tell {daemon-type}.{daemon id or *} injectargs --{name} {value} [--{name} {value}]

osdmonmds 替代 {daemon-type} 。还可以用星号( * )把配置应用到同类型的所有守护进程,或者指定具体守护进程的 ID 。例如,要给名为 ods.0ceph-osd 守护进程提高调试级别,用下列命令:

    ceph tell /run/ceph/ceph-osd.0.asok injectargs --debug-osd 0/5

ceph tell 命令会通过 monitor 起作用。如果你不能绑定 monitor,仍可以登录你要改的那台主机然后用 ceph daemon 来更改。例如:

    ceph daemon /run/ceph/ceph-osd.0.asok config set debug_osd 0/5

3. 启动时

要在启动时激活调试输出(即 dout() ),你得把选项加入配置文件。各进程共有配置可写在配置文件的 [global] 段下,某类进程的配置可写在对应的守护进程段下(如 [mon][osd][mds] )。例如:

    [global]
            debug ms = 1/5

    [mon]
            debug mon = 20
            debug paxos = 1/5
            debug auth = 2

    [osd]
            debug osd = 1/5
            debug filestore = 1/5
            debug journal = 1
            debug monc = 5/20

    [mds]
            debug mds = 1
            debug mds balancer = 1
            debug mds log = 1
            debug mds migrator = 1

4. 加快日志更迭

如果你的系统盘比较满,可以修改 /etc/logrotate.d/ceph 内的日志滚动配置以加快滚动。在滚动频率后增加一个日志 size 选项(达到此大小就滚动)来加快滚动(通过 cronjob )。例如默认配置大致如此:

    rotate 7
    weekly
    compress
    sharedscripts

增加一个 size 选项。

    rotate 7
    weekly
    size 500M
    compress
    sharedscripts

然后,打开 crontab 编辑器。

    crontab -e

最后,增加一条用以检查 /etc/logrorate.d/ceph 文件的语句。

    30 * * * * /usr/sbin/logrotate /etc/logrotate.d/ceph >/dev/null 2>&1

本例中每 30 分钟检查一次 /etc/logrorate.d/ceph 文件。

5. 子系统、日志和调试选项

大多数情况下,你可以通过子系统打开调试日志输出。

5.1 CEPH 子系统概览

各子系统都有日志级别用于分别控制其输出日志和暂存日志(内存中),你可以分别为这些子系统设置不同的记录级别。 Ceph 的日志级别从 1 到 20 , 1 是简洁、 20 是详尽。通常,内存驻留日志不会发送到输出日志,除非:

  • 致命信号出现,或者
  • 源码中的 assert 被触发,或者
  • 明确要求发送。

调试选项允许用单个数字同时设置日志级别和内存级别,这会将二者设置为一样的值。比如,如果你指定 debug ms = 5 , Ceph 会把日志级别和内存级别都设置为 5 。也可以分别设置,第一个选项是日志级别、第二个是内存级别,二者必须用斜线( / )分隔。假如你想把 ms 子系统的调试日志级别设为 1 、内存级别设为 5 ,可以写为 debug ms = 1/5 ,如下:

    debug {subsystem} = {log-level}/{memory-level}
    #for example
    debug mds log = 1/20

Ceph 子系统及其默认日志和内存级别具体见 SUBSYSTEM, LOG AND DEBUG SETTINGS 。一旦你完成调试,应该恢复默认值,或一个适合平常运营的级别。

5.2 日志记录选项

关于日志记录选项的详细信息,可以参考 Ceph 官方文档的对应内容 LOGGING SETTING

5.3 日志打印

ceph维护了一个m_recent队列,所有的消息都会存放到该队列中去,哪怕优先级比较低,不会打印到日志文件中··去。当然了,队列是有大小的限制,否则队列就会膨胀,导致内存耗尽。这个默认的限制是10000.

while (m_recent.m_len > m_max_recent) {
  delete m_recent.dequeue();
}

ceph提供了方法来查看最近的log,这就是log dump方法。

root@hwh:/run/ceph# ceph daemon /run/ceph/ceph-osd.0.asok log dump
{}

表面看啥也没输出,实际上将log dump到了日志文件中,比如我们例子,在log文件中出现:

root@hwh:/var/log/ceph# more ceph-osd.0.log
2023-04-24T04:16:23.028+0000 7ffff72f53c0  0 set uid:gid to 1001:1001 (ceph:ceph)
2023-04-24T04:16:23.028+0000 7ffff72f53c0  0 ceph version 364b013f7 (8364b013f73c0eb09e0795aa96bf222e708831c9) quincy (stable), process ceph-osd, pid 25341
2023-04-24T04:16:23.028+0000 7ffff72f53c0  0 pidfile_write: ignore empty --pid-file
2023-04-24T04:16:23.028+0000 7ffff72f53c0  1 bdev(0x55555861f400 /var/lib/ceph/osd/ceph-0/block) open path /var/lib/ceph/osd/ceph-0/block
2023-04-24T04:16:23.036+0000 7ffff72f53c0  1 bdev(0x55555861f400 /var/lib/ceph/osd/ceph-0/block) open size 128844824576 (0x1dffc00000, 120 GiB) block_size 4096 (4 KiB) non-rotational discard not supported
2023-04-24T04:16:23.036+0000 7ffff72f53c0  1 bluestore(/var/lib/ceph/osd/ceph-0) _set_cache_sizes cache_size 3221225472 meta 0.45 kv 0.45 data 0.06
2023-04-24T04:16:23.036+0000 7ffff72f53c0  1 bdev(0x55555861f900 /var/lib/ceph/osd/ceph-0/block) open path /var/lib/ceph/osd/ceph-0/block
2023-04-24T04:16:23.044+0000 7ffff72f53c0  1 bdev(0x55555861f900 /var/lib/ceph/osd/ceph-0/block) open size 128844824576 (0x1dffc00000, 120 GiB) block_size 4096 (4 KiB) non-rotational discard not supported
2023-04-24T04:16:23.044+0000 7ffff72f53c0  1 bluefs add_block_device bdev 1 path /var/lib/ceph/osd/ceph-0/block size 120 GiB
2023-04-24T04:16:23.044+0000 7ffff72f53c0  1 bdev(0x55555861f900 /var/lib/ceph/osd/ceph-0/block) close
2023-04-24T04:16:23.328+0000 7ffff72f53c0  1 bdev(0x55555861f400 /var/lib/ceph/osd/ceph-0/block) close

6. 源码分析

  • 启动日志
CephContext::CephContext(uint32_t module_type_,
                         enum code_environment_t code_env,
                         int init_flags_)
{
  ...
  _log = new ceph::logging::Log(&_conf->subsys);

  _log_obs = new LogObs(_log);
  _conf.add_observer(_log_obs);
  ...
}

void global_pre_init(
  const std::map<std::string,std::string> *defaults,
  std::vector < const char* >& args,
  uint32_t module_type, code_environment_t code_env,
  int flags)
{
  ...
  //读取配置文件内容,命令里不指明的话,默认使用/etc/ceph/ceph.conf。
  int ret = conf.parse_config_files(c_str_or_null(conf_file_list),
				    &cerr, flags);
  if (ret == -EDOM) {
    cct->_log->flush();
    cerr << "global_init: error parsing config file." << std::endl;
    _exit(1);
  }
  else if (ret == -ENOENT) {
    if (!(flags & CINIT_FLAG_NO_DEFAULT_CONFIG_FILE)) {
      if (conf_file_list.length()) {
	cct->_log->flush();
	cerr << "global_init: unable to open config file from search list "
	     << conf_file_list << std::endl;
        _exit(1);
      } else {
	cerr << "did not load config file, using default settings."
	     << std::endl;
      }
    }
  }
  else if (ret) {
    cct->_log->flush();
    cerr << "global_init: error reading config file. "
         << conf.get_parse_error() << std::endl;
    _exit(1);
  }

  // environment variables override (CEPH_ARGS, CEPH_KEYRING)
  conf.parse_env(cct->get_module_type());

  // command line (as passed by caller)
  conf.parse_argv(args);
    
  // 启动日志
  if (!cct->_log->is_started()) {
    cct->_log->start();
  }
  ...
}
  • 日志flush
void Log::flush()
{
  std::scoped_lock lock1(m_flush_mutex);
  m_flush_mutex_holder = pthread_self();

  {
    /*
    队列m_flush执行swap将m_new里面的log都接了过去。所谓接过去,不过是将指针指向
    队列内容的事情交给临时队列,m_new 头指针和尾指针置成NULL,做了这件事之后,m_new
    又变成了空的队列,好心地给其他线程发了广播之后,就可以解锁m_queue_mutex互斥量了。
    */
    std::scoped_lock lock2(m_queue_mutex);
    m_queue_mutex_holder = pthread_self();
    assert(m_flush.empty());
    m_flush.swap(m_new);
    m_cond_loggers.notify_all();
    m_queue_mutex_holder = 0;
  }

  //主要的写日志的工作就委托给了__flush函数。
  _flush(m_flush, false);
  m_flush_mutex_holder = 0;
}

void Log::_flush(EntryVector& t, bool crash)
{
  long len = 0;
  if (t.empty()) {
    assert(m_log_buf.empty());
    return;
  }
  if (crash) {
    len = t.size();
  }
  for (auto& e : t) {
    auto prio = e.m_prio;
    auto stamp = e.m_stamp;
    auto sub = e.m_subsys;
    auto thread = e.m_thread;
    auto str = e.strv();

    bool should_log = crash || m_subs->get_log_level(sub) >= prio;
    bool do_fd = m_fd >= 0 && should_log;
    bool do_syslog = m_syslog_crash >= prio && should_log;
    bool do_stderr = m_stderr_crash >= prio && should_log;
    bool do_graylog2 = m_graylog_crash >= prio && should_log;
    bool do_journald = m_journald_crash >= prio && should_log;

    if (do_fd || do_syslog || do_stderr) {
      const std::size_t cur = m_log_buf.size();
      std::size_t used = 0;
      const std::size_t allocated = e.size() + 80;
      m_log_buf.resize(cur + allocated);

      char* const start = m_log_buf.data();
      char* pos = start + cur;

      if (crash) {
        used += (std::size_t)snprintf(pos + used, allocated - used, "%6ld> ", -(--len));
      }
      used += (std::size_t)append_time(stamp, pos + used, allocated - used);
      used += (std::size_t)snprintf(pos + used, allocated - used, " %lx %2d ", (unsigned long)thread, prio);
      memcpy(pos + used, str.data(), str.size());
      used += str.size();
      pos[used] = '\0';
      ceph_assert((used + 1 /* '\n' */) < allocated);

      if (do_syslog) {
        syslog(LOG_USER|LOG_INFO, "%s", pos);
      }

      if (do_stderr) {
        std::cerr << m_log_stderr_prefix << std::string_view(pos, used) << std::endl;
      }

      /* now add newline */
      pos[used++] = '\n';

      if (do_fd) {
        m_log_buf.resize(cur + used);
      } else {
        m_log_buf.resize(0);
      }

      if (m_log_buf.size() > MAX_LOG_BUF) {
        _flush_logbuf();
      }
    }

    if (do_graylog2 && m_graylog) {
      m_graylog->log_entry(e);
    }

    if (do_journald && m_journald) {
      m_journald->log_entry(e);
    }

    /*
    ceph维护了一个m_recent队列,所有的消息都会存放到该队列中去,哪怕优先级比
    较低,不会打印到日志文件中去。    ceph提供了方法来查看最近的log,这就是log dump方法。
    ceph daemon /var/run/ceph/ceph-mon.*asok log dump
    */
    m_recent.push_back(std::move(e));
  }
  t.clear();

  _flush_logbuf();
}

  • 日志打印
#define dout_prefix *_dout

#define dout_impl(cct, sub, v)						\
  do {									\
  const bool should_gather = [&](const auto cctX) {			\
    if constexpr (ceph::dout::is_dynamic<decltype(sub)>::value ||	\
		  ceph::dout::is_dynamic<decltype(v)>::value) {		\
      return cctX->_conf->subsys.should_gather(sub, v);			\
    } else {								\
      /* The parentheses are **essential** because commas in angle	\
       * brackets are NOT ignored on macro expansion! A language's	\
       * limitation, sorry. */						\
      return (cctX->_conf->subsys.template should_gather<sub, v>());	\
    }									\
  }(cct);								\
									\
  if (should_gather) {							\
    ceph::logging::MutableEntry _dout_e(v, sub);                        \
    static_assert(std::is_convertible<decltype(&*cct), 			\
				      CephContext* >::value,		\
		  "provided cct must be compatible with CephContext*"); \
    auto _dout_cct = cct;						\
    std::ostream* _dout = &_dout_e.get_ostream();

#define dendl_impl std::flush;                                          \
    _dout_cct->_log->submit_entry(std::move(_dout_e));                  \
  }                                                                     \
  } while (0)

// dout_impl 生成了log_entry 写入到日志文件, dout_prefix 标准输出
#define ldout(cct, v)  dout_impl(cct, dout_subsys, v) dout_prefix


int AdminSocket::register_command(std::string_view cmddesc,
				  AdminSocketHook *hook,
				  std::string_view help)
{
  int ret;
  std::unique_lock l(lock);
  string prefix = cmddesc_get_prefix(cmddesc);
  auto i = hooks.find(prefix);
  if (i != hooks.cend() &&
      i->second.desc == cmddesc) {
    //打印日志
    ldout(m_cct, 5) << "register_command " << prefix
		    << " cmddesc " << cmddesc << " hook " << hook
		    << " EEXIST" << dendl;
    ret = -EEXIST;
  } else {
    ldout(m_cct, 5) << "register_command " << prefix << " hook " << hook
		    << dendl;
    hooks.emplace_hint(i,
		       std::piecewise_construct,
		       std::forward_as_tuple(prefix),
		       std::forward_as_tuple(hook, cmddesc, help));
    ret = 0;
  }
  return ret;
}

提交日志关键函数 void Log::submit_entry(Entry&& e)

void Log::submit_entry(Entry&& e)
{
  std::unique_lock lock(m_queue_mutex);
  m_queue_mutex_holder = pthread_self();

  if (unlikely(m_inject_segv))
    *(volatile int *)(0) = 0xdead;

  // wait for flush to catch up
  while (is_started() &&
	 m_new.size() > m_max_new) {
    if (m_stop) break; // force addition
    m_cond_loggers.wait(lock);
  }
  //加入队列m_new,然后通知线程刷日志
  m_new.emplace_back(std::move(e));
  m_cond_flusher.notify_all();
  m_queue_mutex_holder = 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值