AWS LambdaとAmazon S3は、AWSでよく使われるサービスです。特に、AWSを扱うインフラエンジニアは絶対に触れることになるサービスです。
Lambdaはサーバーレスでコードを実行できるサービスで、S3はオブジェクトストレージとして様々なファイルの保存に適しています。
この記事では、Gatewayエンドポイントについて解説し、Terraformを使用してプライベートサブネットにあるLambda関数から、S3バケットにファイルをアップロードするハンズオンを行います。
Terraformでのリソース構築もAWSを扱うインフラエンジアには必要なスキルですので、ぜひ手を動かして学んでみてください。
Gatewayエンドポイントとは
参考: ゲートウェイエンドポイント概要 https://docs.aws.amazon.com/ja_jp/vpc/latest/privatelink/gateway-endpoints.html#gateway-endpoint-overview
VPCでは、プライベートサブネットにあるリソースからインターネットゲートウェイやNATゲートウェイを使わずに、S3やDynamoDBなどのAWSサービスにアクセスするための仕組みを提供しています。
この仕組みの一つが「VPCエンドポイント」で、その中でもS3にアクセスする際に使われるのが「GatewayタイプのVPCエンドポイント」です。
Gatewayエンドポイントの役割
通常、プライベートサブネット(外部のインターネットからアクセスできないネットワークセグメント)内にあるリソースがS3にアクセスするには、NATゲートウェイやインターネットゲートウェイを通してインターネットに出る必要があります。
Gatewayエンドポイントは、この問題を解決するための仕組みになります。
GatewayエンドポイントをVPC内に作成すると、インターネットを経由せずにプライベートサブネットから直接S3へアクセスできるようになり、データ通信をAWSネットワーク内で完結することができます。
Gatewayエンドポイントのメリット
ここからは、Gatewayエンドポイントにはどんなメリットがあるのかを解説します。特にS3へのアクセスに焦点を当てて、セキュリティやコスト面での利点を見ていきましょう。
セキュリティの向上
Gatewayエンドポイントのメリットの一つは、セキュリティの向上です。
通常、プライベートサブネット内のリソースがS3にアクセスするには、NATゲートウェイやインターネットゲートウェイを通してインターネットに出る必要があります。しかしこの方法では、インターネットを経由するため、外部からの不正アクセスや攻撃のリスクが増します。
Gatewayエンドポイントを使うと、VPC内のリソースがインターネットを経由せずに直接S3にアクセスできます。
データ通信がAWSの内部ネットワークで完結するため、外部からの攻撃のリスクを低減できます。機密情報などをS3に保存する場合、Gatewayエンドポイントを利用することでインターネットに出ることなく安全にデータをやり取りできます。
https://docs.aws.amazon.com/ja_jp/vpc/latest/privatelink/vpc-endpoints-s3.html
コスト削減
NATゲートウェイを利用してプライベートサブネット内のリソースがS3にアクセスする場合、NATゲートウェイを設置している間の利用料金とデータ転送料金がかかります。仮に大量のデータを頻繁にS3とやり取りする場合、コストは積み重なっていきます。
Gatewayエンドポイントを利用することで、NATゲートウェイを作成する必要がなくなるため、NATゲートウェイの料金を丸ごと削減できます。
Gatewayエンドポイント自体には追加の利用料金は発生しませんが、S3へのデータ転送料金は通常通りかかる点は理解しておく必要があります。
設定方法がシンプル
Gatewayエンドポイントの設定は比較的シンプルです。VPC内のルートテーブルにGatewayエンドポイントを追加するだけで利用でき、エンドポイントの作成後はVPC内のすべてのリソースがそのエンドポイントを通じてS3にアクセスできるようになります。
Gatewayエンドポイントの注意点
Gatewayエンドポイントは便利な機能ですが、利用する際にはいくつか注意点があります。ここでは、導入前に押さえておくべき重要なポイントを紹介します。
利用できるサービスは限られている
Gatewayエンドポイントは、すべてのAWSサービスで利用できるわけではありません。2024年9月時点では、S3とDynamoDBの2つのサービスにのみ対応しています。
プライベートサブネットから他のS3とDynamoDB以外のAWSサービスにアクセスする場合は、Interfaceエンドポイントを利用する必要があります。
https://docs.aws.amazon.com/ja_jp/vpc/latest/privatelink/create-interface-endpoint.html
Interfaceエンドポイントを利用する場合、料金が発生することを認識しておく必要があります。
https://aws.amazon.com/jp/privatelink/pricing/
リージョンごとのエンドポイント
Gatewayエンドポイントは、作成したVPCの特定のリージョンでのみ有効です。複数のリージョンでS3やDynamoDBにアクセスする際には、各リージョンで個別にGatewayエンドポイントを設定する必要があります。
セキュリティグループの設定
Gatewayエンドポイント自体にはセキュリティグループを設定することはできません。
代わりに、エンドポイントを通してS3にアクセスするリソース(EC2やLambda)のセキュリティグループルールで通信を制御することができます。
セキュリティグループの設定には、AWSが提供するAWS-managed prefix listsを利用すると便利です。プレフィックスリストを使用すると、S3のIPアドレス範囲を簡単に指定でき、セキュリティグループのルール管理が楽になります。
プレフィックスリストに関しては、公式ドキュメントを参照ください。
コストに関する考慮
前述のとおり、Gatewayエンドポイント自体の利用に追加の料金はかかりませんが、S3へのデータ転送料金は発生します。プライベートサブネットから大量のデータをS3に転送する場合、この転送料金がかかる点は理解しておく必要があります。
その他の注意点に関しては、以下公式ドキュメントを参照ください。
TerraformでGatewayエンドポイントを作成してみよう
ここからは、TerraformでVPC内にLambdaを構築し、Gatewayエンドポイントを通してS3バケットへオブジェクトを配置します。
今回のハンズオンでは、プライベートサブネットを持つVPCを作成し、そのVPC内でLambdaを作成します。そして、Gatewayエンドポイントを通してS3にアクセスするようリソースを作成していきます。
ディレクトリ構成
ディレクトリ構成は以下のように作成しました。
.
├── lambda-functions
│ └── index.mjs
├── lambda-layer
│ └── nodejs
│ ├── node_modules
│ ├── package-lock.json
│ └── package.json
├── main.tf
├── terraform.tfstate
├── terraform.tfstate.backup
└── versions.tf
レイヤー用ディレクトリの作成
今回はAWS SDKをLambdaで使用するため、AWS SDK用のレイヤーを作成します。
以下コマンドでディレクトリを作成し、AWS SDKをインストールします。
mkdir -p lambda-layer/nodejs
cd lambda-layer/nodejs
npm init -y
npm install aws-sdk
index.mjsの作成
実際のLambda関数となる、index.mjs
ファイルを作成します。
// index.mjs
import AWS from 'aws-sdk';
const s3 = new AWS.S3();
export const handler = async (event) => {
const params = {
Bucket: 'gateway-s3-bucket-xxxx',
Key: 'test-file.txt',
Body: 'Create Gateway Endpoint!',
};
try {
await s3.putObject(params).promise();
return {
statusCode: 200,
body: JSON.stringify({ message: 'File uploaded successfully!' }),
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: error.message }),
};
}
};
Bucket
とKey
でそれぞれバケット名とファイル名を指定し、Body
でファイルの内容を作成する簡単な関数を作成します。
main.tf
main.tfを以下のように作成し、GatewayエンドポイントやLambda関数など、必要なリソースを作成します。
# main.tf
# VPCの作成
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
# プライベートサブネットの作成
resource "aws_subnet" "private_subnet" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
}
# ルートテーブルの作成
resource "aws_route_table" "private_route_table" {
vpc_id = aws_vpc.main.id
}
# ルートテーブルにサブネットを関連付け
resource "aws_route_table_association" "subnet_association" {
subnet_id = aws_subnet.private_subnet.id
route_table_id = aws_route_table.private_route_table.id
}
# IAMロールの作成
resource "aws_iam_role" "lambda_upload_role" {
name = "lambda_upload_role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
# IAMポリシーの作成
resource "aws_iam_policy" "lambda_s3_put_policy" {
name = "lambda_s3_put_policy"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject"
],
"Resource": "arn:aws:s3:::gateway-s3-bucket-xxxx/*"
},
{
"Effect": "Allow",
"Action": [
"ec2:CreateNetworkInterface", # ネットワークインターフェイスの作成
"ec2:DescribeNetworkInterfaces", # ネットワークインターフェイスの参照
"ec2:DeleteNetworkInterface" # ネットワークインターフェイスの削除
],
"Resource": "*"
}
]
}
EOF
}
# IAMポリシーのアタッチ
resource "aws_iam_role_policy_attachment" "lambda_policy" {
role = aws_iam_role.lambda_upload_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_iam_role_policy_attachment" "s3_policy_attachment" {
role = aws_iam_role.lambda_upload_role.name
policy_arn = aws_iam_policy.lambda_s3_put_policy.arn
}
# Lambda Layer用ZIPファイルの作成
data "archive_file" "layer_zip" {
type = "zip"
source_dir = "${path.module}/lambda-layer"
output_path = "${path.module}/aws_sdk_layer.zip"
}
# Lambda Layerの作成
resource "aws_lambda_layer_version" "aws_sdk_layer" {
layer_name = "aws_sdk_layer"
compatible_runtimes = ["nodejs20.x"]
filename = data.archive_file.layer_zip.output_path
source_code_hash = data.archive_file.layer_zip.output_base64sha256
}
# Lambda関数用ZIPファイルの作成
data "archive_file" "lambda_zip" {
type = "zip"
source_dir = "${path.module}/lambda-functions"
output_path = "${path.module}/lambda_function.zip"
}
# セキュリティグループの作成
resource "aws_security_group" "lambda_sg" {
name = "lambda_sg"
vpc_id = aws_vpc.main.id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Lambda関数の作成
resource "aws_lambda_function" "s3_upload_lambda" {
function_name = "s3-upload-function"
role = aws_iam_role.lambda_upload_role.arn
handler = "index.handler"
runtime = "nodejs20.x"
filename = data.archive_file.lambda_zip.output_path
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
# VPCの設定
vpc_config {
subnet_ids = [aws_subnet.private_subnet.id]
security_group_ids = [aws_security_group.lambda_sg.id]
}
# LayerをLambda関数に追加
layers = [aws_lambda_layer_version.aws_sdk_layer.arn]
}
# S3 Gatewayエンドポイントの作成
resource "aws_vpc_endpoint" "s3_endpoint" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.ap-northeast-1.s3"
route_table_ids = [aws_route_table.private_route_table.id]
}
# S3バケットの作成
resource "aws_s3_bucket" "gateway_s3_bucket" {
bucket = "gateway-s3-bucket-xxxx"
}
# S3バケットポリシーの作成
resource "aws_s3_bucket_policy" "gateway_s3_policy" {
bucket = aws_s3_bucket.gateway_s3_bucket.bucket
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::gateway-s3-bucket-xxxx/*"
}
]
}
EOF
}
ネットワークインターフェイスの権限を付与
LambdaにアタッチするIAM Policyを以下のように作成します。
# IAMポリシーの作成
resource "aws_iam_policy" "lambda_s3_put_policy" {
name = "lambda_s3_put_policy"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject"
],
"Resource": "arn:aws:s3:::gateway-s3-bucket-xxxx/*"
},
{
"Effect": "Allow",
"Action": [
"ec2:CreateNetworkInterface", # ネットワークインターフェイスの作成
"ec2:DescribeNetworkInterfaces", # ネットワークインターフェイスの参照
"ec2:DeleteNetworkInterface" # ネットワークインターフェイスの削除
],
"Resource": "*"
}
]
}
EOF
}
Action
のec2:Create~
の部分で、VPC内でLambda関数を実行するために必要な3つのEC2アクションを許可しています。
このアクションを許可することで、LambdaがVPC内で動作する際に自動的にネットワークインターフェイスを作成、参照、削除することが可能になります。
バケットポリシーの作成
以下リソースで、作成したS3バケットへLambdaがオブジェクトを配置するための権限を許可します。
# S3バケットポリシーの作成
resource "aws_s3_bucket_policy" "gateway_s3_policy" {
bucket = aws_s3_bucket.gateway_s3_bucket.bucket
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::gateway-s3-bucket-xxxx/*"
}
]
}
EOF
}
Lambda関数、レイヤーの作成
Lambda関数の定義は次のように作成します。
vpc_config
でどのサブネット、セキュリティグループに関数を関連付けるかを定義します。
layers
では、どのレイヤーを関数で使用するかを定義しています。
# Lambda関数の作成
resource "aws_lambda_function" "s3_upload_lambda" {
function_name = "s3-upload-function"
role = aws_iam_role.lambda_upload_role.arn
handler = "index.handler"
runtime = "nodejs20.x"
filename = data.archive_file.lambda_zip.output_path
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
# VPCの設定
vpc_config {
subnet_ids = [aws_subnet.private_subnet.id]
security_group_ids = [aws_security_group.lambda_sg.id]
}
# LayerをLambda関数に追加
layers = [aws_lambda_layer_version.aws_sdk_layer.arn]
}
Lambda関数の定義、レイヤーの追加に関しては、過去ハンズオン記事を参照ください。
https://envader.plus/article/466
https://envader.plus/article/467
https://envader.plus/article/474
AWS CLIでLambda関数のテストを実行
ソースコードを作成後、plan、applyを実行しリソースを作成します。
terraform plan
terraform apply
リソース作成の完了後、AWS CLIでテストを実行します。
aws lambda invoke --function-name <関数名> --payload '{}' output.json
最後に、S3バケットにtest-file.txt
が作成されているかを確認します。
s3://
の後には、作成したバケット名を指定します。
aws s3 ls s3://gateway-s3-bucket-xxxx
2024-xx-xx xx:xx:xx 24 test-file.txt
test-file.txt
が作成されていることが確認できれば完了です。
まとめ
この記事では、AWSのGatewayエンドポイントについて解説しました。
ハンズオンの部分では、IaCツールのTerraformを使用してプライベートサブネット内のLambda関数からS3バケットにファイルをアップロードするまでのリソース作成を行いました。
このハンズオンを通じて、VPC内でLambda関数を動かし、インターネットを経由せずにS3にアクセスする方法を体験いただけたと思います。
AWSを利用するインフラエンジニアにとって、こうした知識は日々の業務に役立つスキルとなりますので、ぜひ何回も手を動かしながら、理解を深めていきましょう。
参考記事
インターフェイスVPCエンドポイント AWS のサービス を使用して にアクセスする
【Terraformハンズオン】同期呼び出しのLambda関数をデプロイしてみよう
【番外編】USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話
プログラミング塾に半年通えば、一人前になれると思っているあなた。それ、勘違いですよ。「なぜ間違いなの?」「正しい勉強法とは何なの?」ITを学び始める全ての人に知って欲しい。そう思って書きました。是非読んでみてください。
「フリーランスエンジニア」
近年やっと世間に浸透した言葉だ。ひと昔まえ、終身雇用は当たり前で、大企業に就職することは一種のステータスだった。しかし、そんな時代も終わり「優秀な人材は転職する」ことが当たり前の時代となる。フリーランスエンジニアに高価値が付く現在、ネットを見ると「未経験でも年収400万以上」などと書いてある。これに釣られて、多くの人がフリーランスになろうとITの世界に入ってきている。私もその中の1人だ。数年前、USBも知らない状態からITの世界に没入し、そこから約2年間、毎日勉学を行なった。他人の何十倍も努力した。そして、企業研修やIT塾で数多くの受講生の指導経験も得た。そこで私は、伸びるエンジニアとそうでないエンジニアをたくさん見てきた。そして、稼げるエンジニア、稼げないエンジニアを見てきた。
「成功する人とそうでない人の違いは何か?」
私が出した答えは、「量産型エンジニアか否か」である。今のエンジニア市場には、量産型エンジニアが溢れている!!ここでの量産型エンジニアの定義は以下の通りである。
比較的簡単に学習可能なWebフレームワーク(WordPress, Rails)やPython等の知識はあるが、ITの基本概念を理解していないため、単調な作業しかこなすことができないエンジニアのこと。
多くの人がフリーランスエンジニアを目指す時代に中途半端な知識や技術力でこの世界に飛び込むと返って過酷な労働条件で働くことになる。そこで、エンジニアを目指すあなたがどう学習していくべきかを私の経験を交えて書こうと思った。続きはこちらから、、、、
エンベーダー編集部
エンベーダーは、ITスクールRareTECHのインフラ学習教材として誕生しました。 「遊びながらインフラエンジニアへ」をコンセプトに、インフラへの学習ハードルを下げるツールとして運営されています。
関連記事
2020.02.25
完全未経験からエンジニアを目指す爆速勉強法
USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話
- キャリア・学習法
- エンジニア
2024.05.13
CICDパイプラインでECSを使用したアプリケーションのデプロイ
今回は、このパイプラインを拡張し、ECSを使用してアプリケーションをデプロイするデプロイステージの設定と動作確認を行います。
- AWS
- ハンズオン
2024.09.22
【Terraformハンズオン】非同期呼び出しのLambda関数をデプロイしてみよう
こちらの記事では、Lambda関数の「非同期呼び出し」に焦点を絞って解説し、Terraformを使ったハンズオンを行います。
- AWS
- Terraform
- ハンズオン
2024.05.12
terraform importコマンド&importブロックハンズオン!既存リソースをTerraformへ取り込んでみよう
この記事では、そんな時に役立つTerraformの機能、importの使い方をハンズオン形式で実際に手を動かしながら学んでいきます。今回のハンズオンでは料金が発生しないよう、VPC、S3バケットの2つに絞って実践します。
- Terraform
- インフラエンジニア