Bursting und Throtteling in OpenStack

Wir haben die vergangenen Monate genutzt, um eine neue Cloud mit OpenStack aufzubauen. Im Zuge dessen, mussten wir eine Möglichkeit finden, die IOPS sowie die Bandbreite, die VMs zur Verfügung haben, zu limitieren.
Das Limitieren der Bandbreite sowie der IOPS erfolgt in OpenStack in sogenannten Flavors. In einem deutschsprachigen Interface von OpenStack werden diese “Varianten” genannt. Flavors werden hier als VM-Templates genutzt, mit denen sich VMs starten lassen. Es werden hier Ressourcen geregelt wie RAM, CPU und Firewallregeln aber eben auch die Limitierungen. Gesetzte Werte können nicht in laufenden VMs überschrieben werden. Möchte man diese ändern, muss die VM gelöscht und neu gebaut werden, nachdem die neuen Werte im Flavor angepasst wurden. Ein Rebuild reicht hier nicht aus.
Hier gibt es jedoch eine Ausnahme. Durch den Einsatz von beispielsweise libvirtd, können jene Beschränkungen mittels “virsh” angepasst werden.

Was sind IOPS und Bandbreite?

Bandbreite und IOPS geben an, wieviel Datendurchsatz sowie Lese und Schreiboperationen einer VM zugeteilt sind. Per Default sind diese unlimitiert, was unter gewissen Umständen zu Problemen führen kann.

Wieso sind Limitierungen sinnvoll?

In einer Cloud mit mehreren Virt-Systemen laufen mehrere VMs. Sind keine Limitierungen gesetzt, kann jede VM soviel Traffic und IOPS erzeugen, wie sie gerade braucht. Das ist natürlich für die Performance entsprechend gut, jedoch verhält es sich dadurch so, dass andere VMs auf dem gleichen Virt entsprechend unperformanter werden. Limitierungen werden daher dazu genutzt ein gleiches Niveau für alle VMs zu schaffen.

Bandbreite

Average

  1. quota:vif_inbound_average
  2. quota:vif_outbound_average

Wie der Name schon sagt, beschränkt man hier inbound (eingehenden) sowie outbound (ausgehenden) Traffic durch einen durchschnittlichen Wert, den diese beiden nicht überschreiten dürfen.

Peak

  1. quota:vif_inbound_peak
  2. quota:vif_outbound_peak

Die Bandbreite kann man auch mit Peak sowie Burst begrenzen. Peak gibt hierbei an, bis zu welchem Limit die Bandbreite genutzt werden darf, als absolutes Maximum. Dieser Wert funktioniert aber nur in Zusammenarbeit mit “Burst”.

Burst

  1. quota:vif_inbound_burst
  2. quota:vif_outbound_burst

Burst gibt nämlich an, wie lange die Bandbreite im Wert “Average” überschritten werden darf. Gemessen wird hier in KB. Setzt man also den Burst auf 1.048.576 KB, darf der Peak Wert solange genutzt werden, bis 1GB (1.048.576 KB) an Daten übertragen wurden. Zu Beachten ist aber, dass dieser Wert für jeden Zugriff neu gilt. Führt man also 3 Kommandos hintereinander aus (3x wget mit && verknüpft) greift der Burst für alle 3 gleichermaßen. Führt man die gleichen Kommandos ebenfalls hintereinander aus, aber verknüpft diese mit einem Sleep, greift der Burst für jedes Kommando neu.
 

IOPS

Throttle

  1. quota:disk_read_iops_sec
  2. quota:disk_total_iops_sec
  3. quota:disk_write_iops_sec

Die lesenden und schreibenden Prozesse der VMs können natürlich auch begrenzt werden. Hier gibt es zwei Möglichkeiten:

  • Limitierung von lesenden sowie schreibenden Prozessen separat
  • Limitierung auf absoluten Wert

Beides in Kombination geht nicht. Es nicht möglich zu konfigurieren, dass es 300 lesende, 300 schreibende und 700 insgesamte IOPS geben soll, würde aber auch keinen Sinn machen. Zu beachten ist, wenn alle 3 Werte gesetzt werden, können diese in einen Konflikt geraten und gegebenenfalls gar nicht greifen.

Burst

  1. quota:disk_write_iops_sec_max
  2. quota:disk_write_iops_sec_max_length

Durch das Bursting auf den Festplatten direkt, kann angegeben werden, mit welcher maximalen Anzahl an IOPS (quota:disk_write_iops_sec_max)eine VM die oben gesetzten Werte, für wie lange (quota:disk_write_iops_sec_max_length) überschreiten darf. Sinnvoll wäre dies, wenn bekannt ist, dass gewisse Prozesse kurzzeitig mehr IOPS benötigen, als freigegeben ist.

Beispiele

Um Limitierungen zu setzen, wird zunächst ein Flavor benötigt. Im Anschluss können die Werte gesetzt werden. Die Dokumentation zum Anlegen von Flavors gibts hier
openstack flavor set {$flavor} --property quota:{$param}={$value}
quota:disk_read_iops_sec='200'
(quota:disk_total_iops_sec='1000')
quota:disk_write_iops_sec='200'
quota:vif_inbound_average='10240'
quota:vif_inbound_burst='20480'
quota:vif_inbound_peak='15360'
quota:vif_outbound_average='10240'
quota:vif_outbound_burst='20480'
quota:vif_outbound_peak='15360'
quota:disk_write_iops_sec_max_length='10'
quota:disk_write_iops_sec_max='1000'
In diesem Beispiel würde man zum Beispiel die lesenden Prozesse auf 200 (quota:disk_read_iops_sec='200') beschränken, ebenso die schreibenden, bei einer eingehenden Brandbreite von 10MB(quota:vif_inbound_average='10240'). Peak liegt bei 20MB und darf für 15MB erreicht werden. Das ist natürlich ein sehr unrealistisch minimalistisches Begrenzungsbeispiel, jedoch sollte die Art und Weise wie es funktioniert verdeutlich worden sein.

Marius Gebert
Marius Gebert
Systems Engineer

Marius ist seit 2013 bei NETWAYS. Er hat 2016 seine Ausbildung zum Fachinformatiker für Systemintegration absolviert und ist nun im Web Services Team tätig. Hier kümmert er sich mit seinen Kollegen um die NWS Plattform und alles was hiermit zusammen hängt. 2017 hat Marius die Prüfung zum Ausbilder abgelegt und kümmert sich in seiner Abteilung um die Ausbildung unserer jungen Kollegen. Seine Freizeit verbringt Marius gerne an der frischen Luft und ist für jeden Spaß zu...

Wenn das MacBook zu heiß ist – cpulimit.sh

Ich mag laute Hardware nicht. Und ich fand es gut, dass mein iBook mit 600MHz PowerPC G3 Prozessor aus dem Jahr 2001 nur im absoluten Notfall den Lüfter eingeschaltet hat. Auch mein MacBook Pro mit 2,4GHz Intel Core 2 Duo Prozessor war im Normalfall nicht zu hören.
Aber mein neues MacBook Pro mit 2,2GHz Intel Core i7 erdreistet sich, bei verschiedenen Gelegenheiten, in den “Jet-mode” zu gehen. Die Ursache ist dann gern mal der Indexer für Spotlight (metadata server – mds) oder ein Video Player (video lan client – vlc). Beide brauchen eigentlich nicht mit voller Power zu laufen um meinen Ansprüchen gerecht zu werden. Ausserdem braucht man meist auch die dedizierte Grafikkarte nicht. Die kann man aber Dank gfxCardStatus einfach abschalten. Achtung, nicht vergessen wieder anzuschalten, wenn man den Beamer dranhängt!
Apple hat leider keine Möglichkeit vorgesehen die CPU zu drosseln um weniger Strom zu verbrauchen und somit den Akku und das Gehör zu schonen. Todtraurig!
Nice ist auch keine Lösung, weil ja dennoch die Power genutzt wird die zur Verfügung steht.
Aber ich habe etwas gefunden was an dieser Stelle weiterhelfen kann: cpulimit. Das ließ sich aber unter Lion nicht compilieren. Es gibt da aber noch diese Variation davon.
An der Shell Variante habe ich ein wenig gefeilt. Nun bekommt man eine Liste der CPU hungrigen Prozesse zur Auswahl und den Erstplatzierten als Vorauswahl.
Das Ergebnis sieht dann so aus:

$ sudo ~/bin/cpulimit.sh
Password:
  This script will throttle a chosen process as you like it.
  Therefore your cpu runs cooler - hopefully.
Chose which process to throttle:
 PPID   PID USER            %CPU %MEM STARTED      TIME     STIME COMMAND
    1    66 root            25,5  5,1 Do08am   73:25.42  32:24.31 mds
  273   436 bschmidt        23,1  0,1 Do08am    1:20.18   0:13.72 Folder Actions Dispatcher
    1 19304 root            15,3  0,1 Sa03pm   62:40.11  51:14.03 activitymonitord
  273 91519 bschmidt        14,6  0,3  7:57pm   0:06.47   0:02.17 mdworker
    1    12 _mdnsresponder  12,6  0,0 Do08am   26:20.00  19:02.96 mDNSResponder
  273 91189 bschmidt        11,6  0,4  7:55pm   0:06.54   0:02.21 mdworker
  273 91520 bschmidt        11,3  0,3  7:57pm   0:06.48   0:02.17 mdworker
  273 91518 bschmidt         9,2  0,3  7:57pm   0:06.36   0:02.14 mdworker
  273 13471 bschmidt         8,7  3,6 Fr02pm  101:01.25  22:25.71 Google Chrome
Which process ID (PID) [guess: 66]?
Chosen:
  PID USER  %CPU %MEM STARTED      TIME COMMAND
   66 root  21,9  5,1 Do08am   73:26.43 /System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Support/mds
Sleep time in seconds (e.g 0.5 or 1 …) [default: 0.5]?
Run time in seconds (e.g 0.5 or 1 …) or percent [default: .5000 = 50%]?
OK. I will throttle PID 66 now and dislay processinfo every 20 seconds …
  PID USER  %CPU %MEM STARTED      TIME COMMAND
   66 root  40,5  4,7 Do08am   73:34.02 /System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Support/mds
  PID USER  %CPU %MEM STARTED      TIME COMMAND
   66 root  32,7  4,8 Do08am   73:34.91 /System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Support/mds
  PID USER  %CPU %MEM STARTED      TIME COMMAND
   66 root   0,0  4,8 Do08am   73:35.15 /System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Support/mds
^C
*** Ouch! Stop throtteling of PID 66. Exiting ***

Mit diesem Script ist es ein Leichtes, Prozesse ausser Rand und Band wieder einzufangen. Erstaunlicher Weise sogar den Video Player, wenn man nur die sleep time kurz genug wählt.
Da das Script nicht lang ist, stell ich es hier einfach mal rein:

#! /bin/bash
# Description:
#
# If you want to decrease the CPU demands for an application
# you can use this very simple and ugly "hack".
# I use it when I convert videos on my MacBook Pro
# to prevent it from getting to warm. Even if the CPU is IDLE this hack
# will prevent the application to use the CPU during Sleep Time
# (in contrast to renice/nice commands which will take all IDLE time).
# This means the conversions will take longer time, but I
# don't care because I run it at night.
#
# There is similar c program for Linux called cpulimit, but it wont
# compile on my Mac.
#
# Usage:  sudo ./cpulimit.sh
#
#################################
# config
sleeptime_default=0.5   # in seconds
runtimep_default=50     # in percent (50 = same as sleeptime)
reporttime=30           # report every x seconds while running
# PROCESSLISTformat: pid has to be on the second position
PROCESSLISTformat="ppid,pid,user,%cpu,%mem,start,cputime,stime,command"
#PROCESSLISTformat="ppid,pid,user,%cpu,%mem,start,cputime,stime,rss,vsz,cpu,re,wq,wqr,stat,nsigs,command"
#################################
# dont change anything below here
pid=""
scriptuser=$(id -u)
# run if user hits control-c
control_c()
{
  echo ""
  if [ "$pid" != "" ]; then
    echo -en "\n*** Ouch! Stop throtteling of PID $pid. Exiting ***\n"
    kill -SIGCONT $pid
  fi
  echo ""
  exit $?
}
# trap keyboard interrupt (control-c)
trap control_c SIGINT
pidstring()
{
  # takes pid as argument
  ps -p $1 -w -o pid,user,%cpu,%mem,start,cputime,command 2>/dev/null
}
isnumber()
{
  if [[ "$1" =~ ^[0-9]*([.][0-9]+)?$ ]] ; then
    return 0
  else
    return 1
  fi
}
#################################
# main
cat << EOF
  This script will throttle a chosen process as you like it.
  Therefore your cpu runs cooler - hopefully.
EOF
if [ "$scriptuser" != "0" ]; then
cat << EOF
  You're not root!
  If you want to controll processes that you dont own,
  please run via sudo.
EOF
fi
PIDSTRING=""
until [ "$PIDSTRING" != "" ]; do
  #PROCESSLIST=$(ps -r -A -f -S | head)
  if [ "$scriptuser" != "0" ]; then
    PROCESSLIST=$(ps -r -u $scriptuser -S -c -o $PROCESSLISTformat 2>/dev/null)
  else
    PROCESSLIST=$(ps -r -A -S -c -o $PROCESSLISTformat 2>/dev/null)
  fi
  if [ "$PROCESSLIST" = "" ]; then
    echo -en "\n*** Sorry, ps dont work as expected on $(uname). Exiting ***\n\n"
    exit 1
  else
    PROCESSLIST=$(echo "$PROCESSLIST" | head)
  fi
  echo -en "\nChose which process to throttle:\n$PROCESSLIST\n"
  PID_GUESS=$(echo "$PROCESSLIST" | head -2 | tail -1 | awk '{print $2}')
  echo -n "Which process ID (PID) [guess: $PID_GUESS]? "
  read pid
  if [ "$pid" = "" ]; then
      pid=$PID_GUESS
  fi
  # test if $pid is valid
  PIDSTRING=$(pidstring $pid)
  if [ "$?" -eq "0"  ]; then
    echo -en "\nChosen:\n$PIDSTRING\n"
  else
    echo -en "\n$pid is not a valid PID for a running process.\n"
    pid=""
  fi
  echo -en "\n"
done
sleeptime=""
until [ "$sleeptime" != "" ]; do
  echo -n "Sleep time in seconds (e.g 0.5 or 1 …) [default: $sleeptime_default]? "
  read sleeptime
  if [ "$sleeptime" = "" ]; then
    sleeptime=$sleeptime_default
  fi
  isnumber $sleeptime || sleeptime=""
done
#echo "sleeptime $sleeptime"
runtime=""
until [ "$runtime" != "" ]; do
  runtime_default=$(echo "scale=4;$sleeptime*$runtimep_default/(100-$runtimep_default)"|bc)
  echo -n "Run time in seconds (e.g 0.5 or 1 …) or percent [default: $runtime_default = $runtimep_default%]? "
  read runtime
  if [[ "$runtime" =~ ^([0-9]*([.][0-9]+)?)%$ ]] ; then
    runtimep_default=${BASH_REMATCH[1]}
  fi
  if [ "$runtime" = "" ]; then
    runtime=$runtime_default
  fi
  isnumber $runtime || runtime=""
done
#echo "runtime $runtime"
# now begin throttle
throttletime=1000000
looptime=$(awk "BEGIN { printf \"%.0f\\n\", ($sleeptime+$runtime)*100 }")
echo "OK. I will throttle PID $pid now and dislay processinfo every $reporttime seconds … "
((reporttime=$reporttime*100))
while true
do
  if [ $throttletime -ge 500 ]; then
    throttletime=0
    PIDSTRING=$(pidstring $pid)
    echo "$PIDSTRING"
  fi
  kill -SIGSTOP $pid || control_c
  sleep $sleeptime
  kill -SIGCONT $pid || control_c
  sleep $runtime
  throttletime=$(($looptime+$throttletime))
done