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

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

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

Terraformのフォーク「OpenTofu」を使ってみる

Terraform(を含むHashiCorp社のソフトウェア製品)のライセンスがBusiness Source License (BSL, or BUSL)になりました。

www.hashicorp.com

一般ユーザーはライセンス変更前にリリースされたソフトウェアについてはこれまでと同じように使えるものの、これからリリースされるバージョンについては一定の制限が加わるようです。

FAQの次の3項にあるように、社内内部や個人的な利用で使う場合は従来どおり使えるという記述があります。一方で依頼を受けて自社所有ではないインフラに対してTerraformを使う場合、どうなのかは正直よくかわかりません。

3. What are the implications of this change for end users of HashiCorp’s open source products?

For end users who are using HashiCorp’s current open source products and new releases using the BSL license for their internal or personal usage, there is no change.
Last updated: August 10, 2023

(参考訳)
3.この変更は、HashiCorpのオープンソース製品のエンドユーザーにどのような影響がありますか?

HashiCorpの現在のオープンソース製品と、内部または個人的な使用にBSLライセンスを使用する新しいリリースを使用しているエンドユーザーについては、変更はありません。

...というわけで(どういうわけで?)、最近話題になったOpenTofuを使ってみます。

OpenTofuとは?

OpenTofuはTerraformのフォークです。コマンドがopentofuであるほかはTerraformと使い勝手は一緒です。 そのため、Terraformプラグインの多くがそのまま利用できます。

github.com

最近プロジェクトがLinux Foundation傘下になったのもメディアに取り上げられていました。

www.publickey1.jp

OpenTofuは成果物を引き続きMozilla Public License v2.0(MPL2.0)のオープンソースライセンスで開発されるようです。 OpenTofuとフォーク元のTerraformがどうなっていくかは見守りたいと思います。

OpenTofuをインストールしてみる

OpenTofuのドキュメントを見ても、Linux以外のインストール方法が書かれていません。そもそもLinuxもaptとyumを使ってパッケージをインス トールする方法以外は書かれていません。他のディストリビューションではどうするのでしょうか。

opentofu.org

TerraformもOpenTofuもGo言語で書かれているので、Goをインストールしてビルド、インストールすればどのOSにもインストールできるようです。OSごとのインストール方法の詳細は今回省略しますが、ざっくり書くと次のような手順です。

まず、Goをインストールします。 Goのバージョンはここ を参考に、それに近いバージョンを公式サイトからダウンロードしてインストールします。

ちなみに私はGo 1.20.8をインストールしました。

ただ、Go 1.21系を使った場合も大丈夫だったと社内で聞いたので、そこまで厳格にバージョンを指定する必要はないのかもしれません。

Goをインストールしたあと、次を実行します。

git clone https://github.com/opentofu/opentofu.git
cd opentofu
make
go install

今後はアップデートする場合は上記を実行して、変更点はgit logなどで確認していく感じでしょうか。なにか動かなくてOpenTofu側の問題であればIssue登録する感じですかね。

Dockerでつかってみる

まずはチュートリアルとして、Dcoekr DesktopでNGINXコンテナーを動かしてみましょう。

チュートリアルサイトにはいくつかのプロバイダー向けのハンズオンが用意されています。

developer.hashicorp.com

まずはこちらを試してみます。

developer.hashicorp.com

Terraformのインストールの部分は行わず、OpenTofuを使います。 というわけでこのページから始めます。

developer.hashicorp.com

以下はviエディタを使っていますが、お好きなエディターで次のようなマニフェストを書いてください。

$ mkdir learn-terraform-docker-container
$ cd learn-terraform-docker-container
$ vi main.tf

terraform {
  required_providers {
    docker = {
      source = "kreuzwerker/docker"
      version = "~> 3.0.1"
    }
  }
}

provider "docker" {}

resource "docker_image" "nginx" {
  name         = "nginx:latest"
  keep_locally = false
}

resource "docker_container" "nginx" {
  image = docker_image.nginx.image_id
  name  = "tutorial"
  ports {
    internal = 80
    external = 8000
  }
}

Dockerのチュートリアル を見ながら、次のように順に実行してみます。すると、Docker DesktopにNGINXコンテナーが作成されてアクセスできるようになります。

$ opentofu init
$ opentofu apply

要らなくなったら、destroyを実行してください。コンテナーが終了(削除?)します。

$ opentofu destroy

OpenStackで使ってみる

社内で個人用にOpenStackを動かしているので、ここにインタンスをOpenTofuで作ってみようと思います。 使うのはTerraformのOpenStack Providerです。

注意点

TerraformのOpenStack Providerを使う場合にハマったポイントをここにまとめました。

ちなみにOpenStack CLIはTerraformでOpenStackをいじるときに必須ではないようです(あると便利ですが)。 使う必要がなければ上の処理は不要です。

ググると古い情報が出てくることがあります。 例えばこのような記述をすると、この頃のこのモジュールはdarwin_arm64に対応していないというエラーが発生するので注意です。

特にApple M1以降のMacを使っている人は注意ですね。

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

用意するマニフェスト

書いたTerraformマニフェストは以下のとおりです。基本的に公式のままです。 2つ目のブロックの認証情報関連は、Dashboard右上からダウンロードできるopenrcに書いてあるので、それを転記します。

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

# Configure the OpenStack Provider
provider "openstack" {
  user_name   = "openstackuser"
  tenant_name = "admin"
  password    = "hogehugahogeeee"
  auth_url    = "https://172.17.28.71:5000/v3/"
  cacert_file = "ssl/cacert.pem"
  key = "ssl/key.pem"
  region      = "microstack"
}

# Create a web server
resource "openstack_compute_instance_v2" "test-server" {
  name              = "my_instance"
  image_id          = "93719fbf-e12e-4cad-b15e-cdd8250d75ba"
  flavor_id         = "2"
  key_pair          = "mykey"
  availability_zone = "nova"
  security_groups   = ["default"]

  network {
    uuid = "10c3220f-7679-4ac5-97af-764c1a9705a4"
  }
}

あとはいつもの流れでinit, fmt, validate, plan, deployを実行していくだけです。 Terraformを使っていたときはコマンド'terraform'のところをopentofuにするだけで、あとは一緒です。

$ opentofu init 

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of terraform-provider-openstack/openstack from the dependency lock file
- Using previously-installed terraform-provider-openstack/openstack v1.51.1

OpenTofu has been successfully initialized!

You may now begin working with OpenTofu. Try running "tofu plan" to see
any changes that are required for your infrastructure. All OpenTofu commands
should now work.

If you ever set or change modules or backend configuration for OpenTofu,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

$ opentofu fmt
openstack-demo.tf

$ opentofu validate
Success! The configuration is valid.

$ opentofu plan

OpenTofu used the selected providers to generate the following execution plan. Resource actions are
indicated with the following symbols:
  + create

OpenTofu will perform the following actions:

  # openstack_compute_instance_v2.test-server will be created
  + resource "openstack_compute_instance_v2" "test-server" {
      + access_ip_v4        = (known after apply)
      + access_ip_v6        = (known after apply)
      + all_metadata        = (known after apply)
      + all_tags            = (known after apply)
      + availability_zone   = "nova"
      + created             = (known after apply)
      + flavor_id           = "2 "
      + flavor_name         = (known after apply)
      + force_delete        = false
      + id                  = (known after apply)
      + image_id            = "93719fbf-e12e-4cad-b15e-cdd8250d75ba"
      + image_name          = (known after apply)
      + key_pair            = "mykey"
      + name                = "my_instance"
      + power_state         = "active"
      + region              = (known after apply)
      + security_groups     = [
          + "default",
        ]
      + stop_before_destroy = false
      + updated             = (known after apply)

      + network {
          + access_network = false
          + fixed_ip_v4    = (known after apply)
          + fixed_ip_v6    = (known after apply)
          + floating_ip    = (known after apply)
          + mac            = (known after apply)
          + name           = (known after apply)
          + port           = (known after apply)
          + uuid           = "10c3220f-7679-4ac5-97af-764c1a9705a4"
        }
    }

  # openstack_networking_floatingip_v2.external will be created
  + resource "openstack_networking_floatingip_v2" "external" {
      + address    = (known after apply)
      + all_tags   = (known after apply)
      + dns_domain = (known after apply)
      + dns_name   = (known after apply)
      + fixed_ip   = (known after apply)
      + id         = (known after apply)
      + pool       = "public"
      + port_id    = (known after apply)
      + region     = (known after apply)
      + subnet_id  = (known after apply)
      + tenant_id  = (known after apply)
    }

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

────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so OpenTofu can't guarantee to take exactly
these actions if you run "tofu apply" now.


$ opentofu apply

OpenTofu used the selected providers to generate the following execution plan. Resource actions are
indicated with the following symbols:
  + create

OpenTofu will perform the following actions:

  # openstack_compute_instance_v2.test-server will be created
  + resource "openstack_compute_instance_v2" "test-server" {
      + access_ip_v4        = (known after apply)
      + access_ip_v6        = (known after apply)
      + all_metadata        = (known after apply)
      + all_tags            = (known after apply)
      + availability_zone   = "nova"
      + created             = (known after apply)
      + flavor_id           = "2"
      + flavor_name         = (known after apply)
      + force_delete        = false
      + id                  = (known after apply)
      + image_id            = "93719fbf-e12e-4cad-b15e-cdd8250d75ba"
      + image_name          = (known after apply)
      + key_pair            = mykey"
      + name                = "my_instance"
      + power_state         = "active"
      + region              = (known after apply)
      + security_groups     = [
          + "default",
        ]
      + stop_before_destroy = false
      + updated             = (known after apply)

      + network {
          + access_network = false
          + fixed_ip_v4    = (known after apply)
          + fixed_ip_v6    = (known after apply)
          + floating_ip    = (known after apply)
          + mac            = (known after apply)
          + name           = (known after apply)
          + port           = (known after apply)
          + uuid           = "10c3220f-7679-4ac5-97af-764c1a9705a4"
        }
    }

  # openstack_networking_floatingip_v2.external will be created
  + resource "openstack_networking_floatingip_v2" "external" {
      + address    = (known after apply)
      + all_tags   = (known after apply)
      + dns_domain = (known after apply)
      + dns_name   = (known after apply)
      + fixed_ip   = (known after apply)
      + id         = (known after apply)
      + pool       = "external"
      + port_id    = (known after apply)
      + region     = (known after apply)
      + subnet_id  = (known after apply)
      + tenant_id  = (known after apply)
    }

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

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

openstack_compute_instance_v2.test-server: Creating...
openstack_compute_instance_v2.test-server: Still creating... [10s elapsed]
openstack_compute_instance_v2.test-server: Still creating... [20s elapsed]
openstack_compute_instance_v2.test-server: Still creating... [30s elapsed]
openstack_compute_instance_v2.test-server: Creation complete after 37s [id=8225be20-8efe-4b40-80c7-bef88f0d674a]

あとはそのインスタンスにFlating IPアドレスを設定すればアクセスできます。

$ openstack floating ip list
$ openstack server add floating ip my_instance 172.16.214.209

$ ping 172.16.214.209
PING 172.16.214.209 (172.16.214.209): 56 data bytes
64 bytes from 172.16.214.209: icmp_seq=0 ttl=62 time=21.701 ms
64 bytes from 172.16.214.209: icmp_seq=1 ttl=62 time=22.057 ms
^C
--- 172.16.214.209 ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 21.701/21.879/22.057/0.178 ms

要らなくなったら、次のようにインスタンスを消します。

$ opentofu destroy
openstack_compute_instance_v2.test-server: Refreshing state... [id=2e63312c-6fe0-4018-9d9a-8c640834cdad]

OpenTofu used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  - destroy

OpenTofu will perform the following actions:

  # openstack_compute_instance_v2.test-server will be destroyed
  - resource "openstack_compute_instance_v2" "test-server" {
      - access_ip_v4        = "192.168.222.133" -> null
      - all_metadata        = {} -> null
      - all_tags            = [] -> null
      - availability_zone   = "nova" -> null
      - created             = "2023-09-26 04:08:52 +0000 UTC" -> null
      - flavor_id           = "2" -> null
      - flavor_name         = "m1.small" -> null
      - force_delete        = false -> null
      - id                  = "2e63312c-6fe0-4018-9d9a-8c640834cdad" -> null
      - image_id            = "93719fbf-e12e-4cad-b15e-cdd8250d75ba" -> null
      - image_name          = "ubuntu-20.04" -> null
      - key_pair            = "mykey" -> null
      - name                = "my_instance" -> null
      - power_state         = "active" -> null
      - region              = "microstack" -> null
      - security_groups     = [
          - "default",
        ] -> null
      - stop_before_destroy = false -> null
      - tags                = [] -> null
      - updated             = "2023-09-26 04:09:15 +0000 UTC" -> null

      - network {
          - access_network = false -> null
          - fixed_ip_v4    = "192.168.222.133" -> null
          - mac            = "fa:16:3e:cd:0f:32" -> null
          - name           = "test" -> null
          - uuid           = "10c3220f-7679-4ac5-97af-764c1a9705a4" -> null
        }
    }

Plan: 0 to add, 0 to change, 1 to destroy.

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

  Enter a value: yes

openstack_compute_instance_v2.test-server: Destroying... [id=2e63312c-6fe0-4018-9d9a-8c640834cdad]
openstack_compute_instance_v2.test-server: Still destroying... [id=2e63312c-6fe0-4018-9d9a-8c640834cdad, 10s elapsed]
openstack_compute_instance_v2.test-server: Destruction complete after 11s

Destroy complete! Resources: 1 destroyed.

OpenTofuでほぼ問題なくTerraformマニフェストを使ってデプロイ出来ることは確認していますが、 唯一Terraformマニフェストでrequired_versionを指定している場合は、調整が必要かもしれません。

terraform {
  required_version = "~> 1.1.0"
  required_providers {
    ...
    }
  }
}

とはいえ今回のパターンでは特に変更することなくデプロイ出来ることが確認できました。 これなら、移行しても大丈夫そうですね。