Docker上にterraformの環境を構築し、クラウドサービス上(AWS)に自動でインフラを構築する。
Docker上にterraform環境を構築したのは、ホスト環境をキレイなままに保っておきたかったからです。この記事では、開発環境と基礎知識をまとめます。
開発環境は、下記で実施しました。
開発環境
- Edition: Windows 11 Home, Version: 22H2, OSビルド: 22621.1702
- WSL2
- Docker Desktop for Windows: 4.20.1 (110738)
- Docker Engine: 24.0.2
- Docker Compose: v2.18.1
- terraform 1.4.6
- aws(provider) 5.4.0
次に、ディレクトリは下記の構成にしました。
ディレクトリ構成
Docker/home/user
└terraform
まずは、必要なソフトをインストールしていきます。
AWS CLIのインストール
下記の公式サイトにアクセス
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
apt install unzip #Linux環境にインストールされていない場合に実行
unzip awscliv2.zip
sudo ./aws/install
インストールが完了されているか、AWSのVersionを確認する。
aws --version
# aws-cli/2.12.1 Python/3.11.3 Linux/5.10.16.3-microsoft-standard-WSL2 exe/x86_64.ubuntu.20 prompt/off
IAMユーザーの作成
AWSマネジメントコンソールから、AWS CLI管理用とTerraform用にIAMユーザーを作成していきます。下記のFullAccess権限を持ったユーザーを作成します。
- AWS CLI管理用:すべてのリソースに対するFullAccess権限
- Terraform用:すべてのリソースに対するFullAccess権限
作成したユーザーをAWS CLIのプロファイルに登録していきます。
AWS CLIを使ってAWSを操作する為に必要な認証情報になります。
#aws configure [--profile] user_name
aws configure #defualt
AWS Access Key ID [None]: *******
AWS Secret Access Key [None]: *******
Default region name [None]: ap-northeast-1
Default output format [None]: json
aws configure --profile terraform
AWS Access Key ID [None]: *******
AWS Secret Access Key [None]: *******
Default region name [None]: ap-northeast-1
Default output format [None]: json
Terraformのインストール
次にTerraformをインストールしていきます。
-
tfenvインストール
- GitHubリポジトリクローン
consolecd ~ git clone --depth=1 https://github.com/tfutils/tfenv.git ~/.tfenv
- パスを通す
consoleecho 'export PATH="$HOME/.tfenv/bin:$PATH"' >> ~/.bash_profile && e ~/.bash_profile> source ~/.bash_profile
-
terraformインストール
consoletfenv list-remote tfenv install <VERSION> tfenv use <VERSION>
-
動作確認
terraform version
git-secrets インストール
git-secretsとは、アクセスキーやシークレットキー等credentials情報をGithubへアップロードすることを防いでくれるツール。
- git-secretsインストール
#リポジトリをクローン
git clone https://github.com/awslabs/git-secrets
#インストール
cd git-secrets
make install
#動作確認
git secrets
######
usage: git secrets --scan [-r|--recursive] [--cached] [--no-index] [--untracked] [<files>...]
or: git secrets --scan-history
or: git secrets --install [-f|--force] [<target-directory>]
or: git secrets --list [--global]
or: git secrets --add [-a|--allowed] [-l|--literal] [--global] <pattern>
or: git secrets --add-provider [--global] <command> [arguments...]
or: git secrets --register-aws [--global]
or: git secrets --aws-provider [<credentials-file>]
######
- git-secrets初期化
git secrets --register-aws --global
git secrets --install ~/.git-templates/git-secrets -f
git config --global init.templatedir ~/.git-templates/git-secrets
- 動作確認
git init
git add .
git commit -m "test"
EC2起動
ここまでの設定が出来たら、テストとして、EC2を起動してみます。
terrafomディレクトリ直下にmain.tfを作成します。
touch main.tf
provider "aws" {
profile = "terraform"
region = "ap-northeast-1"
}
resource "aws_instance" "hello-world" {
ami = "ami-0f9816f78187c68fb" #EC2のamiを確認して下さい。
instance_type = "t2.micro"
}
terraformディレクトリ直下へ移動してから、下記のコマンドを実行します。
terraform init
terraform plan
terraform apply
#############
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 #yesを入力
#############
マネジメントコンソールからEC2を検索し、インスタンスでEC2が起動されていることを確認して下さい。
.tfstateファイル
インスタンスを起動させるとterraformディレクトリに.tfstateファイルが作成される。.tfstateファイルには、terraformを使って構築したクラウドの状態が記載されている。
EC2削除
今回はテストとして、EC2を起動したので、確認出来たら削除しておきます。
下記コマンドを実行し、削除しておく。
terraform destroy
Terraformの基本コマンド
terraform plan
現状のソースコードを元に実行計画を確認
terraform plan [-var <KEY>=<VALUE>] [-var-faile <VAR_FILE>]
# -var <KEY>=<VALUE> 変数の指定
# -var-faile <VAR_FILE> 変数ファイルの指定
terraform apply
ソースコードに従って変更を適用する
terraform apply [-auto-approve][-var <KEY>=<VALUE>] [-var-faile <VAR_FILE>]
# -auto-approve 実行計画の確認なしで適用(yes省略)
# -var <KEY>=<VALUE> 変数の指定
# -var-faile <VAR_FILE> 変数ファイルの指定
terraform destroy
Terraform管理のインフラ環境を削除する
terraform destroy [-auto-approve]
# -auto-approve 実行計画の確認なしで適用(yes省略)
terraform fmt
コードのインデント修正コマンド
terraform fmt
HCL2について
HashiCorpLanguage2:HCL2とは、ハシコープ社の独自言語。
- jsonによく似ているが少し違う。
- 簡単なプログラムが組める。
- #でコメントが記載出来る
- KEY・VALUEを"."ではなく"="で指定
- ヒアドキュメントが利用可能
- ブロックごとに記述する
- ブロックには、接頭辞のブロックタイプ、次に続くラベルがある。
ブロックタイプ
#ブロックタイプの種類:説明
locals #外部から変更できないローカル変数
variable #外部から変更可能な変数
terraform #Terraformの設定
provider #プロバイダ
data #Terraform管理していないリソースの取り込み
resource #Terraform管理対象となるリソース
output #外部から参照できるようにする値
locals(変数)ブロックの設定
- HCL2で利用可能な変数は下記の2種類
locals #ローカル変数。
#プライベートな変数で外部外部から変更できない。
variable #外部から変更可能な変数。
#コマンドライン実行時にオプションやファイル指定で上書きできる
#localsブロックで定義して${local.変数名}で参照
locals {
project = "tastylog"
env = "dev"
}
resource <RESORCE_TYPE> <RESORCE_NAME> {
tags = {
Name = "${local.project}-${local.env}-vpc"
}
}
output <OUTPUT_NAME>{
####
}
variableブロックの設定
#variableブロックで定義して${var.変数名}で参照
variable "project" { #変数名
type = string #変数の型
default = "tastylog" #変数のデフォルト値
}
resource <RESORCE_TYPE> <RESORCE_NAME> {
tags = {
Name = "${var.project}-dev-vpc"
}
}
output <OUTPUT_NAME>{
####
}
データ型
#データの型名:種類・説明
string #プリミティブ:Unicode文字列
number #プリミティブ:数値。整数と少数の両方を表現
bool #プリミティブ:true/falseの2値。
object({<NAME>=<TYPE>, ・・・}) #構造体:キーバリュー型データ
tuple([<TYPE>, ・・・]) #構造体:各列の型が決まっている配列。
list(<TYPE>) #コレクション:特定の型で構成される配列。
map(<TYPE>) #コレクション:キーが文字列の配列
set(<TYPE>) #コレクション:値の重複がない配列
- string
variable "message" {
type = string
default = "Hello World"
}
variable "max_count" {
type = number
default = 10
}
variable "is_enable" {
type = bool
default = true
}
- object
キーバリュー形式で定義されるデータ型
variable "obj_sample" {
type = object({
name = string
age = number
})
default = {
name = "tanaka"
age = 28
}
}
- tuple
配列のN番目にどういった型を使うかが決められたデータ型
variable "tuple_sample" {
type = tuple([
string, number #基本は配列でN番目の型が固定される
])
default = ["tanaka", 28]
}
- list
全て同じ型で指定される配列
variable "list_sample" {
type = list(string)
default = ["tanaka", "sato"]
}
username = var.list_sample[0]
- map
キーが文字列、バリューが指定された型となる配列
variable "map_sample" {
type = map(string)
default = {
"High" = "m5.2xlarge"
"Mid" = "m5.2large"
"Low" = "t2.micro"
}
}
instance = var.map_sample.High
- set
バリューの重複が排除される配列
variable "set_sample" {
type = set(string)
default = [
"tanaka",
"sato",
"tanaka",
"sato"
]
}
[for itm in var.set_sample : itm]
terraformブロックの設定
terraformとプロバイダー(AWS,GCP,Azure)は別物。
それぞれバージョンが存在し、日々変化していく。
- terradformのバージョン固定
required_version #Terraformのバージョン指定。main.tfに記載
- プロバイダーのバージョン固定
required_providers #providerのバージョン指定。main.tfに記載
providerブロックの設定
profile #AWSへアクセスする為のプロファイル(クレデンシャル)
region #デフォルトリージョン
dataブロックの設定
dataブロックでは、管理対象外のリソース(「AWSが作成したリソース」「既存システムのリソース」「外だしの設定」)を参照させる。
取り込みたいリソースによって記述方法が異なる。
outputブロックの設定
作成済みのリソースを外部参照できるようにするブロック。
resource "aws_instance" "hello-world" {
ami = "ami-0ce107...."
instance_type = "t2.micro"
}
output "ec2_instance_id" { #参照時に指定する名前
value = aws_instance.hello-world.id #出力する値
}
リソース参照
HCL2でリソース参照する記述
"."(ドット)で連結して参照
<BLOCK_TYPE>.<LABEL_1>.<LABEL_2>
"."(ドット)連結で表現されるリソースパスを"アドレス"と呼ぶ。
resourceブロックの場合、省略可能。
<LABEL_1>.<LABEL_2> #LABELから始まる
組み込み関数
Hashicorp社HP → "Docs" → "Terraform Language" → "Functions"以下にリファレンスがある。
#種類:説明
numeric #数値演算、絶対値、切り上げ、切り捨て、最大、最小等。
string #文字列操作、フォーマット、結合、抜き出し等。
collection #配列操作、結合、分割、ソート等。
encoding #エンコード、Base64、CSV、JSON、YAMLなどの変換。
filesystem #ファイル操作。ディレクトリ名取得、ファイル読み取り等。
date & time #日付操作、現在時刻取得、データのフォーマット指定など。
Hash & Crypt #ハッシュおよび暗号化、ホスト名取得、サブネット名取得など。
IP Network #CIDR表記の演算、ホスト名取得、サブネット名取得など。
Type Conversion #型変換、bool、string、numberなどへの変換。
組み込み関数の試し方
指定された設定で関数を実行します。
terraform console
[-var <KEY>=<VALUE>] #変数の指定
[-var-file <VAR_FILE>] #変数ファイルの指定
複数リソースの作成
count
複数リソースを生成する方法として、count,for_eachの2つがある。countとfor_eachは同時に作成する事はできない。指定された数分のリソースを生成する。
resource <RESORCE_TYPE> <RESORCE_NAME> {
count = 4
ami = ""
instance_type = ""
tags = {
Name = "${count.index}"
}
}
for_each
指定されたmapまたはsetを展開しながら複数リソース生成する
# mapの場合
resource "aws_vpc" "vpc" {
cidr_block = "192.168.0.0/20"
}
resource "aws_subnet" "subnet" {
for_each = { #キーバリュー形式で指定
"192.168.1.0/24" = "ap-northeast-1a"
"192.168.2.0/24" = "ap-northeast-1c"
"192.168.3.0/24" = "ap-northeast-1d"
}
vpc_id = aws_vpc.vpc.id
#each.keyまたheach.valueで値を利用
cidr_block = each.key
availability_zone = each.value
}
配列を指定したい場合、toset関数を利用してsetへ変換する
# for_eachの場合
resource "aws_iam_user" "user" {
for_each = toset([ #toset()でsetへ変換。重複削除
"tanaka",
"sato"
])
name = each.key #利用可能なのはeach.keyのみ
}
for文
リスト型(list, map, set)を異なるリスト型(list, map)へ変換する構文
#list,set => list
[for s in var.list : upper(s)]
#list,set => map
{for s in var.list :s => upper(s)}
#map => list
[for k, v in var.map : upper(v)]
#map => map
{for k, v in var.list :k => upper(v)}
フィルター
[for s in var.list upper(s) if s != ""]
3項演算子
terraformにif文がないので、if文の代わりに使用する
[var.a != "" ? var.a : "default_value"]
モジュール
モジュールとは、リソース生成処理を1つの塊にして呼び出せるようにしたもの(プログラミングにおける関数のようなもの)。関数というくらいなので、「呼び出し方法」と「定義方法」の2つがある。
- 定義方法
"modules"などの下に別フォルダ作成して定義する
ROOT
├ modules
├ └ MODULE NAME
└ main.tf
モジュールのディレクトリ内にファイルを作成していく。
MODULE NAME
├ variables.tf #引数
├ main.tf #処理
├ output.tf #戻り値
└ README.md #説明
- 呼び出し方法
"modules"ブロックを利用して呼び出す
ROOT
├ modules
├ └ nginx_server
└ main.tf
module "webserver" {
source = "./modules/nginx_server"
#モジュールが必要とする引数を列挙する
instance_type = "t2.micro"
モジュールの出力(output)は"."(ドット)表現(アドレス表現)で参照
ROOT
├ modules
├ └ nginx_server
└ main.tf
module "webserver" {
source = "./modules/nginx_server"
instance_type = "t2.micro"
}
output "instance_id" {
value = module.webserver.id
}