「サーバーレス LAMP スタック – Part 2」をやってみる
Summary
- サーバーレス LAMP スタック – Part 2をベースにlambda -> rds proxy -> rds の挙動確認をしてみる
- Blogとの主な違い
- Lambda以外のインフラ部分はTerraformで構築
- IAM認証は使用しない(理由は後述)
- samでのデプロイは使用しない
- Blogの手順を実施したい場合は、Part1のPHPコンパイル手順時に
--with-mysqli
を入れるようにすれば他は適宜読み替えることで実施可能かと
Detail
今回の構成はこんな感じに
※RouteTable等諸々のものは端折ってる、特にLambda
VPCの構築
BlogではVPC等の構築についてはさらっと記載されているだけなので
Blogの内容をとりあえず実施したい場合はデフォルトVPCを使うことが最速だとは思う。
Aurora DB クラスターを作成する前に、VPC や RDS DB サブネットグループの作成などの前提条件を満たしている必要があります。これをセットアップする方法の詳細については、DBクラスターの前提条件を参照してください。
Part1にも記載したようにインフラの管理部分はどこらへんまでがいいのかを理解するためなので
イチからTerraformで構築してみる。
terraformのバージョンは現行最新の0.14.5
ファイル構成については特別なことはなく、フラットな構造でファイルごとに役割を分けてある。
強いていうと、各サービスで利用するIAM、Security Groupなどはそのサービスの中に含めている。
IAMのポリシーについてはJSONファイルを外だしし、あとから読み込む形とした。
.
├── file
│ ├── rds-proxy-policy.json.tpl
│ └── rds-proxy-role.json
└── stage
├── output.tf
├── provider.tf
├── rds_aurora.tf
├── rds_proxy.tf
├── variables.tf
└── vpc.tf
構成図に合わせてHCLファイルを書き出してみる
今回はtfstateをローカルに置いてるが、実運用時はS3にアップすることが多いかと思う
terraform {
required_version = "0.14.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.0"
}
}
}
# Configure the AWS Provider
provider "aws" {
region = "ap-northeast-1"
}
# https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
name = "${local.environment}-${local.project_name}"
cidr = "10.0.0.0/16"
# Lambda用とDatabase用のSubnetを分けて作成する
azs = ["ap-northeast-1a", "ap-northeast-1c"]
private_subnets = ["10.0.10.0/24", "10.0.20.0/24"]
database_subnets = ["10.0.100.0/24", "10.0.200.0/24"]
# 今回はInternet Gatewayも不要
create_igw = false
# natgatewayも不要なので無効
enable_nat_gateway = false
# 名前解決系
enable_dns_hostnames = true
enable_dns_support = true
# common tags
tags = local.common_tags
}
locals {
project_name = "sample"
manage_tool = "terraform"
environment = "stage"
}
locals {
common_tags = {
Project = local.project_name
ManagedBy = local.manage_tool
Env = local.environment
}
}
まずはVPCを構築
terraform init
terraform plan
terraform apply
Aurora Mysqlの構築
tfファイルのコメントに記載してしまっているが、IAM認証はインスタンスのサイズであったり
推奨事項を確認するに、場合によっては本番環境等では利用しないほうが良さそうであったので
今回検証時にも無効とした。
#############
# RDS Aurora
# (https://registry.terraform.io/modules/terraform-aws-modules/rds-aurora/aws/latest)
#############
# iam_database_authentication_enabledを有効にするためにはt2.small及びt3.small以上
# 利用する際に1秒間に200以上のアクセスがある場合は非推奨とのことなので導入はしない方向
# https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/UsingWithRDS.IAMDBAuth.html
resource "aws_db_parameter_group" "aurora" {
name = "${local.environment}-${local.project_name}-aurora-db-57-param-group"
family = "aurora-mysql5.7"
description = "${local.environment}-${local.project_name}-aurora-db-57-param-group"
}
resource "aws_rds_cluster_parameter_group" "aurora" {
name = "${local.environment}-${local.project_name}-aurora-57-cl-param-group"
family = "aurora-mysql5.7"
description = "${local.environment}-${local.project_name}-aurora-57-cl-param-group"
}
module "aurora" {
source = "terraform-aws-modules/rds-aurora/aws"
version = "~> 3.0"
name = "${local.environment}-${local.project_name}-db"
engine = "aurora-mysql"
engine_version = "5.7.mysql_aurora.2.07.2"
vpc_id = module.vpc.vpc_id
subnets = module.vpc.database_subnets
replica_count = 1
instance_type = "db.t3.small"
username = local.project_name
password = random_password.aurora.result
create_random_password = false
apply_immediately = true
skip_final_snapshot = true
db_parameter_group_name = aws_db_parameter_group.aurora.id
db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.aurora.id
enabled_cloudwatch_logs_exports = ["audit", "error", "general", "slowquery"]
allowed_cidr_blocks = concat(module.vpc.private_subnets_cidr_blocks, module.vpc.database_subnets_cidr_blocks)
allowed_security_groups = [module.aurora.this_security_group_id]
create_security_group = true
iam_database_authentication_enabled = false
tags = local.common_tags
}
# https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password
resource "random_password" "aurora" {
length = 16
special = true
override_special = "_%<>"
}
新規のmoduleが入ったときは再度init
しないと怒られる
エラーとしてinit
してねって出るので、特に迷うことはないかと
RDSは構築に時間がかかるのでのんびり待つことにする。
terraform init
terraform plan
terraform apply
RDS Proxyを構築する
Secrets Managerへの登録及びIAMの作成
AWS Secrets Managerに登録するAuroraの認証情報はProxyで使うので
rds_proxyのファイルに記載する
CLIだと1コマンドですんでいるが、Terraform的にはシークレットの作成と記載の中身が分離している。
このシークレットは、データベースへの接続プールを維持するためにRDS Proxy によって使用されます。
シークレットにアクセスするには、RDS Proxy サービスにアクセス許可を明示的に付与する必要があります。
作成したSecretsManagerにアクセス許可を提供するIAMポリシーも必要になるのでこちらもrds_proxyのファイルに記載する。ポリシーはJSON形式となっており、ファイルに直接記載すると可視性が悪くなるので別途ファイルからの読み出す形に。
こちらの流れはCLIとTerraformの記載内容がマッチしてるのでとくに違和感はなかった。
jsonファイルはfileディレクトリでの管理とした。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "rds.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"secretsmanager:GetResourcePolicy",
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
"secretsmanager:ListSecretVersionIds"
],
"Resource": [
"${secretmanager_aurora_arn}"
]
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": [
"secretsmanager:GetRandomPassword",
"secretsmanager:ListSecrets"
],
"Resource": "*"
}
]
}
############
# RDS Proxy
############
# Getting Started の手順3に相当
# Auroraのユーザ、パスワードをSecretManagerに格納
# Secretに必要な情報はlocalsにまとめる形に
# 即時削除を可能とするようにrecovery_window_in_daysは0
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret#recovery_window_in_days
# Connect to Aurora Mysql from RDS Proxy
# https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/rds-proxy.html#rds-proxy-secrets-arns
locals {
aurora_secret_data = {
username = module.aurora.this_rds_cluster_master_username
password = module.aurora.this_rds_cluster_master_password
engine = "mysql"
host = module.aurora.this_rds_cluster_endpoint
port = module.aurora.this_rds_cluster_port
dbClusterIdentifier = module.aurora.this_rds_cluster_id
}
}
resource "aws_secretsmanager_secret" "aurora" {
name = "${local.environment}-${local.project_name}-aurora"
description = "My test database secret created"
recovery_window_in_days = 0
}
resource "aws_secretsmanager_secret_version" "aurora" {
secret_id = aws_secretsmanager_secret.aurora.id
secret_string = jsonencode(local.aurora_secret_data)
}
# Getting Startedの手順4に相当
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment
# RDS ProxyからSecret Mangerへのアクセス可能にするためのIAMを作る
resource "aws_iam_role" "rds-proxy" {
name = "${local.environment}-${local.project_name}-rds-proxy-role"
assume_role_policy = file("../file/rds-proxy-role.json")
}
# 手順5に相当
resource "aws_iam_policy" "rds-proxy" {
name = "${local.environment}-${local.project_name}-rds-proxy-policy"
policy = templatefile("../file/rds-proxy-policy.json.tpl", { secretmanager_aurora_arn = aws_secretsmanager_secret.aurora.arn })
}
# 手順6に相当
resource "aws_iam_role_policy_attachment" "rds-proxy" {
role = aws_iam_role.rds-proxy.name
policy_arn = aws_iam_policy.rds-proxy.arn
}
RDS Proxyの構築
Blogの手順ではIAM認証を適用するため、IAMAuth値(tfファイル内ではauth内のiam_auth
)をREQUIRED
としているが、本手順では使わないのでtf内ではDISABLE
としている。
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_proxy
# AuroraのIAM認証をfalseにしてるので iam_authはDISABLE
# TLS認証も使用しないのでrequire_tlsはfalse
# https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/rds-proxy.html#rds-proxy-creating
# AuroraとRDS Proxyを一度に作ろうとするとProxy作成時にRDS Instanceができてないエラーが出るのでそれを回避するためにSleepを入れる
resource "time_sleep" "wait_60_seconds" {
depends_on = [module.aurora]
create_duration = "60s"
}
# AuroraのSGではEgressがないためRDS ProxyからAuroraへの接続ができない
# なのでProxy用にSGを作成する
resource "aws_security_group" "rds-proxy" {
name_prefix = "${local.environment}-${local.project_name}-rds-proxy-"
description = "For rds-proxy"
vpc_id = module.vpc.vpc_id
tags = merge(local.common_tags,
{
Name = "${local.environment}-${local.project_name}-rds-proxy"
},
)
lifecycle {
create_before_destroy = true
}
}
resource "aws_security_group_rule" "ingress_rds-proxy" {
type = "ingress"
from_port = module.aurora.this_rds_cluster_port
to_port = module.aurora.this_rds_cluster_port
protocol = "tcp"
cidr_blocks = module.vpc.private_subnets_cidr_blocks
security_group_id = aws_security_group.rds-proxy.id
}
resource "aws_security_group_rule" "egress_rds-proxy" {
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.rds-proxy.id
}
resource "aws_db_proxy" "rds_proxy" {
name = "${local.environment}-${local.project_name}-rds-proxy"
debug_logging = false
engine_family = "MYSQL"
idle_client_timeout = 1800
require_tls = false
role_arn = aws_iam_role.rds-proxy.arn
vpc_security_group_ids = [aws_security_group.rds-proxy.id]
vpc_subnet_ids = module.vpc.database_subnets
auth {
auth_scheme = "SECRETS"
iam_auth = "DISABLED"
secret_arn = aws_secretsmanager_secret.aurora.arn
}
tags = local.common_tags
depends_on = [time_sleep.wait_60_seconds]
}
resource "aws_db_proxy_default_target_group" "rds_proxy" {
db_proxy_name = aws_db_proxy.rds_proxy.name
connection_pool_config {
connection_borrow_timeout = 120
max_connections_percent = 100
max_idle_connections_percent = 50
}
}
resource "aws_db_proxy_target" "rds_proxy" {
db_cluster_identifier = module.aurora.this_rds_cluster_id
db_proxy_name = aws_db_proxy.rds_proxy.name
target_group_name = aws_db_proxy_default_target_group.rds_proxy.name
}
# LambdaからRDS Proxyへ接続するための情報はパラメータストアに格納
# IAM認証を使わないのと、Secret Parameterは呼び出しに費用が発生するため。
# 確認するのも楽になる。
# Lambdaからの接続となるのでEndpointはRDS Proxyのものとなる
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter
# https://qiita.com/NaokiIshimura/items/3d5b1a6db906338ae103
locals {
rds_proxy_ssm_data = {
"/${local.environment}/${local.project_name}/rds_proxy/username" = module.aurora.this_rds_cluster_master_username
"/${local.environment}/${local.project_name}/rds_proxy/password" = module.aurora.this_rds_cluster_master_password
"/${local.environment}/${local.project_name}/rds_proxy/host" = aws_db_proxy.rds_proxy.endpoint
}
}
resource "aws_ssm_parameter" "rds-proxy" {
for_each = local.rds_proxy_ssm_data
name = each.key
value = each.value
type = "SecureString"
tags = local.common_tags
}
VPC 構成での PHP Lambda 関数のデプロイ
SAMを使ったデプロイはしない(いろいろTemplate.ymlを修正する必要があるし)
IAM認証を使わないので、githubのindex.php
も使わないため
Part1のREADMEの手順とほぼ同一となるが、configure時のオプションに--with-mysqli
を追加する
./configure --prefix=/home/ec2-user/environment/php-7-bin/ --with-openssl=/usr/local/ssl --with-curl --with-zlib --with-mysqli
aws lambda publish-layer-version \
--layer-name PHP-example-runtime \
--zip-file fileb://runtime.zip \
--region ap-northeast-1
aws lambda publish-layer-version \
--layer-name PHP-example-vendor \
--zip-file fileb://vendor.zip \
--region ap-northeast-1
レイヤーのアップロードまではPart1と同じ
ブラウザからLambdaの関数を作成する際に「詳細設定」にてVPC等の設定も行うこと
サブネットはPrivate
セキュリティグループは今回についていえば何でも良いので「default」に
作成後 Part1と同様に以下を実施
- layerの追加(runtime,vendor)
- bootstrap.sampleを削除し、src/下にindex.phpを作成
- 作成後「デプロイ」を押してセーブ
- ハンドラを
index
に変更
<?php
function index($data){
$proxyHost=getenv('proxyHost');
$username = getenv('username');
$pass = getenv('password');
$db=getenv('db');
//Connect to Proxy
$mysqli = new mysqli($proxyHost, $username, $pass, $db);
if ($mysqli->connect_errno) {
echo "Error: Failed to make a MySQL connection, here is why: <br />";
echo "Errno: " . $mysqli->connect_errno . "<br />";
echo "Error: " . $mysqli->connect_error . "<br />";
exit;
}
/***** Example code to perform a query and return all tables in the DB *****/
$res = mysqli_query($mysqli,"SHOW TABLES");
while($cRow = mysqli_fetch_array($res))
{
$tables[] = $cRow;
}
echo '<pre>';
print_r($tables);
echo '</pre>';
$mysqli -> close();
return json_encode($tables);
}
php内でgetenv
で値をとってきてるので、Lambdaの「環境変数」に必要な情報を入れていく
username,passはSecretsManagerにて確認可能(tfstateとかからでも確認は可能)
dbはmysql
で(※Aurora作成時にDBを作成していないので今回はmysqlとしている)
proxyHostはブラウザにてAWS管理画面よりRDS -> Proxies
対象のProxy識別子をクリックし、プロキシエンドポイントを入れる。
今回はパラメータストアに入れたのでそちらで確認をしてみる。
aws ssm get-parameters-by-path --path /stage/sample/rds_proxy --with-decryption |jq '.[][]|{"Path": .Name, "Value": .Value}'
{
"Path": "/stage/sample/rds_proxy/host",
"Value": "stage-sample-rds-proxy.proxy-cjsmtzknhogt.ap-northeast-1.rds.amazonaws.com"
}
{
"Path": "/stage/sample/rds_proxy/password",
"Value": "1iP9B9kQs6odIa%j"
}
{
"Path": "/stage/sample/rds_proxy/username",
"Value": "sample"
}
最後にBlogの手順書「Lambda 関数への RDS Proxy 設定の追加」と同じように
Lambdaの一番の下にあるデータベースプロキシの追加を実施する。
その後、Part1と似たような感じでテストを作成するが
今回は受け取る値は何もないので、空とする。
テスト作成後、テストを実施
お疲れ様でした。
さて、不要になったリソースはキレイに片付けたいが今回気をつけたいことが1つある。
Summary
- 多分あとから追記することになるかもしれないけど、もう疲れたので一旦はこれぎり
- 備忘録のはずなのにまる一日かかった・・・
- lambdaに関してはインフラ側ではなく、開発側で管理してもらったほうがよさそう
- ただlambdaのアップ時に必要な情報については確認できるようにしておく必要がある(VPCとか)
- 今回はLambdaの環境変数にいれたが実運用はパラメータストアから参照してもらうほうがおそらくいいだろう
- SecretsMangerは費用がかかるので、特別な理由がない場合はパラメータストアがいいと思う
その他参照した記事等
Discussion
手順にて動作の確認が行えなかったので、手順の修正をした
主な変更点は以下
今回のはおそらく問題なく実施できるはず