Category: AWS Step Functions


AWS Step Functions 및 Amazon API Gateway 연동을 통한 서버리스 기반 승인 기능 구현하기

on | in Amazon API Gateway, AWS Lambda, AWS Step Functions, Serverless |

AWS Step Functions을 사용하는 가장 일반적인 사례는 프로그램 중에 사람이 개입해서 뭔가 승인해야 할 때입니다 (예: 회원 가입 시 이메일 승인 프로세스). Step Functions을 사용하면 상태 머신 이라고 하는 시각적 워크플로에서 일련의 단계별 분산 응용 프로그램 구성 요소를 쉽게 조정할 수 있습니다. 상태 머신을 신속하게 빌드 및 실행하여 응용 프로그램 단계를 안정적이고 확장성 높은 방식으로 실행할 수 있습니다.

이 글에서는 수동 승인 단계를 구현하기 위한 서버리스 디자인 패턴을 설명합니다. Step Functions 활동 작업(Activity Task)를 사용하여 나중에 결정을 내린 사람이 승인 또는 거부를 알려주는 고유한 토큰을 생성 및 반환할 수 있습니다.

기능 구현 단계 소개
Step Functions 상태 머신이 활동 작업 상태로 실행 되면, Step Functions은 활동(Activity)을 스케줄하고 활동 작업자(Activity Woker)를 기다립니다. 활동 작업자는 GetActivityTask를 호출하여 활동 작업을 가져오는 응용 프로그램 입니다. 작업자가 API 작업을 성공적으로 호출하면, 해당 작업자는 콜백 토큰을 포함하는 JSON blob로 작업을 보냅니다.

이 시점에서 상태를 포함한 실행 작업 상태 및 실행 분기가 일시 중지됩니다. 상태 머신에 타임아웃이 지정되어 있지 않으면,  활동 작업 상태는 활동 작업자가 vended 토큰을 사용하여 SendTaskSuccess 또는 SendTaskFailure를 호출 할 때까지 대기합니다. 이러한 일시 중지 기능이 수동 승인 단계를 구현하는 첫 번째 열쇠입니다.

두 번째 열쇠는 서버리스 환경에서 작업을 가져 오는 코드를 분리하고, 토큰을 공유 할 수 있는 한 완료 상태로 응답하고 토큰을 되돌려 보내는 코드에서 토큰을 가져 오는 기능입니다. 여기서 작업자는 단일 활동 작업 상태에 의해 관리되는 서버리스 응용 프로그램입니다.

이를 통해 일정에 따라 호출된 AWS Lambda 함수를 사용하여 활동 작업자를 구현합니다. 이 작업자는 승인 단계와 관련된 토큰을 획득하고, Amazon SES를 사용하여 승인자에게 이메일을 보냅니다.

직접 토큰을 리턴하는 응용 프로그램에서 Step FunctionsSendTaskSuccessSendTaskFailure API를 직접 호출 할 수 있기 때문에 매우 편리합니다. 이메일 클라이언트 또는 웹 브라우저가 토큰을 단계 기능으로 반환 할 수 있도록 Amazon API Gateway를 통해 이러한 두 가지 작업을 제공 하면, 보다 쉽게 작업을 수행 할 수 있습니다. 토큰을 가져 오는 람다 함수와 API 게이트웨이를 통해 토큰을 반환하는 응용 프로그램을 결합하여 서버리스 수동 승인 단계를 구현할 수 있습니다 (아래 그림 참조).

수동 승인이 필요한 상태가 되면, Lambda 함수는 승인 및 거부를 위해 두 개의 하이퍼링크가 포함 된 전자 메일을 준비하여 사용자에게 보냅니다.

권한이 부여 된 사용자가 승인(approval) 하이퍼 링크를 클릭하면 상태가 성공합니다. 사용자가 거절(reject) 링크를 클릭하면 상태가 실패합니다. 또한, 승인에 대한 시간 제한을 설정하도록 선택할 수 있으며, 제한 시간이 지나면 활동 작업 상태에서 재시도/처리 조건을 사용하여 이메일 요청을 재전송 하는 등의 조치를 취할 수 있습니다.

사례: 직원 승진 프로세스 승인
본 서버리스 애플리케이션 패턴의 하나로 이메일을 통해 관리자의 승인을 받는 것과 같은 기능을 포함하는  직원 승진 프로세스를 설계할 수 있습니다. 직원이 승진 후보로 정해되면, 새로운 Step Functions 실행이 시작됩니다. 직원의 이름과 직원 매니저 이메일 주소를 최초 제공합니다.

본 디자인 패턴을 사용하여 수동 승인 단계를 구현하고, SES를 사용하여 이메일을 관리자에게 보냅니다. 태스크 토큰을 획득 한 후, Lambda 함수는 API 게이트웨이가 제공하는 URI에 대한 하이퍼 링크가 포함 된 전자 메일을 생성하여 관리자에게 전송합니다.

본 사례에서는 IAM 역할을 만들 수 있도록 계정 관리 권한이 필요합니다.  또한, SES에 이메일 주소를 이미 등록 했으므로 주소가 보낸 사람/받는 사람으로 이메일을 보낼 수 있습니다. 자세한  정보는 Amazon SES로 전자 메일 보내기를 참조하십시오.

아래와 같은 단계로 서버리스 애플리케이션을 구현해 보겠습니다.

  1. 활동(Activity) 만들기
  2. 상태 머신(State Machine) 만들기
  3. API 생성 및 배포
  4. 활동 작업자 람다 함수 만들기
  5. 프로세스 테스트

단계 1: 활동 만들기
Step Functions 콘솔에서 Task를 선택하고, ManualStep이라는 활동을 작성하십시오.

stepfunctionsfirst_1.png

본 활동의 ARN을 저장해 두시기 바랍니다. (나중에 사용 예정)

stepfunctionsfirst_2.png

단계 2. 상태 머신 만들기
Step Functions 콘솔에서 승진 프로세스를 모델링하는 상태 머신을 만듭니다. 콘솔에서 기본 생성된 IAM 역할 인 StatesExecutionRole-us-east-1을 사용합니다. 상태 머신의 이름에 PromotionApproval를 지정하고 다음 코드를 사용합니다. Resource의 값을 위의 활동 ARN으로 바꾸십시오.

JavaScript
{
  "Comment": "Employee promotion process!",
  "StartAt": "ManualApproval",
  "States": {
    "ManualApproval": {
      "Type": "Task",
      "Resource": "arn:aws:states:us-east-1:ACCOUNT_ID:activity:ManualStep",
      "TimeoutSeconds": 3600,
      "End": true
    }
  }
}

단계 3. API 생성 및 배포
API 게이트웨이를 사용하여 SendTaskSuccess 또는 SendTaskFailure API 작업을 호출하기 위한 공용 URI를 만들고 배포합니다.

먼저, IAM 콘솔로 이동하여 API 게이트웨이가 Step Functions을 호출하는 데 사용할 수 있는 IAM 역할을 만듭니다. 역할 이름을 APIGatewayToStepFunctions로 지정하고 역할 유형으로 Amazon API 게이트웨이 를 선택한 다음 역할을 만듭니다.

IAM 역할을 만든 후, AWSStepFunctionsFullAccess 관리 정책을 추가하십시오.

stepfunctionsfirst_3.png

API 게이트웨이 콘솔에서 StepFunctionsAPI라는 새 API를 만듭니다. 성공(success) 및 실패(fail)이라는 루트 (/) 아래에 두 개의 새 리소스를 만들고 각 리소스에 대해 GET 메서드를 만듭니다.

stepfunctionsfirst_4.png

이제 각 메소드를 구성해야합니다. /fail GET 메소드를 선택하고, 다음 빙식으로 구성하십시오.

  • Integration type: AWS Service 선택
  • AWS Service: Step Functions 선택
  • HTTP method: POST 선택
  • Region: 여러분이 원하는 리전을 선택합니다. (참고. 아직 Step Functions이 지원되는 리전은 AWS Region Table에서 참고하세요.)
  • Action Type: SendTaskFailure 추가
  • Execution: APIGatewayToStepFunctions 역할의 ARN 값 입력

stepfunctionsfirst_5.png

URI를 통해 taskToken을 전달하려면 Method Request 섹션으로 이동하고 taskToken 이라는 URL Query String 매개 변수를 추가하십시오.

stepfunctionsfirst_6.png

Integration Request 섹션으로 이동하여 application/json 유형의 Body Mapping Template을 추가하여 쿼리 문자열 매개 변수를 요청 본문에 삽입합니다. 보안 경고에서 제안한 변경 사항을 허용합니다. When there are no templates defined (Recommended) 본문 패스 동작을 설정합니다. 아래 코드는 이러한 매핑을 수행합니다.

JavaScript
{
   "cause": "Reject link was clicked.",
   "error": "Rejected",
   "taskToken": "$input.params('taskToken')"
}

그런 다음, Save을 눌러 저장합니다.

다음에는 /success GET 메서드를 구성합니다. 구성은 /fail GET 메소드와 매우 유사합니다. 유일한 차이점은 Action입니다. SendTaskSuccess를 선택하고 다음과 같이 매핑을 설정하십시오.

JavaScript
{
   "output": "\"Approve link was clicked.\"",
   "taskToken": "$input.params('taskToken')"
}

API 작업을 구성한 후 API 게이트웨이 콘솔의 마지막 단계는 respond 이라고하는 새로운 단계에 API 작업을 배포하는 것입니다. GET 메소드 중 하나에서 Invoke URL 링크를 선택하여 API를 테스트 할 수 있습니다. 토큰이 URI에 제공되지 않으므로 ValidationException 메시지가 표시되어야 합니다.

stepfunctionsfirst_7.png

단계 4: 활동 작업자를 위한 Lambda 함수 만들기
Lambda 콘솔에서 Node.js 4.3 런타임에 대한 신규 템플릿을 활용하여 CloudWatch Events Schedule 트리거로 람다 함수를 만듭니다. Schedule expression에 입력하는 값은 활동 비율입니다. 이것은 활동 진행 시간이 계획되는 비율보다 커야합니다.

안전 마진(safety margin)은 활동이 계획되지 않은 동안 발생하는 토큰 손실, 재시도 활동 등을 설명합니다. 예를 들어, 승진 액션이 3 번이 발생할 것으로 예상되는 경우, 특정 한 주 동안 하루에 네 번 람다 함수를 실행하도록 예약 할 수 있습니다. 또는, 단일 람다 함수가 여러 활동을 병렬 또는 직렬로 실행될 수 있습니다. 이 때는 분당 1 회의 속도를 사용하지만 트리거는 아직 사용하지 않도록 설정합니다.

stepfunctionsfirst_8.png

이제 Node.js 4.3 코드를 사용하여 Lambda 함수 ManualStepActivityWorker를 만듭니다. 이 함수는 StepTunction에서 taskToken, employee 이름 및 manager 이메일 정보를 수신합니다. 이들 정보를 이메일에 포함시키고 이메일을 관리자에게 보냅니다.

JavaScript

'use strict';
console.log('Loading function');
const aws = require('aws-sdk');
const stepfunctions = new aws.StepFunctions();
const ses = new aws.SES();
exports.handler = (event, context, callback) => {
    
    var taskParams = {
        activityArn: 'arn:aws:states:us-east-1:ACCOUNT_ID:activity:ManualStep'
    };
    
    stepfunctions.getActivityTask(taskParams, function(err, data) {
        if (err) {
            console.log(err, err.stack);
            context.fail('An error occured while calling getActivityTask.');
        } else {
            if (data === null) {
                // No activities scheduled
                context.succeed('No activities received after 60 seconds.');
            } else {
                var input = JSON.parse(data.input);
                var emailParams = {
                    Destination: {
                        ToAddresses: [
                            input.managerEmailAddress
                            ]
                    },
                    Message: {
                        Subject: {
                            Data: 'Your Approval Needed for Promotion!',
                            Charset: 'UTF-8'
                        },
                        Body: {
                            Html: {
                                Data: 'Hi!<br />' +
                                    input.employeeName + ' has been nominated for promotion!<br />' +
                                    'Can you please approve:<br />' +
                                    'https://API_DEPLOYMENT_ID.execute-api.us-east-1.amazonaws.com/respond/succeed?taskToken=' + encodeURIComponent(data.taskToken) + '<br />' +
                                    'Or reject:<br />' +
                                    'https://API_DEPLOYMENT_ID.execute-api.us-east-1.amazonaws.com/respond/fail?taskToken=' + encodeURIComponent(data.taskToken),
                                Charset: 'UTF-8'
                            }
                        }
                    },
                    Source: input.managerEmailAddress,
                    ReplyToAddresses: [
                            input.managerEmailAddress
                        ]
                };
                    
                ses.sendEmail(emailParams, function (err, data) {
                    if (err) {
                        console.log(err, err.stack);
                        context.fail('Internal Error: The email could not be sent.');
                    } else {
                        console.log(data);
                        context.succeed('The email was successfully sent.');
                    }
                });
            }
        }
    });
};

이제 Lambda function handler and role 항목에서 Role에 대해서는 Create a new role을 선택하고 LambdaManualStepActivityWorkerRole를 생성합니다.

stepfunctionsfirst_9.png

IAM 역할에 두 개의 정책을 추가합니다. 하나는 Lambda 함수가 Step Functions를 호출하여 GetActivityTask API 조치를 호출하고 SES를 호출하여 이메일을 보내도록 허용하는 것입니다. 결과는 다음과 같습니다.

JavaScript
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
      "Effect": "Allow",
      "Action": "states:GetActivityTask",
      "Resource": "arn:aws:states:*:*:activity:ManualStep"
    },
    {
      "Effect": "Allow",
      "Action": "ses:SendEmail",
      "Resource": "*"
    }
  ]
}

또한, GetActivityTask API 동작이 60 초 제한 시간으로 롱 폴링(long-polling)을 수행하므로 람다 기능의 제한 시간을 1 분 15 초로 늘립니다. 이를 통해 함수가 활동이 사용 가능하게 될 때까지 기다릴 수 있으며, SES에 이메일을 보내도록 여분의 시간을 제공합니다. 다른 모든 설정의 경우 람다 콘솔 기본값을 사용하십시오.

stepfunctionsfirst_10.png

그런 다음, 액티비티 작업자 람다 함수를 생성 할 수 있습니다.

단계5: 프로세스 테스트 하기

이제 직원 승직 프로세스를 테스트 할 준비가되었습니다.

람다 콘솔에서 ManualStepActivityWorker 람다 함수에서 ManualStepPollSchedule 트리거를 활성화하십시오.

Step Functions 콘솔에서 아래 입력 값을 사용하여 상태 시스템을 새로 시작하십시오.

JavaScript
{ "managerEmailAddress": "name@your-email-address.com", "employeeName" : "Jim" } 

잠시 후 Jim의 프로모션을 승인하거나 거부하는 링크가 포함 된 이메일을 받아야 합니다. 링크 중 하나를 선택하면 실행이 성공하거나 실패합니다.

stepfunctionsfirst_11.png

이 글에서는 AWS Step Functions의 활동 작업, API 게이트웨이가 있는 API 및 승인/실패 프로세스를 전달하는 AWS Lambda 함수가 포함 된 상태 머신을 만들었습니다. 본 디자인 패턴을 사용하여 수동 승인 단계를 구현할 수 있습니다.

질문이나 제안이 있으시면 아래에 의견을 남겨주십시오.


Ali Baghani, Software Development Engineer

이 글은 Implementing Serverless Manual Approval Steps in AWS Step Functions and Amazon API Gateway의 한국어 번역입니다.

AWS Step Functions– 시각적 워크플로 기반 분산 애플리케이션 개발용 신규 서비스

on | in AWS Lambda, AWS re:Invent, AWS Step Functions |

오늘 날 다양한 웹 기반 마이크로 서비스를 연결하여 복잡한 분산 응용 프로그램을 보다 쉽게 만들 수 있어야 합니다. 대부분 개발자는 복잡한 비즈니스 프로세스를 구현하든 간단한 사진 업로드를 위한 처리를 하던지 가급적 관리 작업 보다는 코드 개발에 집중하고, 익숙한 개발 도구와 라이브러리를 사용하면서 견고하고 확장성 높은 비용 효율적인 안정적 응용 프로그램을 구축하기를 바라고 있습니다.

AWS Step Functions 소개
위의 요구 사항에 부합하는 AWS Step Functions을 오늘 출시합니다. 여러분이 만든 애플리케이션의 구성을 시각적 워크플로를 통해 설정하고, Step Functions 콘솔에서 각 머신(machine)에서 높은 확장성으로 진행하는 과정을 정의해 줄 수 있습니다.

각 머신은 상태 세트를 정의하고 이들 사이의 그 상태 값을 이전합니다. 각 단계별 상태는 병렬 혹은 순서대로 활성화 될 수 있습니다. Step Functions에서는 다음 단계로 가기 전에 모든 병렬적인 상태가 완료되도록 합니다. 이들 상태를 기반으로 작업 및 의사 결정 및 컴퓨터를 통한 과정 제어 등을 수행합니다.

아래에는 상태 머신(state machine)에 대한 일부분을 표시한 것입니다.

각 상태 머신의 여러 복제본은 동시에 독립적으로 실행할 수 있습니다. 각 복제본은 실행물(execution)이라고 부릅니다. Step Functions는 수 천개의 실행물을 동시에 실행할 수 있도록 하여 원하는 수준의 확장성을 제공합니다.

어떤 상태가 실행될 때 수행할 작업을 지정하는 데는 두 가지 다른 방법이 있습니다. 먼저, 상태가 실행될 때 동기적으로 호출 될 Lambda 함수를 제공 할 수 있습니다. 둘째, 액티비티(Activity) 이름을 제공할 수 있습니다. 이것은 수행할 작업에 대해(API를 통해) 가져올 장기적인 실행 작업 함수에 대한 참조값입니다. 어느 쪽이든, 코드는 JSON 입력을 통해 JSON 출력을 반환 받습니다.

상태 시스템의 일부로서 오류 처리 동작을 지정하고 로직를 다시 시도 할 수 있습니다. 따라서, 코드 한 부분에서 일시적인 문제로 인해 일시적인 오류가 발생하더라도 원활하게 실행되는 강력한 다중 단계 앱을 만들 수 있습니다.

Step Function 살펴 보기
AWS 관리 콘솔에 들어가 상태 시스템을 설정해 보겠습니다. 정식 애플리케이션은 AWS 단계 함수 API (아래에서 설명)를 사용하여 상태 시스템을 생성하고 실행합니다.

우선 간단한 AWS Lambda 함수를 작성해 보겠습니다.

함수의 ARN 값을 복사합니다.

이제 AWS Step Functions 콘솔로 가서  Create a State Machine을 누르고,  이름(MyStateMachine)과 함께 실행할 기본 샘플 워크 플로를 선택합니다.

Hello World를 클릭하고, 상태 머신에 대한 JSON 모델을 만들기 위해 Parallel 요소를 선택합니다.

{
  "Comment": "A simple example of the Steps language using an AWS Lambda Function",
  "StartAt": "Hello",

  "States": {
    "Hello": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-1:99999999999:function:HelloWord_Step",
      "Next": "Parallel"
    },

    "Parallel": {
      "Type": "Parallel",
      "Next": "Goodbye",
      "Branches": [
        {
          "StartAt": "p1",
          "States": {
            "p1": {
                  "Type": "Task",
                  "Resource": "arn:aws:lambda:eu-west-1:9999999999:function:HelloWord_Step",
              "End": true
            }
          }
        },

        {
          "StartAt": "p2",
          "States": {
            "p2": {
                  "Type": "Task",
                  "Resource": "arn:aws:lambda:eu-west-1:99999999999:function:HelloWord_Step",
              "End": true
            }
          }
        }
      ]
    },

    "Goodbye": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-1:99999999999:function:HelloWord_Step",
      "End": true
    }
  }
}

Preview 를 누르면 아래와 같이 시각적으로 보입니다.

Step Functions을 실행할 IAM 역할을 지정합니다.

이제 모든 설정을 마치고, 상태 머신을 실행할 수 있습니다. 첫번째 함수를 통과하는 JSON 블럭을 가지고 시작해 볼 수 있습니다.

Start Execution을 누르자 마자 상태 머신이 실행되고, 실행 순서에 따라 아래와 같이 상태와 상태 사이의 전이가 일어납니다.

Lambda 콘솔에 가면, 아래와 같이 4번의 함수 실행을 볼 수 있습니다. (시간이 촉박하면, 네 개의 기능을 따로 만들지 않아도 됩니다.)

Step Functions는 각 단계별로 정보를 모두 기록하고 이를 콘솔에서 볼 수 있습니다.

AWS Step Functions API
앞에서 말씀 드린 대로, AWS Step Funcitons는 API로 실행 가능하며, 아래에 주요 기능에 대한 API 함수 입니다.

  • CreateStateMachine –  JSON을 통해 신규 상태 머신 생성
  • ListStateMachines – 상태 머신 목록 가져오기
  • StartExecution – 상태 머신 (비동기적으로) 실행하기
  • DescribeExecution – 실행물 정보 가져오기
  • GetActivityTask – 실행할 신규 작업 가져오기

새 객체가 S3 버킷에 업로드 될 때마다 Lambda 함수를 실행할 수 있습니다. 이 함수는 StartExecution을 호출하여 상태 머신 실행을 시작할 수 있습니다. 상태 머신은 (예를 들어) 이미지 유효성 검사, 다양한 크기 및 형식 병렬 생성, 특정 유형의 콘텐트 확인 및 데이터베이스 항목 업데이트를 수행 할 수 있습니다.

동일한 기능을 AWS CLI에서도 사용할 수 있습니다.

개발 도구
신규 statelint gem 파일을 사용하여 연결할 수 없는 상태, 터미널 상태 누락과 같은 일반적인 오류에 대해 수기 혹은 컴퓨터에서 생성된 JSON 로그를 확인할 수 있습니다.

AWS GitHub 레포지터리에서 다운로드하십시오 (RubyGems) 다음과 같이 설치하십시오.

$ sudo gem install j2119-0.1.0.gem statelint-0.1.0.gem

Here’s what happens if you have a problem:

$ statelint my_state.json
2 errors:
 State Machine.States.Goodbye does not have required field "Next"
 No terminal state found in machine at State Machine.States

잘 진행이 된다면,

$ statelint my_state.json

정식 출시
AWS Step Functions는 오늘 부터 US East (Northern Virginia), US East (Ohio), US West (Oregon), EU (Ireland), Asia Pacific (Tokyo) 리전에서 사용 가능합니다.

AWS 프리티어를 통해  매월 4,000 번의 상태 전이를 수행할 수 있습니다. 그 이후로는 1,000회당  $0.025의 요금을 부가합니다.

Jeff;