Terraformを触ってみた

どーも皆さまお久しぶりです!Technology Department(開発部)でインフラ周りを担当している山口です!
さて今回はインフラストラクチャ定義ツールで有名なTerraformを触ってみましたのでこちらについてご紹介していきます!

まずその前にInfrastructure as Codeについて少しご説明したいと思います。
Infrastructure as Code(IaC)とはインフラをコードで構成管理を行い、実際の構築やパラメータの等の変更も自動的に反映できるプロセスのことです。

AWSやGCPなどのパブリッククラウドを利用してインフラを構成することが多くなってきた中で、通常はコンソールから地道にインフラを構築しておりましたが、IaCはコードにインフラの構成が記述されていることからコマンド一つで同じ構成の環境を用意できたり、どのような構成なのかコード上で管理できるようになりインフラ特有の属人化の排除に繋げられるとメリットは様々です。

IaCを利用するツールとしてはAnsibleやchef等がありますが、今回はTechnology Departmentでも取り入れ始めているTerraformを使用してAWSのEC2インスタンスを作成してみました。

Terraformとは

Terraform
TerraformはHashiCorp社によって開発されたインフラストラクチャ定義ツールです。
HCL(HashiCorp Configuration Language)という⾔語で構成されています。
AWSのみならずGCPやAzureといったパブリッククラウドやDatadogのような監視サービスなど幅広く対応している点が特徴です。

準備編

まずはTerraformが使えるように準備していきます。 HomebrewでTerraform のバージョンマネージャであるtfenvをインストールします。

$ brew install tfenv

次にtfenv経由でTerraform のバージョンを指定してインストールします。

$ tfenv install 0.12.24

Terraformがインストールが完了している確認します。
下記のようにバージョンが出力されたらインストール完了です。

$ terraform --version
Terraform v0.12.24

今回はAWSのリソースを作成するためにアクセスキーとシークレットキーの設定を行います。
クレデンシャルを読み込む方法はいくつか方法はありますが、terraform.tfvarsを使う方法が推奨となっているため、今回はterraform.tfvarsに値を持たせファイルで値を渡すという方法をとります。こちらのファイルはgitignoreでGitの管理対象外にする方が良いでしょう。

access_key  = "アクセスキー"
secret_key  = "シークレットキー"

構築編

続いて実際に構築に入ります。
今回作成するリソースはbg-test-ec2というEC2インスタンス名でインスタンスタイプはt3.nanoで作成します。
基本的にはtfファイルに作成したいリソースを「resource」ブロックで定義しますが、Terraformもモジュールを定義することができ、今回はモジュールを利用することでインスタンスを作成しています。

モジュールを利用するファイル

module "ec2" {
  source                 = "../../modules/ec2"
  ami_id                 = "ami-XXXXXXXXXXXXXX"
  subnet_id              = aws_subnet.public-subnet1a.id
  vpc_security_group_ids = [module.private-sg.security_group_id]
  instance_type          = "t3.nano"
  iam_instance_profile   = aws_iam_instance_profile.ec2-profile.name
  tag_name               = "${var.tag_product}-${var.tag_env}-ec2"
  tag_env                = var.tag_env
  tag_product            = var.tag_product
  tag_type               = "test"
}

モジュール定義するファイル

resource "aws_instance" "ec2" {
  ami                    = var.ami_id
  instance_type          = var.instance_type
  subnet_id              = var.subnet_id
  vpc_security_group_ids = var.vpc_security_group_ids
  iam_instance_profile   = var.iam_instance_profile
  ebs_optimized          = true
  monitoring             = true
  # disable_api_termination  = true
  tags = {
    Name    = var.tag_name
    env     = var.tag_env
    product = var.tag_product
    type    = var.tag_type
  }
}

次にterraform initコマンドでTerraform の実行に必要なバイナリをダウンロードします。
Terraform has been successfully initialized!と表示されていれば成功です。

$ terraform init
Initializing modules...

Initializing the backend...

Initializing provider plugins...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.aws: version = "~> X.XX"

Terraform has been successfully initialized!

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

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

terraform planコマンドでtfファイルに記載したリソースがdry-runのように正常に作成できるか事前に確認できます。
こちらを見てみるとbg-test-ec2というEC2インスタンス名でインスタンスタイプはt3.nanoで作成されることがわかります。

$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

~省略〜

  # module.ec2.aws_instance.ec2 will be created
  + resource "aws_instance" "ec2" {
      + ami                          = "ami-XXXXXXXXXXXXXX"
      + arn                          = (known after apply)
      + associate_public_ip_address  = (known after apply)
      + availability_zone            = (known after apply)
      + cpu_core_count               = (known after apply)
      + cpu_threads_per_core         = (known after apply)
      + ebs_optimized                = true
      + get_password_data            = false
      + host_id                      = (known after apply)
      + iam_instance_profile         = "bg-test-ec2-role"
      + id                           = (known after apply)
      + instance_state               = (known after apply)
      + instance_type                = "t3.nano"
      + ipv6_address_count           = (known after apply)
      + ipv6_addresses               = (known after apply)
      + key_name                     = (known after apply)
      + monitoring                   = true
      + network_interface_id         = (known after apply)
      + outpost_arn                  = (known after apply)
      + password_data                = (known after apply)
      + placement_group              = (known after apply)
      + primary_network_interface_id = (known after apply)
      + private_dns                  = (known after apply)
      + private_ip                   = (known after apply)
      + public_dns                   = (known after apply)
      + public_ip                    = (known after apply)
      + security_groups              = (known after apply)
      + source_dest_check            = true
      + subnet_id                    = (known after apply)
      + tags                         = {
          + "Name"    = "bg-test-ec2"
          + "env"     = "test"
          + "product" = "bg"
          + "type"    = "test"
        }

~省略〜

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

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run

いよいよ実行移りますがterraform applyコマンドで実際に実行してリソースを作成します。
実行するとterraform planコマンドの時と同様に作成される予定のリソースが出力後、本当に作成するか確認されyesと打つと実際にAWSのリソースが作成されます。
最後にApply complete!が出力されると成功となります。

$ terraform apply
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

~省略〜

Plan: 18 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: yes

aws_vpc.vpc: Creating...

~省略〜

module.ec2.aws_instance.ec2: Still creating... [10s elapsed]
module.ec2.aws_instance.ec2: Still creating... [20s elapsed]
module.ec2.aws_instance.ec2: Creation complete after 22s [id=i-0ffd188a384467fbc]

~省略〜

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

実際にAWSのコンソールを確認してみると、bg-test-ec2というEC2インスタンス名でインスタンスタイプはt3.nanoで作成されていることを確認できました。

Terraform実行結果

逆に削除したい場合は、terraform destroyコマンドでTerraformで作成したリソースを削除することができます。

$ terraform destroy

このようにTerraformによりインフラをコード化することで、今までインフラにかけてたリソースを他の作業に割り当てることができたり、環境の差分による問題やヒューマンエラーが防げることは大変メリットと考えております。さらに実運用に向けて他のリソース作成やコードの構成を検証しながらインフラの自動化をどんどん進めていきたいと思います!