Please Donate to Bitcoin Address : [[address]]

Donation of [[value]] BTC Received. Thank You.
[[error]]

QEMU/KVM Sanallaştırma Sunucularında CPU Optimizasyonları

Merhaba,

Bu teknik yazıyı yazma amacım, özellikle production ortamlarında sanallaştırma sunucusu olarak KVM koşturan kişilerin optimizasyon ihtiyaçlarını bir nebze karşılamak. Belki öntanımlı gelen ayarlarla kullanıp, herhangi bir optimizasyona şu ana kadar ihtiyacı olmamış olanlar olabilir. Ancak Openstack vb. gibi “compute” çok yapılan tabiri caizse “hayvan gibi” (bkz: beast :P) uygulamalarda, eğer sanallaştırma platformunuz KVM ise aşağıda bahsedeceğim CPU iyileştirmelerini denemek isteyebilirsiniz.

Ön Bilgiler

KVM’de hiç bir CPU optimizasyonu yapmamışsanız, oluşturduğunuz sanal makineler öntanımlı olarak genelde o an için sanallaştırma sunucusunda hangi CPU boştaysa onun üzerinde koşmaya başlıyor. Ancak bu en ideal performansında sanal makinelerinizi çalıştırabileceğiniz manasına gelmiyor. Çünkü NUMA mimarisi diye bir mimari var. (lütfen devam etmeden wikipedia içeriğini okuyun)

NUMA mimarisinde bir sanal makineyi daha önceden belirlediğiniz bir NUMA node’una kilitlemek, KVM’in öntanımlı “NUMA node’una bakılmaksızın hangi cpu boştaysa onda çalıştır” policy’sine göre çok daha performanslı oluyor. Çünkü bir sanal makineyi bir NUMA düğümü içersindeki bir (ya da birden çok) CPU’ya kilitlediğinizde, ilgili sanal makinenin sistemden bellek ayırma işlemlerinin her zaman ilgili çalıştığı NUMA düğümünün içersinde kalacağını garanti etmiş oluyorsunuz. Aksi takdirde NUMA düğümleri arasında, hiç gereği olmamasına rağmen cross-node memory transport denilen NUMA düğümleri arası bellek taşınması işlemi de yapılması gerekiyor. E bu da yoğun compute ve memory allocation yapılacak olan sanal makinelerde bir performans kaybına yol açıyor haliyle.

E libvirt’in de NUMA için CPU pinning vb. desteği var, o halde nasıl kullanacağız? Şimdi ona bakalım.

libvirt – vCPU Pinning

Sanallaştırma sunucusundan işlemlere başlanmadan önce bilgiler toplanması gerekiyor.

Sanallaştırma sunucusu üzerinde aşağıdaki komutla bazı bilgileri toplayalım, sonra çıktısını inceleyelim :

# virsh nodeinfo

CPU model:           x86_64
CPU(s):              8
CPU frequency:       1600 MHz
CPU socket(s):       1
Core(s) per socket:  4
Thread(s) per core:  1
NUMA cell(s):        2
Memory size:         32936044 KiB

Çıktıda görüldüğü üzere, bu sanallaştırma sunucusunun işlemci mimarisi x86_64 (daha fazla bilgi için lscpu komutunu çalıştırın.)
Bu sunucuda 8 CPU çekirdeği ve 2 NUMA hücresine sahipmiş. 32GB da RAM varmış.
NUMA hücrelerinde hangi CPU’ların yer aldığını daha detaylı görmek için :

# virsh capabilities

<capabilities>

  <host>
    <uuid>38373035-3436-5355-4539-343556363644</uuid>
    <cpu>
      <arch>x86_64</arch>
      <model>Nehalem</model>
      <vendor>Intel</vendor>
      <topology sockets='1' cores='4' threads='1'/>
      <feature name='rdtscp'/>
      <feature name='dca'/>
      <feature name='pdcm'/>
      <feature name='xtpr'/>
      <feature name='tm2'/>
      <feature name='est'/>
      <feature name='vmx'/>
      <feature name='ds_cpl'/>
      <feature name='monitor'/>
      <feature name='dtes64'/>
      <feature name='pbe'/>
      <feature name='tm'/>
      <feature name='ht'/>
      <feature name='ss'/>
      <feature name='acpi'/>
      <feature name='ds'/>
      <feature name='vme'/>
    </cpu>
    <topology>
      <cells num='2'>
        <cell id='0'>
          <memory unit='KiB'>16423380</memory>
          <cpus num='4'>
            <cpu id='1' socket_id='0' core_id='0' siblings='1'/>
            <cpu id='3' socket_id='0' core_id='2' siblings='3'/>
            <cpu id='5' socket_id='0' core_id='1' siblings='5'/>
            <cpu id='7' socket_id='0' core_id='3' siblings='7'/>
          </cpus>
        </cell>
        <cell id='1'>
          <memory unit='KiB'>16512664</memory>
          <cpus num='4'>
            <cpu id='0' socket_id='1' core_id='0' siblings='0'/>
            <cpu id='2' socket_id='1' core_id='2' siblings='2'/>
            <cpu id='4' socket_id='1' core_id='1' siblings='4'/>
            <cpu id='6' socket_id='1' core_id='3' siblings='6'/>
          </cpus>
        </cell>
      </cells>
    </topology>
    ### (çıktı uzun olduğu için sadece lazım olan kısmı aldım) 
    ...

Yukarıdaki XML çıktısını inceleyecek olursak, mevcut topolojiye göre sistemde 2 NUMA hücresi olduğunu, 0 id’li hücrede yer alan CPU core’larının 1,3,5,7 core’ları olduğunu ve 16GB RAM barındırdığını, diğer 1 id’li hücrede yer alan CPU core’larının 0,2,4,6 core’ları olduğunu ve diğer yaklaşık 16 GB RAM de bu hücrede yerleştirildiğini görüyoruz.

Performans kaybına tekrar gelecek olursak, örneğin 2 sanal CPU atadığınız bir sanal makineye, bu sanal core’lardan birinin 0 id’li NUMA hücresinde, diğerinin 1 id’li NUMA hücresinde yer alması cross-node memory transportation olarak yukarıda bahsettiğim ekstra yüke sebebiyet veriyor, bu da performansı düşürüyor.

Sanallaştırma sunucusunda tanımlanmış ve default CPU pinning policy ile çalışan bir sanal makineye göz atalım

# virsh vcpuinfo vm01

VCPU:           0
CPU:            2
State:          running
CPU time:       88705.3s
CPU Affinity:   yyyyyyyy

VCPU:           1
CPU:            7
State:          running
CPU time:       93143.3s
CPU Affinity:   yyyyyyyy

Bu çıktıyı inceleyecek olursak, bu sanal makineye 2 adet sanal CPU core’u eklenmiş, bu sanal CPU core’lar şu anda sanallaştırma sunucusu üzerinde o an için 2 ve 7 id’li CPU core’ları üzerinde koşmaktalar. virsh capabilities komutunun çıktısını tekrar inceleyecek olursanız, bu makineye atanmış host CPU’ların id’si 2 olan NUMA cell id’si 1 olan grupta, diğer 7 id’li CPU’nun sa NUMA cell id’si 0 olan diğer hücrede koştuğunu göreceksiniz. libvirt öntanımlı olarak hangi CPU boştaysa o CPU’ları sanal makine için boot esnasında allocate ettiğinden (CPU Affinity: key-value) bu host makine şu anda cross-node memory transportation işlemi gerçekleştirmek zorunda kalıyor.

Gelelim bu sanal makinenin aynı yerel NUMA hücresinde çalışacağından nasıl emin olacağımıza. vcpu pinning denen işlemi yine virsh ile aşağıdaki şekilde on-the-fly gerçekleştirebiliyoruz :

# virsh vcpupin vm01 0 0

# virsh vcpupin vm01 1 2

# virsh vcpuinfo vm01

VCPU:           0
CPU:            0
State:          running
CPU time:       36.0s
CPU Affinity:   y-------

VCPU:           1
CPU:            2
State:          running
CPU time:       21.6s
CPU Affinity:   --y-----

Çalışan bir sanal makine üzerinde bu işlemi yaptıktan sonra gördüğünüz üzere sanal makine artık bizim belirlediğimiz Host CPU’lar üzerinde ve aynı NUMA hücresi içersinde yer alan bu CPU’lar üzerinde koşacak.

vcpupin komutunun anatomisini inceleyecek olursak :

# virsh vcpupin vm01 1 2
virsh -> an interface program to run on libvirt guests
vcpupin -> pin virtual cpus' to host's CPU cores.
vm01 -> vm/guest name
1 -> second vcpu on the guest
2 -> third CPU core on the libvirt host (0-8)

CPU Affinity değerlerine bakacak olursanız, sadece bizim belirlediğimiz CPU core’larında çalışacağını gösteriyor.

Peki reboot sonrası bu değerler korunacak mı? Hayır. Çünkü bu işlemi makine çalışırken on-the-fly gerçekleştirdik. Bu ayarların bu makine için kalıcı olmasını istiyorsak :

# virsh edit vm01

  <vcpu placement='static' cpuset='0,2'>2</vcpu>

Sanal makinenin XML dosyasında vcpu 2 core atarken, cpuset olarak bu node’ların sadece 0 ve 2 id’li CPU core’larını kullanabileceğini söylüyoruz. Makineyi yeniden başlatıp aşağıdaki şekilde vcpuinfo komutunun çıktısını incelediğinizde de bu ayarların geçerli olduğunu göreceksiniz :

# virsh vcpuinfo vm01

VCPU:           0
CPU:            2
State:          running
CPU time:       22.0s
CPU Affinity:   y-y-----

VCPU:           1
CPU:            0
State:          running
CPU time:       29.2s
CPU Affinity:   y-y-----

Bu değerleri kafadan attığımı düşünecek olanlar olabilir (septik yaklaşırdım ben de olsam :D) aşağıdaki şekilde bu ayarların sanallaştırma sunucusu üzerinde yer aldığını teyit edebilirsiniz :

# grep pid /var/run/libvirt/qemu/vm01.xml

<domstatus state='running' reason='booted' pid='19207'>

# grep 'Cpus_allowed_list' /proc/19207/task/*/status

/proc/19207/task/19207/status:Cpus_allowed_list:        0,2
/proc/19207/task/19209/status:Cpus_allowed_list:        0,2
/proc/19207/task/19210/status:Cpus_allowed_list:        0,2
/proc/19207/task/19212/status:Cpus_allowed_list:        0,2
/proc/19207/task/19986/status:Cpus_allowed_list:        0,2

İşte bu şekilde bir sanal makinenin her zaman aynı NUMA hücresi içersinde aynı core’lar üzerinde çalışacağını garanti edebilirsiniz. Bu ayarların çalıştıracağınız her sanal makine için yapılması gerektiğini söylemiyorum bu arada, performans kasıcam diye sistemi mundar edebilme ihtimaliniz de var 🙂 CPU pinning ihtiyacı olanlar göz gezdirebilirler.

Hepsi bu kadar.

Not: Bu blog yazısı Türkçe yazdığım son “teknik” blog yazısı olacak. Goygoy için yazarsam belki onları Türkçe yazarım. Zaten kimse okumuyo, maksat arşivde bulunsun. 🙂

 


 

2 Comments

Bir cevap yazın

I'm not a freaking robot : Time limit is exhausted. Please reload CAPTCHA.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

24 Ekim 2016

Posted In: Sistem, Teknik

Etiketler:, , , , ,

2 Comments