Amazon Web Services 한국 블로그

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

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의 한국어 번역입니다.