쿠키 기본 설정 선택

당사는 사이트와 서비스를 제공하는 데 필요한 필수 쿠키 및 유사한 도구를 사용합니다. 고객이 사이트를 어떻게 사용하는지 파악하고 개선할 수 있도록 성능 쿠키를 사용해 익명의 통계를 수집합니다. 필수 쿠키는 비활성화할 수 없지만 '사용자 지정' 또는 ‘거부’를 클릭하여 성능 쿠키를 거부할 수 있습니다.

사용자가 동의하는 경우 AWS와 승인된 제3자도 쿠키를 사용하여 유용한 사이트 기능을 제공하고, 사용자의 기본 설정을 기억하고, 관련 광고를 비롯한 관련 콘텐츠를 표시합니다. 필수가 아닌 모든 쿠키를 수락하거나 거부하려면 ‘수락’ 또는 ‘거부’를 클릭하세요. 더 자세한 내용을 선택하려면 ‘사용자 정의’를 클릭하세요.

Step Functions에서 사람 승인을 기다리는 워크플로 배포

포커스 모드
Step Functions에서 사람 승인을 기다리는 워크플로 배포 - AWS Step Functions

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

이 자습서에서는 AWS Step Functions 실행이 작업 중에 일시 중지하고 사용자가 이메일에 응답할 때까지 대기할 수 있는 인간 승인 프로젝트를 배포하는 방법을 알아봅니다. 사용자가 작업을 진행하도록 승인하면 워크플로우는 다음 상태로 진행합니다.

이 자습서에 포함된 AWS CloudFormation 스택을 배포하면 다음을 포함하여 필요한 모든 리소스가 생성됩니다.

  • Amazon API Gateway 리소스

  • AWS Lambda 함수

  • AWS Step Functions 상태 시스템

  • Amazon Simple Notification Service 이메일 주제

  • 관련 AWS Identity and Access Management 역할 및 권한

참고

AWS CloudFormation 스택을 생성할 때 액세스할 수 있는 유효한 이메일 주소를 제공해야 합니다.

자세한 내용은 AWS CloudFormation 사용 설명서CloudFormation 템플릿 사용AWS::StepFunctions::StateMachine 리소스를 참조하세요.

1단계: AWS CloudFormation 템플릿 생성

  1. AWS CloudFormation 템플릿 소스 코드 단원에서 예제 코드를 복사합니다.

  2. AWS CloudFormation 템플릿의 소스를 로컬 시스템의 파일에 붙여 넣습니다.

    이 예제의 경우 파일은 human-approval.yaml입니다.

2단계: 스택 만들기

  1. AWS CloudFormation 콘솔에 로그인합니다.

  2. 스택 생성을 선택한 다음 새 리소스 사용(표준)을 선택합니다.

  3. Create stack(스택 생성) 페이지에서 다음을 수행합니다.

    1. 사전 조건 - 템플릿 준비 섹션에서 준비된 템플릿을 선택합니다.

    2. 템플릿 지정 섹션에서 템플릿 파일 업로드를 선택한 다음 파일 선택을 선택하여 이전에 만든 템플릿 소스 코드가 포함된 human-approval.yaml 파일을 업로드합니다.

  4. Next(다음)를 선택합니다.

  5. 스택 세부 정보 지정 페이지에서 다음 작업을 수행합니다.

    1. 스택 이름에 스택 이름을 입력합니다.

    2. 파라미터에 유효한 이메일 주소를 입력합니다. 이 이메일 주소를 사용하여 Amazon SNS 주제를 구독합니다.

  6. 다음을 선택한 다음 다음을 다시 선택합니다.

  7. 검토 페이지에서 이 IAM 리소스를 생성할 AWS CloudFormation 수 있음을 인정함을 선택한 다음 생성을 선택합니다.

    AWS CloudFormation 가 스택 생성을 시작하고 CREATE_IN_PROGRESS 상태를 표시합니다. 프로세스가 완료되면 CREATE_COMPLETE 상태가 AWS CloudFormation 표시됩니다.

  8. (선택 사항) 스택에 리소스를 표시하려면 스택을 선택하고 [Resources ] 탭을 선택합니다.

3단계: Amazon SNS 구독 승인

Amazon SNS 주제가 생성되면 구독 확인을 요청하는 이메일이 전송됩니다.

  1. AWS CloudFormation 스택을 생성할 때 제공한 이메일 계정을 엽니다.

  2. no-reply@sns.amazonaws.com에서 보낸 AWS Notification - Subscription Confirmation 메시지를 엽니다.

    이메일에는 Amazon SNS 주제에 대한 Amazon 리소스 이름과 확인 링크가 있습니다.

  3. Confirm subscription(구독 확인) 링크를 선택합니다.

    구독 확인 이메일의 예시 스크린샷입니다.

4단계: 상태 시스템 실행

  1. HumanApprovalLambdaStateMachine 페이지에서 실행 시작을 선택합니다.

    실행 시작 대화 상자가 표시됩니다.

  2. 실행 시작 대화 상자에서 다음을 수행합니다.

    1. (선택 사항) 생성된 기본값을 재정의하려면 사용자 지정 실행 이름을 입력합니다.

      비 ASCII 이름 및 로깅

      Step Functions는 비 ASCII 문자가 포함된 상태 시스템, 실행, 활동 및 레이블 이름을 허용합니다. 이러한 문자는 Amazon CloudWatch에서 작동하지 않으므로 CloudWatch에서 지표를 추적할 수 있도록 ASCII 문자만 사용하는 것이 좋습니다.

    2. 입력 상자에 다음 JSON 입력을 입력하여 워크플로를 실행합니다.

      { "Comment": "Testing the human approval tutorial." }
    3. 실행 시작을 선택합니다.

      ApprovalTest 실행 시스템 실행이 시작되고 Lambda 콜백 작업에서 일시 중지됩니다.

    4. Step Functions 콘솔은 실행 ID가 제목인 페이지로 이동합니다. 이 페이지를 실행 세부 정보 페이지라고 합니다. 실행이 진행되는 동안 또는 완료된 후에 이 페이지에서 실행 결과를 검토할 수 있습니다.

      실행 결과를 검토하려면 그래프 보기에서 개별 상태를 선택한 다음 단계 세부 정보 창에서 개별 탭을 선택하여 입력, 출력 및 정의가 포함된 각 상태의 세부 정보를 각각 봅니다. 실행 세부 정보 페이지에서 볼 수 있는 실행 정보에 대한 자세한 내용은 실행 세부 정보 개요 섹션을 참조하세요.

      콜백 대기 중인 실행
  3. 이전에 Amazon SNS 주제에 사용한 이메일 계정에서 필수 승인 AWS Step Functions 제목이 있는 메시지를 엽니다.

    이 메시지에는 Approve(승인)Reject(거부)에 대한 별도의 URL이 포함됩니다.

  4. Approve(승인) URL을 선택합니다.

    선택을 기반으로 워크플로우가 계속됩니다.

    콜백 대기 중인 실행

AWS CloudFormation 템플릿 소스 코드

이 AWS CloudFormation 템플릿을 사용하여 인적 승인 프로세스 워크플로의 예를 배포합니다.

AWSTemplateFormatVersion: "2010-09-09" Description: "AWS Step Functions Human based task example. It sends an email with an HTTP URL for approval." Parameters: Email: Type: String AllowedPattern: "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$" ConstraintDescription: Must be a valid email address. Resources: # Begin API Gateway Resources ExecutionApi: Type: "AWS::ApiGateway::RestApi" Properties: Name: "Human approval endpoint" Description: "HTTP Endpoint backed by API Gateway and Lambda" FailOnWarnings: true ExecutionResource: Type: 'AWS::ApiGateway::Resource' Properties: RestApiId: !Ref ExecutionApi ParentId: !GetAtt "ExecutionApi.RootResourceId" PathPart: execution ExecutionMethod: Type: "AWS::ApiGateway::Method" Properties: AuthorizationType: NONE HttpMethod: GET Integration: Type: AWS IntegrationHttpMethod: POST Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaApprovalFunction.Arn}/invocations" IntegrationResponses: - StatusCode: 302 ResponseParameters: method.response.header.Location: "integration.response.body.headers.Location" RequestTemplates: application/json: | { "body" : $input.json('$'), "headers": { #foreach($header in $input.params().header.keySet()) "$header": "$util.escapeJavaScript($input.params().header.get($header))" #if($foreach.hasNext),#end #end }, "method": "$context.httpMethod", "params": { #foreach($param in $input.params().path.keySet()) "$param": "$util.escapeJavaScript($input.params().path.get($param))" #if($foreach.hasNext),#end #end }, "query": { #foreach($queryParam in $input.params().querystring.keySet()) "$queryParam": "$util.escapeJavaScript($input.params().querystring.get($queryParam))" #if($foreach.hasNext),#end #end } } ResourceId: !Ref ExecutionResource RestApiId: !Ref ExecutionApi MethodResponses: - StatusCode: 302 ResponseParameters: method.response.header.Location: true ApiGatewayAccount: Type: 'AWS::ApiGateway::Account' Properties: CloudWatchRoleArn: !GetAtt "ApiGatewayCloudWatchLogsRole.Arn" ApiGatewayCloudWatchLogsRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - apigateway.amazonaws.com Action: - 'sts:AssumeRole' Policies: - PolicyName: ApiGatewayLogsPolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - "logs:*" Resource: !Sub "arn:${AWS::Partition}:logs:*:*:*" ExecutionApiStage: DependsOn: - ApiGatewayAccount Type: 'AWS::ApiGateway::Stage' Properties: DeploymentId: !Ref ApiDeployment MethodSettings: - DataTraceEnabled: true HttpMethod: '*' LoggingLevel: INFO ResourcePath: /* RestApiId: !Ref ExecutionApi StageName: states ApiDeployment: Type: "AWS::ApiGateway::Deployment" DependsOn: - ExecutionMethod Properties: RestApiId: !Ref ExecutionApi StageName: DummyStage # End API Gateway Resources # Begin # Lambda that will be invoked by API Gateway LambdaApprovalFunction: Type: 'AWS::Lambda::Function' Properties: Code: ZipFile: Fn::Sub: | const { SFN: StepFunctions } = require("@aws-sdk/client-sfn"); var redirectToStepFunctions = function(lambdaArn, statemachineName, executionName, callback) { const lambdaArnTokens = lambdaArn.split(":"); const partition = lambdaArnTokens[1]; const region = lambdaArnTokens[3]; const accountId = lambdaArnTokens[4]; console.log("partition=" + partition); console.log("region=" + region); console.log("accountId=" + accountId); const executionArn = "arn:" + partition + ":states:" + region + ":" + accountId + ":execution:" + statemachineName + ":" + executionName; console.log("executionArn=" + executionArn); const url = "https://console.aws.amazon.com/states/home?region=" + region + "#/executions/details/" + executionArn; callback(null, { statusCode: 302, headers: { Location: url } }); }; exports.handler = (event, context, callback) => { console.log('Event= ' + JSON.stringify(event)); const action = event.query.action; const taskToken = event.query.taskToken; const statemachineName = event.query.sm; const executionName = event.query.ex; const stepfunctions = new StepFunctions(); var message = ""; if (action === "approve") { message = { "Status": "Approved! Task approved by ${Email}" }; } else if (action === "reject") { message = { "Status": "Rejected! Task rejected by ${Email}" }; } else { console.error("Unrecognized action. Expected: approve, reject."); callback({"Status": "Failed to process the request. Unrecognized Action."}); } stepfunctions.sendTaskSuccess({ output: JSON.stringify(message), taskToken: event.query.taskToken }) .then(function(data) { redirectToStepFunctions(context.invokedFunctionArn, statemachineName, executionName, callback); }).catch(function(err) { console.error(err, err.stack); callback(err); }); } Description: Lambda function that callback to AWS Step Functions FunctionName: LambdaApprovalFunction Handler: index.handler Role: !GetAtt "LambdaApiGatewayIAMRole.Arn" Runtime: nodejs18.x LambdaApiGatewayInvoke: Type: "AWS::Lambda::Permission" Properties: Action: "lambda:InvokeFunction" FunctionName: !GetAtt "LambdaApprovalFunction.Arn" Principal: "apigateway.amazonaws.com" SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ExecutionApi}/*" LambdaApiGatewayIAMRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Action: - "sts:AssumeRole" Effect: "Allow" Principal: Service: - "lambda.amazonaws.com" Policies: - PolicyName: CloudWatchLogsPolicy PolicyDocument: Statement: - Effect: Allow Action: - "logs:*" Resource: !Sub "arn:${AWS::Partition}:logs:*:*:*" - PolicyName: StepFunctionsPolicy PolicyDocument: Statement: - Effect: Allow Action: - "states:SendTaskFailure" - "states:SendTaskSuccess" Resource: "*" # End Lambda that will be invoked by API Gateway # Begin state machine that publishes to Lambda and sends an email with the link for approval HumanApprovalLambdaStateMachine: Type: AWS::StepFunctions::StateMachine Properties: RoleArn: !GetAtt LambdaStateMachineExecutionRole.Arn DefinitionString: Fn::Sub: | { "StartAt": "Lambda Callback", "TimeoutSeconds": 3600, "States": { "Lambda Callback": { "Type": "Task", "Resource": "arn:${AWS::Partition}:states:::lambda:invoke.waitForTaskToken", "Parameters": { "FunctionName": "${LambdaHumanApprovalSendEmailFunction.Arn}", "Payload": { "ExecutionContext.$": "$$", "APIGatewayEndpoint": "https://${ExecutionApi}.execute-api.${AWS::Region}.amazonaws.com/states" } }, "Next": "ManualApprovalChoiceState" }, "ManualApprovalChoiceState": { "Type": "Choice", "Choices": [ { "Variable": "$.Status", "StringEquals": "Approved! Task approved by ${Email}", "Next": "ApprovedPassState" }, { "Variable": "$.Status", "StringEquals": "Rejected! Task rejected by ${Email}", "Next": "RejectedPassState" } ] }, "ApprovedPassState": { "Type": "Pass", "End": true }, "RejectedPassState": { "Type": "Pass", "End": true } } } SNSHumanApprovalEmailTopic: Type: AWS::SNS::Topic Properties: Subscription: - Endpoint: !Sub ${Email} Protocol: email LambdaHumanApprovalSendEmailFunction: Type: "AWS::Lambda::Function" Properties: Handler: "index.lambda_handler" Role: !GetAtt LambdaSendEmailExecutionRole.Arn Runtime: "nodejs18.x" Timeout: "25" Code: ZipFile: Fn::Sub: | console.log('Loading function'); const { SNS } = require("@aws-sdk/client-sns"); exports.lambda_handler = (event, context, callback) => { console.log('event= ' + JSON.stringify(event)); console.log('context= ' + JSON.stringify(context)); const executionContext = event.ExecutionContext; console.log('executionContext= ' + executionContext); const executionName = executionContext.Execution.Name; console.log('executionName= ' + executionName); const statemachineName = executionContext.StateMachine.Name; console.log('statemachineName= ' + statemachineName); const taskToken = executionContext.Task.Token; console.log('taskToken= ' + taskToken); const apigwEndpint = event.APIGatewayEndpoint; console.log('apigwEndpint = ' + apigwEndpint) const approveEndpoint = apigwEndpint + "/execution?action=approve&ex=" + executionName + "&sm=" + statemachineName + "&taskToken=" + encodeURIComponent(taskToken); console.log('approveEndpoint= ' + approveEndpoint); const rejectEndpoint = apigwEndpint + "/execution?action=reject&ex=" + executionName + "&sm=" + statemachineName + "&taskToken=" + encodeURIComponent(taskToken); console.log('rejectEndpoint= ' + rejectEndpoint); const emailSnsTopic = "${SNSHumanApprovalEmailTopic}"; console.log('emailSnsTopic= ' + emailSnsTopic); var emailMessage = 'Welcome! \n\n'; emailMessage += 'This is an email requiring an approval for a step functions execution. \n\n' emailMessage += 'Check the following information and click "Approve" link if you want to approve. \n\n' emailMessage += 'Execution Name -> ' + executionName + '\n\n' emailMessage += 'Approve ' + approveEndpoint + '\n\n' emailMessage += 'Reject ' + rejectEndpoint + '\n\n' emailMessage += 'Thanks for using Step functions!' const sns = new SNS(); var params = { Message: emailMessage, Subject: "Required approval from AWS Step Functions", TopicArn: emailSnsTopic }; sns.publish(params) .then(function(data) { console.log("MessageID is " + data.MessageId); callback(null); }).catch( function(err) { console.error(err, err.stack); callback(err); }); } LambdaStateMachineExecutionRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: states.amazonaws.com Action: "sts:AssumeRole" Policies: - PolicyName: InvokeCallbackLambda PolicyDocument: Statement: - Effect: Allow Action: - "lambda:InvokeFunction" Resource: - !Sub "${LambdaHumanApprovalSendEmailFunction.Arn}" LambdaSendEmailExecutionRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: "sts:AssumeRole" Policies: - PolicyName: CloudWatchLogsPolicy PolicyDocument: Statement: - Effect: Allow Action: - "logs:CreateLogGroup" - "logs:CreateLogStream" - "logs:PutLogEvents" Resource: !Sub "arn:${AWS::Partition}:logs:*:*:*" - PolicyName: SNSSendEmailPolicy PolicyDocument: Statement: - Effect: Allow Action: - "SNS:Publish" Resource: - !Sub "${SNSHumanApprovalEmailTopic}" # End state machine that publishes to Lambda and sends an email with the link for approval Outputs: ApiGatewayInvokeURL: Value: !Sub "https://${ExecutionApi}.execute-api.${AWS::Region}.amazonaws.com/states" StateMachineHumanApprovalArn: Value: !Ref HumanApprovalLambdaStateMachine
프라이버시사이트 이용 약관쿠키 기본 설정
© 2025, Amazon Web Services, Inc. 또는 계열사. All rights reserved.