IT STUDY LOG
Sprint - API Gateway와 서버리스 애플리케이션 본문
#학습 목표
- DynamoDB에 레코드를 추가하는 간단한 람다 함수를 하나 만들고, API Gateway를 통해 이를 호출하는 예제
- Lambda 함수에 JSON 형식의 payload를 싣고 실행하면 DynamoDB에 해당 payload가 저장
#해결 과제
💡다음 아키텍처로 구성된 서버리스 애플리케이션을 배포
- API Gateway - Lambda - DynamoDB
💡직접 API Gateway로 실행
💡 API Gateway의 인증 기능을 이용해서, HTTP 요청에 특정 API Key를 사용하는 예제를 다음 두 가지 방법으로 구현
- API Key
- 권한 부여자
💡CloudWatch Logs를 통해서 API 호출을 모니터링할 수 있어야 함
#과제 항목별 진행 상황
✏️ API Gateway - Lambda 배포 Instruction
Step 1 : Lambda to DynamoDB SAM templates 다운
$ git clone https://github.com/aws-samples/serverless-patterns/
Step 2 : Lambda-dynamodb로 이동해 template.yaml 파일의 Runtime을 수정
$ cd serverless-patterns/lambda-dynamodb
# template.yaml
- Runtime: nodejs12.x
+ Runtime: nodejs14.x
Step 3 : sam build 명령어를 통해 함수가 빌드되는지 확인
$ sam build
Building codeuri: .../serverless-patterns/lambda-dynamodb/src runtime: nodejs14.x metadata: {} architecture: x86_64 functions: LambdaPutDynamoDB
Running NodejsNpmBuilder:NpmPack
Running NodejsNpmBuilder:CopyNpmrcAndLockfile
Running NodejsNpmBuilder:CopySource
Running NodejsNpmBuilder:NpmInstall
Running NodejsNpmBuilder:CleanUpNpmrc
Running NodejsNpmBuilder:LockfileCleanUp
Build Succeeded
Built Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yaml
Commands you can use next
=========================
[*] Validate SAM template: sam validate
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {{stack-name}} --watch
[*] Deploy: sam deploy --guided
Step 4 : sam deploy --guided 명령어를 통해 함수가 빌드되는지 확인
$ sam deploy --guided
Configuring SAM deploy
======================
Looking for config file [samconfig.toml] : Found
Reading default arguments : Success
Setting default arguments for 'sam deploy'
=========================================
Stack Name [lambda-to-dynamodb]:
AWS Region [ap-northeast-2]:
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [y/N]: n
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: y
#Preserves the state of previously provisioned resources when an operation fails
Disable rollback [y/N]: n
Save arguments to configuration file [Y/n]:
SAM configuration file [samconfig.toml]:
SAM configuration environment [default]:
Looking for resources needed for deployment:
Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-ubujxdz5rfok
A different default S3 bucket can be set in samconfig.toml and auto resolution of buckets turned off by setting resolve_s3=False
Saved arguments to config file
Running 'sam deploy' for future deployments will use the parameters saved above.
The above parameters can be changed by modifying samconfig.toml
Learn more about samconfig.toml syntax at
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
Uploading to lambda-to-dynamodb/b38b202e2b5bff16cd97a71d47b73087 1130928 / 1130928 (100.00%)
Deploying with following values
===============================
Stack name : lambda-to-dynamodb
Region : ap-northeast-2
Confirm changeset : False
Disable rollback : False
Deployment s3 bucket : aws-sam-cli-managed-default-samclisourcebucket-ubujxdz5rfok
Capabilities : ["CAPABILITY_IAM"]
Parameter overrides : {}
Signing Profiles : {}
Initiating deployment
=====================
Uploading to lambda-to-dynamodb/782b0313f9e2219cee119519e5141c8a.template 1156 / 1156 (100.00%)
Waiting for changeset to be created..
CloudFormation stack changeset
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Operation LogicalResourceId ResourceType Replacement
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
* Modify LambdaPutDynamoDB AWS::Lambda::Function False
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Changeset created successfully. arn:aws:cloudformation:ap-northeast-2::changeSet/samcli-deploy1683691126/25d0b333-4a71-41e4-b8d0-3d88f2816163
2023-05-10 12:58:58 - Waiting for stack create/update to complete
CloudFormation events from stack operations (refresh every 5.0 seconds)
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus ResourceType LogicalResourceId ResourceStatusReason
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
UPDATE_IN_PROGRESS AWS::Lambda::Function LambdaPutDynamoDB -
UPDATE_COMPLETE AWS::Lambda::Function LambdaPutDynamoDB -
UPDATE_COMPLETE_CLEANUP_IN_PROGRESS AWS::CloudFormation::Stack lambda-to-dynamodb -
UPDATE_COMPLETE AWS::CloudFormation::Stack lambda-to-dynamodb -
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CloudFormation outputs from deployed stack
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Key DynamoDbTable
Description DynamoDb Table
Value lambda-to-dynamodb-DynamoTable-1VKHDFAO14BW3
Key LambdFunction
Description LambdaPutDynamoDB function Arn
Value lambda-to-dynamodb-LambdaPutDynamoDB-RkTVQTa8WYqP
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Successfully created/updated stack - lambda-to-dynamodb in ap-northeast-2
$
Step 5 : 함수가 빌드, 배포되고 잘 동작하는지 확인
$ aws lambda invoke --function-name arn:aws:lambda:ap-northeast-2:<AWS ACCOUNT ID>:function:lambda-to-dynamodb-LambdaPutDynamoDB --invocation-type Event --payload '{ "Metadata": "Hello" }' response.json --cli-binary-format raw-in-base64-out
{
"StatusCode": 202
}
✏️ API 게이트웨이 - Lambda
Step 1 : AWS Lambda 콘솔에서 생성된 함수명 선택 후 트리거 추가
Step 2 : API 게이트웨이를 생성
- API 게이트웨이를 선택
- 새 API를 생성
- REST API 유형
- 보안은 "열기"
Step 3 : API 엔드포인트에 HTTP 요청을 보내면, 함수를 호출하고 요청의 상세 내용이 DynamoDB에 저장되는지 확인
1. API 게이트웨이의 endpoint 확인
2. 엔드포인트에 curl 명령어로 요청 전송하거나 웹 브라우저에 접속해 테스트
$curl -X POST -H "Content-Type: application/json" -d "{ \"metatdata\":\"test!\" }" https://1234567890.execute-api.ap-northeast-2.amazonaws.com/default
3. sam deploy 시 생성된 DynamoDB에 정상적으로 데이터가 입력되었는지 확인
✏️ API 게이트웨이에 제한 추가하기
Step 1 : POST 전용으로만 작동하게 만들기
$curl -X POST -H "Content-Type: application/json" -d "{ \"metatdata\":\"only POST test!\" }" https://1234567890.execute-api.ap-northeast-2.amazonaws.com/default
Step 2 : 본문만 저장하도록 만들기
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: MIT-0
*/
// dependencies
const AWS = require('aws-sdk');
const moment = require('moment');
const documentClient = new AWS.DynamoDB.DocumentClient();
exports.handler = async (event) => {
let params = {
TableName : process.env.DatabaseTable,
Item: {
ID: Math.floor(Math.random() * Math.floor(10000000)).toString(),
created: moment().format('YYYYMMDD-hhmmss'),
metadata:event["metadata"],
}
}
console.log(params)
try {
let data = await documentClient.put(params).promise()
console.log(data)
}
catch (err) {
console.log(err)
return err
}
return {
statusCode: 200,
body: 'OK!',
}
}
Step 3 : API 키를 이용한 인증 추가하기
1. 메서드에서 API 키를 요구하도록 설정
- AWS Management Console에 로그인하고 https://console.aws.amazon.com/apigateway/에서 API Gateway 콘솔을 선택
- REST API를 선택
- API Gateway 기본 탐색 창에서 리소스를 선택
- 리소스에서 새 메서드를 생성하거나 기존 메서드를 선택
- 메서드 요청(Method Request)을 선택
- Settings(설정) 섹션에서 API Key Required(API 키가 필요함)에 true를 선택
- 확인 표시 아이콘을 선택하여 설정을 저장
- API를 배포하거나 다시 배포하여 요구 사항을 적용
2. API 키 생성
- AWS Management Console에 로그인하고 https://console.aws.amazon.com/apigateway/에서 API Gateway 콘솔을 선택
- REST API를 선택
- API Gateway 기본 탐색 창에서 API 키를 선택
- 작업 드롭다운 메뉴에서 API 키 생성(Create API Key)을 선택
- API 키 생성(Create API Key)에서 다음을 수행 (혹은 가져오기를 통해 CSV로 설정 가능)
- 이름 입력 필드에 API 키 이름(예: MyFirstKey)을 입력
- 자동 생성을 선택하여 API Gateway가 키 값을 생성하도록 하거나, 사용자 지정을 선택하여 수동으로 키를 입력
- 저장을 선택
- 필요에 따라 앞의 단계를 반복하여 API 키를 추가 생성
3. 사용량 계획 생성
- Amazon API Gateway 기본 탐색 창에서 사용량 계획을 선택한 다음 생성을 선택
- 사용량 계획 생성(Create Usage Plan) 선택
- 이름에 계획의 이름을 입력(예: Plan_A).
- 설명에 계획에 대한 설명을 입력
- Enable throttling(조절 활성화)를 선택하고 Rate(속도)(예: 100) 및 Burst(버스트)(예: 200)를 설정
- 할당량 활성화(Enable quota)를 선택하고, 선택한 시간 간격(예: 월)에 대한 값(예: 5000)을 지정하도록 설정
- [Next]를 선택
- 계획에 단계를 추가하려면 연결된 API 단계(Associated API Stages) 창에서 다음을 수행
- API 단계 추가(Add API Stage)를 선택
- API 드롭다운 목록에서 사용할 API를 선택
- 단계 드롭다운 목록에서 Stage를 선택
- 확인 표시 아이콘을 선택하여 저장
- 메서드 조절을 구성하려면 다음을 수행
- 메서드 조절 구성(Configure Method Throttling)을 선택
- 리소스/메서드 추가(Add Resource/Method)를 선택
- 리소스 드롭다운 메뉴에서 리소스를 선택
- 방법 드롭다운 메뉴에서 방법을 선택
- (Rate(requests per second)(속도(초당 요청 속도))(예: 100)와 Burst(버스트)(예: 200)를 설정
- 확인 표시 아이콘을 선택하여 저장
- 닫기를 선택
- 계획에 키를 추가하려면 API 키(API Keys) 탭에서 다음을 수행
- 기존 키를 사용하려면 사용량 계획에 API 키 추가(Add API Key to Usage Plan)를 선택
- 이름에 추가할 API 키 이름(생성한 API 키 이름)을 입력
- 확인 표시 아이콘을 선택하여 저장
- 필요할 경우, 앞의 단계를 반복해 이 사용량 계획에 다른 기존 API 키를 추가
- 사용량 계획 생성을 마치려면 완료를 선택
- 사용량 계획에 API 단계를 더 추가하려면 API 단계 추가(Add API Stage)를 선택해 앞의 단계를 반복
Step 3 : 권한 부여자를 이용한 인증 부여하기
0. 권한 부여자 종류
- 토큰 기반 Lambda 권한 부여자(TOKEN 권한 부여자라고도 함): 보유자 토큰(JSON Web Token(JWT) 또는 OAuth 토큰)에 있는 호출자의 자격 증명을 받음
- 요청 파라미터 기반 Lambda 권한 부여자(REQUEST 권한 부여자라고도 함) : 헤더, 쿼리 문자열 파라미터, stageVariables 및 $context 변수의 조합으로 호출자의 자격 증명을 수신
1. 토큰 기반의 Lambda 권한 부여자 함수 생성 (Node.js)
- Lambda 콘솔에서 함수 생성(Create function)을 선택
- 새로 작성을 선택
- 함수 이름을 입력
- 함수 생성을 선택
- 다음 코드를 복사하여 코드 편집기에 붙여넣기
-
// A simple token-based authorizer example to demonstrate how to use an authorization token // to allow or deny a request. In this example, the caller named 'user' is allowed to invoke // a request if the client-supplied token value is 'allow'. The caller is not allowed to invoke // the request if the token value is 'deny'. If the token value is 'unauthorized' or an empty // string, the authorizer function returns an HTTP 401 status code. For any other token value, // the authorizer returns an HTTP 500 status code. // Note that token values are case-sensitive. export const handler = function(event, context, callback) { var token = event.authorizationToken; switch (token) { case 'allow': callback(null, generatePolicy('user', 'Allow', event.methodArn)); break; case 'deny': callback(null, generatePolicy('user', 'Deny', event.methodArn)); break; case 'unauthorized': callback("Unauthorized"); // Return a 401 Unauthorized response break; default: callback("Error: Invalid token"); // Return a 500 Invalid token response } }; // Help function to generate an IAM policy var generatePolicy = function(principalId, effect, resource) { var authResponse = {}; authResponse.principalId = principalId; if (effect && resource) { var policyDocument = {}; policyDocument.Version = '2012-10-17'; policyDocument.Statement = []; var statementOne = {}; statementOne.Action = 'execute-api:Invoke'; statementOne.Effect = effect; statementOne.Resource = resource; policyDocument.Statement[0] = statementOne; authResponse.policyDocument = policyDocument; } // Optional output with custom properties of the String, Number or Boolean type. authResponse.context = { "stringKey": "stringval", "numberKey": 123, "booleanKey": true }; return authResponse; }
-
- 배포를 선택
2. 권한 부여자 생성 후 테스트
- Lambda 권한 부여자 함수는 IAM 정책을 반환하는 것 외에 호출자의 보안 주체 ID도 반환해야 하며 선택적으로 통합 백엔드로 전달될 수 있는 추가 정보를 포함하는 context 객체를 반환 가능 (자세한 내용은 Amazon API Gateway Lambda 권한 부여자의 출력 섹션을 참조)
- API Gateway 콘솔에서 권한 부여자를 생성할 API를 선택 후 권한 부여자를 선택
- 새로운 권한 부여자 생성을 선택
- 권한 부여자 이름을 입력
- 유형에 대해 Lambda를 선택
- Lambda 함수에서 Lambda 권한 부여자 함수를 만든 리전을 선택하고 드롭다운 목록에서 함수 이름을 선택
- Lambda 호출 역할은 공란
- Lambda 이벤트 페이로드의 경우 토큰을 선택
- 토큰 소스에 authorizationToken을 입력
- 생성을 선택한 다음 Grant & Create(권한 부여 및 생성)를 선택
- 테스트를 선택
- 인증 토큰 값에 allow (deny, unauthorized 등)를 입력
- 테스트를 선택
- 토큰 값이 'allow'인 경우 권한 부여자 함수 테스트는 200 OK HTTP 응답 및 IAM 정책을 반환하고 메서드 요청이 성공
- 토큰 값이 'deny'인 경우 권한 부여자 함수 테스트는 200 OK HTTP 응답 및 Deny IAM 정책을 반환하고 메서드 요청이 실패 (테스트 환경 밖에서는 권한 부여자 함수가 403 Forbidden HTTP 응답을 반환하고 메서드 요청이 실패)
- 토큰 값이 'unauthorized'이나 빈 문자열인 경우 권한 부여자 함수 테스트는 401 Unauthorized HTTP 응답을 반환하고 메서드 호출이 실패
- 그 외 토큰의 경우 클라이언트는 500 Invalid token 응답을 수신
# allow 시
Execution log for request de167097-b2da-4178-a3d6-11a2e4f98c3a
Tue May 09 11:50:02 UTC 2023 : Starting authorizer: uw0rhn for request: de167097-b2da-4178-a3d6-11a2e4f98c3a
Tue May 09 11:50:02 UTC 2023 : Incoming identity: ***ow
Tue May 09 11:50:02 UTC 2023 : Endpoint request URI: https://lambda.ap-northeast-2.amazonaws.com/2015-03-31/functions/arn:aws:lambda:ap-northeast-2::function:lambda-to-dynamodb-LambdaPutDynamoDB-RkTVQTa8WYqP/invocations
Tue May 09 11:50:02 UTC 2023 : Endpoint request headers: {X-Amz-Date=20230509T115002Z, x-amzn-apigateway-api-id=asjdflkajdslf, Accept=application/json, User-Agent=AmazonAPIGateway_asjdflkajdslf, Host=lambda.ap-northeast-2.amazonaws.com, X-Amz-Content-Sha256=1dc508af52d0d547a376b1a9c211cb6589558158d1e5fe1f75aacdf9d4ac4f23, X-Amzn-Trace-Id=Root=1-645a336a-5430185fd94306b520ad2690, x-amzn-lambda-integration-tag=de167097-b2da-4178-a3d6-11a2e4f98c3a, Authorization=**************************************************************************************************************************************************************************************************************************************************************************************************************************************************19aeef, X-Amz-Source-Arn=arn:aws:execute-api:ap-northeast-2::asjdflkajdslf/authorizers/uw0rhn, X-Amz-Security-Token=IQoJb3JpZ2luX2VjEFsaDmFwLW5vcnRoZWFzdC0yIkcwRQIhAJIGpx+Lj6xbRkgpqC0+okM9edS+C+snaVRYjWWZzUEgAiA07klItGV8/OqPtwtEkEEZKTZPAHuz7h5soBkrPz [TRUNCATED]
Tue May 09 11:50:02 UTC 2023 : Endpoint request body after transformations: {"type":"TOKEN","methodArn":"arn:aws:execute-api:ap-northeast-2::asjdflkajdslf/ESTestInvoke-stage/GET/","authorizationToken":"allow"}
Tue May 09 11:50:02 UTC 2023 : Sending request to https://lambda.ap-northeast-2.amazonaws.com/2015-03-31/functions/arn:aws:lambda:ap-northeast-2::function:lambda-to-dynamodb-LambdaPutDynamoDB-RkTVQTa8WYqP/invocations
Tue May 09 11:50:02 UTC 2023 : Authorizer result body before parsing: {"principalId":"user","policyDocument":{"Version":"2012-10-17","Statement":[{"Action":"execute-api:Invoke","Effect":"Allow","Resource":"arn:aws:execute-api:ap-northeast-2::asjdflkajdslf/ESTestInvoke-stage/GET/"}]},"context":{"stringKey":"stringval","numberKey":123,"booleanKey":true}}
Tue May 09 11:50:02 UTC 2023 : Using valid authorizer policy for principal: **er
# deny 시
Execution log for request bacfead0-29d5-4f4d-85e3-c04c8d027006
Tue May 09 11:49:23 UTC 2023 : Starting authorizer: uw0rhn for request: bacfead0-29d5-4f4d-85e3-c04c8d027006
Tue May 09 11:49:23 UTC 2023 : Incoming identity: **ny
Tue May 09 11:49:23 UTC 2023 : Endpoint request URI: https://lambda.ap-northeast-2.amazonaws.com/2015-03-31/functions/arn:aws:lambda:ap-northeast-2::function:lambda-to-dynamodb-LambdaPutDynamoDB-RkTVQTa8WYqP/invocations
Tue May 09 11:49:23 UTC 2023 : Endpoint request headers: {X-Amz-Date=20230509T114923Z, x-amzn-apigateway-api-id=asjdflkajdslf, Accept=application/json, User-Agent=AmazonAPIGateway_asjdflkajdslf, Host=lambda.ap-northeast-2.amazonaws.com, X-Amz-Content-Sha256=34ad40e000856138173e0db769795082d0f0b7495d901623b27e9d785ae96a53, X-Amzn-Trace-Id=Root=1-645a3343-e8a424804e6c72da185f4ec0, x-amzn-lambda-integration-tag=bacfead0-29d5-4f4d-85e3-c04c8d027006, Authorization=**************************************************************************************************************************************************************************************************************************************************************************************************************************************************a108f6, X-Amz-Source-Arn=arn:aws:execute-api:ap-northeast-2::asjdflkajdslf/authorizers/uw0rhn, X-Amz-Security-Token=IQoJb3JpZ2luX2VjEFsaDmFwLW5vcnRoZWFzdC0yIkcwRQIgEbDxNMMagNRNxC5WpHgx0Qo37YsOsSIAX2BkOY+Qj7oCIQCLyML+3y9hOj7cVItv7npmjXZU3lRn0I2oH9yVWh [TRUNCATED]
Tue May 09 11:49:23 UTC 2023 : Endpoint request body after transformations: {"type":"TOKEN","methodArn":"arn:aws:execute-api:ap-northeast-2::asjdflkajdslf/ESTestInvoke-stage/GET/","authorizationToken":"deny"}
Tue May 09 11:49:23 UTC 2023 : Sending request to https://lambda.ap-northeast-2.amazonaws.com/2015-03-31/functions/arn:aws:lambda:ap-northeast-2::function:lambda-to-dynamodb-LambdaPutDynamoDB-RkTVQTa8WYqP/invocations
Tue May 09 11:49:23 UTC 2023 : Authorizer result body before parsing: {"principalId":"user","policyDocument":{"Version":"2012-10-17","Statement":[{"Action":"execute-api:Invoke","Effect":"Deny","Resource":"arn:aws:execute-api:ap-northeast-2::asjdflkajdslf/ESTestInvoke-stage/GET/"}]},"context":{"stringKey":"stringval","numberKey":123,"booleanKey":true}}
Tue May 09 11:49:23 UTC 2023 : Using valid authorizer policy for principal: **er
3. Lambda 권한 부여자를 사용하도록 API 메서드를 구성
- 새 메서드를 생성하거나 기존 메서드를 선택 필요하다면 새 리소스를 생성
- Method Execution(메서드 실행)에서 Method Request(메서드 요청) 링크를 선택
- 설정에서 권한 부여 드롭다운 목록을 확장하여 방금 생성한 Lambda 권한 부여자를 선택한 후, 확인 표시 아이콘을 선택하여 저장
- 사용자 지정 권한 부여 토큰을 백엔드로도 전달하려는 경우, 선택적으로 Method Request(메서드 요청) 페이지의 Add header(헤더 추가)를 선택하고 이름에 API에 대한 Lambda 권한 부여자를 생성할 때 지정한 토큰 소스 이름과 일치하는 헤더 이름을 입력 후 확인 아이콘 선택해 저장 (REQUEST 권한 부여자에는 이 단계가 적용되지 않음)
- Deploy API(API 배포)를 선택하여 단계에 API를 배포하고 Invoke URL(URL 호출) 값을 API 호출 시 사용 (단계 변수를 사용하는 REQUEST 권한 부여자의 경우 Stage Editor(단계 편집기)가 열려 있는 동안 필요한 단계 변수를 지정하고 값을 지정해야함)
4. 권한 부여자와 API Key가 정상적으로 작동하는지 테스트
case 1 : API Key만 Header에 전송했을 경우
case 2 : API Key와 허용하는 권한 부여자를 전송했을 경우
case 3 : API Key와 거부하는 권한 부여자를 전송했을 경우
#TROUBLE SHOOTING LOG
📝 문제 1 : 권한관리자 test시 Execution failed due to an authorizer error, AuthorizerFailureException 에러
- 원인 : js, mjs 차이
- 해결 방안 : 추후 작성
#References
https://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/Streams.Lambda.Tutorial2.html
https://github.com/awslabs/aws-apigateway-lambda-authorizer-blueprints
https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/lambda-intro-execution-role.html
'devops bootcamp 4 > pair/team log' 카테고리의 다른 글
Sprint - Terraform x AWS (0) | 2023.05.17 |
---|---|
Sprint - 서버리스 애플리케이션 (0) | 2023.05.11 |
Sprint - 도메인 주도 설계 실습 (0) | 2023.05.04 |
Sprint - 환경 변수 설정 (0) | 2023.04.24 |
Sprint - 서버 배포 파이프라인 (0) | 2023.04.24 |