[小ネタ] WindowsでTerraformを試してみた
しばたです。
クラスメソッドに入社して以来環境構築にはCloudFormationを使ってきたのですが、CloudFormationにもそこそこ慣れ「そろそろTerraformに手を出そう」と思ったので試してみました。
Terraform入門系のブログ記事は既に腐るほどあるので本記事で得られるものはさほど無いかもしれませんが軽い小ネタだと思ってご覧ください。
本記事ではWindows環境へのTerraformのインストールと簡単な環境構築までを行っていきます。
【追記】TerraformUtil
主にWindows向けにTerraform周りの環境構築(tfenv
相当の機能やtflint
のインストールなど)を担うツールを自作しました。
このPowerShellモジュールを使ってインストールを簡易に行うことも可能です。
検証環境
検証環境には私の開発機である64bit版 Windows 10 May 2019 Update(1903)を使います。
またテキストエディタにはVisual Studio Code + Terraform拡張を使用しています。
(Visual Studio Code周りの構築手順には触れません)
インストール
Windows版のTerraformはZipアーカイブ中に単一の実行ファイルであるterraform.exe
があり、このファイルを展開すればインストール完了です。
PowerShell(PowerShell Coreでも可)からだと以下の様な感じでインストールおよびPATH環境変数の設定までを行えます。
# 現時点での最新版である Ver.0.12.8 をインストール
$uri = 'https://releases.hashicorp.com/terraform/0.12.8/terraform_0.12.8_windows_amd64.zip'
$outPath = Join-Path $env:TEMP 'terraform_0.12.8_windows_amd64.zip'
$destPath = 'C:\hashicorp\terraform'
# ZIPファイルのダウンロード
Invoke-WebRequest -Uri $uri -OutFile $outPath
# ZIP展開+PATH環境変数の更新
Expand-Archive -Path $outPath -DestinationPath $destPath
[Environment]::SetEnvironmentVariable('PATH', [Environment]::GetEnvironmentVariable('PATH', 'User') + ";$destPath", 'User')
# ZIP削除
Remove-Item $outPath
これでC:\hashicorp\terraform
にterraform.exe
が展開されます。
コンソールを再起動すればPATHが通るので、こんな感じでTerraformのコマンドを呼び出せる様になります。
試してみた
今回は以前の記事で作成した様なベストプラクティスに沿った高可用性ネットワークを作ってみます。
tfファイル
適当なディレクトリ(本記事ではC:\temp\tfsample\
)に以下の2ファイルを作成します。
AWS認証情報の設定方法は様々ですが、今回はお試しということもありProvider設定に直接Access Key/Secret Keyを記述しています。
認証情報の記述は環境に応じて適切な方式を選んでください。
provider "aws" {
access_key = "<your access key>"
secret_key = "<your secret key>"
region = "ap-northeast-1"
}
resource "aws_vpc" "vpc" {
cidr_block = "172.16.0.0/16"
instance_tenancy = "default"
enable_dns_support = "true"
enable_dns_hostnames = "true"
tags = {
Name = "${var.system_name}-${terraform.workspace}-vpc"
}
}
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.vpc.id
tags = {
Name = "${var.system_name}-${terraform.workspace}-igw"
}
}
resource "aws_subnet" "public_subnet1" {
vpc_id = aws_vpc.vpc.id
cidr_block = "172.16.1.0/24"
tags = {
Name = "${var.system_name}-${terraform.workspace}-public-subnet1"
}
}
resource "aws_subnet" "public_subnet2" {
vpc_id = aws_vpc.vpc.id
cidr_block = "172.16.2.0/24"
tags = {
Name = "${var.system_name}-${terraform.workspace}-public-subnet2"
}
}
resource "aws_route_table" "public_rt1" {
vpc_id = aws_vpc.vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
tags = {
Name = "${var.system_name}-${terraform.workspace}-rt1"
}
}
resource "aws_route_table_association" "public-rta1" {
subnet_id = aws_subnet.public_subnet1.id
route_table_id = aws_route_table.public_rt1.id
}
resource "aws_route_table_association" "public-rta2" {
subnet_id = aws_subnet.public_subnet2.id
route_table_id = aws_route_table.public_rt1.id
}
resource "aws_eip" "eip1" {
vpc = true
tags = {
Name = "${var.system_name}-${terraform.workspace}-eip1"
}
}
resource "aws_nat_gateway" "ngw1" {
allocation_id = aws_eip.eip1.id
subnet_id = aws_subnet.public_subnet1.id
tags = {
Name = "${var.system_name}-${terraform.workspace}-ngw1"
}
}
resource "aws_eip" "eip2" {
vpc = true
tags = {
Name = "${var.system_name}-${terraform.workspace}-eip2"
}
}
resource "aws_nat_gateway" "ngw2" {
allocation_id = aws_eip.eip2.id
subnet_id = aws_subnet.public_subnet2.id
tags = {
Name = "${var.system_name}-${terraform.workspace}-ngw2"
}
}
resource "aws_subnet" "private_subnet1" {
vpc_id = aws_vpc.vpc.id
cidr_block = "172.16.11.0/24"
tags = {
Name = "${var.system_name}-${terraform.workspace}-private-subnet1"
}
}
resource "aws_subnet" "private_subnet2" {
vpc_id = aws_vpc.vpc.id
cidr_block = "172.16.12.0/24"
tags = {
Name = "${var.system_name}-${terraform.workspace}-private-subnet2"
}
}
resource "aws_route_table" "private_rt1" {
vpc_id = aws_vpc.vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_nat_gateway.ngw1.id
}
tags = {
Name = "${var.system_name}-${terraform.workspace}-rt1"
}
}
resource "aws_route_table_association" "private-rta1" {
subnet_id = aws_subnet.private_subnet1.id
route_table_id = aws_route_table.private_rt1.id
}
resource "aws_route_table" "private_rt2" {
vpc_id = aws_vpc.vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_nat_gateway.ngw2.id
}
tags = {
Name = "${var.system_name}-${terraform.workspace}-rt2"
}
}
resource "aws_route_table_association" "private-rta2" {
subnet_id = aws_subnet.private_subnet2.id
route_table_id = aws_route_table.private_rt2.id
}
variable "system_name" {
type = "string"
default = "mysystem"
description = "Your System Name"
}
terraform init
tfファイルを記述したのでterraform init
コマンドでバックエンドの初期化処理を行います。
今回はバックエンドの設定はデフォルトのままローカルファイルで行います。
cd C:\temp\tfsample
terraform init
.terraform
フォルダ配下にTerraform AWS providerなどの実行ファイルがインストールされ初期処理が完了します。
terraform workspace
今回はtfファイル中でワークスペース名を使っているのでterraform workspace
コマンドで適当なワークスペース(dev
)を定義しておきます。
terraform workspace new dev
terraform validate / plan
terraform validate
やterraform plan
コマンドで設定内容の確認を行います。
planの表示がCloudFormationよりあっさりして分かりやすいのが良いですね。
terraform apply
設定に問題がなければterraform apply
コマンドで実環境に反映させます。
今回は特に追加パラメーターは指定せず実行します。
terraform apply
途中で処理を続行させるか確認されるのでyes
を入力して続行します。
以降はAWSリソースが作成されていきます。
無事リソースの作成が完了しました。
terraform destroy
作ったリソースが不要になったらterraform destroy
で環境を破壊します。
この場合も途中で処理を続行させるか確認されるのでyes
を入力して続行します。
terraform destroy
最後に
ざっとこんな感じです。
Windows環境でも特別なことを意識せずにTerraformは使えました。
TerraformのAWS Providerで定義されるリソース構成は割とCloudFormationのリソース構成と近い印象を受け、HCLコードの記述量や見通しの良さを考えるとベストプラクティスとか深く考えずとりあえずTerraformで環境定義を書いてしまうのもアリかなぁという印象を受けました。
CloudFormationとTerraformの使い分けについてまだ自分の中の答えは出ていませんが、しばらくは両方使いながら考えていきたいと思います。
補足
補足として環境面でハマった点について触れておきます。
今回はエディタにVisual Studio Code + Terraform拡張 Ver.1.4.0を使いましたが、Terraform拡張 Ver.1.4.0ではHCL 2.0(Terraform 0.12以降)の文法には正式に対応しておらず、試験的な機能としてHCL 2.0対応がなされています。
今回試験的な機能を有効にしてHCLSP 2.0に対応させてみたところterraform plan / terraform applay
といったコマンドがタイムアウトする事象に遭遇してしまいました。
おそらくGitHubの以下のIssueに該当すると思うのですが、いまいちはっきりしません...
私は試せていませんがもしかしたらWindows以外の環境でも発生するかもしれませんのでVisual Studio Code + Terraform拡張をお使いの方は気を付けてください。