이두잉의 AWS 세상

AWS Lambda 활용 EIP 변경

2017.04.12 17:47 - leedoing leedoing

방화벽 문제 등이나 기타 이유로 Elastic IP를 사용하는 서비스를 운영할 때 SPOF 발생

(과거 NAT Gateway가 생기기 전에 NAT Instance 단일 장애 지점 문제와 같은...ELB는 아직 EIP를 지원하지 않음) 

1. 필요한 Policy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
    "Version""2012-10-17",
    "Statement": [
        {
            "Sid""Stmt1491964068000",
            "Effect""Allow",
            "Action": [
                "ec2:AssociateAddress",
                "ec2:DescribeAddresses"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}
cs

2. Role 생성(Trust relationsships)

1
2
3
4
5
6
7
8
9
10
11
12
{
  "Version""2012-10-17",
  "Statement": [
    {
      "Effect""Allow",
      "Principal": {
        "Service""lambda.amazonaws.com"
      },
      "Action""sts:AssumeRole"
    }
  ]
}
cs


3. Lambda 구성

Lambda의 경우 세 가지 방법으로 Source를 등록할 수 있음. 직접, S3를 통해, ZIP 파일을 올리는 세 가지 방식 사용 가능.

node_module이 필요한 경우 ZIP 파일 형태로 Upload 해야 함. (Lambda 실행 파일은 index.js)

[ip, port, ec2Array 수정 필요]

(https://github.com/leedoing/eip-swap.git)


3-1. Script(node v4.x)

config.json 

1
2
3
4
5
6
7
{
        "eip" : "13.124.48.32",
        "port" : "8080",
        "ec2Ids" : ["i-0a62787523bb4cdf1",
                    "i-0e57c7b10e4137a6d"],
        "threshold" : 3
}
cs


eip-swap.js (람다 핸들러 추가 필요 및 아직 Lambda에서는 ES6 지원하지 않음)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
'use strict'
var AWS = require('aws-sdk');
AWS.config.region = 'ap-northeast-2'
var ec2 = new AWS.EC2();
var tcpp = require('tcp-ping');
var waterfall = require('async-waterfall');
var CronJob = require('cron').CronJob;
var sleep = require('system-sleep');
var async = require('async');
var config = require('../config/config.json');
 
var ec2Describe = function(eip, callback){
        const params = {
        }
        ec2.describeAddresses(params, function(err, data){
                if(err) console.log(err, err.stack);
                else{
                        for(var i = 0;  i < data.Addresses.length; i++){
                                if(data.Addresses[i].PublicIp == eip){
                                                callback(data.Addresses[i]);
                                }
                        }
                }
        });
}
 
var tcpChecker = function(eip, port, callback){
        tcpp.probe(eip, port, function(err, result){
                callback(result);
        });
}
 
var ec2AssociateAddress = function(allId, ec2Ids, ec2Id){
        var ec2TargetId = '';
        (ec2Id == ec2Ids[0]) ? ec2TargetId = ec2Ids[1] : ec2TargetId = ec2Ids[0]
        var params = {
                AllocationId: allId,
                InstanceId: ec2TargetId
        }
        ec2.associateAddress(params, function(err, data) {
                if (err) console.log(err, err.stack); // an error occurred
                else     console.log(data);           // successful response
                });
}
 
waterfall(
        [
                function(callback){
                        const ec2info = config;
                        callback(null, ec2info);
                },
                function(ec2info, callback){
                        var tcpCheckNum = 0;
                        for(var i=0; i < ec2info.threshold; i++){
                                tcpChecker(ec2info.eip, ec2info.port, function(result){;
                                        result ? null : tcpCheckNum++;
                                });
                                sleep(10000); //tcp module default timeout 5s, sleep time > 5s
                        }
                                console.log(tcpCheckNum);
                        (tcpCheckNum == ec2info.threshold ) ? callback(null, ec2info) : callback(null);
                },
                function(ec2info, callback){
                        ec2Describe(ec2info.eip, function(data){
                                callback(null, ec2info, data);
                        });
                },
                function(ec2info, ec2Address, callback){
                        ec2AssociateAddress(ec2Address.AllocationId, ec2info.ec2Ids, ec2Address.InstanceId);
                        callback(null'done');
                }
        ], function(err, result){
                if(err) console.log(err);
                else console.log(result);
        }
)
 
cs


3-2. Lambda Configuration


3-3. Lambda trigger(5분마다 실행) / (CloudWatch Event Rules은 미리 생성)


3-4. Test

10초 주기로 3번 체크 후 3번 모두 실패했기 때문에 eip-swap 진행. 실제 EC2 Instance를 확인하면 EIP가 swap 됌.

Log는 CloudWatch Logs에서 확인 가능.


4. 정리

Lambda를 통해 AWS API를 호출하고 AWS 서비스 관리가 가능. (apex, node-lambda 와 같은 lambda 배포/테스트 도구 및 모듈이 존재)

참고로 리매핑 비용(월 최초 100번 이상)이 $0.10 이니 요금 폭탄을 안 맞도록 조심...)



추가로 Lambda에 VPC를 지정하면 Internet 구간 통신 불가. (AWS API 호출 및 Public IP TCP Check 불가능) 

no vpc 혹은 VPC 내의 NAT Gateway를 통해 가능. (https://aws.amazon.com/ko/blogs/korea/new-access-resources-in-a-vpc-from-your-lambda-functions/)