본문 바로가기
AWS

[AWS] API GateWay로 Lambda를 호출하여 S3의 파일을 SFTP 서버로 데이터 전송하기 (lambda import)

by 삼콩 2022. 3. 3.

본 포스팅에서는 Lambda로 SFTP 서버에 S3 버킷의 파일을 SFTP 서버로 전송하는 방법을 다룬다.

Lambda는 AWS에서 제공하는 서버리스 서비스로 AWS 리소스와 연계가 용이하다는 장점이 있다. 이 Lambda는 특정 이벤트가 발생하였을 때 동작하며, 이를 트리거라고 한다. AWS에서는 EventBridge (cron을 작성하여 주기적으로 lambda 실행 가능), API GateWay 등 다양한 트리거를 제공하며 본 포스팅에서는 API GateWay로 REST API를 lambda에 보내는 방법을 테스트한다. (매월 28일 데이터 전송과 같이 주기적인 작업이 필요 할 경우 EventBridge를 사용하면 좋다)

 

본 테스트에는 다음과 같은 사전 준비가 필요하다. 

  • SFTP 서버
  • SFTP 서버에 접근 할 수 있는 private key

 

전체적인 구조는 다음과 같으며 SFTP - S3 (sftp 용 버킷) 사이에는 별도의 SFTP 서버를 사용해도 된다.

포스팅에서의 작업 순서는

  1. Private Key 업로드
  2. Lambda 함수 작성
    1. 함수 생성
    2. 권한 수정
    3. 코드 작성
    4. Layer 추가
  3. API GateWay 테스트

순으로 진행한다.

나는 SFTP 서버를 AWS Transfer family 기반 S3 SFTP 서버로 정하여 테스트 하였으며, SFTP 서버 구성과, key 생성 및 등록 방법은 다음 포스팅에 상세히 적혀있다.

2022.02.25 - [AWS/S3] - [AWS/Transfer family, S3] AWS Transfer family와 S3로 SFTP 서버 구성하기

1. Private key 업로드

SFTP에 접근하는 방법은 ID/PW, RSA키 방식이 있으며, 본 포스팅에서는 RSA 키 방식을 사용한다. SFTP 접근을 위해서는 private key를 Lambda 함수가 가지고 있어야 하지만 Lambda는 서버리스이므로 환경 내에 해당 데이터를 영구 저장할 수 없다.

따라서 S3에 private key를 저장하고 S3로부터 Lambda가 private key를 임시로 다운로드 받아오도록 구현한다.

 

S3에는 다음과 같은 구조에 업로드한다.

  • 출발지 S3 버킷/private_key/

 

2. Lambda 함수 작성

2.1 Lambda 함수 생성

함수 기본 정보 (이름, 런타임 설정)

Python을 사용할 예정이므로 런타임은 Python3.8 선택한다.

  • Lambda > 함수 > 함수생성

 

 

2.2 Lambda 함수 권한 수정

Lambda 생성시 기본으로 IAM 역할이 함께 생성되며 이 역할에는 Cloudwatch logs 에 Lambda의 print, error log 등을 기록할 수 있는 권한이 주어져있다. 

 

Lambda에 기본 생성되는 역할, 함수명+난수로 구성됨

본 포스팅에서는 Lambda 함수가 S3에 액세스하여 데이터를 다운로드 받아와야하므로 다음과 같이 정책을 추가한다.

테스트를 위해 s3:*로 범위를 두었으나, 필요한 동작에따라 권한을 추가하자

 

2.3 Lambda 함수 작성

동작 단계는 총 세 단계이다.

  1. 트리거에 따른 API 내용 확인
  2. S3 (출발지 버킷) 에서 파일 다운로드 (SFTP 서버 다운로드
  3. SFTP 서버로 전송

언어는 Python을 사용하였으며, SFTP 통신을 위한 paramiko 라이브러리를 사용하였다.

이번 테스트에서 lambda_handler에 입력되는 event 값은 API로부터 입력되는 요청으로서, 형식은 다음과같다.

 

 

* event 입력 형식 (이 이벤트 형식은 3. API GateWay 테스트에서 사용됨) 

{
    "operation": "send",
    "data": {
        "bucket": "출발지 BUCKET",
        "sourceDir": "대상파일이 존재하는 경로",
        "sourceFile": "파일이름", 
        "destination": "SFTP에 저장될 경로"
    }
}

 

* Lambda 코드 (작성된 Lambda 코드는 event 입력 형식에 따라 달라질 수 있으며, 본 포스팅에서는 이 event 값을 기준으로 코드 작성을 하였다.)

import json
import boto3
import os
import paramiko

lambda_rsaKey = "/tmp/key/"
lambda_data = "/tmp/s3_data/"

def lambda_handler(event, context):
    
    # event json에서 operation 값 확인
    operation = event['operation']
    
    operations = ['send']

    
    if operation in operations:
        s3_get_object(event.get('data'))
        sftp(event.get('data'))
    else:
        raise ValueError('Unrecognized operation "{}"'.format(operation))
    

def s3_get_object(data):
    print(os.listdir("/tmp"))
    
    try:
        print(os.makedirs(lambda_data))
        print(os.makedirs(lambda_rsaKey))
    except FileExistsError:
        pass

    sourceDir = data["sourceDir"]
    sourceFile = data["sourceFile"]
    
    s3 = boto3.resource('s3')
    # 데이터 다운로드
    s3.meta.client.download_file('l23058-sbpark-bucket', sourceDir + sourceFile, lambda_data + sourceFile)
    # private_key 다운로드
    s3.meta.client.download_file('l23058-sbpark-bucket', 'private_key/user_linux_key', lambda_rsaKey + 'user_linux_key')
    

def sftp(data):
    print(os.listdir("/tmp"))
    print(os.listdir(lambda_data))
    print(os.listdir(lambda_rsaKey))
    
    sourceFile = data["sourceFile"]
    destination = data["destination"]
    
    host = "SFTP IP or ENDPOINT"
    port = 22
    username = "SFTP_USER_NAME"
    sftp_key = lambda_rsaKey + "/user_linux_key"
    
    sftp_key = paramiko.RSAKey.from_private_key_file(sftp_key)
    transport = paramiko.Transport((host, port))
    transport.start_client(event=None, timeout=15)
    transport.get_remote_server_key()
    transport.auth_publickey(username, sftp_key, event=None)
    sftp = paramiko.SFTPClient.from_transport(transport)
    
    sftp.put(lambda_data + sourceFile, destination + sourceFile)
    
    sftp.close()
    transport.close()

 

2.4 Lambda Layer 추가

위 코드에서 사용된 paramiko 라이브러리는 파이썬에서 기본으로 제공하고있지 않은 외부 라이브러리이다.

AWS에서는 이러한 외부 라이브러리 및 기타 종속성을 Lambda에서 사용할 수 있도록 zip 형태로 패키징하는 Layer 기능을 제공한다.

 

AWS에서는 각 런타임에 따라 다음과 같은 구조를 권고하니 반드시 지켜서 업로드해야한다.

 

출처: https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/configuration-layers.html

 

레이어 생성에 대한 보다 자세한 내용은 이전 포스팅에서 확인 할 수 있다.

2022.03.03 - [AWS] - [AWS/Lambda] Lambda에 외부 라이브러리 import 하기 (Lambda layer)

 

생성된 레이어를 람다 함수에 등록한다.

 

3. API GateWay 테스트

마지막으로 API GateWay를 생성하고 Post 방식으로 테스트를 진행해본다.

 

3.1 API GateWay 생성

API 생성 > REST API 선택 > 새 API > API 이름 입력

 

3.2 POST 매서드 생성

API가 생성되었으면 POST 메서드를 생성한다.

 

POST 요청에 대한 설정을 입력한다.

 - 이때 작성한 람다 함수 이름을 입력해야 함

 

3.3 테스트 내용을 작성해서 Lambda 함수 작동 테스트

생성된 POST 메서드에서 테스트 클릭

 

테스트에 입력 할 내용은 2.3 Lambda 함수 작성 단계에서 잠시 소개한 event 형식에 따른다.

테스트를 클릭 후 대상 SFTP 서버를 확인하면 해당 파일이 추가된 것을 알 수 있다.

본 포스팅의 경우 SFTP 서버를 S3로 사용하였으므로 (테스트 목적으로 Transfer Family를 사용하여 S3 SFTP를 사용하였음) 버킷을 확인한 결과, 1.PNG가 testDir에 추가된 것을 확인할 수 있었다. 

 

 

 

[참고]

https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/configuration-layers.html

https://docs.aws.amazon.com/ko_kr/apigateway/latest/developerguide/how-to-deploy-api.html

https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/services-apigateway-tutorial.html

 

 

댓글