この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
みなさん、http://checkip.amazonaws.com/をご存知ですか?
AWSの地味〜なサービスで、ローカルコンピューターのパブリックIPを表示するだけのサービスです。 AWS Batchのドキュメントに記載されています。 https://docs.aws.amazon.com/ja_jp/batch/latest/userguide/get-set-up-for-aws-batch.html
このサービス、シェルでcurlと組み合わせて、AWS CLIでSecurityGroupにあけるGlobalIPを指定する時に使ったり、とても地味に使えるやつなんです。
このサービス、AWSのサービスを組み合わせれば簡単に作れるのでは? と、思って作ってみたら、30分くらいでできたのでご紹介いたします。
構成図
ALBで受けて、Lambdaに流すだけです。
構築手順
9割方、CloudFormationで作ります。
---
AWSTemplateFormatVersion: '2010-09-09'
Description: checkip
Parameters:
CidrPrefix:
Type: String
Default: 10.101
Resources:
# VPC
Igw:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-igw"
IgwAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref Igw
VpcId: !Ref Vpc
RouteDefault:
Type: AWS::EC2::Route
DependsOn: IgwAttachment
Properties:
RouteTableId: !Ref RouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref Igw
RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref Vpc
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-route-table"
SubnetApp0:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select [ 0, !GetAZs '' ]
CidrBlock: !Sub "${CidrPrefix}.0.0/24"
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-app-subnet-0"
VpcId: !Ref Vpc
SubnetApp1:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select [ 1, !GetAZs '' ]
CidrBlock: !Sub "${CidrPrefix}.1.0/24"
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-app-subnet-1"
VpcId: !Ref Vpc
SubnetRouteTableAttachment0:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RouteTable
SubnetId: !Ref SubnetApp0
SubnetRouteTableAttachment1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RouteTable
SubnetId: !Ref SubnetApp1
Vpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Sub "${CidrPrefix}.0.0/16"
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-vpc"
# SecurityGroup
SecurityGroupAlb:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${AWS::StackName}-alb-sg"
GroupDescription: !Sub "${AWS::StackName}-alb-sg"
SecurityGroupIngress:
- CidrIp: 0.0.0.0/0
IpProtocol: tcp
FromPort: 80
ToPort: 80
VpcId: !Ref Vpc
# ALB
# CloudFormationではTargetGroupにLambdaを設定することができないので、ALBルールとあわせてマネコンで作る。
ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub "${AWS::StackName}-alb"
Type: application
Scheme: internet-facing
IpAddressType: ipv4
SecurityGroups:
- !Ref SecurityGroupAlb
Subnets:
- !Ref SubnetApp0
- !Ref SubnetApp1
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-alb"
# Lambda
CheckIpLambda:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: |
# -- coding: utf-8 --
def lambda_handler(event, context):
print('Event: {}'.format(event))
return {
'statusCode': 200,
'statusDescription': '200 OK',
'isBase64Encoded': False,
'headers': {
'Content-Type': 'text/html; charset=utf-8'
},
'body': '{}\n'.format(event['headers']['x-forwarded-for'])
}
Description: !Sub "${AWS::StackName}-lambda"
FunctionName: !Sub "${AWS::StackName}-lambda"
Handler: index.lambda_handler
MemorySize: 128
Role: !GetAtt RoleLambda.Arn
Runtime: python3.7
Timeout: 10
RoleLambda:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
RoleName: !Sub "${AWS::StackName}-role"
LogsPutLambdaPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
PolicyDocument:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogStreams"
],
"Effect": "Allow",
"Resource": ["*"]
}
]
}
ManagedPolicyName: !Sub "${AWS::StackName}-policy"
Roles:
- !Ref RoleLambda
CloudFormationではまだ、ALBのTargetGroupにLambdaを設定することができないので、ここだけマネジメントコンソールから作ります。
TargetGroupを作成します。
ターゲットの種類にLambdaを選択して、リストからCFnで作成したLambdaを選択します。
CFnで作成したALBを選んでリスナーを追加します。
リスナーのプロトコルはHTTPのままにして、アクションの追加から転送先を選択します。
転送先に、先程作ったTargetGroupを選んで保存します。
これだけで出来上がりです。
動作確認
ALBのDNS名でブラウザからアクセスしてみます。
パブリックIPが表示されることが確認できました。
Lambdaの解説
Lambdaのpythonプログラムはこれだけです。
# -- coding: utf-8 --
def lambda_handler(event, context):
print('Event: {}'.format(event))
return {
'statusCode': 200,
'statusDescription': '200 OK',
'isBase64Encoded': False,
'headers': {
'Content-Type': 'text/html; charset=utf-8'
},
'body': '{}\n'.format(event['headers']['x-forwarded-for'])
}
ALBから受け取るイベントの中に、クライアントの送信元IPアドレスを指す x-forwarded-for
があります。
これを利用すれば、LambdaからクライアントのIPアドレスが参照できそうです。
実際にALBアクセスしてLambdaを動かしてイベントの中身をCloudWatchLogsで見てみると、確かに入っています。
なので、イベントの中に入っているx-forwarded-forの値を参照してALBへreturnしてやります。
ALBからLambdaが受け取るイベントとALBへの応答の詳細が知りたい方は、ドキュメントを参照ください。
ロードバランサーからのイベントの受け取り | ターゲットとしての Lambda 関数 - Elastic Load Balancing
ロードバランサーへの応答 | ターゲットとしての Lambda 関数 - Elastic Load Balancing
おわりに
実際このWebサービスをLinuxで構築しようとすると、割とめんどくさそうだなーという気がします。 が、AWSを利用することでサクッとWebサービスが構築できます。
さらにRoute53使ってドメイン名つけるとか、 ALBとAWS Certificate Managerを組み合わせてHTTPS化するとかも簡単にできます。 AWSを利用してインフラ構築で楽しましょう。