Raspberry PiでOpenStackクラスター ストレージ拡張編

■ はじめに

こんにちは、SREの平です。前回の 『Raspberry PiでOpenStackクラスターを作ってみよう』 ( https://developer.so-tech.co.jp/entry/2022/08/23/113000 ) の続編となります。

32GBのSDカードを使っておうちクラウドを作成しているので、インスタンス(以下VM)のストレージボリュームもこのSDから切り出して使いました。 しかしこの方法の問題として、容量が小さく速度も遅いのと、VMと物理サーバーが紐づいているので、VMを他の物理サーバーにマイグレーションするにはスナップショットを取ったりと面倒です。

対策は簡単で、ボリュームを仮想化すればいいだけなのですが、ちょうど私の自宅にはSynologyのNASがありましたので、これをNFS共有サーバーとして使い、VMのボリュームをSDカードからNFSに変更するというのを、1つ目にやってみたいと思います。

2つ目に、このNFS共有のボリュームをbootableで作成してVMにアタッチするのをterraformで作成し、このVMでS3互換のMinIOをdockerで立ち上げて AWS CLI でバケットの作成や操作をしてみます。

最後に、terraformのバックエンドをさきほど作成したVMに向けて、構成管理ファイルを永続化するサンプルを実行して、今回は終わりにします。

■ この資料におけるIPアドレス

最初にこの資料で使うIPをまとめておきます。

項目 内容
10.234.15.11 Openstack の Controller
10.234.15.12~1x Openstack の Compute
10.234.15.250 Synology NAS
192.168.4.33 MinIOをインストールするVM

■ Synologyでボリュームを仮想化する

  • SynologyのNASで、openstack用のvolume2を作成します。

    自宅の共有ディレクトリ用に元々Volumeを1つ切っていました。 今回実験用に別のVolume(Volume2)を切ります。 また今後別の目的でVolume3も使う予定ですので、切り直しされる方は小さくてもいいのでVolumeを3つ用意しておくと便利かもしれません。なおQNAPやフリーのNASソフトでもやることはほとんど一緒なのでその場合は適宜読み替えていただけますでしょうか。

    メインメニュー > ストレージマネージャ > ストレージ > ボリュームの作成

    項目 内容
    ストレージプール 空きがあるプールをプルダウンで選択する
    割り当てられたサイズを修正 1000GB など適宜
    ボリュームの説明 for_openstack など適宜

    これで『ボリューム2』が作成されました。

  • 共有フォルダを作成します。

    コンパネ > 共有フォルダ > 『作成』を押す

    項目 内容
    名前 c-volumes
    説明 for_cinder-volume
    場所 ボリューム2 など上記で作成したボリュームを選択する
    マイネットワークでこの共有フォルダを非表示にする ■(チェックする)
    無許可のユーザーにサブフォルダとファイルを見せない □(チェックしない)
    ごみ箱を有効にする □(チェックしない)
    この共有フォルダを暗号化する □(チェックしない)
    ローカルユーザー(プルダウンで選択する)
    guest ■(読み取り/書き込みにチェックする)
    ローカルグループ(プルダウンで選択する)
    administrators ■(読み取り/書き込みにチェックする)
    users ■(読み取り/書き込みにチェックする)
  • 作成した共有フォルダにNFSの権限を付与します。

    作成したフォルダ > 『編集』を押す 『NFS権限』タブを押し、『作成』ボタンを押す。

    項目 内容
    ホスト名またはIP *
    特権 読み取り/書き込み
    squash 全ユーザーをguestにマップ
    セキュリティ sys
    非同期を有効にする ■(チェックする)
    非優先ポートからの接続を許可する(1024以上のポート) □(チェックしない)
    マウントしたサブフォルダへのアクセスを許可する □(チェックしない)

    これでマウントパス『synas01:/volume2/c-volumes』のNFS共有が作成されました。

■ Openstackでcinderの導入

Openstackにcinderをインストールします。不明な点は公式マニュアルも参照しながら進めるといいと思います。

cf. cinder-volume https://docs.openstack.org/cinder/zed/install/index-ubuntu.html

cf. NFSドライバー https://docs.openstack.org/cinder/zed/configuration/block-storage/drivers/nfs-volume-driver.html

================ ここからコントローラー ================

まずControllerにSSHし、cinderユーザー等を作成します。

※ここでのパスワードは cinder123 にしました。

sudo -i
mysql -u root -p
CREATE DATABASE cinder;
GRANT ALL PRIVILEGES ON cinder.* TO 'cinder'@'localhost' IDENTIFIED BY 'cinder123';
GRANT ALL PRIVILEGES ON cinder.* TO 'cinder'@'%' IDENTIFIED BY 'cinder123';
exit
exit

admin環境変数を読み込みます。

. admin-openrc

cinderユーザーの作成等をします。

※ここでのパスワードは cinder123 にしました。

openstack user create --domain default --password-prompt cinder
openstack role add --project service --user cinder admin
openstack service create --name cinderv3 --description "OpenStack Block Storage" volumev3

APIエンドポイントを作成します。

openstack endpoint create --region RegionOne volumev3 public http://pios01:8776/v3/%\(project_id\)s
openstack endpoint create --region RegionOne volumev3 internal http://pios01:8776/v3/%\(project_id\)s
openstack endpoint create --region RegionOne volumev3 admin http://pios01:8776/v3/%\(project_id\)s

コンポーネントの導入と起動に必要な設定を行います。

sudo -i
apt install cinder-api cinder-scheduler cinder-volume nfs-common
sed -i -e 's*# Domain = localdomain*Domain = local*' /etc/idmapd.conf

/etc/nova/nova.conf に以下を追加します。

[cinder]
os_region_name = RegionOne

nfsサーバーの設定ファイルを記述します /etc/cinder/nfs_shares

10.234.15.250:/volume2/c-volumes

設定ファイルを記述します /etc/cinder/cinder.conf

[DEFAULT]
rootwrap_config = /etc/cinder/rootwrap.conf
api_paste_confg = /etc/cinder/api-paste.ini
iscsi_helper = lioadm
volume_name_template = volume-%s
volume_group = cinder-volumes
verbose = True
state_path = /var/lib/cinder
lock_path = /var/lock/cinder
volumes_dir = /var/lib/cinder/volumes
enabled_backends = nfs
# for glance
glance_api_servers = http://pios01:9292
# for cinder-scheduler
transport_url = rabbit://openstack:rabbitmq123@pios01
auth_strategy = keystone
my_ip = 10.234.15.11
# for cinder-volume
enabled_backends = nfs
#default_volume_type = nfs
[database]
connection = mysql+pymysql://cinder:cinder123@pios01/cinder
[keystone_authtoken]
www_authenticate_uri = http://pios01:5000
auth_url = http://pios01:5000
memcached_servers = pios01:11211
auth_type = password
project_domain_name = default
user_domain_name = default
project_name = service
username = cinder
password = cinder123
[oslo_concurrency]
lock_path = /var/lib/cinder/tmp
[nfs]
volume_driver = cinder.volume.drivers.nfs.NfsDriver
nfs_shares_config = /etc/cinder/nfs_shares
nfs_mount_point_base = $state_path/mnt

データ入力を行います。

su -s /bin/sh -c "cinder-manage db sync" cinder

再起動し、永続化します。

systemctl restart nova-api.service
systemctl restart cinder-scheduler.service
systemctl restart cinder-volume.service
systemctl restart apache2.service

systemctl enable cinder-scheduler.service
systemctl enable cinder-volume.service

ボリュームタイプを作成します。

exit
. admin-openrc
openstack volume type create nfs
openstack volume type list

================ ここからコンピュート ================

コンピュートで作業します。コンピュートが複数台の場合、それら全部で行います。

※ "nvme-cli" はログにwarningが出てしまうとき追加します。

sudo -i
apt install nfs-common
sed -i -e 's*# Domain = localdomain*Domain = local*' /etc/idmapd.conf

/etc/nova/nova.conf に以下を追加します。

[cinder]
os_region_name = RegionOne

novaを再起動します。

systemctl restart nova-compute.service

正常性確認:Openstackのコンソールでbootableなボリュームを作成してみる

VMのローカルディスクをNFS経由のボリュームにしてみます。

プロジェクト>ボリューム>ボリューム>ボリュームの作成 を押す
項目 内容
ボリューム名 vol-test 等
ボリュームソース イメージ
イメージをソースとして使用する ubuntu-22.04 等
種別 nfs
サイズ(GiB) 10 (GiB) 等(必要に応じて)
アベイラビリティーゾーン nova
グループ グループなし

正常性確認:VMにアタッチして起動してみる

プロジェクト>コンピュート>インスタンス インスタンスの起動 を押す
項目 内容
インスタンス名 test2 等
アベイラビリティゾーン nova
インスタンス数 1
ブートソース ボリューム
インスタンス時にボリュームを削除 はい(必要に応じてだがDB等はいいえを選択するのが望ましい)
ボリューム vol-test 等
フレーバー micro 等
ネットワーク taira-network
セキュリティグループ default
キーペア 適当なものを選択
カスタマイズスクリプト 適当なものを入力

無事に起動できて、SSHでログインし、ボリュームからbootしているのが確認できたらここまでの設定は大丈夫です。 確認のために作ったVMは削除してください。

■ terraformをインストールし、VMを作成する

普段使っているPCや作業用VMなどにterraformをインストールします。 今回はtfenvを使って(たまたま手元のバージョンがこれだったという理由だけですが)0.12.31をインストールします。

git clone https://github.com/tfutils/tfenv.git ~/.tfenv
echo 'export PATH="$HOME/.tfenv/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
tfenv install 0.12.31
tfenv use 0.12.31

MinIOを作成するmain.tfを用意します。

なお、ユーザー名やキーペア名は適宜。ネットワークID、ubuntuのイメージIDなどのIDは環境ごとによって異なりますので、これも適宜書き換えてください。main.tfの下に各ID確認コマンドを記述しておきます。

cloud-configの中でssh_authorized_keysも貼っていますが、そちらも適宜書き換えてください。

# 認証情報を記述します。
provider "openstack" {
  user_name   = "taira"
  tenant_name = "taira-project"
  password    = "taira123"
  auth_url    = "http://10.234.15.11:5000/v3/"
}

# 固定IP用のポートを作成します。
resource "openstack_networking_port_v2" "port_minio" {
  name           = "port_minio"
  network_id     = "c4bffb81-530e-438a-9f6b-c9b783b2394e" # ネットワークIDを指定します
  admin_state_up = "true"

  fixed_ip {
    subnet_id = "f53aaea3-1a79-481a-a163-6e83a4b29b42"    # サブネットIDを指定します
    ip_address = "192.168.4.33"                           # インスタンスに割り当てるIPv4アドレスを指定します
  }
}

# ボリュームの作成をします。ポート作成後にボリュームを作成するのはVMがポートを認識できるようにするための時間稼ぎです。
resource "openstack_blockstorage_volume_v3" "vol-minio" {
  name        = "vol-minio"
  size        = 100                                         # ボリュームのサイズをGB単位で指定します
  volume_type = "nfs"                                       # ボリュームのタイプを指定します
  image_id    = "ca70fb5c-79ec-4ff5-b7b7-7f5e864fa3e2"      # ここではubuntuのイメージIDを指定しています
  depends_on = [openstack_networking_port_v2.port_minio]
}

# インスタンスの作成をします。
resource "openstack_compute_instance_v2" "vm-minio" {
  name        = "vm-minio"
  image_name  = "ubuntu-22.04"                              # インスタンスに使用するイメージ名を指定します
  flavor_name = "micro"                                     # インスタンスに使用するフレーバー名を指定します
  key_pair    = "キーペア名を書いてください"                 # インスタンスに使用するキーペアの名前を指定します
  block_device {
    uuid = openstack_blockstorage_volume_v3.vol-minio.id # ボリュームIDを指定します
    source_type = "volume"                               # ボリュームをインスタンスにアタッチするための設定です
    destination_type = "volume"                          # ボリュームをインスタンスにアタッチするための設定です
    boot_index = 0                                       # ボリュームをブートデバイス(dev/vda)に指定します
    delete_on_termination = true                         # インスタンス削除時にボリュームを削除するかどうかを指定します
  }
  user_data   = <<EOL
#cloud-config
timezone: Asia/Tokyo
package_upgrade: true
packages:
  - zip
  - avahi-daemon
  - avahi-utils
  - ca-certificates
  - curl
  - gnupg
keyboard:
  layout: jp
write_files:
  - path: /etc/systemd/timesyncd.conf
    content: |
      [Time]
      NTP=ntp.nict.jp
      FallbackNTP=ntp.ubuntu.com
users:
  - name: taira
    shell: /bin/bash
    groups: adm,admin
    sudo: ["ALL=(ALL) NOPASSWD:ALL"]
    ssh_authorized_keys:
      - ssh-rsa AAAほげほげ証明書省略xxxxxxxx= hoge
runcmd:
  - [ systemctl, restart, systemd-timesyncd.service ]
  - [ systemctl, restart, avahi-daemon.service ]
EOL
  network {
    name      = "taira-network"                             # インスタンスに接続するネットワークの名前を指定します
    port      = openstack_networking_port_v2.port_minio.id  # インスタンスに接続するポートのIDを指定します
  }
  depends_on = [openstack_blockstorage_volume_v3.vol-minio]
}

参考:各ID確認コマンド

※admin-openrcなどを読み込んでから行うこと。

項目 内容
ネットワーク/サブネットID openstack network list
イメージID openstack image list

初期化(プロバイダーなどをダウンロード)して確認(plan)、実行(apply)します。

terraform init
terraform plan
terraform apply

■ MinIOをdockerで立ち上げる

cf. Install Docker Engine on Ubuntu https://docs.docker.com/engine/install/ubuntu/

作成したインスタンスにsshします ※ユーザー名のtairaは、適宜変更してください。

ssh taira@192.168.4.33

dockerをインストールします。

sudo apt update
sudo apt -y install ca-certificates curl gnupg
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update && sudo apt -y install docker-ce docker-ce-cli containerd.io
sudo usermod -aG docker $USER

ログインし直すと使えるようになっていますので確認します。

docker run hello-world

MinIOを作成するdocker-compose.ymlを用意します

docker-compose.yml

version: '3'

volumes:
  minio_data: {}                # 永続化のため名前付きボリュームを作成します
                                # "/var/lib/docker/volumes/${USER}_minio_data/_data/"になると思います
                                # ディレクトリーは初回起動時に自動で作成されます

services:
  minio:
    image: minio/minio:latest
    container_name: standalone-minio
    ports:
      - "9000:9000"                 # サービス用   HOST_PORT:CONTAINER_PORT
      - "80:80"                     # コンソール用
    environment:
      MINIO_ROOT_USER: minio
      MINIO_ROOT_PASSWORD: minio123
      MINIO_REGION_NAME: ap-northeast-1
    command: server /data --console-address :80 # 自宅用なのでconsoleを80にします
    volumes:
      - minio_data:/data            # "minio_data"ボリュームを/dataとしてマウントし、
                                    # minioが/dataをバケットの/として扱います

Detachedモードで起動します

docker compose up -d

MinIOの管理画面にアクセスしてみましょう。 UsernameにはMINIO_ROOT_USER、PasswordにはMINIO_ROOT_PASSWORDを入力します。画面が開けば大丈夫でしょう。

http://192.168.4.33/

■ MinIOでBucketsを作成する

MinIOのコンソールで作成しても問題ありませんが、S3互換ということで、AWS CLIにMinIOの認証情報を追記し作業してみます。

cf. AWS CLI with MinIO Server https://min.io/docs/minio/linux/integrations/aws-cli-with-minio.html

現在使っているPCのAWS CLIの設定に以下のように追加します。

~/.aws/config などに以下追記

[profile vm-minio]
region = ap-northeast-1
aws_access_key_id = minio
aws_secret_access_key = minio123

以下コマンドにて初期状態を確認してみると、Bucketsはないのでそのまま返ってくるはずです。

aws --profile vm-minio --endpoint-url http://192.168.4.33:9000 s3 ls

terraform用のバケット(tf-backend)を以下のように作成します。

aws --profile vm-minio --endpoint-url http://192.168.4.33:9000 s3 mb s3://tf-backend

再び確認してみると以下のように作成されたバケット名が返ってくるはずです。

2023-04-xx hh:mm:nn tf-backend

■ terraformのバックエンドをMinIOにし、構成管理ファイルを永続化する

今回はボリュームを3個、作成してみます。前回のmain.tfファイルとは別の場所で作成しますが、変更点はbackendをs3で指定しているところです。

terraform {
  backend "s3" {
    bucket = "tf-backend"
    region = "ap-northeast-1"
    key    = "rpi-openstack/os-minio/terraform.tfstate"
    endpoint = "http://192.168.4.33:9000"
    access_key = "minio"
    secret_key = "minio123"
    skip_region_validation = true
    skip_credentials_validation = true
    skip_requesting_account_id = true
    skip_get_ec2_platforms = true
    skip_metadata_api_check = true
    force_path_style = true
  }
}

provider "openstack" {
  user_name   = "taira"
  tenant_name = "taira-project"
  password    = "taira123"
  auth_url    = "http://10.234.15.11:5000/v3/"
}

# 繰り返しはvariableで定義しておきます
variable "volumes" {
  default = {
    "volume1" = {
      name = "volume1"
      size = 1
    }
    "volume2" = {
      name = "volume2"
      size = 2
    }
    "volume3" = {
      name = "volume3"
      size = 3
    }
  }
}

resource "openstack_blockstorage_volume_v3" "volume" {
  for_each = var.volumes

  name = each.value.name
  size = each.value.size
}

初期化して確認、実行します。

terraform init
terraform plan
terraform apply

無事にボリュームは3個できましたでしょうか? 確認目的なのでできたら破棄します。

terraform destroy

■ 最後に

今回はボリュームの仮想化や、(なんちゃってではありますが)S3互換のブロックデバイスなど、ストレージにフォーカスしておうちクラウドを拡張させてみました。 ストレージを仮想化すると自由度が向上し、IaCで構成管理の永続化などが出来はじめるので、クラウド感(笑)がマシマシになるのがお分かりいただけたかもしれません。 また、ストレージを物理サーバーから切り離した結果、SDカードはVMの運用とは切り離されて、NFSの許す限り巨大なディスクをVMにアタッチでき、SDは物理サーバーがbootするときに使ったり、ローカルで動くプロセスのログを保存する程度しか使わなくなって、本体の長寿命化にも貢献できたと思います。

今後もこのクラウド環境を拡張していく予定ですので、引き続きお付き合いいただければ幸いです。よろしくお願いいたします。