はじめに
DMM WEBCAMP Advent Calendar 2021の12日目 を担当させていただきます。
前日の記事: @keijitsumori さんの[Rails] param is missing or the value is empty error と向き合う
普段DMM WEBCAMPでメンターをしている@koseiinfratopです!
よろしくお願いします
背景
- 近年、さまざまなインターネットサービスがクラウド化しているので自分も主要なクラウドサービス(AWS, GCP, Azure, さくら)を使ってみたいと思いWordPressを構築してみました。各種サービスのサイトのGUIをぽちぽちしてインスタンス等を立ち上げて、立ち上がったインスタンスにSSHし各種設定。。。。
QiitaやZenn等の情報共有サイトを見てもインフラの構築系記事は大体がGUIをベースとした記事が多いと思います。
それでもいいのですが、、、最近ではインフラの構築をコードで宣言するサービスが人気を集めています。
その1サービスがTerraformです。
今回はTerraformを使用してWordPressを構築するハンズオンを紹介しようと思います。
事前準備と注意事項
-
本記事ではTerraformの使い方・記述方法については割愛しています。
Terraformの基本知識については5分で分かるTerraform(Infrastructure as Code)を、
Terraformの構文についてはAWSでTerraform入門をご覧になってください。 -
本記事はAWS,さくらのクラウド, GCP等のクラウドサービスを使用したことがある人を対象としています。
0. 今回作成するインフラの構成図と使用するサービス、ファイル構成
構成図
使用するサービス
- Terraform
- AWS
- Terraformがインストールされている環境(今回は標準でTerraformがインストールされているAWSのCloud9を使用しています。)
ファイル構成
- Terraformのアーキテクチャはまだベストプラクティスがないかつ、今回のゴールはコードを使用してインフラを構築することに焦点を当てているためこのようなファイル構成になっています。
ec2-user:~/environment $ ls -1
init.sh
main.tf
mariadb_config.sh
nginx_config.sh
nginx_default
prepareWordPress.sql
README.md
terraform.tfstate
terraform.tfstate.backup
terraform.tfvars
variable.tf
wordpress_config.sh
------------------------------------------------------
ec2-user:~/environment $ ls ~/.ssh -1
id_rsa.pub
id_rsa
1. 構築
1-0 SSH鍵を用意する
- 作成したEC2にSSHでログインできるようにするために
ssh-keygen -b 4096
コマンドを実行してSSH鍵を作成する。
1-0.1 ワークスペースを初期化する
- 下記コマンドを実行してワークスペースを初期化する。
- terraformを使用する上で必須コマンドとなる。
terrform init
1-1 providerの宣言
- terraformではAWS,GCP,Azure等の様々なクラウドサービスにインフラを構築することができます。
まずはどのサービスを使用してインフラを構築するのかを宣言します。
provider "aws" {
region = "ap-northeast-1"
access_key = var.access_key
secret_key = var.secret_key
}
解説
provider "aws"
: 今回はAWSにインフラを構築するので"aws"
と記述しています。
-
region = "ap-northeast-1"
: 東京リージョンにインフラを構築するので"ap-northeast-1"
と記述しています。 -
access_key, secret_key
: クレデンシャル情報値を記述します。
* access_keyとsecret_keyの取得についてはこちらをご覧ください。
GUIだと。。。
a. AWSのサイトにログインしヘッダーのリージョンを"東京"に選択する。
b. IAMダッシュボードにアクセスし、access_keyとsecret_keyを取得するためにユーザの作成
1-2 VPCの宣言
- 仮想ネットワークの作成
main.tf
に追記
# document: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc
resource "aws_vpc" "main" {
cidr_block = "10.1.0.0/16"
instance_tenancy = "default"
enable_dns_hostnames = "false"
enable_dns_support = "true"
tags = {
Name = "WordPress_vpc"
}
}
#### 解説
instance_tenancy
: デフォルトでは"default"
値なので"default"
と記述する。
-
enable_dns_hostnames
: VPC がパブリック IP アドレスを持つインスタンスへのパブリック DNS ホスト名の割り当てをサポートするかどうかを決定している。今回は不必要なので"false"
と記述する。 -
enable_dns_support
: デフォルトでは"true"
値なので"true"
と記述する。
GUIだと。。。
-
こちらからVPCを作成する
1-3 サブネットの宣言
- サブネットを3つ作成する
-
main.tf
に追記
# document: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet#map_public_ip_on_launch
resource "aws_subnet" "public-a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.1.1.0/24"
availability_zone = "ap-northeast-1a"
map_public_ip_on_launch = "true"
tags = {
Name = "WP-PublicSubnet-1a"
}
}
resource "aws_subnet" "private-a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.1.2.0/24"
availability_zone = "ap-northeast-1a"
tags = {
Name = "WP-PrivateSubnet-1a"
}
}
resource "aws_subnet" "private-d" {
vpc_id = aws_vpc.main.id
cidr_block = "10.1.10.0/24"
availability_zone = "ap-northeast-1d"
tags = {
Name = "WP-PrivateSubnet-1d"
}
}
#### 解説
vpc_id
: "1-2 VPCの作成"で作成したVPCと紐付ける
GUIだと。。。
-
こちらからサブネットを作成する。
1-4 ゲートウェイの宣言
- インターネットに出るためのゲートウェイの作成
-
main.tf
に追記
#document: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/internet_gateway
resource "aws_internet_gateway" "gw" {
vpc_id = aws_vpc.main.id
tags = {
Name = "WP-InternetGateway"
}
}
#### 解説
vpc_id
: "1-2 VPCの作成"で作成したVPCと紐付ける。
GUIだと。。。
- 特に設定する必要はない。
1-5 VPCルーティングテーブルを作成する
- ラスト リゾート ゲートウェイを作成する。
-
-
main.tf
に追記
-
# document: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table
resource "aws_route_table" "public-table" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.gw.id
}
tags = {
Name = "vpc-public-table"
}
}
#### 解説
vpc_id
: "1-2 VPCの作成"で作成したVPCと紐付ける。
-
route { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.gw.id }
: 全てのトラフィックは"aws_internet_gateway.gw.id"のインターネットゲートウェイに送信するようにする。
GUIだと。。。
- 特に設定する必要はない。
1-6 "VPCルーティングテーブルを作成する" で作成したルーティングテーブルをメインルーティングテーブルに関連付ける。
#document: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association
resource "aws_main_route_table_association" "public-a" {
vpc_id = aws_vpc.main.id
route_table_id = aws_route_table.public-table.id
}
#### 解説
vpc_id
: "1-2 VPCの作成"で作成したVPCと紐付ける。
-
route_table_id
: "1-5 スタティックルートの作成" で作成したルーティングをルーティングテーブルに追加するためにaws_route_table.public-table.id
と記述する。
GUIだと。。
- 特に設定する必要はない。
実際のルーティングテーブル
![スクリーンショット 2021-12-10 6.21.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541740/b2c54ac8-08ad-b24a-dd95-64224fc4ee59.png) 下記画像のレコードは"1-2 VPCの作成"で作成したVPCと紐付けているためデフォルトで追加されている。 ![スクリーンショット 2021-12-10 6.22.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541740/dfe215c0-6fc9-8b47-b352-4b76f5b9166d.png)1-7 セキュリティグループの宣言
- webserverとdbserver用のセキュリティグループを作成する。
-
main.tf
に追記
# document: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group
resource "aws_security_group" "app" {
name = "WP-Web-DMZ"
description = "WordPress Web App Security Group"
vpc_id = aws_vpc.main.id
tags = {
Name = "WP-Web-DMZ"
}
}
resource "aws_security_group" "db" {
name = "WP-DB"
description = "WordPress Mysql Security Group"
vpc_id = aws_vpc.main.id
}
#### 解説
vpc_id
: "1-2 VPCの作成"で作成したVPCと紐付ける。
GUIだと。。。
- こちらからセキュリティグループを作成する
1-8 セキュリティルールを宣言する
- インバウンドルール・アウトバウンドルールを適宜宣言し、必要なセキュリティグループに紐付ける。
-
main.tf
に追記
document: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule
resource "aws_security_group_rule" "ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.app.id
}
resource "aws_security_group_rule" "http" {
type = "ingress"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.app.id
}
resource "aws_security_group_rule" "mysql" {
type = "ingress"
from_port = 3306
to_port = 3306
protocol = "tcp"
source_security_group_id = aws_security_group.app.id
security_group_id = aws_security_group.db.id
}
resource "aws_security_group_rule" "all" {
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.app.id
}
#### 解説
security_group_id
: "1-7 セキュリティグループの作成"で作成したセキュリティグループ内で紐付けたいセキュリティグループに紐づける。
-
source_security_group_id
: type(今回なら"ingress")に応じてアクセスを許可するセキュリティグループID。(今回ならセキュリティグループ"app"が適用されているインスタンスからの通信であれば許可する。) -
from_port = 0 to_port = 0 protocol = "-1"
: 全てのトラフィックへの通信を許可する。
GUIだと。。。
下記画像のようにインバウンド・アウトバウンドルールに適宜追加していく。
実際に作成されたセキュリティグループの1例
![スクリーンショット 2021-12-10 7.08.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541740/b9b1cc6f-6e53-6a62-2789-3d2b09c24981.png)1-9 DBのサブネットグループを宣言する
- DB用のサブネットを定義する。
-
main.tf
に追記
# document: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_subnet_group
resource "aws_db_subnet_group" "main" {
name = "wp-dbsubnet"
description = "WordPress DB_Mysql_ Subnet"
subnet_ids = [aws_subnet.private-a.id, aws_subnet.private-d.id]
tags = {
Name = "WordPressDB"
}
}
#### 解説
subnet_ids
: "1-3 サブネットの作成"で作成したサブネットの中からDB用のサブネットを記述する。
GUIだと。。。
- こちらからDBサブネットグループを作成する。
実際に作成されたDBサブネットグループ
![スクリーンショット 2021-12-11 3.08.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541740/a2f3ce15-3ad0-6369-5f4e-7a8296f58c4f.png) スクリーンショット 2021-12-11 3.08.471-10 DBのインスタンスを宣言する
-
main.tf
に追記
resource "aws_db_instance" "db" {
identifier = "wp-mysql-db" #lowcase
allocated_storage = 5
engine = "mysql"
engine_version = "8.0.23"
instance_class = "db.t2.micro"
storage_type = "gp2"
username = var.username
password = var.password
backup_retention_period = 0
skip_final_snapshot = true
apply_immediately = true
vpc_security_group_ids = [aws_security_group.db.id]
db_subnet_group_name = aws_db_subnet_group.main.name
lifecycle {
ignore_changes = [password]
}
}
#### 解説
identifier
: DBの一意な識別子を割り当てる。*全て小文字でなければいけない。
(省略した場合はAWS側で一意な識別子が割り当てられる。)
-
apply_immediately
: RDSへの設定変更を即時反映するかどうかの設定。即時反映したいのでtrue
を記述する。 -
skip_final_snapshot
: インスタンス削除時にスナップショットの取得をスキップするかどうかの設定。コスト節約のためtrue
を記述する。 -
lifecycle { ignore_changes = [password] }
: ライフサイクルの設定。Terraformで管理しない項目を記述する。passwordの変更はTerraformとして無視する。セキュリティの観点からインスタンス構築後、手動でパスワードを変更するため。
GUIだと。。。
- こちらからDBを作成する。
1-11 SSH接続するための公開鍵を宣言する。
- "1-0 SSH鍵を用意する"で作成したキーペアの公開鍵を指定する。
-
main.tf
に追記
document: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/key_pair
resource "aws_key_pair" "deployer" {
key_name = "test-key"
public_key = file("~/.ssh/id_rsa.pub")
}
#### 解説
key_name
: キーペア名を記述する。
-
public_key
: Terraformの'file'メソッドを使用して"1-0 SSH鍵を用意する"で事前に用意したキーペアの公開鍵ファイルパスを記述する。
GUIだと。。。
- EC2を起動するときに画像ようなモーダルが出てくるところで設定する。
1-12 EC2を宣言する
- ubuntuを使用してEC2を作成する。
-
main.tf
に追記
# document: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance
resource "aws_instance" "app" {
ami = var.ami
instance_type = "t2.micro"
subnet_id = aws_subnet.public-a.id
associate_public_ip_address = true
vpc_security_group_ids = [aws_security_group.app.id]
private_ip = "10.1.1.100"
key_name = aws_key_pair.deployer.id
provisioner "file" {
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
source = "prepareWordPress.sql"
destination = "/home/ubuntu/prepareWordPress.sql"
}
provisioner "remote-exec" {
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
scripts =[
"init.sh",
"nginx_config.sh",
]
}
provisioner "remote-exec" {
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
inline = [
"sudo apt update -y",
"sudo apt install default-mysql-client -y",
"sudo mysql -u ${var.username} -p${var.password} -h ${aws_db_instance.db.address} < /home/ubuntu/prepareWordPress.sql"
]
}
provisioner "file" {
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
source = "nginx_default"
destination = "/home/ubuntu/default"
}
provisioner "remote-exec" {
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
script = "wordpress_config.sh"
}
tags = {
Name = "WordPressServer"
}
}
#### 解説
associate_public_ip_address = true
- VPC内のインスタンスにパブリックIPアドレスを関連付けるために
true
を記述する。
private_ip = "10.1.1.100"
- このEC2で使用するプライベートIPを明示的に指定するために固定IPを記述している。
key_name = aws_key_pair.deployer.id
- "1-11 SSH接続するための公開鍵を宣言する。"で宣言した公開鍵をEC2に登録するために記述している。
provisioner "file" {
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
source = "prepareWordPress.sql"
destination = "/home/ubuntu/prepareWordPress.sql"
}
- EC2にファイルを転送している。
内部ではscpコマンドを使用してファイル転送をしている。
scpコマンドで表した場合
scp -i ~/.ssh/id_rsa prepareWordPress.sql ubuntu@self.public_ip:/home/ubuntu/prepareWordPress.sql
host = self.public_ip
>> - 宛先のアドレス(`host`)にEC2のパブリックIP(`self.public_ip`)を指定している。
provisioner "remote-exec" {
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
scripts = [
"init.sh",
"nginx_config.sh",
]
}
> - EC2で実行したいシェルスクリプトを記述している。
> 内部ではSSH接続をして、`scripts`に記述しているスクリプトファイルを実行している。
###### GUIだと。。。
- <a href="https://ap-northeast-1.console.aws.amazon.com/ec2/v2/home?region=ap-northeast-1#LaunchInstanceWizard:" target="_blank" rel="noopener">こちら</a>からEC2を作成する。
## 1-13 EC2のグローバルIPを確認する
- main.tfに追記
```:main.tf
output "WordPressServer_EC2__PublicIP" {
value = aws_instance.app.public_ip
}
解説
- 後の動作確認のためにEC2に割り振られたグローバルIPをターミナルに出力するために記述する。
~補足~ 後の項目"1-17 定義したファイルを実行しWordPressを構築する"でterraform apply
コマンドを実行し正常に終了したらターミナルにグローバルIPが出力される。
1-14 スクリプトファイル、ダンプファイル、Terraformで使用している変数系のファイルを作成する
- こちらに関しての説明は割愛させていただきます。
- 詳しい変数値などはこちらをご覧ください。
1-15 作成したファイルのコードフォーマット
- 下記コマンドを実行してコード整形を行う。
terraform fmt
1-16 記述した設定ファイルの変更内容を確認する
- 下記コマンドを実行して定義内容が意図した通りに反映されるかを確認する。
terraform plan
1-17 定義したファイルを実行しWordPressを構築する
- 下記コマンドを実行してWordPressを構築する。
terraform apply
動作確認
- ターミナルに出力されたグローバルIPを使用して
http://グローバルIP/wordpress
にアクセスする。 - 下記の画像のような画面が表示される。
- 言語を設定し、DBの設定等を行うとwordpressのサイトを表示できるようになる。
最後に
- このようにGUIを一切使用せずにインフラ構築を行うことができます。
- 今回はメンテナンス等は全く気にせずコードを書きましたが、メンテナンスを意識したコードに随時書き換えていこうと思います。
- 皆さんもぜひ、プログラミングでインフラを構築してみてください!!!
インフラがおもしろくない可能性これ、、、一切なーい!!!
参考資料
https://reboooot.net/post/what-is-terraform/
https://kazuhira-r.hatenablog.com/entry/2020/06/20/172541
https://registry.terraform.io/
https://qiita.com/toshihirock/items/6a46fcba165a0b34a1f4
https://www.lac.co.jp/lacwatch/service/20200903_002270.html
https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/vpc-dns.html