Terraform ベストプラクティス 2020 春 ~moduleやめてみた~

Terraform ベストプラクティス 2020 春 ~moduleやめてみた~

概要

#InfraStudy に刺激を受け、書きます!

2019 年に以下記事を書いてから早 1 年、terraform 運用歴を重ね、2020 年春のベストプラクティスを更新しました。

例によって、まず結論、

続きを読む

terraform 0.11 系に対応した GitHub Actions 作った & tflint も入れてみた♪

概要

Terraform 用の GitHub Actions として hashicorp 社にて以下リポジトリが用意されています。

hashicorp/terraform-github-actions

ですが、上記のリポジトリでは、 terraform の最新版 (2019-09-30 時点 0.12.9) にのみ適用しています。

hashicorp/terraform-github-actions を folk して
0.11 系がなかった為、 0.11 系に対応した terraform-github-actions を以下リポジトリに作成しました。

kenzo0107/terraform-github-actions

続きを読む
Terraform 運用ベストプラクティス 2019 ~workspace をやめてみた等諸々~

Terraform 運用ベストプラクティス 2019 ~workspace をやめてみた等諸々~

2020-05-05 追記 2020 年春のベストプラクティス更新しています。


以前 terraform で workspace 毎に tfstate 管理する方法を執筆しましたが、実運用上いくつかの問題がありました。

結論、現在は workspace 運用をやめています。

workspace 運用例

まずは実際の運用例です。

もっとうまいことやってるぞ!という話はあろうかと思いますが、まずはありがちなケースを紹介します。

続きを読む
terraform workspace で環境毎に tfsate 管理

terraform workspace で環境毎に tfsate 管理

terraform workspace で環境毎に tfsate 管理した話です。

追記 2019/04/17

追記時点で workspace は運用時点の問題が多くあった為、利用していません。以下記事ご参考いただければと思います。

概要

Terraform tfstate の管理をかつて
0.8 系では -backend-config でせっせと環境(stg,prod) 毎に bucket を変えて、
なんてコードを見てきました。

ですが、
workspace で 1 つの bucket に 環境毎に保管できる様になりました。

厳密には環境毎でなくとも
リソースの集合毎、module 毎等で管理するイメージですが

今回はイメージを捉えやすく環境毎で分けました。

歴史

  • 0.5 で S3 で管理、
  • < 0.9 では、 remote config で管理場所を設定
  • = 0.9 では、terraform workspace で同一ディレクトリで複数のリソース群を管理

とより利用しやすくなりました。

前提

以下条件とします。

  • tfstate は backend.tf で s3 管理

移行手順

既存 terraform で tfstate 確認

  • 想定の実行計画通りか確認します。
  • 異なる場合は、そもそも現環境と差分が生じている、及び、tfstate が正しく取得できていない等、問題ありなのでそちらを修正します。
1
$ terraform plan

tfstate ファイル取得

local に terraform.tfstate を取得します。
中身を確認してリソースの設定がある程度問題ないか確認しておきます。

  • 0.8 系
1
2
3
4
5
6
$ terraform remote config \
-backend=s3 \
-backend-config="bucket=tfstate.bucket" \
-backend-config="key=terraform.tfstate" \
-backend-config="region=ap-northeast-1" \
-backend-config="profile=aws-hogehoge"
  • 0.9 系以降
1
macOS%$ terraform state pull > terraform.tfstate

terraform 0.11.x (2017 年 12 月現在最新) へバージョンアップ

Homebrew ならば upgrade で!

1
macOS%$ brew upgrade terraform

state 管理を backent.tf で記述

既にこの様に設定されている方はスキップです。特に普遍的な書き方です。

1
2
3
4
5
6
7
8
9
terraform {
backend "s3" {
bucket = "tfstate.bucket"
key = "terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
profile = "aws-hogehoge"
}
}

Workspace 作成

  • Workspace stg 作成
1
$ terraform workspace new stg
  • workspace リスト一覧
1
2
3
$ terraform workspace list
default
* stg

tfstate を push

1
$ terraform state push -force .terraform/terraform.tfstate

これで S3 tfstate.bucketenv:/stg/ ディレクトリ以下に terraform.tfstate が push されました。
実際に S3 を見て確認してみてください。

env でなく env: なのが肝です。

実行計画確認

1
$ terraform plan

想定の実行計画通りか確認して問題なければ移行完了です。

おまけ

terraform を指定したバージョンで実行するには
one-off Container で実行できる様に Makefile でラップする、
が今の所自分の中のベストプラクティスです。

これによって local 環境に依存せず指定したバージョンの terraform 実行が可能となります。

one-off Container とは

one-off Container は Docker コンテナを run --rm で 1 度のコマンド実行の為だけに起動する手法です。

Makefile で Docker コマンドをラップしておくと
TERRAFORM_VERSION を変更するだけで
指定の terraform バージョンを利用できます。

以下は 0.11.1 の例です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
TERRAFORM_VERSION=0.11.1

DOCKER=docker run --rm -v ~/.ssh:/root/.ssh:ro -v ~/.aws:/root/.aws:ro -v ${PWD}:/work -w /work hashicorp/terraform:${TERRAFORM_VERSION}

$(eval ENV := $(shell ${DOCKER} workspace show))

ifeq (${ENV}, default)
$(error select workspace ! ex: make init ENV=<stg|prod>)
endif

default: init

init:
# tfstate ファイル初期化
${DOCKER} init
# workspace 作成. "; true" は既に作成済みエラーをスキップする為
${DOCKER} workspace new ${ENV}; true
# 作成した workspace を選択
${DOCKER} workspace select ${ENV}
# 作成した workspace の tfstate ファイルを同期
${DOCKER} init

plan:
${DOCKER} plan

apply
${DOCKER} apply -auto-approve
  • make init ENV=stg 実行で以下まとめてました
    • tfstate 初期化
    • workspace stg 作成
    • 選択した workspace の tfstate で初期化

きっとさらに素敵なベストプラクティスがあれば教えてください!

参考になれば幸いです。

Terraform でキーペア登録し起動した EC2 に SSH接続

Terraform でキーペア登録し起動した EC2 に SSH接続

今回やること

  • Mac ローカルで公開鍵、秘密鍵を生成
  • Terraform で EC2 起動、セキュリティグループで SSH (ポート 22)許可、key-pair 登録

Terraform の Hello World 的なチュートリアルと思っていただけたら幸いです。

環境

  • Mac OS 10.12.3 (Sierra)
  • Terraform 0.9.1

公開鍵、秘密鍵生成

RSA フォーマットで鍵を生成します。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ssh-keygen -t rsa

Enter file in which to save the key (/Users/kenzo_tanaka/.ssh/id_rsa): /Users/kenzo_tanaka/.ssh/terraform-test
Enter passphrase (empty for no passphrase): (空のままEnter)
Enter same passphrase again: (空のままEnter)
...
...

// 生成されたか確認
$ ls ~/.ssh/terraform-test*

/Users/kenzo_tanaka/.ssh/terraform-test # 秘密鍵
/Users/kenzo_tanaka/.ssh/terraform-test.pub # 公開鍵

公開鍵を起動した EC2 インスタンスに登録し
秘密鍵でアクセスします。

以下のように利用する予定です。

1
$ ssh -i ~/.ssh/terraform-test <ec2 user>@<ec2 public ip>

Terraform 設定ファイル

  • Point !

    • resource "aws_key_pair" で使用する公開鍵設定をしています。
    • resource "aws_security_group" で SSH(ポート 22)を開いてます。
    • resource "aws_instance" で使用しているセキュリティグループの指定は vpc_security_group_ids を利用
      • セキュリティグループの条件追加・削除する場合にインスタンスを一度削除し作り直すことをしたくない場合に vpc_security_group_ids を利用すると良いです。
  • main.tf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
provider "aws" {
access_key = "${var.access_key}"
secret_key = "${var.secret_key}"
region = "${var.region}"
}

resource "aws_instance" "example" {
ami = "${lookup(var.amis, var.region)}"
instance_type = "t2.nano"
key_name = "${aws_key_pair.auth.id}"
vpc_security_group_ids = ["${aws_security_group.default.id}"]
}

resource "aws_key_pair" "auth" {
key_name = "${var.key_name}"
public_key = "${file(var.public_key_path)}"
}

resource "aws_security_group" "default" {
name = "terraform_security_group"
description = "Used in the terraform"

# SSH access from anywhere
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
  • variables.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
variable "access_key" {}
variable "secret_key" {}
variable "region" {
default = "ap-northeast-1"
}

variable "amis" {
type = "map"
default = {
us-east-1 = "ami-13be557e"
us-west-2 = "ami-21f78e11"
ap-northeast-1 = "ami-1bfdb67c"
}
}

variable "key_name" {
description = "Desired name of AWS key pair"
}

variable "public_key_path" {
description = <<DESCRIPTION
Path to the SSH public key to be used for authentication.
Ensure this keypair is added to your local SSH agent so provisioners can
connect.

Example: ~/.ssh/terraform.pub
DESCRIPTION
}
  • terraform.tfvars
1
2
3
4
5
access_key = "A******************Q"
secret_key = "q**************************************Z"

key_name = "terraform-test"
public_key_path = "~/.ssh/terraform-test.pub"

いざ実行

  • 実行計画確認
1
$ terraform plan
  • 実行
1
$ terraform apply

確認

  • AWS コンソール上で起動確認

    • キーペアに terraform-test が指定されています。
    • vpc, subnet も自動的にアタッチされてます。
  • キーペア
    一応キーペアを見てみると登録されているのがわかります。
  • セキュリティグループ確認
  • SSH ログイン確認
1
$ ssh -i ~/.ssh/terraform-test ubuntu@ec2-54-65-244-25.ap-northeast-1.compute.amazonaws.com

無事 SSH ログインできました!

所感

terraform を見ながら各パラメータの利用意図を確認しながら
設定してみましたが
パラメータの説明自体はざっくりで利用方法まではわからないです。

Teffaform のチュートリアルに始まり
その他 Stack Overflow
適宜パターンを蓄積していく学習が程よいと思います。

参考

Terraform で AWS インフラストラクチャ!

Terraform で AWS インフラストラクチャ!

Terraform とは

  • インフラ構成や設定をコードにより実行計画を確認しながら自動化できるツール
  • AWS, Google Cloud 等多数のクラウドサービスで利用可能
  • HashiCorp 社製

今回やること

  • インスタンス起動
  • Elastic IP 付きインスタンス起動
  • インスタンス破棄

非常にミニマムなインフラ構築をしてみます。
※個人のアカウントでも無料枠を使えば数十円しか掛からなかったです。

環境

  • Mac OS Sierra X 10.12.3 16D32
  • Terraform 0.9.1

terraform インストール

1
$ brew install terraform

バージョン確認

1
2
3
$ terraform version

Terraform v0.9.1

では、早速使ってみます。

EC2 instance (t2.micro) 起動

  • main.tf 作成
1
2
3
4
5
6
7
8
9
10
provider "aws" {
access_key = "A******************Q"
secret_key = "q**************************************Z"
region = "ap-northeast-1"
}

resource "aws_instance" "example" {
ami = "ami-71d79f16"
instance_type = "t2.micro"
}
  • 実行計画確認
1
$ terraform plan
  • 実行
1
$ terraform apply

Amazon Console からインスタンスが起動されたことが確認できます。

変数を別ファイルで管理

上記 main.tf を github 等で管理するとなると
access_key, secret_key が露見されてしまいます。

その為、以下の様に別ファイルで管理することが望ましいです。

  • main.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
variable "access_key" {}
variable "secret_key" {}
variable "region" {
default = "ap-northeast-1"
}

provider "aws" {
access_key = "${var.access_key}"
secret_key = "${var.secret_key}"
region = "${var.region}"
}

resource "aws_instance" "example" {
ami = "ami-71d79f16"
instance_type = "t2.micro"
}
  • terraform.tfvars
    • terraform 実行時に自動で読み込まれるファイル
1
2
access_key = "A******************Q"
secret_key = "q**************************************Z"
  • 実行計画確認
1
2
3
4
5
$ terraform plan

...

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

正しく実行できることが確認できました。

terraform.tfvars ファイルは .gitignore に登録しておくなど
絶対に公開されない様な設定が望ましいと思います。

EC2 instance (t2.micro) AMI 変更

  • main.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
variable "access_key" {}
variable "secret_key" {}
variable "region" {
default = "ap-northeast-1"
}

provider "aws" {
access_key = "${var.access_key}"
secret_key = "${var.secret_key}"
region = "${var.region}"
}

resource "aws_instance" "example" {
ami = "ami-047aed04"
instance_type = "t2.micro"
}
  • 実行計画

変更される内容が表示されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ terraform plan

...

-/+ aws_instance.example
ami: "ami-71d79f16" => "ami-047aed04" (forces new resource)
associate_public_ip_address: "true" => "<computed>"
availability_zone: "ap-northeast-1a" => "<computed>"
ebs_block_device.#: "0" => "<computed>"
ephemeral_block_device.#: "0" => "<computed>"
instance_state: "running" => "<computed>"
instance_type: "t2.micro" => "t2.micro"
ipv6_addresses.#: "0" => "<computed>"
key_name: "" => "<computed>"
network_interface_id: "eni-f4a214bb" => "<computed>"
placement_group: "" => "<computed>"
private_dns: "ip-172-31-31-239.ap-northeast-1.compute.internal" => "<c
omputed>"
private_ip: "172.31.31.239" => "<computed>"
public_dns: "ec2-52-199-88-146.ap-northeast-1.compute.amazonaws.com"
=> "<computed>"
public_ip: "52.199.88.146" => "<computed>"
root_block_device.#: "1" => "<computed>"
security_groups.#: "0" => "<computed>"
source_dest_check: "true" => "true"
subnet_id: "subnet-7a79cc0d" => "<computed>"
tenancy: "default" => "<computed>"
vpc_security_group_ids.#: "1" => "<computed>"


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

最初に作成したインスタンスは破棄され、新たにインスタンスを作成していることがわかります。

terraform で新規作成・変更ができました。

次は破棄してみましょう。

EC2 instance (t2.micro) 破棄

  • 実行計画確認

破棄対象のリソースが表示されます。

1
2
3
4
5
$ terraform plan -destroy

...

- aws_instance.example
  • 実行
1
2
3
4
5
6
7
8
9
10
$ terraform destroy

Do you really want to destroy?
Terraform will delete all your managed infrastructure.
There is no undo. Only 'yes' will be accepted to confirm.

Enter a value: yes (← yes 入力)
...

Destroy complete! Resources: 1 destroyed.

Amazon コンソールで破棄されたことを確認できます。

インスタンス起動し Elastic IP (固定 IP) 設定

  • main.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
variable "access_key" {}
variable "secret_key" {}
variable "region" {
default = "ap-northeast-1"
}

provider "aws" {
access_key = "${var.access_key}"
secret_key = "${var.secret_key}"
region = "${var.region}"
}

resource "aws_instance" "example" {
ami = "ami-047aed04"
instance_type = "t2.micro"
}

resource "aws_eip" "ip" {
instance = "${aws_instance.example.id}"
}
  • 実行計画確認
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
$ terraform plan

...

+ aws_eip.ip
allocation_id: "<computed>"
association_id: "<computed>"
domain: "<computed>"
instance: "${aws_instance.example.id}"
network_interface: "<computed>"
private_ip: "<computed>"
public_ip: "<computed>"
vpc: "<computed>"

+ aws_instance.example
ami: "ami-047aed04"
associate_public_ip_address: "<computed>"
availability_zone: "<computed>"
ebs_block_device.#: "<computed>"
ephemeral_block_device.#: "<computed>"
instance_state: "<computed>"
instance_type: "t2.micro"
ipv6_addresses.#: "<computed>"
key_name: "<computed>"
network_interface_id: "<computed>"
placement_group: "<computed>"
private_dns: "<computed>"
private_ip: "<computed>"
public_dns: "<computed>"
public_ip: "<computed>"
root_block_device.#: "<computed>"
security_groups.#: "<computed>"
source_dest_check: "true"
subnet_id: "<computed>"
tenancy: "<computed>"
vpc_security_group_ids.#: "<computed>"


Plan: 2 to add, 0 to change, 0 to destroy.
  • 実行
1
$ terraform apply

Elastic IP が設定されたインスタンスが起動していることが確認できます。
※ただ、起動しただけで接続できないことがわかります(>_<) 次回実施します

[f:id:kenzo0107:20170323230208p:plain]

  • 実行計画確認

破棄される Elastic IP, インスタンスが確認できます。

1
2
3
4
5
6
7
$ terraform plan -destroy

...

- aws_eip.ip

- aws_instance.example
  • 実行
1
2
3
4
5
$ terraform destroy

...

Destroy complete! Resources: 2 destroyed.

全インスタンスが破棄されていることが確認できました。

その他便利な設定

Map 設定

  • region 毎に AMI を選択し terraform apply 時に変数指定し選択可能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...

variable "amis" {
type = "map"
default = {
us-east-1 = "ami-13be557e"
us-east-2 = "ami-71d79f16"
us-west-1 = "ami-00175967"
us-west-2 = "ami-06b94666"
ap-northeast-1 = "ami-047aed04"
}
}

...

resource "aws_instance" "example" {
ami = "${lookup(var.amis, var.region)}"
instance_type = "t2.micro"
}

ex) region us-west-2 を選択

1
$ terraform apply -var region=us-west-2

出力設定

生成された Elastic IP の値が知りたいときなど便利です。

  • main.tf
1
2
3
4
5
6
7
resource "aws_eip" "ip" {
instance = "${aws_instance.example.id}"
}

output "ip" {
value = "${aws_eip.ip.public_ip}"
}

出力値が確認できます。

1
2
3
4
5
6
7
$ terraform apply

...

Outputs:

ip = 52.197.157.206
  • terraform output

より明示的にパラメータを絞って表示できます。

1
2
3
$ terraform output ip

52.197.157.206
  • show
1
2
3
4
5
6
7
$ terraform show

...

Outputs:

ip = 52.197.157.206

構成のグラフ化

1
$ terraform graph | dot -Tpng > graph.png
  • graph.png
  • dot コマンドがない場合は graphviz インストール
1
$ brew install graphviz

総評

簡単でしょ?と言われているようなツールでした ♪

引き続きプロビジョニングや AWS の各種設定をしていきたいと思います。

次回 EC2 インスタンスを起動し、ローカル環境で作った鍵をキーペア登録し SSH ログインを実施します。