Go言語でLVMを操作して、論理ボリュームやスナップショットを作成

Linux の 仮想化ソフトウェア Libvirt/KVM を利用する時に、ネットワークの仮想化では Open vSwitch (OVS) があり、ストレージの仮想化は Logical Volume Manager (LVM) を併用すると便利な環境が作れる。これは仮想化環境で利用するために、LVM を Go言語で操作する方法について検討したメモである。


## LVMの概要とスナップショットボリューム

LVMは、外部ストレージ装置の助けを借りずにLinux OS で、ディスクドライブの２重化 や RAIDアレイ を構成する機能を提供して、ドライブなどのハードウェアが故障した時に、保存したデータを失う事態を回避することができる。さらに、論理ボリュームのある時点の状態を固定化したスナップショットを作成することができる。このスナップショットボリュームを更新しても、元になった論理ボリュームのデータが変更されることは無い。スナップショットの元になったオリジンボリュームに変更を加えても、スナップショットボリュームの内容が書き換わることが無い。

そのため、スナップショットは、バックアップ、テスト、仮想マシン環境で、有用な機能だ。そのユースケースを以下に挙げる。

### バックアップ用途
スナップショットの取得実行は、一瞬で完了する。そのため、データベースの更新を一秒程度止めて、スナップショットを取得する。その後、スナップショットのデータを、数十分の時間をかけながら、テープ装置やオブジェクトストレージに転送する。商用データベースなどは、このために更新を一時保留するコマンドを備えている。この手法によって、更新途上の一貫性が欠如したデータをバックアップすることを回避できる。

もしも、スナップショットがなければ、データベースの稼働中に、時間をかけて取得したバックアップは、一貫性が失われ、ハードウェア障害時のデータの復元に利用することができないのだ。サービスの停止が難しいシステムのデータバックアップには、スナップショットの機能は欠かせないのだ。

![backup-flow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/94c60fa5-a152-fa06-34b9-0742d30cb513.png)

### テスト環境
アプリケーション開発において、正しく整合性をもったテストデータは必須だ。それは本番システム同等の一貫性のあるデータを持っていなければならない。そして、プログラムのテスト後に、再び初期状態の戻して、再テストできる必要がある。欠陥を含む開発途上のプログラムを完成させるためには、この様な手間は避けられない。
ストレージに、スナップショットの機能があれば、スナップショットで作成したボリュームに対して、テストを実行して更新をかけることができる。プログラムが更新したデータを検証して、問題が見つかれば、修正後に再びスナップショットを取得して、テストを実行すれば良い。データを生成したり、コピーするなどの手間を大幅に削減することができる。

![develop-diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/cb38148c-2a53-997f-ac9f-b5b66780e2c3.png)


### 仮想マシン環境
クラウドの環境では、OSをメディアからインストールするのではなく、実行可能なOSイメージをテンプレートとして、仮想マシンを起動する。また、カスタマイズしたOSをテンプレート化して保存しておき、再利用することも可能だ。これにより、仮想マシンのOS環境のセットアップの手間を減らし、アプリケーションの実行環境の展開速度を高速化できる。
この時に、テンプレートとなるOSイメージをボリュームコピーしてたのでは、数十分の時間を要する。OSイメージが収められたボリュームから、スナップショットボリュームを作成して、仮想マシンを起動することで、プロビジョニングの時間は数十秒に短縮できる。

![vm-start-with-x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/7e774277-095e-7d31-833a-eaf9fbaa95ad.png)

## Go言語によるLVMパッケージの利用

システム運用の自動化ソフトウェアを開発することで、業務の効率化を目指すSREにとって、Go言語は必要不可なスキルだ。Go言語には、優れたパッケージが提供され、GitHubに登録されたリポジトリと連携できるという魅力がある。そこで、LVMコマンドを、Go言語から操作して、LVMの作業を自動化について検証してみたい。

LVMに関する Go言語のパッケージを https://pkg.go.dev/ で検索すると、20を超えるパッケージがリストされた。そのなかで、利用に適するものを探した。

その結果、lvmコマンドをラップするパッケージ、Python からアクセスするためのライブラリをGo言語から活用するパッケージなどがあった。今回はlvmコマンドをラップして利用するタイプのパッケージ github.com/filcloud/lvm が機能が豊富で、使えそうであった。これは github.com/mesosphere/csilvm をフォークして、LVMに関する部分を切り出したもので、少し機能追加されていた。

しかし、今回使いたいスナップショットが無いことが解ったので、独自に機能を追加したコードを開発することにした。 github.com/filcloud/lvmをフォークして、github.com/takara9/lvm を作成して、コードを追加した。


## LVMパッケージへスナップショットの機能の追加

スナップショットを取得できるようにするために、元のコードに、以下のコマンドを実行する機能を追加する必要がある。

~~~
lvcreate --size メタ情報容量 --snapshot --name LV名 オリジンボリューム
~~~

そこで、以下の関数名の機能を追加した。コードの追加、および、テストコードの追加の具体的変更箇所は、次のURLで確認できる。https://github.com/takara9/lvm/commit/a0db0d296c62a54f2dd889067d3e55b705b428b8

    func (vg *VolumeGroup) CreateLogicalVolumeSnapshot(name string, sizeInBytes uint64, tags []string, orgLv string) (*LogicalVolume, error)


## 機能追加したGoパッケージの利用方法

今回は、スナップショットの機能を使いしただけであるが、周辺も含めて、論理ボリュームの作成、削除、スナップショットの取得について、サンプルコードを提示したい。


### 論理ボリューム追加

以下に、１ギガの論理ボリュームを追加するサンプルコードを提示する。
import の"github.com/takara9/lvm"が、独自に機能追加したパッケージだ。

処理の概要は、`lvm.LookupVolumeGroup(VolGroup)` を使ってボリュームグループの存在をチェックする。次に同じ論理ボリューム名が既に存在しないかを `vg.LookupLogicalVolume(new_lv)`　でチェックする。
そして、`vg.CreateLogicalVolume(new_lv, new_lv_size, nil)`を使って新規の論理ボリュームを作成する。ここで作成したボリュームは、Libvirt関連コマンドで、仮想マシンの起動ディスクとして、OSをインストールすることができる。

~~~
package main

import (
    "fmt"
    "github.com/takara9/lvm"  // 機能追加パッケージ
)

func main() {

     var VolGroup = "vg1"   // 既存のボリュームグループ名
     var new_lv = "lv-test" // 論理ボリューム名
     var new_lv_size uint64
     new_lv_size = 1024 * 1024 * 1024 // 1G

     // ボリュームグループを指定
     vg,err := lvm.LookupVolumeGroup(VolGroup)
     if err != nil {
          panic(err)
     }

     // 論理ボリュームの存在チェック
     _, err = vg.LookupLogicalVolume(new_lv)
     if err == nil {
          fmt.Printf("%v is exist!", new_lv)
          panic("stop")
     }

     // 論理ボリュームの作成
     lv, err := vg.CreateLogicalVolume(new_lv, new_lv_size, nil)
     fmt.Println(lv,err)
}
~~~


### 論理ボリュームの削除

論理ボリュームを操作するための情報を `vg.LookupLogicalVolume(new_lv)` によって取得する。そして、`lv.Remove()` によって削除できる。

~~~
     // 論理ボリュームの存在チェック
     lv, err := vg.LookupLogicalVolume(new_lv)
     if err != nil {
          panic(err)
     }

     // 論理ボリュームの削除
     err = lv.Remove()
     if err != nil {
          panic(err)
     }
~~~


### スナップショットの生成

次のコードは、論理ボリューム lv01 を元にして、スナップショットボリューム lvxx を作成するコードである。元のボリュームとスナップショットボリュームに変更が発生すると、lvxxにデータが保存される。それ以外のデータは、元のlv01をアクセスすることになる。更新データを保存する領域のサイズを`new_lv_size`に保存する。容量が少ないとスナップショットボリュームを維持することができない。
これらのパラメーターを指定して `vg.CreateLogicalVolumeSnapshot(new_lv, new_lv_size, new_lv_tags, orgin_lv)` により、スナップショットボリュームを作成することができる。

~~~
package main
import (
    "fmt"
    "github.com/takara9/lvm"
)
func main() {
     VolGroup := "vg1"   
     orgin_lv := "lv01"  // 元になる論理ボリューム
     new_lv   := "lvxx"  // スナップショットのボリューム名
     // 更新データの保存領域
     var new_lv_size uint64 = 1024 * 1024 * 1024
     new_lv_tags := []string{"snapshot","marmot"}

     // ボリュームグループを指定
     vg,err := lvm.LookupVolumeGroup(VolGroup)
     if err != nil {
          panic(err)
     }

     // スナップショットボリューム名の存在チェック
     _, err = vg.LookupLogicalVolume(new_lv)
     if err == nil {
          fmt.Printf("%v is exist!", new_lv)
          panic("stop")
     }

     // スナップショット　ボリュームの作成
     lv, err := vg.CreateLogicalVolumeSnapshot(new_lv, new_lv_size, new_lv_tags, orgin_lv)
     fmt.Println(lv,err)
}
~~~

元になるボリューム OSをインストールしておき、スナップショットで作成したボリュームをOSディスクに仮想マシンを起動すれば、短時間でOSを起動することができる。

スナップショットを維持するためのデータ領域を自動拡張することができる。その設定方法は、参考資料3を参照いただきたい。



## 実行例

ソースコードを編集して、ビルド、実行するまでのコマンドは以下になる。

~~~
$ mkdir lvm-snapshot-test
$ cd lvm-snapshot-test
$ vi test-snapshot.go  　　　　　 # 上記ソースコードを編集
$ go mod init main　　　　　　　　＃ go.mod を作成
$ go mod tidy                    # パッケージのダウンロード
$ go build test-snapshot.go      # 実行モジュールのビルド
$ sudo ./test-snapshot.go　　　　 # スナップショット作成実行
~~~

実行前は Origin となる lv01 だけが存在している。実行後はスナップショットボリューム lvxx が作られる。

~~~
# 実行前
$ sudo lvs
  LV   VG  Attr       LSize  Pool Origin Data%  Meta%
  lv01 vg1 owi-a-s--- 16.00g

$ sudo ./test-snapshot.go

# 実行後
$ sudo lvs
  LV   VG  Attr       LSize  Pool Origin Data%  Meta%
  lv01 vg1 owi-a-s--- 16.00g
  lvxx vg1 swi-a-s---  1.00g      lv01   0.00
~~~


## 参考資料
1. Linux Logical Volume Manager (LVM) tutorial, https://linuxconfig.org/linux-lvm-logical-volume-manager
2. Red Hat Enterprise Linux 8, 第9章 論理ボリュームのスナップショット, https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/8/html/configuring_and_managing_logical_volumes/snapshot-of-logical-volumes_configuring-and-managing-logical-volumes
3. Automatically Extend LVM Snapshots, https://dustymabe.com/2012/03/04/automatically-extend-lvm-snapshots/