0. Terraform と Terragrunt と Ansible をインストールする #
Homebrew でインストールします。
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
brew install terragrunt
brew install ansible
terraform
、terragrunt
、ansible
コマンドを実行してインストールできたことを確認します。
terragrunt --version
> terragrunt version 0.86.2
terraform --version
> Terraform v1.13.1
ansible --version
> ansible [core 2.19.2]
1. プロビジョニング定義を書いてみる #
以下の要件を満たすように作成します。
- ap-northeast-1 リージョンの既存の subnet の中に Amazon Linux の t3.micro インスタンスを起動します。
- EC2 インスタンス起動時の処理(ユーザデータ)で Ansible をインストール、実行します。
- Ansible で Go をインストールし、Go のスクリプトをビルドします。Systemd のサービスとして Go のバイナリを登録します。
- 補足:ユーザデータはインスタンス初回起動時にしか実行されません。インスタンス再起動時にもスクリプトが実行されるようにするためには Systemd のサービスとして登録します。
- Go のバイナリが実行されると “Hello, World!” と書かれたテキストファイル
hello.txt
を作成します。 - 起動した EC2 インスタンスに Session Manager(SSM)で接続できるようにします。(https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html)
なお、AWS CLI では SSO(Single Sign-On)を利用して AWS にログインすることを前提としています。
ディレクトリ構成 #
my-provisioning/
├ .gitignore
├ my_userdata.sh
├ site.yml
├ main.go
├ terragrunt.hcl
└ main.tf
.gitignore
#
.terragrunt-cache/
.terragrunt-stack/
my_userdata.sh
#
ユーザデータスクリプトです。EC2 インスタンスの起動時に実行されるよう、後述の Terraform(main.tf
) で指定しています。
https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html
Ansible をインストールし、Ansible を実行するのに必要なファイルを用意したうえで後述の site.yml
の内容を実行します。
#!/bin/bash
set -eux
yum update -y
# Install Ansible
amazon-linux-extras enable ansible2
yum install -y ansible
# Create directory if not exists
mkdir -p /opt/my-app
# Create site.yml file based on the input
cat <<'EOF' > /opt/my-app/site.yml
${my_playbook}
EOF
# Create main.go file based on the input
cat <<'EOF' > /opt/my-app/main.go
${my_go}
EOF
chmod 0644 /opt/my-app/main.go
# Execute Ansible Playbook
ansible-playbook -i "localhost," -c local /opt/my-app/site.yml
site.yml
#
Ansible の Playbook です。Go をインストールし、Go のスクリプトを実行します。
- hosts: localhost
connection: local
become: true
tasks:
- name: Install Go
yum:
name: golang
state: present
- name: Initialize go.mod
command: /usr/bin/env bash -lc 'cd /opt/my-app && [ -f go.mod ] || go mod init my-app'
args:
chdir: /opt/my-app
- name: Resolve modules
command: /usr/bin/env bash -lc 'cd /opt/my-app && go mod tidy'
args:
chdir: /opt/my-app
- name: Build Go
command: /usr/bin/env bash -lc 'cd /opt/my-app && CGO_ENABLED=0 go build -ldflags "-s -w" -o /usr/local/bin/my-app main.go'
args:
chdir: /opt/my-app
- name: Create systemd unit for executing my-app on boot
copy:
dest: /etc/systemd/system/my-app.service
mode: "0644"
content: |
[Unit]
Description=My App
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/my-app
WorkingDirectory=/opt/my-app
Restart=on-failure
RestartSec=5s
Environment=GOWORK=off
Environment=GOMODCACHE=/opt/my-app/go-mod-cache
[Install]
WantedBy=multi-user.target
- name: Reload systemd
systemd:
daemon_reload: true
- name: Enable and start my-app
systemd:
name: my-app.service
enabled: true
state: started
main.go
#
Go のスクリプトです。“Hello, World!” と書かれたテキストファイル hello.txt
を作成します。
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
content := "Hello, World!"
dir := "/opt/my-app/"
fileName := "hello.txt"
filePath := filepath.Join(dir, fileName)
err := os.WriteFile(filePath, []byte(content), 0644)
if err != nil {
fmt.Println("作成に失敗しました:", err)
return
}
fmt.Printf("作成しました: %s\n", filePath)
}
terragrunt.hcl
#
Terragrunt の設定ファイルです。ステートファイルの保存先や Terraform を実行する際の引数(inputs)などを指定しています。
locals {
aws_profile = "my_profile" # 自分の環境に合わせて書き換える - AWS SSO でログインする際のプロファイル名
aws_remote_state_s3_region = "ap-northeast-1"
aws_remote_state_s3_bucket_name = "my-remote-state-bucket"
aws_remote_state_s3_file_key = "terraform.tfstate"
aws_ec2_region = "ap-northeast-1"
aws_ec2_vpc_id = "vpc-xxxxxxxxxxxxxxxxx" # 自分の環境に合わせて書き換える - ap-northeast-1 内の既存の VPC ID
aws_ec2_subnet_id = "subnet-xxxxxxxxxxxxxxxxx" # 自分の環境に合わせて書き換える - ap-northeast-1 内の既存の subnet ID
aws_ec2_ami_id = "ami-0228232d282f16465" # Amazon Linux 2023 x86_64
aws_ec2_instance_type = "t3.micro"
aws_ec2_name = "my-ec2"
}
terraform {
source = "./"
extra_arguments "aws_profile" {
commands = [
"init",
"apply",
"refresh",
"import",
"plan",
"taint",
"untaint"
]
env_vars = {
AWS_PROFILE = "${local.aws_profile}"
}
}
}
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
config = {
encrypt = true
region = local.aws_remote_state_s3_region
bucket = local.aws_remote_state_s3_bucket_name
key = local.aws_remote_state_s3_file_key
}
}
inputs = {
ec2_region = local.aws_ec2_region
ec2_vpc_id = local.aws_ec2_vpc_id
ec2_subnet_id = local.aws_ec2_subnet_id
ec2_ami_id = local.aws_ec2_ami_id
ec2_instance_type = local.aws_ec2_instance_type
ec2_name = local.aws_ec2_name
}
main.tf
#
Terraform の定義ファイルです。variable
で Terragrunt の inputs
で指定した値を受け取り処理を行います。
# ------------------------------------------------------------------------------
# Terraform Configuration
# ------------------------------------------------------------------------------
terraform {
required_version = ">= 0.13"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# ------------------------------------------------------------------------------
# Variables
# ------------------------------------------------------------------------------
variable "ec2_region" {
type = string
}
variable "ec2_vpc_id" {
type = string
}
variable "ec2_subnet_id" {
type = string
}
variable "ec2_ami_id" {
type = string
}
variable "ec2_instance_type" {
type = string
}
variable "ec2_name" {
type = string
}
# ------------------------------------------------------------------------------
# Provider Configuration
# ------------------------------------------------------------------------------
provider "aws" {
region = var.ec2_region
}
# ------------------------------------------------------------------------------
# Resources - IAM
# ------------------------------------------------------------------------------
resource "aws_iam_role" "this" {
name = "my-ssm-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy_attachment" "this" {
role = aws_iam_role.this.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
resource "aws_iam_instance_profile" "this" {
name = "my-ssm-profile"
role = aws_iam_role.this.name
}
# ------------------------------------------------------------------------------
# Resources - Security Group
# ------------------------------------------------------------------------------
resource "aws_security_group" "this" {
name = "my-egress-security-group"
description = "Security group for allow any outbound traffic"
vpc_id = var.ec2_vpc_id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "my-egress-security-group"
}
}
# ------------------------------------------------------------------------------
# Resources - EC2
# ------------------------------------------------------------------------------
resource "aws_instance" "this" {
ami = var.ec2_ami_id
instance_type = var.ec2_instance_type
subnet_id = var.ec2_subnet_id
vpc_security_group_ids = [aws_security_group.this.id]
associate_public_ip_address = false
iam_instance_profile = aws_iam_instance_profile.this.name
tags = {
Name = var.ec2_name
}
user_data = templatefile("${path.module}/my_userdata.sh", {
my_playbook = file("${path.module}/site.yml")
my_go = file("${path.module}/main.go")
})
user_data_replace_on_change = true
}
2. プロビジョニングを実行してみる #
aws sso login するプロファイル名(my_profile 部分)は自身の環境に合わせて書き換えてください。
プロビジョニングの実行 #
aws sso login --profile my_profile
terragrunt plan
terragrunt apply -auto-approve
デプロビジョニングの実行 #
aws sso login --profile my_profile
export AWS_PROFILE=my_profile
export AWS_SDK_LOAD_CONFIG=1
terragrunt destroy