CCCMKホールディングス TECH LABの Tech Blog

TECH LABのエンジニアが技術情報を発信しています

ブログタイトル

IaC(Infrastructure as Code)について調べ、Terraformに入門しました。

IaC(Infrastructure as Code)について調べ、Terraformに入門しました。

こんにちは、CCCMKホールディングス TECH LABの三浦です。

はてなブログでは今週のお題で「習慣にしたいこと・していること」が設定されているそうです。私は最近英語の勉強が習慣化出来なくて悩んでいます・・・。特に発音の勉強をしているのを周りに聞かれるのが恥ずかしくて、一人になれる時間や場所を探しているうちにどんどん後回しになってしまっている感じです。なんとかしないといけないな、と思っています。

これまでAIやML周りの開発に携わってきたのですが、最近はクラウドでそれらを稼働するためにどういった構成が必要なのか、インフラの観点まで含めて考えなければならないことが増えてきました。AI/ML周りは変化が激しく、様々なサービスがどんどん新規に提供されています。AI/MLの技術やサービスは利用されてこそ価値を発揮するのですが、より早く利用出来るようにするだけでなく、それらは安全に利用されなければなりません。そのため、AI/MLに携わる側からベストプラクティスなインフラ構成を調べ、提示することが必要です。

普段Microsoft Azureを利用していますが、Azureではリソースを管理できるAzure PortalというGUIツールが提供されています。私はこれまでAzure Portalを使ってリソースを作ったり設定していたのですが、必要なリソースの種類や数が増えてくるとこの作業がとても負担に感じるようになってきました。また、複雑な構成になるとどのリソースをどういった設定で、どういう順番で作成するのかといった記録も必要ですが、この辺りも抜け漏れがあったりして何とかしたいな、と考えていました。

そんな中、必要なインフラをコードで記述するIaC(Infrastructure as Code)という技術を知り、IaCを使うことでこれらの問題を解決できるように感じました。今回はIaCがどのような技術か、それを実現するための代表的なツールであるTerraformを使用したIaCの具体的なイメージについてまとめてみたいと思います。

IaC(Infrastructure as Code)とは

MicrosoftのDevOpsのドキュメントの1セクションにIaCについての解説があり、それを参考にするとIaCとはネットワークや仮想マシン、ストレージなどのインフラストラクチャーをコードで定義し、コードに従って構築することと言えると思います。

learn.microsoft.com

同じソースコードからいつでも同じアプリケーションが生成されるように、IaCによって同じコードからいつでも同じインフラを生成することが出来るようになります。またソフトウェアソースコードと同様、Gitなどのソースコード管理システムでバージョン管理が可能になります。

IaCの導入によって以下のメリットが得られます。

インフラの一貫性を保つ

IaCではインフラを定義したソースコードをTerraformなどのIaCツールで実行し、インフラの構築を行います。そのため同じソースコードと同じツールを使えば、どの環境からでも同じインフラを構築出来るという一貫性を得ることが出来ます。インフラの構築に人手を介する箇所が大幅に削減されるため、設定漏れなどの人的ミスの回避にも繋がります。また、インフラに変更を加える場合は手動で直接変更を加えるのではなくソースコードを編集することで継続的に一貫性を保ち続けることが出来ます。

インフラのテストが可能になる

たとえばIaCの代表的なツールであるTerraformでは、関連性のあるインフラリソースをまとめたモジュール単位でソースコードを記述出来ます。それらのソースコードに対してテストを行うことでインフラの単体テスト、全てのソースコードをまとめて実行することでインフラの結合テストが可能になります。

インフラの構築を効率化出来る

ネットワークなどの一部設定だけを変更し、それ以外は同じ内容で複数の環境に同時にインフラを構築するケースもあると思います。その場合、IaCでは可変になる箇所を変数にし、ループ処理でその変数を動かして一度に複数の環境を構築することが出来ます。また、一度作成したソースコードを再利用して別の環境の構築に利用することも可能になります。

Terraform

TerraformはHashiCorp社によるIaCツールです。

www.terraform.io

IaCのツールは命令型宣言型の実行方法を採用しているものに分けることが出来、Terraformは宣言型を採用しています。

命令型はこのリソースを作成して、次にこのリソースを作成する、といったように、目標のインフラを構築するための手順をソースコードで指定する方法です。一方宣言型は目標のインフラを構築するために必要になるリソースはこれとこれ、といった形でソースコードの中に宣言します。そしてそれらのリソースをどのような手順で作成するのかはツール側で計画します。

命令型はより細やかな指定が出来そうですが、宣言型はより容易に実行できる強みがあると考えられます。

Terraformの仕組み

Terraformはコマンドラインで操作するCUIツールです。Terraformを介してAWSやAzureなどの様々なクラウドに対する操作を行うことが出来ます。Terraformでは、たとえばAzureであればAzureRMといったように、様々なプロバイダがそれぞれのクラウドに対応したPluginを提供しており、それらのPluginを通じて各クラウドサービスのAPIを呼び出してリソースの操作を行います。

PluginはTerraformのプロジェクトを初期化する際に、対応するものがTerraformのレジストリからダウンロードされます。

HCLを用いた設定ファイルの記述

Terraformではインフラを定義するコードをHashicorp Configuration Language (HCL)という独自の言語を用いて記述します。設定ファイルはリソースを定義するもの、プロバイダを定義するもの、変数を定義するものなどように、機能に応じて複数のファイルにまたいで記述することが出来ます。

リソースの状態を保持するStateファイル

Terraformは設定ファイルに基づくリソースの新規作成だけでなく、すでにあるリソースの更新や削除も行うことが出来ます。特に更新や削除を行う場合、すでにクラウド上に構築済みのリソースの状態をTerraformが把握する必要があります。これを可能にするのがStateファイルです。

StateファイルはJson形式で記述されたテキストファイルで、現在のクラウドにあるリソースの状態が記述されています。Terraformはクラウド上のリソースに操作を行うたびにStateファイルを更新し、クラウド上のリソースの最新の状態を把握しています。

Stateファイルはローカルのプロジェクトフォルダ内に保持することが出来ます。一人で開発する場合はこれで問題がないのですが、複数人で開発する場合は全員が常に同じ内容のStateファイルを共有していないと不整合が生じる恐れがあります。その場合はStateファイルをクラウド上のストレージに格納し、全員が同一のStateファイルを共有するようにすることが出来ます。

初期化(init)からクラウドへの適用(apply)までの流れ

色々な情報を見ると、Terraformのプロジェクトの初期化(init)からクラウド環境への適用(apply)までは基本的には以下のような手順を取っています。

初期化(init)

設定ファイルの記述の後、最初に行うのがプロジェクトの初期化です。設定ファイルで指定された使用するプロバイダの情報を元に、必要なPluginのダウンロードが行われます。

設定ファイルの検証(validate)

設定ファイルの内容に不正な個所が無いか、検証を行います。たとえばHCLの構文を間違えていないか、定義されていない変数がないか、といった実行前に確認できる内容を検証します。

計画の作成(plan)

Terraformは宣言型の方法を採用しているので、設定ファイルに記述されたリソースをどのように作成するのかはTerraformが判断します。Terraformではクラウドへの適用の前に設定ファイルに基づいて計画を作成します。

この段階ではまだクラウドのリソースへの操作は行われません。計画の作成でエラーが発生することもあります。

クラウドへの適用(apply)

計画が問題なく完了したら、計画に基づいてクラウド上のリソースへの操作が行われます。この処理が完了すると、Stateファイルが生成されたり最新の内容に更新され、現在のクラウド上のリソースの状態の反映が行われます。

リソースの削除(destroy)

プロジェクトで作成したリソースが不要になった場合は一括で削除することも出来ます。

試してみる

Terraformの仕組みが少し分かってきたので、実際にTerraformを使ってクラウドにリソースを作成してみました。

参考にしたドキュメントはTerraform Registry上のAzure Providerのページです。

registry.terraform.io

このページにExample UsageとしてリソースグループとVirtual Networkを作成する設定ファイルの例が掲載されていたので、そちらを参考にしながら試してみました。

プロジェクの構成

Terraform用のプロジェクト用のフォルダを作成し、その中に以下の様に設定ファイルを作成し、格納しました。

.
└── first_tf
    ├── main.tf
    ├── variable.tf
    └── output.tf

それぞれのファイルの役割は、以下の様になっています。

  • main.tf
    providerやresourceを定義します。
  • variable.tf
    プロジェクト実行時に指定可能な変数を定義します。今回は各リソース名につける接頭文字やリソースを作成するロケーションを変数にしました。
  • output.tf 実行時にコンソールに出力する内容を定義します。クラウドに作成されるリソースグループとVirtual Networkの名前を出力するようにしました。

それぞれファイルの内容は次の様にしました。

main.tf

# We strongly recommend using the required_providers block to set the
# Azure Provider source and version being used
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "=3.0.0"
    }
  }
}

# Configure the Microsoft Azure Provider
provider "azurerm" {
  skip_provider_registration = true
  features {}
}

# Create a resource group
resource "azurerm_resource_group" "rg" {
  name     = format("%s-%s", var.resource_name_prefix, "rg")
  location = var.resource_location
}

# Create a virtual network within the resource group
resource "azurerm_virtual_network" "vnet" {
  name                = format("%s-%s", var.resource_name_prefix, "vnet")
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  address_space       = ["10.0.0.0/16"]
}

variable.tf

variable "resource_name_prefix" {
  type        = string
  default     = "example"
  description = "リソース名のprefix"
}

variable "resource_location" {
  type        = string
  default     = "Japan East"
  description = "リソースを配置するlocation"
}

output.tf

output "resource_group_name" {
  value       = azurerm_resource_group.rg.name
  sensitive   = false
  description = "リソースグループ名"
  depends_on  = []
}

output "vnet_name" {
  value       = azurerm_virtual_network.vnet.name
  description = "Virtual Network名"
  depends_on  = []
}

また、作業前に対象のサブスクリプションに対するContributorロールを設定したサービスプリンシパルを作成し、設定しておきます。次のドキュメントを参考にしました。

learn.microsoft.com

実行する

最初にterraform initを実行し、必要なPlugin等をダウンロードします。

terraform init

コマンドを実行すると、以下のようなメッセージがコンソールに表示されます。

Initializing provider plugins...
- Finding hashicorp/azurerm versions matching "3.0.0"...
- Installing hashicorp/azurerm v3.0.0...
- Installed hashicorp/azurerm v3.0.0 (signed by HashiCorp)
...

次にterraform validateを実行してコードに不正な箇所がないか確認をします。もし不正な箇所があった場合は以下の様にエラーが表示されます。

│ Error: Reference to undeclared resource
│
│   on output.tf line 9, in output "vnet_name":
│    9:   value       = azurerm_resource_group.vnet.name
│
│ A managed resource "azurerm_resource_group" "vnet" has not been declared in the root module.

output.tfでVirtual Networkの名前を取得しようとしている箇所で、azurerm_virtual_networkと書くべき箇所をazurerm_resource_groupと書いてしまったことが原因のエラーでした。

修正して再度実行し、問題がなければ成功した旨のメッセージを確認することが出来ます。

Success! The configuration is valid.

今度はリソース作成の前の計画を行います。terraform planのオプション-varで変数に入れたい値を指定することが出来ます。

terraform plan -var "resource_name_prefix=test" -var 'resource_location="Japan East"' -out plan.tfplan

成功すると次のようなメッセージが表示されます。

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

Changes to Outputs:
  + resource_group_name = "test-rg"
  + vnet_name           = "test-vnet"

この段階ではまだリソースの作成は行われていません。最後にapplyコマンドを実行することで、クラウドへのリソースの作成が行われます。

terraform apply "plan.tfplan"

以下のメッセージが表示され、リソースがクラウドに作成されます。

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

Outputs:

resource_group_name = "test-rg"
vnet_name = "test-vnet"

リソースが不要になった場合はdestroyを実行します。

terraform destroy

消しても良いか確認を求められるので、"yes"を入力し、実行すると今回作成したリソースが削除されます。

`terraform destroy`実行時のメッセージ

まとめ

今回はIaCについて調べ、IaCの代表的なツールであるTerraformを使って実際にAzureのリソースを作る手順を試してみました。今回は使わなかったのですが、TerraformのHCLではループ処理など便利な機能が備わっているのでドキュメントを参考に今後も使っていこうと思います。