JP7FKFの備忘録

ヒトは,忘れる生き物だから.

STM32 HAL ADCの基礎の基礎

おことわり・前提

  • STM32CubeIDEを使った話をします.
  • あくまで自分用のメモという目的が主.
  • 逐次updateしたり追記したりがあるかもしれません.

本題

  • DMAで連続変換するときの鍵

    • clockはsystem clock devidedなものを入れると楽.Async clockは別途記述が必要.
    • continuous conversion enableにしてDMA continuous request enableにしておくとDMA割り込みが入りまくることに注意.ADCが終わるたびにDMA走る.
      • これを防ぐにはcontinuous conversion disableとして普通にタイマ割り込みでADCを回すのが良さそう.DMA continuous requestはenableのまま.
    • DMAはcircularを選ぶ
    • DMAのmemory はincrementにしておく
    • DMA continuous request enable
    • 上記をやっておかないと連続変換されなかったり,DMAに連続的に変換結果が入らずにハマる.
      • もちろん理解して変更すればいいが,基本上記でいいかなぁという気持ち.
  • code examples

constexpr  adcChannels = 3; //number of using ADC channels
static uint16_t adcData[adcChannels];
if (HAL_ADC_Start_DMA(&hadc,(uint32_t *)adcData, adcChannels) != HAL_OK)
  Error_Handler();

これで逐次clockに応じてADCが動作しDMAで adcData[]に変換結果が転送される. 変換完了後にcallbackしたい場合は下記等が便利.変換完了時にcallbackされる.

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *AdcHandle){
  //do something
}
if(HAL_ADCEx_Calibration_Start(&hadc) != HAL_OK)
    Error_Handler();
  • タイマ割り込みにする場合

    • CubeMXでADCのconfigを下記のようにする.ADCのチャネル,DMA転送など基本的なconfigはできている前提で
      • ADC_Regular_ConversionMode->External Trigger Conversion SourceTimer1 Trigger Out eventに. f:id:jp7fkf:20200603210138p:plain
    • さらにCubeMXでTIM Timer1のconfigをする.プリスケーラやカウンタ値等のタイマ割り込みを発生させるconfigは書けている前提で
      • Trigger Output(TRGO) Parameters -> Trigger Event SelectionUpdate Eventに. f:id:jp7fkf:20200603210133p:plain これでタイマ割り込みでADCがトリガされるはず.
  • ADCのキャネルとデータの入り方 ch1, ch2, ch3, ch4, ch5, ch6, ch7 を有効にしていたとして

constexpr  adcChannels = 8; //number of using ADC channels
static uint16_t adcData[adcChannels];
if (HAL_ADC_Start_DMA(&hadc,(uint32_t *)adcData, adcChannels) != HAL_OK)
  Error_Handler();

このようなコードを書いた場合,変換結果は

ch1: adcData[0];
ch2: adcData[1];
ch3: adcData[2];
ch4: adcData[3];
ch5: adcData[4];
ch6: adcData[5];
ch7: adcData[6];
ch8: adcData[7];

に対応して格納される.

ch1, ch3, ch5, ch7を有効(ch2, ch4, ch6は無効)にしていたとして

constexpr  adcChannels = 4; //number of using ADC channels
static uint16_t adcData[adcChannels];
if (HAL_ADC_Start_DMA(&hadc,(uint32_t *)adcData, adcChannels) != HAL_OK)
  Error_Handler();

このようなコードを書くと

ch1: adcData[0];
ch3: adcData[1];
ch5: adcData[2];
ch7: adcData[3];

仮にここで

constexpr  adcChannels = 3; //number of using ADC channels

としてしまった場合,書くchの変換結果が同一の添字(配列index)で取得できなくなる.
具体的にはリングバッファのようにずれてしまう.
したがってHAL_ADC_Start_DMA()に渡すbufはchに対し十分余裕を持ちlengthは有効化しているadcチャネルと同一値とすることが大変重要である.
有効化しているadcチャネル数よりも少なくても多くてもずれが生じる.
この状態で気づかずに変換結果を取得すると逐次値が変動してハマる.ノイズのよう見見えてしまうことがあるかもしれない.
念の為adcの各chが同一の添字で同じものが取り続けられているかをserial(uart)等を利用し事前に確認しておくことが望ましい.

  • ちなみにプロトタイプはこんな感じになっている: HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length)

zabbix-agentがmemory leakしていたのでZabbix communityにBug patch/reportを出してみた話

// ちなみのこのbugreportを出したのもmergeされたのももうしばらく前(2019年末から2020年頭)の話.

私は仕事でも趣味(?)でも,OSSの監視ツールであるZabbixに大変お世話になっている.
zabbixは主にネットワークやサーバなどのインフラに強みをもった監視ツールで,サーバならzabbix-agent, ネットワーク機器ならSNMPなどを主に用いて,各種ステータスやメトリクスの監視,可視化が行えるツールである.

先日このzabbixから一つのalertが飛んできた.アラート内容は available memory に関するアラート.これが何を意味するかと言うとつまり,空きメモリに余裕がないということである. グラフ波形を見る限り日を追うごとにメモリが少しずつ侵食されており,典型的なメモリリークの症状であった.また身の回りの他の人からも同様の症状があるという報告を得た. top/htop等を見てmemoryを専有するプロセスを探したところ zabbix-agentd と書かれたプロセスのメモリ使用率が著しく高いことがわかった. ここまでで,何らかの原因でzabbix-agentdがmemory leakしていることがわかった.

zabbixがOSSであることを知っていたため,私は原因を突き止め,デバッグし,まず自分の身の回りで生じているメモリリークによる影響を排除しようと考えた. また,まだreportが上がっていないのであればzabbixのOSS communityに対しbug patch/reportをあげ,自分自身が少しでも普段利用しているzabbixに貢献し,また同様のことで困っている方の助けに少しでもなれればうれしい,と考えた. 今回はその過程を少し記録しておく.

Root Causeの特定

top等のコマンドを用いて特定できるのはあくまでどのプロセスがメモリを専有しているか,という情報のみである.
ここからzabbix-agentdがメモリを大きく専有していることまではわかったが,ここからやるべきことは "zabbix-agentdのどこでこのメモリリークが生じているか" を確かめることである.
メモリリークを検出する方法はたくさんの手法があるが,今回の場合実行ファイルがあるだけで利用できるという点にメリットを感じて Valgrind を用いた.
Valgrindの詳細な動作については,私は理解していないが,おそらくはサンドボックス上でバイナリを実行し,その実行時に特にメモリを取り扱うシステムコール等をwrapすることでリーク等を検知できる仕組みが備わっているのではないかと思う.

zabbix-agentdをValgrindを食わせて実行した結果,次のような結果が得られた(一部抜粋).

==27583== HEAP SUMMARY:
==27583==     in use at exit: 109,825 bytes in 188 blocks
==27583==   total heap usage: 44,181 allocs, 43,993 frees, 4,111,671 bytes allocated
==27583==
==27583== 1 bytes in 1 blocks are definitely lost in loss record 1 of 38
==27583==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==27583==    by 0x162F4D: zbx_malloc2 (in /usr/sbin/zabbix_agentd.orig)
==27583==    by 0x168C40: zbx_strncpy_alloc (in /usr/sbin/zabbix_agentd.orig)
==27583==    by 0x168EA4: zbx_strcpy_alloc (in /usr/sbin/zabbix_agentd.orig)
==27583==    by 0x13F420: VFS_FILE_CONTENTS (in /usr/sbin/zabbix_agentd.orig)
==27583==    by 0x12C91C: process (in /usr/sbin/zabbix_agentd.orig)
==27583==    by 0x128C9C: ??? (in /usr/sbin/zabbix_agentd.orig)
==27583==    by 0x12911E: listener_thread (in /usr/sbin/zabbix_agentd.orig)
==27583==    by 0x14FF44: zbx_thread_start (in /usr/sbin/zabbix_agentd.orig)
==27583==    by 0x11A597: MAIN_ZABBIX_ENTRY (in /usr/sbin/zabbix_agentd.orig)
==27583==    by 0x1516A7: daemon_start (in /usr/sbin/zabbix_agentd.orig)
==27583==    by 0x11A9FA: main (in /usr/sbin/zabbix_agentd.orig)
==27583==
==27583== 98,304 bytes in 1 blocks are definitely lost in loss record 38 of 38
==27583==    at 0x4C31D2F: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==27583==    by 0x162FFC: zbx_realloc2 (in /usr/sbin/zabbix_agentd.orig)
==27583==    by 0x168CC0: zbx_strncpy_alloc (in /usr/sbin/zabbix_agentd.orig)
==27583==    by 0x168EA4: zbx_strcpy_alloc (in /usr/sbin/zabbix_agentd.orig)
==27583==    by 0x13F420: VFS_FILE_CONTENTS (in /usr/sbin/zabbix_agentd.orig)
==27583==    by 0x12C91C: process (in /usr/sbin/zabbix_agentd.orig)
==27583==    by 0x128C9C: ??? (in /usr/sbin/zabbix_agentd.orig)
==27583==    by 0x12911E: listener_thread (in /usr/sbin/zabbix_agentd.orig)
==27583==    by 0x14FF44: zbx_thread_start (in /usr/sbin/zabbix_agentd.orig)
==27583==    by 0x11A597: MAIN_ZABBIX_ENTRY (in /usr/sbin/zabbix_agentd.orig)
==27583==    by 0x1516A7: daemon_start (in /usr/sbin/zabbix_agentd.orig)
==27583==    by 0x11A9FA: main (in /usr/sbin/zabbix_agentd.orig)
==27583==
==27583== LEAK SUMMARY:
==27583==    definitely lost: 98,305 bytes in 2 blocks
==27583==    indirectly lost: 0 bytes in 0 blocks
==27583==      possibly lost: 0 bytes in 0 blocks
==27583==    still reachable: 11,520 bytes in 186 blocks
==27583==         suppressed: 0 bytes in 0 blocks
==27583== Reachable blocks (those to which a pointer was found) are not shown.
==27583== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==27583==
==27583== For counts of detected and suppressed errors, rerun with: -v
==27583== Use --track-origins=yes to see where uninitialised values come from
==27583== ERROR SUMMARY: 592818 errors from 11 contexts (suppressed: 0 from 0)

1 bytes in 1 blocks are definitely lost in loss record 1 of 38 などの出力がみられる.
これはこの部分でmemory leakがある(具体的にはメモリへのポインタを失う)ことを示している.
続けて,lostしたと考えられる箇所が記述されている.

上記に示した2件ではどうやら zbx_strncpy_alloc() 内部で利用されているzbx_malloc2(), zbx_realloc2()で生じているように見受けられる.
ここのコードを見てみる.

void    zbx_strncpy_alloc(char **str, size_t *alloc_len, size_t *offset, const char *src, size_t n)
{
        if (NULL == *str)
        {
                *alloc_len = n + 1;
                *offset = 0;
                *str = (char *)zbx_malloc(*str, *alloc_len);
        }
        else if (*offset + n >= *alloc_len)
        {
                while (*offset + n >= *alloc_len)
                        *alloc_len *= 2;
                *str = (char *)zbx_realloc(*str, *alloc_len);
        }

        while (0 != n && '\0' != *src)
        {
                (*str)[(*offset)++] = *src++;
                n--;
        }

        (*str)[*offset] = '\0';
}

ref: zabbix/str.c at 4.4.5 · zabbix/zabbix · GitHub

内部でzbx_malloc()zbx_realloc()がcallされていることがわかる.この定義を見ると

#define zbx_calloc(old, nmemb, size)  zbx_calloc2(__FILE__, __LINE__, old, nmemb, size)
#define zbx_malloc(old, size)   zbx_malloc2(__FILE__, __LINE__, old, size)
#define zbx_realloc(src, size)    zbx_realloc2(__FILE__, __LINE__, src, size)
#define zbx_strdup(old, str)    zbx_strdup2(__FILE__, __LINE__, old, str)

ref:zabbix/common.h at b93f5c4fc09286ec199910c7d450b8a4bf4dd3de · zabbix/zabbix · GitHub

zbx_malloc2(), zbx_realloc2()がcallされる.これらをみてみると.

void  *zbx_malloc2(const char *filename, int line, void *old, size_t size)
{
  int max_attempts;
  void  *ptr = NULL;

  /* old pointer must be NULL */
  if (NULL != old)
  {
    zabbix_log(LOG_LEVEL_CRIT, "[file:%s,line:%d] zbx_malloc: allocating already allocated memory. "
        "Please report this to Zabbix developers.",
        filename, line);
  }

  for (
    max_attempts = 10, size = MAX(size, 1);
    0 < max_attempts && NULL == ptr;
    ptr = malloc(size), max_attempts--
  );

  if (NULL != ptr)
    return ptr;

  zabbix_log(LOG_LEVEL_CRIT, "[file:%s,line:%d] zbx_malloc: out of memory. Requested " ZBX_FS_SIZE_T " bytes.",
      filename, line, (zbx_fs_size_t)size);

  exit(EXIT_FAILURE);
}
void  *zbx_realloc2(const char *filename, int line, void *old, size_t size)
{
  int max_attempts;
  void  *ptr = NULL;

  for (
    max_attempts = 10, size = MAX(size, 1);
    0 < max_attempts && NULL == ptr;
    ptr = realloc(old, size), max_attempts--
  );

  if (NULL != ptr)
    return ptr;

  zabbix_log(LOG_LEVEL_CRIT, "[file:%s,line:%d] zbx_realloc: out of memory. Requested " ZBX_FS_SIZE_T " bytes.",
      filename, line, (zbx_fs_size_t)size);

  exit(EXIT_FAILURE);
}

ref: zabbix/misc.c at b93f5c4fc09286ec199910c7d450b8a4bf4dd3de · zabbix/zabbix · GitHub

このように書かれている.まずこの周辺の処理に問題がある可能性がある.

また,別の観点では,このmalloc()realloc()をcallする際,ポインタとsizeを引数として渡している事がわかる.
このポインタやsizeが不適切に設定されている可能性も考えられる.

結論から述べると,これはcallする側に問題があった.
debugをすすめていくと,Valgrindのリーク元として記載されている関数であるVFS_FILE_CONTENTSのうち,このzbx_readを用いた判定がwhileループを抜けることなく不自然に繰り返されている事に気がついた. 本来であればこのロジックはzbx_read()でファイルディスクリプタからbufferに取り込み,utf8に文字コード変換をしcontentsとして保存するようなロジックであると考えられ,whileを複数回繰り返すことはあれど,zbx_read()からの戻り値が常に返却されることは考えにくい. ここからたどっていき,zbx_read()が返却するnbytesが不適切に返却されている可能性が高いことがわかった.

int zbx_read(int fd, char *buf, size_t count, const char *encoding)
{
  size_t    i, szbyte, nbytes;
  const char  *cr, *lf;
  zbx_offset_t  offset;

  if ((zbx_offset_t)-1 == (offset = zbx_lseek(fd, 0, SEEK_CUR)))
    return -1;

  if (0 >= (nbytes = read(fd, buf, (unsigned int)count)))
    return (int)nbytes;

  find_cr_lf_szbyte(encoding, &cr, &lf, &szbyte);

  for (i = 0; i <= nbytes - szbyte; i += szbyte)
  {
    if (0 == memcmp(&buf[i], lf, szbyte)) /* LF (Unix) */
    {
      i += szbyte;
      break;
    }

    if (0 == memcmp(&buf[i], cr, szbyte)) /* CR (Mac) */
    {
      /* CR+LF (Windows) ? */
      if (i < nbytes - szbyte && 0 == memcmp(&buf[i + szbyte], lf, szbyte))
        i += szbyte;

      i += szbyte;
      break;
    }
  }

  if ((zbx_offset_t)-1 == zbx_lseek(fd, offset + (zbx_offset_t)i, SEEK_SET))
    return -1;

  return (int)i;
}

ref: zabbix/file.c at b93f5c4fc09286ec199910c7d450b8a4bf4dd3de · zabbix/zabbix · GitHub

このどこかで不適切なreturnが存在する可能性がある.
前述の通りwhileループを抜けることなく繰り返していることから,正の値の返却があり,可能性は2つに絞られる. 最後の

return (int)i;

もしくは10行目の

  if (0 >= (nbytes = read(fd, buf, (unsigned int)count)))
    return (int)nbytes;

である.

可能性としてかなり絞られたのでprintf debugしかり,logとして出力してdebugした.

int  zbx_read(int fd, char *buf, size_t count, const char *encoding)
{
  size_t          i, szbyte, nbytes;
  const char      *cr, *lf;
  zbx_offset_t    offset;

  if ((zbx_offset_t)-1 == (offset = zbx_lseek(fd, 0, SEEK_CUR)))
          return -1;

  if (0 >= (nbytes = read(fd, buf, (unsigned int)count)))
  {
          zabbix_log(LOG_LEVEL_CRIT, "zbx_read: read() returned: %d nbytes", (int)nbytes);
          return (int)nbytes;
  }

  find_cr_lf_szbyte(encoding, &cr, &lf, &szbyte);

  for (i = 0; i <= nbytes - szbyte; i += szbyte)
  {
          if (0 == memcmp(&buf[i], lf, szbyte))   /* LF (Unix) */
          {
                  i += szbyte;
                  break;
          }

          if (0 == memcmp(&buf[i], cr, szbyte))   /* CR (Mac) */
          {
                  /* CR+LF (Windows) ? */
                  if (i < nbytes - szbyte && 0 == memcmp(&buf[i + szbyte], lf, szbyte))
                          i += szbyte;

                  i += szbyte;
                  break;
          }
  }

  if ((zbx_offset_t)-1 == zbx_lseek(fd, offset + (zbx_offset_t)i, SEEK_SET))
          return -1;

  zabbix_log(LOG_LEVEL_CRIT, "zbx_read: zbx_read() returned: %d nbytes", i);
  return (int)i;
}

returnの直前にreturnとして返却する値をlogに表示した.
得られた結果はこうだ.

19207:20191202:093521.406 zbx_read: read() returned: -1 nbytes
19207:20191202:093521.406 zbx_read: zbx_read() returned: 66 nbytes

気づくべきなのは1行目.

  if (0 >= (nbytes = read(fd, buf, (unsigned int)count)))
  {
          zabbix_log(LOG_LEVEL_CRIT, "zbx_read: read() returned: %d nbytes", (int)nbytes);
          return (int)nbytes;
  }

というブロックに対して

19207:20191202:093521.406 zbx_read: read() returned: -1 nbytes

というログが出力されている.

意図としてはread()からのreturn値が0以上である場合if内部を実行する.
具体的にはzabbix_log()を実行し(int)nbytesをreturnするようなロジックを書きたいと考えられる.

しかしこのログ出力からは,return値が-1であるにも関わらずログに出力されている(if内部が実行されている).
このことからキャストが不足している可能性があることに気づく. 軽率に

  if (0 >= (int)(nbytes = read(fd, buf, (unsigned int)count)))
  {
          zabbix_log(LOG_LEVEL_CRIT, "zbx_read: read() returned: %d nbytes", (int)nbytes);
          return (int)nbytes;
  }

とintキャストしてコンパイル,実行したところ,このメモリリークが解消された. 原因はnbytessize_tとして宣言されていたことによりread()のreturnが-1である場合でも正の値として判定されているところにあった.
これによりreturnされるべきでない-1というread()からの返り値が正の値としてint castされて返却されてしまいVFS_FILE_CONTENTS()におけるwhileでの異常なループが起きていたと考えられる.

上記の内容から原因はzbx_read内部における不適切な値返却だとわかった.

これらの内容をzabbix communityにbug patchとして報告した.まぁ実際はreviewerが色々訂正してくれたからbug report程度なんだけど. f:id:jp7fkf:20200520224911p:plain

内容を読んだ人はわかると思うが,当初私はValgrindの出力から,zbx_strncpy_alloc() 内部で利用されているzbx_malloc2(), zbx_realloc2()に問題があるのではないかと信じ込み,不適切な報告をしてしまっている. しかし対応していただいたレビューアには,「その部分は問題ないように見える」と優しく教えてもらった. のちに手元で検証したところ,やはり問題はなく,自分のc言語に対する理解力の不足と思い込みがこのミスを招いている.

しかしそれとは別に私は上記のcast不足を提言し,改善したコードの提示をした. そのコードはレビュアーによってより改善(具体的にはsize_t型(unsigned)として宣言されていたnbytesssize_t(signed)として定義するように変更)され,このメモリリークのbugは解消された.

めでたしめでたし.
これによって私の身の回りのメモリリークは落ち着いた.よかった.

また,これをきっかけにVFS_FILE_CONTENTS 内で2箇所memory leakの危険性があることがわかり,ついでになおしてもらった.これに気づいたのはreviewerだったけど,この報告がきっかけになっていたのはうれしい.

P.S. BugfixとしてChangelogに名前載せてくれた.ありがとう!
f:id:jp7fkf:20200520224920p:plain

まとめ

いつもお世話になってるzabbixに1nmくらいは貢献した気分.
OSSへの貢献ってなかなか技術力がない自分には難しいところがあるが,やれる部分でなんとか協力してやっていきたい.
また不適切な報告をしたのは大変申し訳ない事案であったので技術力をもうちょい高めていきたい.
C言語ぜんぜんわかってなくてすまん」といったらありがたいお言葉をいただいた.気が休まる思い.ありがたい. f:id:jp7fkf:20200520224916p:plain:w400

なんにせよリークが治って個人的には助かった.
英語でbugの議論したりするの,ちょっと楽しかった.まだまだだけど.
つい先日zabbix5.0がリリースされて,zabbix-agent2というgoで書かれた新しいagentも登場している.このcで書かれたagentがいつまで使われるのかわからないが,しばらくは使われる気もする.なんにせよ人に使われるものに1nmでも貢献できたことが嬉しい.
そういえばValgrindをつかってこうやってまともにメモリリークを調べたのも初めてのことだったっぽい.意外となんとかなるものだ.

Special Thanks(Reviewer)!

  • Glebs Ivanovskis
  • Andris Mednis
  • Martins Abele

References

Ubuntu18.04にELK Stackを構成する

概要

  • Ubuntu18.04に
  • Elasticsearch(7.7)まわりのdebパッケージを
  • aptレポジトリから引っ張ってきて
  • インストールして
  • Logstash(log insert) -> Elasticsearch(processiog) <-> Kibana(visualize)ができる ところまでを実施します.

基本的に下記を参照します

Elastic Stack Install Battle

Install PGP key

$ wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
OK

add repository to apt source list

$ echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" | sudo tee -a /etc/apt/sources.list.d/elastic-7.x.list
deb https://artifacts.elastic.co/packages/7.x/apt stable main

Install Elasticsearch

$ sudo apt update
$ sudo apt install elasticsearch
$ sudo apt install elasticsearch
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following NEW packages will be installed:
  elasticsearch
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 314 MB of archives.
After this operation, 527 MB of additional disk space will be used.
Get:1 https://artifacts.elastic.co/packages/7.x/apt stable/main amd64 elasticsearch amd64 7.7.0 [314 MB]
Fetched 314 MB in 5min 57s (881 kB/s)
Selecting previously unselected package elasticsearch.
(Reading database ... 116882 files and directories currently installed.)
Preparing to unpack .../elasticsearch_7.7.0_amd64.deb ...
Creating elasticsearch group... OK
Creating elasticsearch user... OK
Unpacking elasticsearch (7.7.0) ...
Setting up elasticsearch (7.7.0) ...
Created elasticsearch keystore in /etc/elasticsearch/elasticsearch.keystore
Processing triggers for ureadahead (0.100.0-21) ...
Processing triggers for systemd (237-3ubuntu10.40) ...

ここまででelasticsearch自体のインストール自体は完了.

  • ヒープサイズのconfig
    JVMで使わせるヒープサイズをチューンする.一般には物理メモリの約半分を割り当てればいいそう. ただし32GBらへんに圧縮オブジェクトポインタのしきい値があるらしく,だいたい26GB以下にしておけば安全そう.
  • ref: Setting the heap size | Elasticsearch Reference [7.7] | Elastic
    今回は物理メモリとして16GB割り当てているので8GBにしておく.
$ sudo cat /etc/elasticsearch/jvm.options | head -30
## JVM configuration

################################################################
## IMPORTANT: JVM heap size
################################################################
##
## You should always set the min and max JVM heap
## size to the same value. For example, to set
## the heap to 4 GB, set:
##
## -Xms4g
## -Xmx4g
##
## See https://www.elastic.co/guide/en/elasticsearch/reference/current/heap-size.html
## for more information
##
################################################################

# Xms represents the initial size of total heap space
# Xmx represents the maximum size of total heap space

-Xms8g
-Xmx8g

################################################################
## Expert settings
################################################################
##
## All settings below this section are considered
## expert settings. Don't tamper with them unless
$ sudo vim /etc/elasticsearch/elasticsearch.yml
# ---------------------------------- Network -----------------------------------
#
# Set the bind address to a specific IP (IPv4 or IPv6):
#
network.host: <bind_ip>
#
# Set a custom port for HTTP:
#
http.port: 9200
#
# For more information, consult the network module documentation.

脳死したければ network.host: "0.0.0.0"とか入れておけばいいと思う.v6なら network.host: "::0"とか?

  • 起動してみる.
    構築したホスト上でcurl "http://localhost:9200/" を打つと実際にelasticsearchにアクセスすることができる.
$ sudo systemctl start elasticsearch.service
$ sudo systemctl enable elasticsearch.service
$ sudo systemctl status elasticsearch.service
● elasticsearch.service - Elasticsearch
   Loaded: loaded (/usr/lib/systemd/system/elasticsearch.service; disabled; vendor preset: enabled)
   Active: active (running) since Sat 2020-05-16 14:33:17 UTC; 1 day 13h ago
     Docs: https://www.elastic.co
 Main PID: 3301 (java)
    Tasks: 83 (limit: 4915)
   CGroup: /system.slice/elasticsearch.service
           ├─3301 /usr/share/elasticsearch/jdk/bin/java -Xshare:auto -Des.networkaddress.cache.ttl=60 -Des.networkadd
           └─3505 /usr/share/elasticsearch/modules/x-pack-ml/platform/linux-x86_64/bin/controller

May 16 14:33:01 elk01 systemd[1]: Starting Elasticsearch...
May 16 14:33:17 elk01 systemd[1]: Started Elasticsearch.

$ curl "http://localhost:9200/"
{
  "name" : "ela01",
  "cluster_name" : "elc01",
  "cluster_uuid" : "xxxxxxxxxxxxxxxxxxxxxx",
  "version" : {
    "number" : "7.7.0",
    "build_flavor" : "default",
    "build_type" : "deb",
    "build_hash" : "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "build_date" : "2020-05-12T02:01:37.602180Z",
    "build_snapshot" : false,
    "lucene_version" : "8.5.1",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}
  • [つぶやき]: javaをinstallしてないのに動いたなーと思ったらelasticsearchのパッケージを入れるとjavaも一緒に入ってくるっぽい
$ /usr/share/elasticsearch/jdk/bin/java --version
openjdk 13.0.2 2020-01-14
OpenJDK Runtime Environment AdoptOpenJDK (build 13.0.2+8)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 13.0.2+8, mixed mode, sharing)

debパッケージをdpkgでいれたりtarballで引っ張ってきて展開したjavaまでついてくるかは知らない. 前はaptでも別にjdk入れないといけなかった気がしたけど,もう忘れてしまった.少なくとも最近のaptからのinstallでは一緒についてくるらしい.
- Elastic Support Matrix | Elasticsearch
と思ったらこれのサポートはあくまでelasticsearchを動かすだけにすぎなかった.elasticsearch本体だけなら動くけどlogstashとかはこのjdk verでは動かない. javaまでのpathが通ってない(and JAVA_HOMEが適切に環境変数に設定されていないと)こんな感じでapt installでerrorを吐く.

$ sudo apt install logstash
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following NEW packages will be installed:
  logstash
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 167 MB of archives.
After this operation, 295 MB of additional disk space will be used.
Get:1 https://artifacts.elastic.co/packages/7.x/apt stable/main amd64 logstash all 1:7.7.0-1 [167 MB]
Fetched 167 MB in 4min 54s (569 kB/s)
Selecting previously unselected package logstash.
(Reading database ... 103575 files and directories currently installed.)
Preparing to unpack .../logstash_1%3a7.7.0-1_all.deb ...
Unpacking logstash (1:7.7.0-1) ...
Setting up logstash (1:7.7.0-1) ...
could not find java; set JAVA_HOME or ensure java is in PATH
chmod: cannot access '/etc/default/logstash': No such file or directory
dpkg: error processing package logstash (--configure):
 installed logstash package post-installation script subprocess returned error exit status 1

なのでやっぱりELK stackを構築するなら
$ sudo apt install openjdk-8-jre
するのが正解.最近のELKコンポーネントだとopenjdk-11でもいいっぽいけどまぁ好きなの使えばいい気がする.ただjdk9以降はaptとかでさくっと入らないっぽく見えるからめんどそう.

Install logstash and Kibana

$ java -version
openjdk version "1.8.0_252"
OpenJDK Runtime Environment (build 1.8.0_252-8u252-b09-1~18.04-b09)
OpenJDK 64-Bit Server VM (build 25.252-b09, mixed mode)

javaが入ってる(PATHが通る)ことを確認して

sudo apt install logstash
sudo apt install kibana

でさくっとはいる.javaへのPATHが通ってないと失敗するので注意.

  • kibanaのconfig
$ sudo vim /etc/kibana/kibana.yml
server.port: 5601
server.host: "<bind_ip>"
elasticsearch.hosts: ["http://localhost:9200"]

<bind_ip>には前述のとおりlistenするipを入れる.

  • logstashのconfig
$ sudo vim /etc/logstash/jvm.options
-Xms8g
-Xmx8g
  • 起動
$ sudo systemctl start logstash
$ sudo systemctl start kibana
$ sudo systemctl enable logstash
$ sudo systemctl enable kibana

$ sudo systemctl status logstash
● logstash.service - logstash
   Loaded: loaded (/etc/systemd/system/logstash.service; disabled; vendor preset: enabled)
   Active: active (running) since Sat 2020-05-16 15:33:30 UTC; 1 day 11h ago
 Main PID: 7282 (java)
    Tasks: 43 (limit: 4915)
   CGroup: /system.slice/logstash.service
           └─7282 /usr/bin/java -Xms4g -Xmx4g -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseC
...(snip)...

$ sudo systemctl status kibana
● kibana.service - Kibana
   Loaded: loaded (/etc/systemd/system/kibana.service; enabled; vendor preset: enabled)
   Active: active (running) since Sat 2020-05-16 14:40:41 UTC; 1 day 12h ago
 Main PID: 4569 (node)
    Tasks: 11 (limit: 4915)
   CGroup: /system.slice/kibana.service
           └─4569 /usr/share/kibana/bin/../node/bin/node /usr/share/kibana/bin/../src/cli -c /etc/kibana/kibana.yml
...(snip)...

ex. syslog insert to elasticsearch via logstash

syslogをlogstash経由でelasticsearchにいれてkibanaでみるところまでをやってみる. まずはsyslog pluginをlogstash-pluginを使って入れる.

$ /usr/share/logstash/bin/logstash-plugin list syslog
logstash-filter-syslog_pri
logstash-input-syslog
$ /usr/share/logstash/bin/logstash-plugin install logstash-input-syslog
ERROR: File /usr/share/logstash/Gemfile does not exist or is not writable, aborting
$ sudo /usr/share/logstash/bin/logstash-plugin install logstash-input-syslog
Validating logstash-input-syslog
Installing logstash-input-syslog
Installation successful

次にlogstashのconfigを作っていく. 適当にsyslogを受ける例を書くとこんな感じ.

$ cat /etc/logstash/conf.d/01-syslog.conf
input {
  syslog {
      port => 10514
      type => "syslog"
  }
}

filter {
} # filter

output {
  if "syslog" in [type] {
    elasticsearch {
      hosts => ["localhost:9200"]
      index => "logstash-syslog"
   }
    # stdout { codec => rubydebug }
  }
}

ポート番号1024以下はpriviledgedなのでroot権限等がないとpermission deniedで弾かれる.
rootで実行するのも一つの手ではあるが,下記などではport forward(redirect)する例が挙げられている.
今回は10514としてそのまま利用する.

configを書いたらlogstash をrestartする.logstashの嫌なところはこのconfigを変えたらrestartする必要があるところ. 前段にkafkaなどのqueueをいれたりするのがこのrestartによるデータ欠損回避手段のうちの一つかもしれない.

$ sudo systemctl restart logstash

さて,ここまででlogstash -> elasticsearch ->kibanaのパイプラインが完成した.
実際にlogを突っ込んで見る.

logger -n localhost -P 10514 -t mytest -p user.notice --rfc3164 "TEST LOG"

これで送られた"はず"である.

ここからはkibanaでみていこう.
kibanaへはconfigした通り,デフォルトでは5601ポートでアクセスする.
kibanaでデータを取り扱う上でまずindex-patternを作る必要がある. Management -> Index Managementでelasticsearch側に先程のsyslogのindexが作成されていることを確認.
これがないとindex patternを作っても見れない.これがない場合elasticsearchに正常にinsertされていない可能性が高いのでまずはそのdebugをしよう.
この画面ではlogstashのconfigでindexとして指定したlogstash-syslogが見えている. f:id:jp7fkf:20200518215937p:plain elasticsearchにindexがあることが確認できたら,今度はkibana側のindex patternsを見る デフォルトだと何も登録されていないので,Create Index Patternを押す f:id:jp7fkf:20200518220014p:plain するとelasticsearchのindexが候補で見えてくる. f:id:jp7fkf:20200518220020p:plain index patternの定義をするので,textboxにindex patternの名前(ワイルドカード可)を入れて1つに絞る.
ここではlogstash-syslogそのまま入れると一つに絞ることができるのでNext Stepが押せるようになる. f:id:jp7fkf:20200518220026p:plain 次にtime filter firldを指定する.ここではlogstashでつける@timestampをそのまま使う. これでCreate Index Patternを押す. f:id:jp7fkf:20200518220046p:plain するとIndexがつくられて f:id:jp7fkf:20200518220031p:plain Index Patternのできあがり. f:id:jp7fkf:20200518220035p:plain discover画面に行って確認すると,無事先ほどのloggerコマンドで転送したログが見える. f:id:jp7fkf:20200518220040p:plain

ここまでで一通りELKスタックを用いてsyslogをkibanaで見えるようになった.

 まとめ

  • Ubuntu18.04にElastic Stack (ELK, Logstash, Elasticsearch, Kibana)をaptパッケージを用いて構築した
  • Logstash(log insert) -> Elasticsearch(processiog) <-> Kibana(visualize)のパイプラインにsyslogを投入しkibanaで観測した.

logstashにはこの他にも色々なpluginがあり,様々なデータをELKスタックを用いてデータ分析/可視化することができる . また,logstash以外にもelasticsearchへの出力機能を持ったコンポーネント(ex. fluentd)などが数多くあるので,それらとうまく組み合わせて柔軟な分析基盤の構成ができそうである.

個人的にはDHCPサーバのリースログからクライアントのMACアドレスのベンダコード(OUI)をもとにベンダをを可視化したり,xflow(sflow/netflow)を食わせてgeo-ipの緯度経度データとIPアドレスを突き合わせてどの国/regionとの通信が多いのかを可視化したりすることもやってみたが,なかなかおもしろい. flowデータをもとにすれば通信をポート番号ベースで可視化したりすることができ,特定ポート宛などのDDoSの検知や内部から外部への不正な大量トラフィック(マルウェア感染端末からのDNS Amp.など)の検出もできそうである.可視化で得られた異常なデータをクリックすることでそのデータの詳細なレコードもkibanaならすぐに見ることができてユーザ体験はとてもよい.
可視化することは人間に直感的にあらゆる物事を短時間で理解させることを手助けする大きな価値のある手法であると思うので,是非このような可視化/分析ツール活用していきたいものである.

Synology NAS(DS918+)とESXi 6.5をiSCSIする

わりと簡単にできたのでメモっておく.

やりたいこと

  • SynologyのNAS(DS918+)とESXi 6.5の間をiSCSIでつなぐ.
  • Synology NASがtarget, ESXiがinitiator
  • CHAP認証してみる.

やりかた

やる

まずはSynology NAS側でiSCSI targetを設定する. f:id:jp7fkf:20200307232316p:plain iSCSI managerからtargetを新規作成する.CHAPしたいときはCHAP有効化して認証情報をいれておく.別に後から有効化することもできる(一時的なstorage断は伴うが). f:id:jp7fkf:20200307232324p:plain LUNが未作成の場合/割り当てるLUNがない場合はここでLUNを作成する.すでに作成済みの場合はそれを割り当てる事もできる.適当にsizeやらを設定して作成する. f:id:jp7fkf:20200307232329p:plain LUNを作成したいボリュームや容量を決める.
スペース割り当てと書いてある部分はthick/thin provisioningが選べるはず.
thick provisioningはLUN作成時に割り当て容量のすべてを実際のディスクに割り当てをするが,thinの場合は必要になったらその都度割り当てするようなイメージ. thinの場合は利用した分だけ割り当てられるのでストレージの利用効率がいいが,その特性上必要になったら都度割り当て, zeronize等の処理が入るので若干のパフォーマンス低下が存在することがあるというのが懸念点かと思う. それに対しthin provisioningは初めに割り当て容量の全てをzeronizeする.この処理が入るのは割り当て時のみであるため利用中の割り当て/zeronize等によるパフォーマンス低下は気にしなくていいと考えられる. f:id:jp7fkf:20200307232334p:plain 見直して良ければ適用してLUN, targetの作成を終える.
これでSynology NAS側のsettingは終了.IQNとSynology NASのIPがのちに必要になる. ここからはinitiatorとなるESXi側で設定を行なっていく. ストレージ > アダプタタブを選択し,今回はソフトウェアiSCSIを利用するのでそいつを選択. f:id:jp7fkf:20200307232339p:plain iSCSIを有効にし,CHAP認証を利用する場合はCHAPを有効化して認証情報を入れる.
ポートバインディングではiSCSIを利用するVMkernel NICを選ぶ.これはmanagement VMkernel NICとは別にしておくのがよさそう.iSCSI用にVMkernel NICをあらかじめ割り当てておくとよい.
固定ターゲットの部分にSynology NASのIQNとIP Addressを入れる.動的ターゲットとしてIP Addressを入れるだけでも接続と思われるが,より限定的/明示的に設定したいので今回は固定ターゲットとして設定する.
入力を終えたら設定を保存して適用する. f:id:jp7fkf:20200307232344p:plain 設定が正常に行われるとデバイスiSCSI接続されたNASが見える.
// degradeしているのが見えているが,これはiSCSIのuplinkが冗長でないためのよう.複数のuplinkをvswitchにいれてやれば解決すると思う.
もちろんSynology NAS側でも接続されていることが確認できる(iSCSI managerで確認可能).
ここまでくればあとはこのiSCSI targetに対してVMFSを設定して利用するだけだ.
f:id:jp7fkf:20200307232349p:plain ストレージ > データストア > 新しいデータストア からデータストア設定wisardに入る.
新規にVMFSを作成する.あとはぽちぽち進んで,できあがる. f:id:jp7fkf:20200307232422p:plain データストア もちゃんと見えている.めでたし

気になること.

References

ubuntuでstrongswanしてlogを設定するとapparmorに殴られた

ubuntuの鎧が硬かったのでメモっておく.charonはデフォでsyslogにlogを吐いてくるのでややウザく,/var/log/charon/logにログ出力しようとしたのが事の発端.
例えばこんな風にして軽率に/var/log配下にlogを書こうとする

jp7fkf@lab1:~$ cat /etc/strongswan.d/charon-logging.conf
charon {
    filelog {
        default = 1
        /var/log/charon.log {
            path = /var/log/charon.log
            time_format = %b %e %T
            ike_name = yes
            append = no
            default = 1
            flush_line = yes
            time_add_ms = yes
            net = 2
            tls = 2
            knl = 2
        }
    }
}

しかしlogは/var/log/charon.logには出てこない. syslogを覗いてみるとこんなmessageが.

Feb 27 22:17:48 lab1 kernel: [ 8212.229038] audit: type=1400 audit(1582820000.905:38): apparmor="DENIED" operation="mknod" profile="/usr/lib/ipsec/charon" name="/var/log/charon.log" pid=16202 comm="charon" requested_mask="c" denied_mask="c" fsuid=0 ouid=0

apparmor="DENIED"
と言われているのでapparmorで/var/log/charon.logへの書き込みが弾かれてるっぽい.

適切に権限をあげればいい気がする.
apparmorに/var/log/charon.log*への書き込み権限を加える.
/etc/apparmor.d/配下でcharonのapparmor settingを見ていじる.

jp7fkf@lab1:~$ sudo cat /etc/apparmor.d/usr.lib.ipsec.charon
# ------------------------------------------------------------------
#
#   Copyright (C) 2016 Canonical Ltd.
#
#   This program is free software; you can redistribute it and/or
#   modify it under the terms of version 2 of the GNU General Public
#   License published by the Free Software Foundation.
#
#   Author: Jonathan Davies <jonathan.davies@canonical.com>
#           Ryan Harper <ryan.harper@canonical.com>
#
# ------------------------------------------------------------------

#include <tunables/global>

/usr/lib/ipsec/charon flags=(attach_disconnected) {
  #include <abstractions/base>
  #include <abstractions/nameservice>
  #include <abstractions/authentication>
  #include <abstractions/openssl>
  #include <abstractions/p11-kit>

  capability ipc_lock,
  capability net_admin,
  capability net_raw,

  # allow priv dropping (LP: #1333655)
  capability chown,
  capability setgid,
  capability setuid,

  # libcharon-extra-plugins: xauth-pam
  capability audit_write,

  # libstrongswan-standard-plugins: agent
  capability dac_override,

  capability net_admin,
  capability net_raw,

  network,
  network raw,

  /bin/dash                 rmPUx,

  # libchron-extra-plugins: kernel-libipsec
  /dev/net/tun              rw,

  /etc/ipsec.conf           r,
  /etc/ipsec.secrets        r,
  /etc/ipsec.*.secrets      r,
  /etc/ipsec.d/             r,
  /etc/ipsec.d/**           r,
  /etc/ipsec.d/crls/*       rw,
  /etc/opensc/opensc.conf   r,
  /etc/strongswan.conf      r,
  /etc/strongswan.d/        r,
  /etc/strongswan.d/**      r,
  /etc/tnc_config           r,

  /proc/sys/net/core/xfrm_acq_expires   w,

  /run/charon.*             rw,
  /run/pcscd/pcscd.comm     rw,

  /usr/lib/ipsec/charon     rmix,
  /usr/lib/ipsec/imcvs/     r,
  /usr/lib/ipsec/imcvs/**   rm,

  /usr/lib/*/opensc-pkcs11.so rm,

  /var/lib/strongswan/*     r,

  /var/log/charon.log*       rw, # ここに/var/log/charon.log*のrw権限をつける.

  # for using the ha plugin (LP: #1773956)
  @{PROC}/@{pid}/net/ipt_CLUSTERIP/ r,
  @{PROC}/@{pid}/net/ipt_CLUSTERIP/* rw,

  # Site-specific additions and overrides. See local/README for details.
  #include <local/usr.lib.ipsec.charon>
}

あとはapparmorをreloadさせる.
sudo systemctl reload apparmor.service

するとlogがちゃんと出てくる.

jp7fkf@lab1:~$ cat /var/log/charon.log
Feb 27 22:22:44.144 00[DMN] Starting IKE charon daemon (strongSwan 5.6.2, Linux 5.0.0-1031-gcp, x86_64)
Feb 27 22:22:44.454 00[KNL] known interfaces and IP addresses:
Feb 27 22:22:44.454 00[KNL]   lo
Feb 27 22:22:44.454 00[KNL]     127.0.0.1
Feb 27 22:22:44.454 00[KNL]     ::1
(omit...)

めでたし.

systemdにpython scriptをservice登録してdaemon化する

もう十分internetに知見が転がっていると思うが,pythonをdarmon化したくなったので自分のためにもメモっておく. 適当にservice fileを書いてやればいいだけ.もはやpythonだからとか関係ない. ただshebangは書いておかないとダメかも.permission的にはもちろん実行権限をつけておく.

ex:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

(omit...)
  • service fileを書く
% cat /etc/systemd/system/testscript_py.service
[Unit]
Description = testscript_py

[Service]
ExecStart = /home/jp7fkf/testscript_py/run.py
Restart = always
Type = simple

[Install]
WantedBy = multi-user.target
  • daemon-reloadでservice fileを読み込む
% sudo systemctl daemon-reload
  • 読まれた.適宜 sudo systemctl enable testscript_py やらをすればいい
[jp7fkf@lab1 18:11:52] ~
% systemctl status testscript_py
● testscript_py.service - testscript_py
   Loaded: loaded (/etc/systemd/system/testscript_py.service; enabled; vendor preset: enabled)
   Active: active (running) since Wed 2020-03-04 00:47:02 JST; 17h ago
 Main PID: 1857 (python3)
    Tasks: 12 (limit: 4434)
   CGroup: /system.slice/testscript_py.service
           └─1857 /home/jp7fkf/.pyenv/versions/3.8.1/bin/python3 /home/jp7fkf/testscript_py/run.py

OVAをqcow2にconvertする

OVAイメージをqcow2にconvertして利用したくなったので調べてみた. VMを構成するためのファイルというのは,いくつかの種類がある. よく聞くのがova, ovf, qcow2などではないだろうか. 今回はこれらのファイルについての意味合いの理解を深め,仮想イメージを変換することを試してみる.

OVAファイルとは何者か

OVAとは"Open Virtual Appliance"のアクロニムである. これはOVF,仮想ディスクイメージ,その他付随するファイルをtarでarchiveしたものだと思えばよいようだ.

ではOVFとは何か

OVFとは"Open Virtualization Format"のアクロニム.

Open Virtualization Format(OVFとOVA)を参照すると,OVFパッケージに内包されるファイルには下記のような種類があるようだ.

  • 記述子: 記述子ファイルにより,その仮想マシンの仮想ハードウェアが定義される.このファイルには仮想ディスク,サービス,およびゲストオペレーティングシステムに関する記述や,ライセンス契約書(EULA),アプライアンス内の仮想マシンの起動および停止手順,サービスのインストール手順などの情報が含まれる.記述子ファイルの拡張子は,.ovf である.
  • マニフェスト: パッケージに含まれる各ファイルのSHA-1ダイジェスト値で,パッケージの破損を検出する.マニフェストファイルの拡張子は,.mf である.
  • 署名: パッケージに含まれるX.509証明書の公開キーで署名されたマニフェストのダイジェスト値で,パッケージの所有者の検証に使用される.署名ファイルの拡張子は,.cert である.
  • 仮想ディスク: OVFは,ディスクイメージの形式についての仕様ではない.OVFパッケージには仮想ディスクを構成するファイルを含むが,その形式は仮想ディスクをエクスポートした仮想化製品により異なる.XenServerで作成するOVFパッケージでは,Dynamic VHD形式のディスクイメージが使用される.VMware製品やVirtual BoxのOVFパッケージでは,ストリーム最適化のVMDK形式が使用される.

このうち,記述子と呼ばれるファイルがOVFファイルを指す. このファイルがどういう役割を持っているかというと,仮想マシンのconfiguration(CPU, Memory, NIC, GuestOSの種類, etc...),ストレージ,ネットワーク構成,その他メタデータ等がxmlで記述されている. OVFファイルがあれば,あるVM(群)の構成を構築することが可能ということである.ただしこのOVFにはディスクイメージは含まないため,その他の手段でディスクイメージを提供する必要がある.具体的にはvmdk等が挙げられる.

OVFパッケージにはこの記述子(ovf)とイメージ(vmdk, etc...)のほか,マニフェストファイル(.mf)と署名ファイル(.cert)が含まれることが多いようである. マニフェストファイルはovaに含まれる各ファイルのsha-1ダイジェストが格納されており,ファイルの破損を検知する目的で利用されるようだ. また,署名ファイルにはマニフェストファイルをX.509証明書の公開キーで署名したダイジェスト値が記載されており,所有者(distributor)の検証が行えるようになっているようだ.

OVFファイルとOVFパッケージは似て非なるもので,OVFパッケージにはOVFファイルが常に含まれるが,場合によっては上記のマニフェスト,署名,仮想ディスクが含まれ,これをtarでarchiveしたものがOVAと呼ばれるファイルとなる. なので,tar -xf hoge.ova すると上記の4つのファイル群が得られることになる.

vmdkをqcow2に変換する

ovaがtarアーカイブであることがわかったので,tar -xf <.ova> で展開すると,仮想ディスクイメージが得られるはずである. 仮想ディスクイメージの種類にはいくつか存在するが,メジャーなものはqemu-img コマンドを用いて変換することができるようだ.

qemu-imgコマンドはqemu-utilsに内包されているようなので,installされていない場合は sudo apt install qemu-utils 等で入手できそうである.

qemu-imgを用いた変換の容量は下記の通りである.

qemu-img convert [-f format] [-O output_format] <input_image_file> <output_image_file>

これで相互にイメージが変換できるはずだ.

実際にやってみる

vyosのOVAイメージ vyos-1.1.7-amd64-signed.ova を例に上記の変換までを実施してみる.

[jp7fkf@lab1]$ ls
vyos-1.1.7-amd64-signed.ova
[jp7fkf@lab1]$
[jp7fkf@lab1]$ tar -xvf vyos-1.1.7-amd64-signed.ova
VyOS-1.1.7-signed.ovf
VyOS-1.1.7-signed.mf
VyOS-1.1.7-signed.cert
VyOS-1.1.7-signed-disk1.vmdk
[jp7fkf@lab1]$ ls
vyos-1.1.7-amd64-signed.ova  VyOS-1.1.7-signed-disk1.vmdk  VyOS-1.1.7-signed.ovf
VyOS-1.1.7-signed.cert       VyOS-1.1.7-signed.mf

tar archiveを展開するとovf, vmdk, mf, certの4つのファイルが出てくる.

マニフェストファイルを見てみよう

[jp7fkf@lab1]$ cat VyOS-1.1.7-signed.mf
SHA1(VyOS-1.1.7-signed.ovf)= f075a5ea7058d16734c82fbe0097abebacc769ce
SHA1(VyOS-1.1.7-signed-disk1.vmdk)= a9e83ab5c700a920d657d6d1b52f8dda3e160efc

想定通り,OVFとvmdkについてのsha-1 digestが記載されている.certはmfから生成されるので,ここには載っていない.

certを見てみると,

[jp7fkf@lab1]$ cat VyOS-1.1.7-signed.cert
SHA1(VyOS-1.1.7-signed.mf)= c76c44df048078b1528a7e0174ab98d2add165c4c91c8d25b58c4561c1368b5c2ccf523e1f614b40dde75a99323ccce3c3a836dae7eabbb5b22964d517c34d2eefbabd70878437aee9694113ca55469e50625b3c7c3efeee93b53ffb036f2ca8aaa8f0d7653cdcb0b38a7063a38769f5ab1c132bedf96b10b2cb11670c5a5b5ad01097f8317fa0a6e9b8882eea36b87bfa3c1efa4adaf7866fba72a03a4651d060f079a4163965a89accb507a671fb228c70e9fb6d1d4e1d8b1a9087a4d80ca73b9b34687624847d6dfd609a917932d088c5f3fbbea9ab49f0b2499fb0f3ea428cc3500513f67159acb03e1326bbcb2b678ee296f0a9ba0a5f9365e198aadb54da0c28678610c2a11ec6126f01cb1985bd44d90293be99bb37c55e41f1483c68c6f2e84604e9e3637af6cb963007616adf9960515e4ef79becbac152c153cc587cb6bcdd2ef580561504d2f8fc7122f5c68072ea8dcd4cba434db4cb53cbbc01130edea3c9a73e114c7ff95709f4a5087e6b026bac3c0ce6b0ebae28c7a15d9767ed3b24b702c1586a70c32bf5eb62aa3ec9c18bea2e43e32b36b023bf592166f10f8079d155eac41010bca46b954c9c9741c934af3fcb4dda687e53fa813d271bcac3ad86117b3238ed512d729edb2fe1ec224bc0a71b872b5ed7d485efb95d374057daddfdb8365593b7cf182e698c88656900dd67e3c14a73dd225786b277
-----BEGIN CERTIFICATE-----
MIIGADCCBOigAwIBAgIQIgpv5xjEXl10TvXTz+syBTANBgkqhkiG9w0BAQsFADB1
MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjEpMCcGA1UECxMg
U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIzAhBgNVBAMTGlN0YXJ0
Q29tIENsYXNzIDMgT2JqZWN0IENBMB4XDTE2MDMwODA1MTIzNVoXDTE5MDMwODA1
MTIzNVowYTELMAkGA1UEBhMCRVMxDzANBgNVBAgMBk1hZHJpZDERMA8GA1UEBwwI
QWxjb3Jjb24xFjAUBgNVBAoMDVNlbnRyaXVtIFMuTC4xFjAUBgNVBAMMDVNlbnRy
aXVtIFMuTC4wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDfWjvOnc3m
FWnFdKphQxS1IicZVN0YOoWrDCKbl89hLQD5w76JjWfmLm9yceIjgKIhHvmbHNWH
FpXhTpzLw12YvfrUzjhFTePNDrdHPLbXSaV5F2xkArPLrASFxOVwXJ3kHJPjfs1U
NvG6FEcwfUWC+t2Fzsdto9nI5U44qtU7d9C60Mprx3qnpej4mzgQsc61jCPatARN
cjA/xxC51zxlB4mX+599SZHewX8HyyZtXYPCcq47LzeQqqhqub0LUFKpe47sjzv/
6qzTpz62O7OsKRp1zecCHA0pwvtuFWydQMksvTqSb6l/vXZ1Yf45bvuEpJjePS1N
aMXGa5cpvzSY67YKS3XnaNmnCY2d3T3d0X624CZ+PB7LcSGbcaOH9pMglsASCEBy
Hd0RuTcjWUQaxU5zqcl3H+HmP92sMjum5g6irX47oTi3CvJnK7TdFahNJdyepejo
cVfaVGXf2BnHD6veupjMGBCpecYAvRDBnqrctYBKwNt39+PdA9Cnkn4n9Vx+GFX2
xdP6Wnw+ot9m2JbBeBr4A+BXqb4MXd9eCM2mRBduNliAdN0QrLtJLVPL/1NPUd5D
3XgezSwArkHZduxg2eqdaZd1l0Tn2dNyXOxl7wasSjyENBJR9ovlhDas0eA8Ll5V
nsQc0mV6s+QCzwCoW3lQc8G9hN27sLc9gwIDAQABo4IBnjCCAZowDgYDVR0PAQH/
BAQDAgeAMB8GA1UdJQQYMBYGCCsGAQUFBwMDBgorBgEEAYI3PQEBMAkGA1UdEwQC
MAAwHQYDVR0OBBYEFOfLPzVM5pWho/73a/g9s8ZaPvksMB8GA1UdIwQYMBaAFGZ6
ns2cc4ZqaaCu+oy7GI8I7NUEMG0GCCsGAQUFBwEBBGEwXzAkBggrBgEFBQcwAYYY
aHR0cDovL29jc3Auc3RhcnRzc2wuY29tMDcGCCsGAQUFBzAChitodHRwOi8vYWlh
LnN0YXJ0c3NsLmNvbS9jZXJ0cy9zY2EuY29kZTMuY3J0MDYGA1UdHwQvMC0wK6Ap
oCeGJWh0dHA6Ly9jcmwuc3RhcnRzc2wuY29tL3NjYS1jb2RlMy5jcmwwIwYDVR0S
BBwwGoYYaHR0cDovL3d3dy5zdGFydHNzbC5jb20vMFAGA1UdIARJMEcwCAYGZ4EM
AQQBMDsGCysGAQQBgbU3AQIEMCwwKgYIKwYBBQUHAgEWHmh0dHA6Ly93d3cuc3Rh
cnRzc2wuY29tL3BvbGljeTANBgkqhkiG9w0BAQsFAAOCAQEAuyMGjwa/FgoZzEgL
5w78V5y9oUDrEN1rRgglcMc80Tvcv5Nv3JrS1LoUw8GXVMHkcxl+g86atWUNBXuD
5HZf9xRLBknOw5L6szOm0Wdptdcc42Iu27tUCYY2Lf8e1Qo6JzUXP/Z2q+vZEqDh
l/moBXxIss26tUsU4JzZe9mpgdZt3lxeD/NT13rWhcDVvuRVlUtysT4LBGm0j+5q
K8sMkYSDwoL8lj8t4gnhq9C3Sz6lf3kETJpNfZIBvZuwqcWtGrK3gFqojTljBfzu
pK+M/HqsgMkFVWOaY/x/SrcWPRX7b/hZkbcpXBkkTDwDegpwtlWrL8NuM9OfFoMn
/5oB2w==
-----END CERTIFICATE-----

mfのsha-1 digestと署名が記載があることがわかる.

続いてOVFをはじめの20行についてのみ見てみると,

[jp7fkf@lab1]$
[jp7fkf@lab1]$ cat VyOS-1.1.7-signed.ovf  | head -n 20
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated by VMware VirtualCenter Server, User: FLEXIMOVIL.LOCAL\syncer, UTC time: 2016-04-10T19:20:43.849526Z-->
<Envelope vmw:buildId="build-3339084" xmlns="http://schemas.dmtf.org/ovf/envelope/1" xmlns:cim="http://schemas.dmtf.org/wbem/wscim/1/common" xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1" xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData" xmlns:vmw="http://www.vmware.com/schema/ovf" xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <References>
    <File ovf:href="VyOS-1.1.7-signed-disk1.vmdk" ovf:id="file1" ovf:size="245504000"/>
  </References>
  <DiskSection>
    <Info>Virtual disk information</Info>
    <Disk ovf:capacity="4" ovf:capacityAllocationUnits="byte * 2^30" ovf:diskId="vmdisk1" ovf:fileRef="file1" ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized" ovf:populatedSize="254869504"/>
  </DiskSection>
  <NetworkSection>
    <Info>The list of logical networks</Info>
    <Network ovf:name="management.gs.es">
      <Description>The management.gs.es network</Description>
    </Network>
  </NetworkSection>
  <vmw:IpAssignmentSection ovf:required="false" vmw:protocols="IPv4" vmw:schemes="dhcp">
    <Info>Supported IP assignment schemes</Info>
  </vmw:IpAssignmentSection>
  <VirtualSystem ovf:id="VyOS-1.1.7">
[jp7fkf@lab1]$

xml形式であることがわかる.細かく見ていくと,Disk SectionやNetwork Sectionなど,VMの構成に関する情報が記載されていることが見えてくる.

ではvmdkをqcow2に変換してみよう

[jp7fkf@lab1]$ qemu-img convert -f vmdk -O qcow2 VyOS-1.1.7-signed-disk1.vmdk VyOS-1.1.7-signed-disk1.qcow2
[jp7fkf@lab1]$ ls
vyos-1.1.7-amd64-signed.ova  VyOS-1.1.7-signed-disk1.qcow2  VyOS-1.1.7-signed.mf
VyOS-1.1.7-signed.cert       VyOS-1.1.7-signed-disk1.vmdk   VyOS-1.1.7-signed.ovf
[jp7fkf@lab1]$ file vyos-1.1.7-amd64-signed.ova
vyos-1.1.7-amd64-signed.ova: POSIX tar archive
[jp7fkf@lab1]$ file VyOS-1.1.7-signed-disk1.qcow2
VyOS-1.1.7-signed-disk1.qcow2: QEMU QCOW Image (v3), 4294967296 bytes
[jp7fkf@lab1]$

想定通り,qcow2に変換できていそうだということがわかる.

まとめ

  • OVAファイルが何者か,また内包されるOVF, 仮想イメージ,マニフェスト,署名についての意味合い,実態についてまとめた.
  • 仮想ディスクイメージファイルの相互の変換をqemu-imgコマンドを利用して行うことができることを述べた.
  • 実際にvyosのOVAファイルを用いてコマンドを実行し,変換が行われていることを確認した.

References