使用Terraform(0.12)从VPC创建EC2和RDS的示例
利用这篇文章创作的东西
-
- VPC
-
- RDS(Aurora)
-
- EC2(踏み台用途)
- EC2キーペア
预计将RDS部署在VPC的私有子网中,因此无法从本地访问。
如果在RDS的配置中将公共可访问性打开,则可以进行访问,但出于安全考虑,最好部署一台跳板服务器(EC2)。
此外,我认为构建的EC2也可以替代常规的应用服务器。
Terraform版本为0.12.19。
前提条件 tí
- awscliが使えること
$ brew install awscli
$ aws --version
aws-cli/1.17.0 Python/3.8.1 Darwin/19.2.0 botocore/1.14.0
- Terraform(0.12.19)が使えること
$ wget https://releases.hashicorp.com/terraform/0.12.19/terraform_0.12.19_darwin_amd64.zip
$ unzip terraform_0.12.19_darwin_amd64.zip -d /usr/local/bin/
$ terraform -v
Terraform v0.12.19
目录结构
.
├── README.md
├── components
│ ├── db
│ │ ├── backend.tf
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ ├── provider.tf
│ │ └── variables.tf
│ ├── ec2
│ │ ├── backend.tf
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ ├── provider.tf
│ │ └── variables.tf
│ ├── network
│ │ ├── backend.tf
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ ├── provider.tf
│ │ └── variables.tf
│ └── securitygroup
│ ├── backend.tf
│ ├── main.tf
│ ├── outputs.tf
│ ├── provider.tf
│ └── variables.tf
├── environments
│ ├── production
│ │ └── terraform.tfvars
│ └── staging
│ └── terraform.tfvars
└── modules
├── iam_role
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
├── key_pair
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
└── securitygroup
├── main.tf
├── outputs.tf
└── variables.tf
这次,我们准备了以下四个组件。
-
- db
-
- ec2
-
- network
- securitygroup
AWS部署是按照这些单位进行的。
部署顺序为network→securitygroup→db→ec2。
同样,关于模块,我准备了以下三个选项。
-
- iam_role
-
- key_pair
- securitygroup
这些已经被模块化,以便每次创建时可以重复使用定义。
而且,我们将环境分为测试环境和生产环境,并通过terraform工作区来进行环境划分。
事前准备
初始化
首先,请从awscli中输入您自己的AWS认证信息。
$ aws configure --profile { 自身で決めたProfile名 }
AWS Access Key ID [None]: { 自身のアクセスキー }
AWS Secret Access Key [None]: { 自身のシークレットアクセスキー }
Default region name [None]: ap-northeast-1
Default output format [None]:
在Terraform的定义中使用的名称为Profile名。应避免使用默认的名称。
创建S3存储桶
事先手动创建S3存储桶。
此存储桶是用于保存Terraform状态的重要存储桶。
Terraform将根据存储在该存储桶中的信息来构建AWS资源。
可以在控制台界面上完成,也可以使用命令行界面完成。
aws s3 --profile { 自身で決めたProfile名 } mb s3://{ 自身で決めたバケット名 }
创建工作空间
请前往每个组件并执行terraform init和terraform workspace new {环境名称}。
$ cd ./components/db/
$ terraform init
$ terraform workspace new staging
$ terraform workspace new production
$ terraform workspace select staging
适用于所有四个组件。
provider.tf的描述
为每个组件准备一个provider.tf。
-
- db
-
- ec2
-
- network
- securitygroup
让我们分别按照以下的方式进行定义。
variable "profile" {
default = "{ 自身で決めたProfile名 }"
}
provider "aws" {
version = "= 2.45.0"
region = "ap-northeast-1"
profile = var.profile
}
backend.tf的描述
针对以下每个组件,我会准备一个backend.tf文件。
-
- db
-
- ec2
-
- network
- securitygroup
请将上述字符串放入component名中。如果是db的话,key将会是”db/terraform.tfstate”。
terraform {
required_version = "= 0.12.19"
backend "s3" {
region = "ap-northeast-1"
encrypt = true
bucket = "{ 先ほど作成したS3バケット }"
key = "{ component名 }/terraform.tfstate"
profile = "{ 自身で決めたProfile名 }"
}
}
值得注意的是,由于此次情况下按组件进行了状态分离,因此无法直接访问每个组件的资源信息。
举个例子,比如我想要创建一个 EC2 实例,但不知道应该指定哪个 VPC……就是这样的情况。
在这种情况下,我们可以在 backend.tf 中将具有依赖关系的组件定义为数据。
在EC2上需要创建VPC和安全组,因此需要列出网络和安全组组件。
terraform {
required_version = "= 0.12.19"
backend "s3" {
region = "ap-northeast-1"
encrypt = true
bucket = "{ 先ほど作成したS3バケット }"
key = "bastion/terraform.tfstate"
profile = "{ 自身で決めたProfile名 }"
}
}
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "{ 先ほど作成したS3バケット }"
key = "env:/${terraform.workspace}/network/terraform.tfstate"
region = "ap-northeast-1"
profile = "{ 自身で決めたProfile名 }"
}
}
data "terraform_remote_state" "securitygroup" {
backend = "s3"
config = {
bucket = "{ 先ほど作成したS3バケット }"
key = "env:/${terraform.workspace}/securitygroup/terraform.tfstate"
region = "ap-northeast-1"
profile = "{ 自身で決めたProfile名 }"
}
}
由于db组件需要指定VPC,所以我们需要在network中进行记录。
同样,securitygroup组件也需要指定VPC,所以我们需要在network中进行记录。
请注意,尽管DB和安全组组件确实需要,但由于它们不会在未来进行修改的定义中,因此我们将它们放在db组件中编写。
创建一个VPC
我将创建以下内容。
-
- VPC
-
- パブリックルートテーブル
-
- プライベートルートテーブル (2つ) ※一応将来的にNATGatewayを分けられるようにしてる
-
- インターネットゲートウェイ
-
- パブリックサブネット (2つ)
- プライベートサブネット (2つ)
VPC 可以翻译为「虚拟私人网络」。
resource "aws_vpc" "default" {
cidr_block = "10.1.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "sample-${terraform.workspace}"
}
}
公共路由表
resource "aws_route_table" "public" {
vpc_id = aws_vpc.default.id
tags = {
Name = "sample-public-route-table-${terraform.workspace}"
}
}
私有路由表
resource "aws_route_table" "private_0" {
vpc_id = aws_vpc.default.id
tags = {
Name = "sample-private-route-table-0-${terraform.workspace}"
}
}
resource "aws_route_table" "private_1" {
vpc_id = aws_vpc.default.id
tags = {
Name = "sample-private-route-table-1-${terraform.workspace}"
}
}
互联网网关
resource "aws_internet_gateway" "default" {
vpc_id = aws_vpc.default.id
tags = {
Name = "sample-internet-gateway-${terraform.workspace}"
}
}
resource "aws_route" "public" {
route_table_id = aws_route_table.public.id
gateway_id = aws_internet_gateway.default.id
destination_cidr_block = "0.0.0.0/0"
}
公共子网
resource "aws_subnet" "public_0" {
vpc_id = aws_vpc.default.id
cidr_block = "10.1.0.0/24"
map_public_ip_on_launch = true
availability_zone = "ap-northeast-1c"
tags = {
Name = "sample-public-subnet-0-${terraform.workspace}"
}
}
resource "aws_subnet" "public_1" {
vpc_id = aws_vpc.default.id
cidr_block = "10.1.1.0/24"
map_public_ip_on_launch = true
availability_zone = "ap-northeast-1d"
tags = {
Name = "sample-public-subnet-1-${terraform.workspace}"
}
}
resource "aws_route_table_association" "public_0" {
subnet_id = aws_subnet.public_0.id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "public_1" {
subnet_id = aws_subnet.public_1.id
route_table_id = aws_route_table.public.id
}
私人子网
resource "aws_subnet" "private_0" {
vpc_id = aws_vpc.default.id
cidr_block = "10.1.10.0/24"
map_public_ip_on_launch = false
availability_zone = "ap-northeast-1c"
tags = {
Name = "sample-private-subnet-0-${terraform.workspace}"
}
}
resource "aws_subnet" "private_1" {
vpc_id = aws_vpc.default.id
cidr_block = "10.1.11.0/24"
map_public_ip_on_launch = false
availability_zone = "ap-northeast-1d"
tags = {
Name = "sample-private-subnet-1-${terraform.workspace}"
}
}
resource "aws_route_table_association" "private_0" {
subnet_id = aws_subnet.private_0.id
route_table_id = aws_route_table.private_0.id
}
resource "aws_route_table_association" "private_1" {
subnet_id = aws_subnet.private_1.id
route_table_id = aws_route_table.private_0.id
}
我会在outputs.tf文件中写下稍后从其他组件引用的资源。
output "sample_vpc_id" {
value = aws_vpc.default.id
}
output "sample_vpc_public_subnet_0_id" {
value = aws_subnet.public_0.id
}
output "sample_vpc_public_subnet_1_id" {
value = aws_subnet.public_1.id
}
output "sample_vpc_private_subnet_0_id" {
value = aws_subnet.private_0.id
}
output "sample_vpc_private_subnet_1_id" {
value = aws_subnet.private_1.id
}
output "sample_vpc_cider_block" {
value = aws_vpc.default.cidr_block
}
创建安全组
安全组
尝试将安全组创建模块化。
模块
variable "name" {}
variable "vpc_id" {}
variable "port" {}
variable "cider_blocks" {
type = list(string)
}
resource "aws_security_group" "default" {
name = var.name
vpc_id = var.vpc_id
tags = {
Name = var.name
}
}
resource "aws_security_group_rule" "ingress" {
type = "ingress"
from_port = var.port
to_port = var.port
protocol = "tcp"
cidr_blocks = var.cider_blocks
security_group_id = aws_security_group.default.id
}
resource "aws_security_group_rule" "egress" {
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.default.id
}
output "security_group_id" {
value = aws_security_group.default.id
}
您可以通过以下方式从组件中调用模块。
本次设置的入站规则是SSH(端口=22)。让我们将其设置为只允许从自己的IP地址SSH访问。
组件
module "ec2" {
source = "../../modules/securitygroup"
name = "ec2-sg-${terraform.workspace}"
vpc_id = data.terraform_remote_state.network.outputs.sample_vpc_id
port = 22
cider_blocks = var.ec2_access_ip
}
请注意从network组件中拉取资源信息的vpc_id。
条件是它在先前创建的components/network/outputs.tf中有所记载,
并且在components/securitygroup/backend.tf中以terraform_remote_state形式进行了记录。
创建RDS(Aurora)
在RDS中,将创建以下项目。
-
- Auroraモニタリング用IAMロール
-
- クラスターパラメーターグループ
-
- DBパラメーターグループ
-
- DBサブネットグループ
-
- Auroraセキュリティグループ
-
- Auroraクラスター
- Auroraインスタンス
用于Aurora监控的IAM角色
我将尝试将IAM角色的创建模块化。
variable "name" {}
variable "policy" {}
variable "identifier" {}
resource "aws_iam_role" "default" {
name = var.name
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
data "aws_iam_policy_document" "assume_role" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = [var.identifier]
}
}
}
resource "aws_iam_policy" "default" {
name = var.name
policy = var.policy
}
resource "aws_iam_role_policy_attachment" "default" {
policy_arn = aws_iam_policy.default.arn
role = aws_iam_role.default.name
}
output "iam_role_arn" {
value = aws_iam_role.default.arn
}
output "iam_role_name" {
value = aws_iam_role.default.name
}
当模块创建好后,让我们从组件中试着调用一下。
AWS官方已经存在名为AmazonRDSEnhancedMonitoringRole的策略。
让我们应用它。
注意,每个服务都有一个固定的标识符,请注意。
本次的标识符是monitoring.rds.amazonaws.com。
data "aws_iam_policy" "aurora_monitoring_policy" {
arn = "arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole"
}
data "aws_iam_policy_document" "aurora_monitoring" {
source_json = data.aws_iam_policy.aurora_monitoring_policy.policy
}
module "aurora_monitoring_role" {
source = "../../modules/iam_role"
name = "aurora_monitoring_role"
identifier = "monitoring.rds.amazonaws.com"
policy = data.aws_iam_policy_document.aurora_monitoring.json
}
集群参数组
这次我们将把charset相关的配置改成utf8mb4,并将时区设为东京。
此外,请根据需求设置适当的参数。
resource "aws_rds_cluster_parameter_group" "sample" {
name = "sample-cluster-paameter-group-${terraform.workspace}"
family = "aurora-mysql5.7"
parameter {
name = "character_set_client"
value = "utf8mb4"
}
parameter {
name = "character_set_connection"
value = "utf8mb4"
}
parameter {
name = "character_set_database"
value = "utf8mb4"
}
parameter {
name = "character_set_results"
value = "utf8mb4"
}
parameter {
name = "character_set_server"
value = "utf8mb4"
}
parameter {
name = "time_zone"
value = "Asia/Tokyo"
}
}
数据库参数组
resource "aws_db_parameter_group" "sample" {
name = "sample-db-paameter-group-${terraform.workspace}"
family = "aurora-mysql5.7"
}
DB子网组
请注意,子网的ID需要从network组件中获取资源信息。
条件是在刚刚创建的components/network/outputs.tf中有记录,
并且在components/db/backend.tf中以terraform_remote_state的形式进行了记录。
resource "aws_db_subnet_group" "sample" {
name = "sample-db-subnet-group-${terraform.workspace}"
subnet_ids = [
data.terraform_remote_state.network.outputs.sample_vpc_private_subnet_0_id,
data.terraform_remote_state.network.outputs.sample_vpc_private_subnet_1_id,
]
}
奥罗拉安全集团
也许本来应该用securitygroup组件来创建,但由于它是针对终生固定的入站设置,我认为也可以在db组件中创建。
如果还没有创建用于安全组的模块,请回到本文稍作确认。
在本次中,Port = 3306将允许使用VPC内(CiderBlock)进行访问。
由于是私有子网,无法从外部访问,
但假设在同一VPC内部署了位于公共子网的EC2实例,那么就可以进行访问。
module "aurora_sg" {
source = "../../modules/securitygroup"
name = "sample-db-${terraform.workspace}"
vpc_id = data.terraform_remote_state.network.outputs.sample_vpc_id
port = 3306
cider_blocks = [data.terraform_remote_state.network.outputs.sample_vpc_cider_block]
}
极光集群
在集群中,需要注意的是主密码。
根据本文的方法,首先设置一个随意的密码,然后在控制台界面上手动更改。
请注意生命周期栏。从下次更改开始,将忽略主密码的更改设置。
resource "aws_rds_cluster" "sample" {
cluster_identifier = "sample-${terraform.workspace}"
master_username = "sample"
master_password = "initial_password" # 手動で変更すること
database_name = "sample"
backup_retention_period = 7
preferred_backup_window = "09:30-10:00" # UTC
preferred_maintenance_window = "wed:10:30-wed:11:00" # UTC
engine = "aurora-mysql"
engine_version = "5.7.mysql_aurora.2.07.1"
port = 3306
vpc_security_group_ids = [module.aurora_sg.security_group_id]
db_subnet_group_name = aws_db_subnet_group.sample.name
db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.sample.name
storage_encrypted = true
deletion_protection = var.deletion_protection
enabled_cloudwatch_logs_exports = ["audit", "error", "general", "slowquery"]
skip_final_snapshot = false
final_snapshot_identifier = "sample-${terraform.workspace}-final-snapshot"
lifecycle {
ignore_changes = [master_password]
}
}
奥罗拉实例
值此一时,我们预计在暂存环境和生产环境中,实例数量和实例类型会有所差异。
具体而言,暂存环境将使用一台小规格的实例,
生产环境将使用两台中到大规格的实例。
我们将在下面对这些变量进行定义。
# Auroraスペック
instance_class = "db.t3.small"
# Auroraインスタンス数
cluster_instance_count = 1
# Auroraスペック
instance_class = "db.r5.large"
# Auroraインスタンス数
cluster_instance_count = 2
resource "aws_rds_cluster_instance" "sample" {
count = var.cluster_instance_count
identifier = "sample-${terraform.workspace}-${count.index}"
cluster_identifier = aws_rds_cluster.sample.id
instance_class = var.instance_class
db_subnet_group_name = aws_db_subnet_group.sample.name
db_parameter_group_name = aws_db_parameter_group.sample.name
monitoring_role_arn = module.aurora_monitoring_role.iam_role_arn
monitoring_interval = 60
engine = "aurora-mysql"
engine_version = "5.7.mysql_aurora.2.07.1"
ca_cert_identifier = "rds-ca-2019"
# 変更をすぐに適用する場合
# apply_immediately = true
}
创建EC2实例
在本文中,我们创建了一个用作跳板服务器的EC2,但也可以作为应用程序服务器。在这种情况下,请确保创建适当的安全组。
在EC2组件中创建的资源如下:
-
- キーペア
- インスタンス
密钥对
密钥对进行模块化。
请注意文件权限。
当进行SSH连接时,会出现错误。
例如)file_permission = “0400”
公钥文件(public_key_file)和私钥文件(private_key_file)用于指定密钥的输出位置。
variable "public_key_file" {}
variable "private_key_file" {}
variable "key_name" {}
resource "tls_private_key" "keygen" {
algorithm = "RSA"
rsa_bits = 4096
}
resource "local_file" "private_key_pem" {
filename = var.private_key_file
content = tls_private_key.keygen.private_key_pem
file_permission = "0400"
}
resource "local_file" "public_key_openssh" {
filename = var.public_key_file
content = tls_private_key.keygen.public_key_openssh
file_permission = "0600"
}
output "private_key_file" {
value = var.private_key_file
}
output "private_key_pem" {
value = tls_private_key.keygen.private_key_pem
}
output "public_key_file" {
value = var.public_key_file
}
output "public_key_openssh" {
value = tls_private_key.keygen.public_key_openssh
}
resource "aws_key_pair" "key_pair" {
key_name = var.key_name
public_key = tls_private_key.keygen.public_key_openssh
}
output "key_name" {
value = aws_key_pair.key_pair.key_name
}
这是组件的处理。
module "ec2_key" {
source = "../../modules/key_pair"
public_key_file = "./ec2-${terraform.workspace}.id_rsa.pub"
private_key_file = "./ec2-${terraform.workspace}.id_rsa"
key_name = "ec2-${terraform.workspace}"
}
实例
让我们指定先前创建的密钥对。
请注意,AMI是Amazon Linux2。虽然本文中直接写入了AMI的ID,
但根据要求,您可以自由选择。
resource "aws_instance" "ec2" {
ami = "ami-011facbea5ec0363b"
instance_type = "t3.nano"
availability_zone = "ap-northeast-1c"
ebs_optimized = false
vpc_security_group_ids = [data.terraform_remote_state.securitygroup.outputs.ec2_security_group_id]
key_name = module.ec2_key.key_name
subnet_id = data.terraform_remote_state.network.outputs.sample_vpc_public_subnet_0_id
associate_public_ip_address = true
tags = {
Name = "sample-${terraform.workspace}"
}
}
部署
终于要部署了!在执行部署命令时,请不要忘记指定环境变量文件。此外,请注意工作空间。当要部署到暂存环境时,请选择terraform工作空间为staging。
$ cd ./components/network/
$ terraform workspace select staging
$ terraform plan -var-file="../../environments/$(terraform workspace show)/terraform.tfvars"
$ terraform apply -var-file="../../environments/$(terraform workspace show)/terraform.tfvars"
如果以网络→安全组→数据库→EC2的顺序进行部署,我认为应该可以顺利完成。
以上 – In Chinese, this term typically means “above” or “the above.” However, without additional context, it is challenging to provide a more accurate paraphrase.