OpenVZ と LXC で目に見える CPU の数


同じ問題に遭遇したため、このブログ投稿が見つかる場合があります.現時点では、解決策がないことに注意してください.この最初の段落を削除して、すぐに解決策を追加したいと考えています;)

Virtuozzo/OpenVZ 仮想化を使用する Jelastic に YugabyteDB をインストールしようとしましたが、yb-master の起動に失敗しました:

*** Check failure stack trace: ***
F0805 13:33:58.388178    28 locks.h:201] Check failed: cpu < n_cpus_ (21 vs. 6) 
    @     0x7f2760d809a1  yb::(anonymous namespace)::DumpStackTraceAndExit()
    @     0x7f276016200d  google::LogMessage::Fail()
    @     0x7f2760164536  google::LogMessage::SendToLog()
    @     0x7f2760161a6a  google::LogMessage::Flush()
    @     0x7f2760165159  google::LogMessageFatal::~LogMessageFatal()
    @     0x7f27695992c8  yb::percpu_rwlock::get_lock()
    @     0x7f276958ebcd  yb::log::Log::Reserve()
    @     0x7f27695905bb  yb::log::Log::AsyncAppendReplicates()
    @     0x7f2769849e67  yb::consensus::LogCache::AppendOperations()
    @     0x7f276982b0d2  yb::consensus::PeerMessageQueue::AppendOperations()
    @     0x7f276985b8e5  yb::consensus::RaftConsensus::AppendNewRoundsToQueueUnlocked()
    @     0x7f276985a2b3  yb::consensus::RaftConsensus::AppendNewRoundToQueueUnlocked()
    @     0x7f276985a7df  yb::consensus::RaftConsensus::BecomeLeaderUnlocked()
    @     0x7f276986fcc4  yb::consensus::RaftConsensus::DoElectionCallback()
    @     0x7f2760e1ec54  yb::ThreadPool::DispatchThread()
    @     0x7f2760e1b40f  yb::Thread::SuperviseThread()
    @     0x7f275c6de694  start_thread
    @     0x7f275be1b41d  __clone


要するに、これは、この percpu_rwlock() アサートで、CPU 数が使用可能な CPU の数よりも大きいことを意味します...これはどのように可能ですか?

どうやら、OpenVZ では、報告される CPU の数 (ここでは n_cpus_) は K8 によって可視化された仮想 CPU の数ですが、論理 CPU の数 (ここでは cpu) はハイパーバイザーから取得され、VM プロセッサ全体をカバーできます.

それを確認するために、両方の値を取得するこの小さな C プログラムを作成しました.
  • sched_getcpu() からの論理 CPU 番号
  • _SC_NPROCESSORS_CONF からの
  • 可視 CPU 数 ( lscpu で確認できるものと同じ)

  • cat > sched_getcpu.c <<CAT
    #define _GNU_SOURCE
    #include <stdio.h>
    #include <unistd.h>
    int main(void) {
        printf("sched_getcpu = %3d _SC_NPROCESSORS_CONF = %3d\n", sched_getcpu(),sysconf(_SC_NPROCESSORS_CONF));
        return 0;
    }
    CAT
    type gcc || yum install -y gcc
    gcc sched_getcpu.c && for i in {1..10} ; do ./a.out ; done
    


    OpenVZ 仮想化を使用して、Jelastic のコンテナーでこれを実行しています.

    [root@yb-tserver-0 ~]# yum install -y virt-what
    Package virt-what-1.18-4.el7.x86_64 already installed and latest version
    Nothing to do
    [root@yb-tserver-0 ~]# virt-what
    openvz
    lxc
    


    結果は次のとおりです.

    [root@yb-tserver-0 ~]# gcc sched_getcpu.c && for i in {1..10} ; do ./a.out ; done
    sched_getcpu =  31 _SC_NPROCESSORS_CONF =   6
    sched_getcpu =  15 _SC_NPROCESSORS_CONF =   6
    sched_getcpu =  17 _SC_NPROCESSORS_CONF =   6
    sched_getcpu =  17 _SC_NPROCESSORS_CONF =   6
    sched_getcpu =  19 _SC_NPROCESSORS_CONF =   6
    sched_getcpu =  21 _SC_NPROCESSORS_CONF =   6
    sched_getcpu =  19 _SC_NPROCESSORS_CONF =   6
    sched_getcpu =  19 _SC_NPROCESSORS_CONF =   6
    sched_getcpu =  21 _SC_NPROCESSORS_CONF =   6
    sched_getcpu =  19 _SC_NPROCESSORS_CONF =   6
    [root@yb-tserver-0 ~]#
    


    残念ながら、私が実行しているプロセッサの数は常に CPU の数 (K8 によってオフラインにされた CPU を差し引いたもの) よりも大きくなります. 1 つの値はホストから取得され、もう 1 つは Kubernetes 制限から取得されます.また、プロセッサ アフィニティを管理したい YugabyteDB (または OpenJDK には same problem または puppet があった) のようなプログラムは、回避策を見つける必要があります.

    では、コンテナーはオンラインの vCPU のみを表示する必要がありますか?おそらくですが、少なくともプロセッサ番号と一致している必要があります.これは、1/8 OCPU を実行している Oracle Cloud フリー VM の例です.したがって、OS から 2 CPU が表示されます.

    [opc@a ~]$ lscpu | grep -E "^Model|^CPU|^Hyper"
    
    CPU op-mode(s):        32-bit, 64-bit
    CPU(s):                2
    CPU family:            23
    Model:                 1
    Model name:            AMD EPYC 7551 32-Core Processor
    CPU MHz:               1996.250
    Hypervisor vendor:     KVM
    


    これは、64 スレッド プロセッサ上の 2 つの vCPU です.

    仮想化は KVM です.

    [root@yb-tserver-0 cores]# virt-what
    lxc
    kvm
    


    そして、私の小さなプログラムは一貫した sched_getcpu() 数値を示します:

    [root@yb-tserver-0 cores]# for i in {1..10} ; do ./a.out ; done
    sched_getcpu =   6 _SC_NPROCESSORS_CONF =   8
    sched_getcpu =   1 _SC_NPROCESSORS_CONF =   8
    sched_getcpu =   4 _SC_NPROCESSORS_CONF =   8
    sched_getcpu =   3 _SC_NPROCESSORS_CONF =   8
    sched_getcpu =   5 _SC_NPROCESSORS_CONF =   8
    sched_getcpu =   7 _SC_NPROCESSORS_CONF =   8
    sched_getcpu =   1 _SC_NPROCESSORS_CONF =   8
    sched_getcpu =   4 _SC_NPROCESSORS_CONF =   8
    sched_getcpu =   6 _SC_NPROCESSORS_CONF =   8
    sched_getcpu =   2 _SC_NPROCESSORS_CONF =   8
    


    また、目に見える CPU の数のみを表示する機能もあります (LXC が/sys 内のすべてのホストのものを表示した場合、bug と見なされていました).

    YugabyteDB マスター サーバーで数値がどこから来ているか見てみましょう.
  • cpusched_getcpu() in yb::percpu_rwlock::get_lock() から来ています
  • n_cpus_n_cpus_ = base::MaxCPUIndex() + 1; からのもので、sysinfo.cc/sys/devices/system/cpu/present を解析し、私の OpenVZ VM では次のようになります.

  • root@node88695-yb-demo ~ $ cat /sys/devices/system/cpu/present
    0-5
    


    これは正しい動作のように見えますが、一部のハイパーバイザーで sched_getcpu() から得られる数値と一致しません.おそらく、このアサートを削除する必要があります (OSX に対して行われているため).

    私の汚いハックは、コマンド (私の場合は exec /home/yugabyte/bin/yb-master) の前に次を追加して、LD_PRELOAD で sched_getcpu() をハイジャックし、常に _SC_NPROCESSORS_CONF から最後のプロセッサ番号を返します.

    yum install -y gcc && echo -e "#define _GNU_SOURCE\n#include <unistd.h>\nint sched_getcpu (void) { return sysconf(_SC_NPROCESSORS_CONF)-1 ; };\n" > sched_getcpu.c && gcc -shared -o /tmp/sched_getcpu.so -fPIC sched_getcpu.c ; export LD_PRELOAD=/tmp/sched_getcpu.so && 
    


    /sys/devices/system/cpu の代わりにカスタム/tmp/devices/system/cpu から読み取ることで、より広い範囲の CPU を提供する代替手段もあります.

    echo 0-64 > /tmp_devices_system_cpu_present ; sed -e 's@/sys/devices/system/cpu/present@/tmp_devices_system_cpu_present@g' -i /home/yugabyte/lib/yb/libgutil.so
    


    この方法で既存の yb-master ステートフル セットにパッチを適用して、sched_getcpu が最高のオンライン セットを返すように強制します.

    for i in yb-master yb-tserver ; do kubectl get statefulsets $i -n yb-demo -o yaml | awk '/^ *exec [/]home[/]yugabyte[/]bin[/]yb-master/{sub(/exec/,patch" exec")}{print}' patch='yum install -y gcc ; echo -e "#define _GN
    U_SOURCE\\n#include <unistd.h>\\nint sched_getcpu (void) { return sysconf(_SC_NPROCESSORS_CONF)-1 ; };\\n" > sched_getcpu.c ; gcc -shared -o /tmp/sched_getcpu.so -fPIC sched_getcpu.c ; export LD_PRELOAD=/tmp/sched_getcpu.so ; ' | kubectl apply -f /dev/stdin -n yb-demo ; done
    


    または、ホスト CPU に対応する 0 ~ 64 の範囲をハードコードします.

    for i in yb-master yb-tserver ; do kubectl get statefulsets $i -n yb-demo -o yaml | tee $i.b.yaml | awk '/^ *exec [/]home[/]yugabyte[/]bin[/]yb-/{sub(/exec/,patch" exec")}{print}' patch=' echo 0-64 > /tmp_devices_system_cpu_present ; sed -e 's@/sys/devices/system/cpu/present@/tmp_devices_system_cpu_present@g' -i /home/yugabyte/lib/yb/libgutil.so ; ' | tee $i.e.yaml | kubectl apply -f /dev/stdin -n yb-demo ; done
    


    ただし、これは完全にサポートされていないことに注意してください.解決策については https://github.com/yugabyte/yugabyte-db/issues/9619 を確認してください