ローカルコンピューターのパブリックIPを確認する checkip.amazonaws.com を自分で作ってみる
みなさん、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を利用してインフラ構築で楽しましょう。