とことんDevOps | 日本仮想化技術のDevOps技術情報メディア

DevOpsに関連する技術情報を幅広く提供していきます。

日本仮想化技術がお届けする「とことんDevOps」では、DevOpsに関する技術情報や、日々のDevOps業務の中での検証結果、TipsなどDevOpsのお役立ち情報をお届けします。
主なテーマ: DevOps、CI/CD、コンテナ開発、IaCなど
読者登録と各種SNSのフォローもよろしくお願いいたします。

Terraform/OpenTofuのOpenStack Providerを使ってインスタンスを複数作成してみる

Terraformは色々なクラウドに対応しており、色々な環境を用意する場合に便利なデプロイツールです。 OpenTofuはTerraformのフォークであり、オープンソースライセンスで開発されています。

対象となるバージョンはそれぞれ次のとおりです。

% terraform version
Terraform v1.7.3
% tofu version
OpenTofu v1.6.1

これらは共通してプロバイダーと言うものが存在しており、それらを使うことで色々なインフラに対して環境を作成できます。 今回使うのはOpenStack Providerです。

registry.terraform.io

これらを使って、OpenStackインスタンスを作成してみます。 今回使うOpenStack環境は、Jujuを使ってOpenStack Baseをデプロイしている構成です。

charmhub.io

OpenStackは複数のコンポーネントで構成されるプライベートクラウドインフラです。 コンポーネントの構成によってマニフェストを記述する必要があります。

最低限知っておく必要があるのは、次のような感じでしょうか。 ここら辺がわからないと、OpenStack Providerを使うのは難しいと思います。

サービス OpenStackサービス名
Identity Keystone
Compute Compute
Image Glance
Network Neutron
Volume Cinder
Object Store Swift
Web UI Horizon

マニフェストを書く

基本的には次の二つを確認しておけば書くことが可能です。

一つ目のブロックでは、OpenStack Providerを使うと言う宣言をしています。 二つ目のブロックでは、OpenStack Providerの認証情報を書いています。ただ、このブロックにはユーザー名、パスワードなどの情報は書かないようにします。 認証はダウンロードできるadmin-openrc.shのようなファイルを読み込んでから実行する形で対応します。

最低限必要なのはauth_urlです。これはIdentityのService Endpoint URLを記述します。これはadmin-openrc.shに書かれているものをそのまま記述します。 証明書はroot証明書を使っている場合はcacert_fileで証明書を指定します。root証明書の準備については過去の記事を参考にしてください。

tech.virtualtech.jp

三つ目、四つ目のブロックがインスタンスの構成の部分です。 このあと、それぞれ詳しくみていきます。

terraform {
  required_providers {
    openstack = {
      source  = "terraform-provider-openstack/openstack"
      version = "~> 1.53.0"
    }
  }
}

# Configure the OpenStack Provider
provider "openstack" {
   auth_url    = "https://172.17.28.107:5000/v3" 
   cacert_file = "./root-ca.crt"
}

# インスタンスを起動し、ボリュームを作成し、ボリュームをブロックデバイスとして接続する
resource "openstack_compute_instance_v2" "instance_1" {
  name            = "instance_1"
  flavor_id       = "4036f5c3-1fae-48d1-800d-42a43ed1229e"
  key_pair        = "ytkey"
  security_groups = ["default"]

  network {
    uuid = "a3e2b0f4-10e8-43a0-bfd9-ac2eefd22fb6"
  }

  block_device {
    uuid                  = "76dc961e-91f1-4c23-9e4e-d919ebdb827d"
    source_type           = "image"
    destination_type      = "volume"
    volume_size           = 4
    boot_index            = 0
    delete_on_termination = true
  }
}

# instance_1 が起動したあと、次のインスタンスが起動する
resource "openstack_compute_instance_v2" "instance_2" {
  name            = "instance_2"
  flavor_id       = "4036f5c3-1fae-48d1-800d-42a43ed1229e"
  key_pair        = "ytkey"
  security_groups = ["default"]  
  depends_on = [openstack_compute_instance_v2.instance_1]

  network {
    uuid = "a3e2b0f4-10e8-43a0-bfd9-ac2eefd22fb6"
  }

  block_device {
    uuid                  = "76dc961e-91f1-4c23-9e4e-d919ebdb827d"
    source_type           = "image"
    destination_type      = "volume"
    volume_size           = 4
    boot_index            = 0
    delete_on_termination = true
  }
}

インスタンスの作成周り

「resource "openstack_compute_instance_v2"」はコンピュートサービスに対して、インスタンスの作成を指示します。 nameはインスタンス名、flavor_idはどのようなスペックでインスタンス(今回の構成の場合KVMの仮想マシン) を起動するかをFlavorで定義しており、そのIDを指定しています。 key_pairは、OpenStackインスタンスは基本は公開鍵認証を行うので、OpenStackに登録した公開鍵を指定しています。 security_groupsはインスタンスに対してどのセキュリティグループを使うか指定しています。 かなり端折って説明すると、セキュリティグループはインスタンスに対してどういったポートやネットワークからのアクセスを許可するかを セキュリティグループで定義します。defaultはたいてい用意されているので、それを使う例にしています(が、SSHしたりpingをなげたりしたい場合、セキュリティグループの編集が必要です)。

networkのブロックはどのネットワークをインスタンスに割り当てるかを指定します。ここも環境によって異なる部分ですが、基本的なOpenStackではテナントネットワークとプロバイダーネットワークが用意されています。テナントネットワークは外部とは分離しているネットワークで内部アクセスのために使います。外部からインスタンスへアクセスするために使うのがプロバイダーネットワークです。プロバイダーネットワークはインターネット側と繋がっており、そのネットワークからFlating IPアドレスを払い出して、インスタンスに設定することで外部クライアントから各プロトコル(例えばICMPやSSHなど)を使ってアクセスできます。ここではテナントネットワークを指定しています。

block_deviceはインスタンスに対してCinderボリュームを指定する場合に設定をします。ここもOpenStack固有なのでCinderについて理解が必要ではあるのですが、OpenStack Dashboardでインスタンスを作ったことがあれば、それぞれの項目の意味は理解できると思います。本例ではブートソースとしてイメージボリュームを使い、ルートディスクとして使うことを想定しています。ボリュームサイズはフレーバーのディスクサイズに準じたものを設定します。

delete_on_termination = trueを設定している場合、インスタンスを削除するとボリュームも削除されるように設定されます。 2個目のインスタンスの同様ですが、2個目の方にdepends_on = [openstack_compute_instance_v2.instance_1]と記述しています。これにより、「instance_1」が作成されたら「instance_2」の作成が行われます。たいていの場合はそんな指定をしなくてもうまくいくのですが、遅いストレージを使っていたり、慎重に一つ一つインスタンスを起動したい場合はこのような方法で対応できます。

# インスタンスを起動し、ボリュームを作成し、ボリュームをブロックデバイスとして接続する
resource "openstack_compute_instance_v2" "instance_1" {
  name            = "instance_1"
  flavor_id       = "4036f5c3-1fae-48d1-800d-42a43ed1229e"
  key_pair        = "ytkey"
  security_groups = ["default"]

  network {
    uuid = "a3e2b0f4-10e8-43a0-bfd9-ac2eefd22fb6"
  }

  block_device {
    uuid                  = "76dc961e-91f1-4c23-9e4e-d919ebdb827d"
    source_type           = "image"
    destination_type      = "volume"
    volume_size           = 4
    boot_index            = 0
    delete_on_termination = true
  }
}

# instance_1 が起動したあと、次のインスタンスが起動する
resource "openstack_compute_instance_v2" "instance_2" {
  name            = "instance_2"
  flavor_id       = "4036f5c3-1fae-48d1-800d-42a43ed1229e"
  key_pair        = "ytkey"
  security_groups = ["default"]  
  depends_on = [openstack_compute_instance_v2.instance_1]

  network {
    uuid = "a3e2b0f4-10e8-43a0-bfd9-ac2eefd22fb6"
  }

  block_device {
    uuid                  = "76dc961e-91f1-4c23-9e4e-d919ebdb827d"
    source_type           = "image"
    destination_type      = "volume"
    volume_size           = 4
    boot_index            = 0
    delete_on_termination = true
  }
}

さて、flavor_id,key_pair,security_groups,network uuid,block_deviceはどう調べるかですが、OpenStack Dashboardにアクセスしてuuidを調べるか、次のようにコマンドで確認できます。

//フレーバーはインストール直後はない場合があるので、作っておいたものを指定します。
% openstack flavor list
+--------------------------------------+-----------+------+------+-----------+-------+-----------+
| ID                                   | Name      |  RAM | Disk | Ephemeral | VCPUs | Is Public |
+--------------------------------------+-----------+------+------+-----------+-------+-----------+
| 4036f5c3-1fae-48d1-800d-42a43ed1229e | m1.small  | 2048 |   40 |         0 |     1 | True      |
| 93bcc26d-5bdb-4488-9338-4df456eac5f8 | m1.large  | 2048 |   60 |         0 |     2 | True      |
| c4b24684-f6b0-4e2d-9706-5d79f551d860 | m1.medium | 2048 |   40 |         0 |     2 | True      |
| fc2fafe5-0048-4b4e-bacd-1908f714be1f | m1.micro  | 1024 |    4 |         0 |     1 | True      |
+--------------------------------------+-----------+------+------+-----------+-------+-----------+

//今回使うフレーバーは次の方法で作成したものです。
% openstack flavor create --vcpus 1 --ram 1024 --disk 4 m1.micro


//キーペアはDashboardなどから登録しておくか、キーペアを新規作成しておく
% openstack keypair list
+-------+-------------------------------------------------+------+
| Name  | Fingerprint                                     | Type |
+-------+-------------------------------------------------+------+
| ytkey | xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx | ssh  |
+-------+-------------------------------------------------+------+

% openstack security group list
+--------------------------------------+---------+------------------------+----------------------------------+------+
| ID                                   | Name    | Description            | Project                          | Tags |
+--------------------------------------+---------+------------------------+----------------------------------+------+
| 10dd8b62-4941-4a7f-8cdc-7090f3861ae4 | default | Default security group | b56701abc3774a47aa724196c767470d | []   |
| 76a38a61-e958-4659-93da-e44cd39e7871 | default | Default security group | 9b14ab6eac7b46e3ba9300923b468708 | []   |
+--------------------------------------+---------+------------------------+----------------------------------+------+

//インスタンスに割り当てるのは本例ではuser-net側のネットワーク
% openstack network list
+--------------------------------------+--------------+--------------------------------------+
| ID                                   | Name         | Subnets                              |
+--------------------------------------+--------------+--------------------------------------+
| a3e2b0f4-10e8-43a0-bfd9-ac2eefd22fb6 | user-net     | 93402464-a898-40e0-adc9-866ec1dc1ec3 |
| e95c8920-e61f-4627-9850-4392333311bb | external-net | d6926128-1332-46ec-bb18-bcf4a0c1b1a6 |
+--------------------------------------+--------------+--------------------------------------+


//block_deviceのUUIDはイメージボリュームを使うので、ベースとするイメージのIDを確認
% openstack image list
+--------------------------------------+--------------+--------+
| ID                                   | Name         | Status |
+--------------------------------------+--------------+--------+
| 76dc961e-91f1-4c23-9e4e-d919ebdb827d | ubuntu-20.04 | active |
+--------------------------------------+--------------+--------+

これら調べたIDをマニフェストに書き込んでいきます。

Terraformによるデプロイ

openstackコマンドを実行する場合にadmin-openrc.shなどを読み込んでいるはずなので問題ないはずですが、まだであればTerraformでOpenStackを触る前にsource admin-openrc.sh (ファイル名は環境やデプロイ方法による)を実行しておきます。

準備ができたら初期化を行います。これにより、Terraformマニフェストに記述したプロバイダーなどがダウンロードされます。

terraform init

次に、マニフェストファイルの内容に問題がないかチェックを行います(出力される内容は長いので割愛します)。

terraform plan

そしてこれを実行すると、OpenStackにインスタンスが2個作成されるはずです。たいていの場合は定義エラーはterraform planの時点で発覚するのですがまれに漏れることがあり、terraform applyの実行をした時にエラーが発生したら、エラーに対応してください。

何もエラーが出なければEnter a valueのところで、メッセージにあるように「yes」と入力してEnterターン!...でインスタンスがデプロイされます。

terraform apply
...
Plan: 2 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: 

次のような感じで出力されるはずです。要する時間は「ストレージ」の性能によります。

...
openstack_compute_instance_v2.instance_1: Creating...
openstack_compute_instance_v2.instance_1: Still creating... [10s elapsed]
openstack_compute_instance_v2.instance_1: Still creating... [20s elapsed]
openstack_compute_instance_v2.instance_1: Still creating... [30s elapsed]
openstack_compute_instance_v2.instance_1: Still creating... [40s elapsed]
openstack_compute_instance_v2.instance_1: Creation complete after 49s [id=8964f1d8-265c-4477-a670-0dbd301f978e]
openstack_compute_instance_v2.instance_2: Creating...
openstack_compute_instance_v2.instance_2: Still creating... [10s elapsed]
openstack_compute_instance_v2.instance_2: Still creating... [20s elapsed]
openstack_compute_instance_v2.instance_2: Still creating... [30s elapsed]
openstack_compute_instance_v2.instance_2: Still creating... [40s elapsed]
openstack_compute_instance_v2.instance_2: Still creating... [50s elapsed]
openstack_compute_instance_v2.instance_2: Creation complete after 54s [id=c67083c9-9f64-4bd3-86cf-7f824c7c5194]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

//二つインスタンスができた。
% openstack server list
+--------------------------------------+------------+--------+------------------------+--------------------------+----------+
| ID                                   | Name       | Status | Networks               | Image                    | Flavor   |
+--------------------------------------+------------+--------+------------------------+--------------------------+----------+
| c67083c9-9f64-4bd3-86cf-7f824c7c5194 | instance_2 | ACTIVE | user-net=192.168.1.52  | N/A (booted from volume) | m1.small |
| 8964f1d8-265c-4477-a670-0dbd301f978e | instance_1 | ACTIVE | user-net=192.168.1.154 | N/A (booted from volume) | m1.small |
+--------------------------------------+------------+--------+------------------------+--------------------------+----------+

作ったインスタンスを削除するにはterraform destroyを実行します。要する時間は「ストレージ」の性能によります。

% terraform destroy
...
Plan: 0 to add, 0 to change, 2 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

OpenTofuでデプロイしてみる

事前にOpenTofuをインストール している必要がありますが、インストール済みであれば同じマニフェストを使ってデプロイできると思います。やってみましょう。

tofu init
tofu plan
tofu apply
Do you want to perform these actions?
  OpenTofu will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes 

同じように、インスタンスを作成できるはずです。削除はtofu destroyと実行します。 Terraformと同じマニフェストを使って、OpenStackインスタンスの作成や削除ができることがわかりました。

Terraformを使うかOpenTofuを使うかですが、一般ユーザー的な観点ではどちらでも良いと思います。 そもそもTerraformのtfstateファイルとかを管理するのが辛いのでTerraform Cloudを使っている場合は、どっちを選択すると言う話にはなりません。

一方でプロジェクトや企業のポリシー、プロジェクトの活動内容によってはTerraformを使う場合、有償のライセンス契約が必要だったりTerraformを使ってはいけない場合があります。 ちなみに弊社は該当しないので、TerraformもOpenTofuもどっちも使えます。

TerraformもOpenTofuも両方入れる人は多くないと思いますが、万が一その場合は、必ずデプロイする前に初期化を実行するようにしてください。 初期化を実行することで、Terraformであればregistry.terraform.ioから、OpenTofuであればregistry.opentofu.orgから、プロバイダーを取得してそれを利用してデプロイを行います。

  • 今回使ったプロバイダー
    • registry.terraform.io/terraform-provider-openstack/openstack v1.53.0
    • registry.opentofu.org/terraform-provider-openstack/openstack v1.53.0

と言うわけで、Terraform/OpenTofuをつかってOpenStackインスタンスの制御ができると言う話題でした。