DevOps BootCamp

Final Project 회고

cloudmaster 2023. 6. 12. 22:55

개요 

새로운 프로젝트에서는 처음에 어떤 것부터 시작해야 할까? 
CI/CD 파이프라인에서 보면  'plan - code - build - test - release - deploy - operate - monitering' 순으로 실행된다.
일단 진행할 프로젝트에 대한 주제를 선정하는 것이 필요하다. 
또한 주제에 대해서 고객의 요구사항을 반영하는 것이 필요한데, 고객의 요구사항은 대체로 불명확하므로, 구체화시키는 것이 필요하며, 진행하면서 느꼈던 점에 대해 작성해보고자 한다.

 

 

 최종 ERD와 아키텍처 다이어그램

 

✔︎ ERD(An Entity Relationship Diagram)

 

 

✔︎ 아키텍처 다이어그램

plan


주제 선정 

일단 우리 팀은 컨테이너 기반 시스템을 이용하여 만들 주제를 생각해야 했으며, MSA를 적용하고자 하였다.
트위치에서 시청자들이 미션을 걸면 스트리머가 그것을 수행하여, 시청자들이 미션과 같이 걸었던 금액을 받아가는 시스템이 있는데, 그것을 쿠버네티스를 이용하여 구축해보고자 했다.
또한 느슨한 결합을 이용하기 위해서 미션 성공 시 AWS의 SNS와 SQS를 이용하여, 이메일로 성공여부를 보내고자 하였다.

 

 

요구 사항 분석 

기능 요구 사항 

  • 개인 사용자와 스트리머는 로그인 기능을 통해 토큰을 발급받을 수 있다.
  • 개인 사용자 새로운 미션을 입력 및 조회할 수 있다.
  • 스트리머는 미션에 대해 성공 및 실패를 선택할 수 있다.
  • 성공 및 실패가 결정된 경우 개인 사용자는 해당 미션의 결과에 대한 email을 받을 수 있다.
  • 스트리머가 방송 중이어야 해당 스트리머에 대한 미션을 생성할 수 있다.
  • 미션을 수행하는 제한시간 설정할 있다.

 

 

고민 사항 

유저가 로그인을 하였을 때 토큰을 발급하여, 회원인지 아닌지 구분을 하려고 하였지만, 토큰 기능을 어떤 식으로 넣을 것인가 고민을 하게 되었다.
 -> jwt 토큰을 사용하기로 함

 

 

인프라 요구 사항 

  • 사용되는 애플리케이션들은 컨테이너로 구동되어야 한다.
  • 시스템 전반에 가용성, 내결함성, 확장성, 보안성이 고려된 서비스들이 포함되어야 한다.
  • 하나 이상의 컴퓨팅 유닛에 대한 CI/CD 파이프라인이 구성되어야 한다.
  • 시스템 메트릭 또는 저장된 데이터에 대한 하나 이상의 시각화된 모니터링 시스템이 구축되어야 한다.

 

 

고민 사항

1. 내결함성을 위해 sqs, sns를 이용하여 이벤트 트리거로 lambda를 사용하려고 했는데, 어느 기능까지를 lambda로 구현할 것인가를 결정하기 어려웠다.
 -> 이벤트마다 분기를 위해서 eventbridge를 사용하기로 했으며, lanbda의 기능은 이벤트가 왔을 때, 그 데이터를 이용해서 데이터베이스에 데이터를 변경시키기로 했다.
1. 유저 cash를 차감하는 기능, 베팅할 때마다 미션의 금액을 추가하는 기능,
2. 성공 시 스트리머에게 미션 금액을 지불하는 기능,
3. 실패시 미션에 베팅한 유저들에게 베팅 금액을 반환하는 기능


2. CI/CD 파이프라인을 구현하려고 할 때, CI는 깃 액션을 쓰려고 했고, CD는 EKS는 젠킨스, 아르고 CD라는 배포 도구가 있다는 것을 알게 되었다.

 

 

프로젝트 이해

우리 팀이 선정했던 주제와  요구 사항에 대해서 담당 엔지니어 분에게 설명하는 미팅을 가졌다. 

 

기능 요구 사항

 기능이 너무 많아서, 미션에 대한 기능에 집중하도록 요구받았으며, 구성했던 인프라가 돋보이지 않는다는 의견을 들었다.

 

 

AWS 인프라 구성 및 요구 사항 반영

 

EKS

 EKS는 AWS에서 제공하는 관리형 Kubernetes 서비스이기 때문에 Kubernetes 클러스터를 쉽게 관리하고 확장 가능
ECS와 고민했지만, 더 많은 기능과 유연성 때문에 EKS를 채택했다

 

RDS

애플리케이션이 해킹당했을 경우, database에 저장된 데이터의 피해를 막고, 보안성, 확장성, 가용성 등을 확보할 수 있다.
RDS는 데이터베이스 인스턴스의 프로비저닝, 패치 및 백업 등을 자동으로 관리해 주기 때문에 개발자들은 이러한 작업에 시간을 소비하지 않고 애플리케이션 개발에 집중할 수 있다.
RDS는 데이터베이스의 보안을 위해 데이터 암호화, 액세스 제어, VPC 내에서의 프라이빗 액세스 등의 기능을 제공하여 신뢰성과 안전성을 보장한다.
또한, RDS는 필요에 따라 자동으로 확장이 가능하며, 가용 영역 간의 복제를 통해 고가용성을 제공한다. 이러한 이유 때문에 채택하게 되었다.


 

DynamoDB

  AWS에서 dynamodb라는 nosql 서비스를 지원했고, 실시간 및 대용량의 데이터 저장과 처리, 자동으로 데이터의 확장성과 가용성을 관리하여 애플리케이션의 부하에 따라 자원을 조정, 서버리스 서비스라서 애플리케이션 개발자는 인프라 운영에 대한 부담을 덜 수 있기 때문에 채택하게 되었다.

 

EventBridge

 이벤트 중심 아키텍처를 구축하기 위해 사용되는 서비스이다. 이벤트 중심 아키텍처는 시스템의 다양한 구성 요소 간에 발생하는 이벤트에 중점을 둔 방식으로 시스템을 설계하는 접근 방식이다.
 느슨한 결합(loose coupling)을 통해 시스템을 구성하므로, 각 구성 요소는 독립적으로 작동하고 확장 가능하며, 이벤트를 통해 상호작용할 수 있다.
 다양한 소스에서 발생하는 이벤트를 감지하고, 필요한 대상으로 이벤트를 라우팅 하며, 이러한 이유들 때문에 sqs 대신 사용하게 되었다.

 

vpc 엔드포인트

 Amazon Web Services(AWS)에서 제공하는 서비스로, VPC 내에서 AWS 서비스에 안전하게 연결하기 위한 기능을 제공하며, VPC 내의 리소스와 AWS 서비스 간의 트래픽이 AWS 네트워크를 통해 전송되지 않고, 직접적으로 전달된다.
퍼블릭 인터넷을 통한 통신을 우회하므로, 인터넷을 통한 외부로의 액세스가 필요하지 않을 때 보안상 이점을 제공한다.
 AWS 서비스와의 통신이 AWS의 전용 네트워크를 통해 이루어지므로, 인터넷 트래픽 비용을 절감할 수 있다.

 

※ AWS 네트워크 : Amazon Web Services (AWS) 클라우드 환경 내에서 리소스 간의 통신과 데이터 전송을 지원하는 인프라스트럭처

퍼블릭 인터넷을 우회 : 일반적으로 보안 및 개인 정보 보호를 강화하기 위해 인터넷 트래픽을 안전하게 전송하는 방법. 이를 통해 트래픽이 암호화되고, 신원이 확인되며, 제삼자로부터의 감시나 불법적인 액세스로부터 보호될 수 있다.

 

lambda

AWS에서 제공하는 서버리스 컴퓨팅 서비스로, 이벤트에 응답하여 코드를 실행하는 함수형 서비스이며, 서비스가 필요한 시점에 자동으로 트리거 되도록 설계할 수 있다. 느슨한 결합을 구현하여 애플리케이션의 유연성과 확장성을 향상할 수 있다. 이러한 이유로 채택하였다

 

sns

 AWS에서 제공하는 관리형 메시지 및 알림 서비스, 다양한 종류의 메시지 전송을 지원하며, 미션 별로 데이터를 전송하거나, 이메일을 보내는 등 다양한 통신 기능을 활용하기 위해 채택했다.

 

 

고민 사항

1.  '만약 시청자가 미션을 생성할 때, 또는 그 미션에 대해 캐시를 베팅할 때, 미션 테이블에 캐시를 추가하는 부분을 이벤트 드리븐 기반인 lambda로 만들어 보면 어떨까?'

2. 컨테이너 기반인 시스템에서 lambda 등 이벤트 기반 서비스를 사용할 때, 어떻게 인프라를 구성할 것인가 

3. 'EKS와 eventbridge를 어떻게 연결할까?'

4. 'lambda 하나로 여러 이벤트에 대한 적절한 로직을 구현해 RDS를 수정하거나, SNS를 통해서 이메일을 보내면 어떨까?'
 -> 'lambda 하나당 하나의 기능으로 구성하는 것이 좋다'라는 의견을 들었다

 

lambda 하나 당 하나의 기능으로 구성하는 이유
  1. 간결성: 하나의 기능에만 집중해서 코드가 짧아지면 가독성이 좋아지며, 유지보수와 연장을 더욱 쉽게 만들어준다.
  2. 지속성: 각 Lambda는 특정 기능을 실행하는 기준 단위로 존재. 다른 곳에서 재사용할 수 있다는 장점이 있다. 예를 들어, 한 Lambda 신호를 여러 이벤트 발생에서 트리거로 사용하거나 다른 Lambda 신호를 호출하여 재사용할 수 있다.
  3. 확장성: Lambda는 서버리스의 핵심 요소로 사용되는 경우가 있음. 단일 기능 단위로 분리되어 있고, 따라서 이 이상을 필요에 따라 결합하거나 전체를 조정하여 시스템을 확장할 수 있다. 수평 및 수직 확장이 가능하고 더 큰 규모의 작업을 처리할 수 있게 됨.
  4. 테스트 가능: 하나의 Lambda는 기본적으로 테스트할 수 있다.
  5. 관리 및 활용성: Lambda는 단일 기능 단위로 관리 및 배포 배포. 따라서 개발자는 기능을 수정하고 배포할 때 전체 시스템에 대한 영향을 최소화할 수 있다.

 

 

인프라 


원래는 콘솔에서 먼저 작업해야 하지만, terraform으로 하기로 했다. 이렇게 한 이유는 결국 테라폼으로 해야 하니, 처음부터 해보자는 의견이 많았기 때문이다.

팀원과의 협업을 위해서 업무 분담을 하기로 했으며, 본인은 terraform 작업을 통해 인프라 구성 작업을 맡기로 했다.

 -> 사실 EKS를 직접 만들어보고 싶었지만, 테라폼 작업을 2명이서 했기 때문에 서로 코드 충돌이 안 나도록 인프라 영역을 나눠서 개발해야 했다.

 

고민 사항

'1. RDS의 로그인 ID와 PASSWORD는 민감 정보인데 어떻게 구성해야 할까?'
 -> 팀원과의 논의의 결과로 AWS 서비스 중 하나인 Secret Manager를 사용하기로 함

'2. terraform의 코드를 왜 모듈로 작성 할까?'

'3. 협업을 위해 깃허브를 사용했는데, 깃 레파지토리의 최신 내용과 로컬에서 작업하고 있는 내용이 달라서 충돌이 일어났다. 어떻게 해결해야 할까?'
 
1. 작업 시작 전 PR로 인한 변경 사항 확인
 2. 변경 사항이 있을 시 로컬 작업 브랜치에서 git pull upstream develop
  -> 최신 반영된 develop branch의 내용을 가져옴(덮어쓰기 x)

 

 

테라폼 모듈을 작성해서 사용하는 이유

  1. 재사용성: 모듈을 작성하여 특정 구성 요소나 리소스 패턴을 추상화하고, 이를 다른 인프라스트럭처 프로젝트에서 재사용할 수 있다. 모듈을 사용하면 비슷한 패턴의 인프라스트럭처를 반복적으로 작성할 필요 없이, 모듈을 호출하여 필요한 리소스를 생성할 수 있다.
  2. 간소화된 관리: 모듈은 인프라스트럭처를 논리적으로 분리하고 구성할 수 있도록 도와준다. 모듈을 사용하면 인프라스트럭처의 특정 부분을 수정하거나 업데이트하는 데 필요한 변경 사항을 집중적으로 다룰 수 있다. 또한 모듈은 구성 및 관리의 일관성을 유지하면서 팀 간 협업을 개선할 수 있다.
  3. 모듈화 된 설계: 모듈은 인프라스트럭처의 모듈화 된 설계를 가능하게 한다. 이를 통해 큰 규모의 인프라스트럭처를 관리하고 여러 팀 또는 프로젝트 간에 모듈을 공유할 수 있다. 모듈은 작은 부분으로 나누어지며, 각 부분은 독립적으로 관리될 수 있다. 이로 인해 유연성과 확장성이 향상되며, 대규모 인프라스트럭처에서도 유지 보수 및 확장이 쉬워진다.
  4. 테스트 및 검증: 모듈은 단위 테스트와 같은 소프트웨어 개발 관행을 인프라스트럭처 코드에 적용할 수 있게 한다. 모듈은 독립적으로 테스트되고 검증될 수 있으며, 인프라스트럭처 코드의 신뢰성을 향상하고 버그를 예방하는 데 도움을 준다.
  5. 커뮤니티 공유

 

 

테라폼 모듈 작업 

처음 모듈별로 코드를 작성할 때, 서로 다른 모듈 안에 있는 리소스를 module. 형식으로 사용하고 싶었다.
하지만 다른 모듈에 리소스를 찾지 못하는 상태였다.
 해결 : 다른 모듈에 있는 리소스를 사용하려면 outputs.tf를 이용하여 사용하려는 모듈에 리소스를 명시하고, 그 리소스를 받을 모듈에 variables.tf에 명시해야 한다. 또한 루트 디렉터리에 있는 main.tf에 variables.tf에 지정한 변수명 = outputs.tf에 있는 변수명 형식으로 사용하겠다는 명시를 해줘야 한다 자세한 과정은 아래에 있다.

 

테라폼 모듈 사용 방법

● 테라폼 디렉터리 하위에 module로 쓰기 위한 디렉터리 생성

 

 

● 테라폼 모듈이 위치한 경로를 상대 경로로 지정해서 이용할 수 있다.

module "mission_link_db" {
    source = "./modules/database"
}

 

 

 테라폼 각 모듈에 있는 자원을 output로 받아와서 다른 모듈에 사용할 수 있다.

# modules/vpc/outputs.tf
output "vpc_id" {
  value = aws_vpc.mission_link_vpc.id
}

 

 

 테라폼 outputs.tf 파일에 있는 자원을 사용하기 위해 variables.tf 작성

# modules/database/variables.tf
variable "vpc_id_db" {
  description = "Value of the Name tag for the vpc_id"
  type        = string
  default     = "default"
}

 

 

 테라폼 outputs.tf 파일에 있는 자원을 사용하기 위한 코드 작성

module "mission_link_db" {
    source = "./modules/database"
    
    # vpc 모듈의 outputs.tf 파일에 있는 vpc_id를 database 모듈의 variables.tf의 vpc_id_db에 적용
    vpc_id_db = module.mission_link_vpc.vpc_id
}
resource "aws_security_group" "mission_link_prvsg" {

# variables.tf에 있는 vpc_id_db를 vpc_id에 적용
    vpc_id = var.vpc_id_db
}

 

 

Secret manager에 대한 고민

 database 구성 작업을 하면서, 위에서 고민한 것처럼 secret manager에 대한 고민을 지금까지 하게 됐는데, 처음에는 secret manager에 테라폼으로 만든 값을 넣으려고 했다.
 하지만 그 값은 variables.tf에 적혀있었고, 그 정보가 없으면 다른 팀원들은 나중에 database에 접근을 못하기 때문에 github에 공유해야 한다는 생각을 했다
 하지만 그렇게 된다면 민감 정보 즉 database username이나 password 등을 외부에 노출시키기 때문에 해킹에 위험이 있을 수 있다는 생각을 했다. 고민을 했을 때 해결 방법은 다음과 같다.

1.. tfvars를 써서 동료들과만 공유하고 github에 올리지 않는다.
2. github action으로 테라폼을 자동 배포할 때 github secret 기능을 쓴다.
3. aws console로 secret manager을 만들고, 만든 내용을 받아오는 코드를 쓴다.
4. 랜덤으로 username, password 등을 만들고 만든 사람이 팀원과 공유한다.

 

 

lambda, eventbridge, sns 구성

database 모듈을 다 구축한 후, lambda, eventbridge, sns를 구축하기로 했다.
lambda는 terraform 측에서 제공하는 모듈을 써서 만들기로 했다.
"terraform-aws-modules/lambda/aws" 이 부분에 source가 있다.
이 모듈을 이용하면, source_path = "${path.module}/source" 이렇게 기반으로 만들 코드를 지정할 수 있다
여기서 문제가 발생했다 오류는 다음과 같다.

 


 

Error: local-exec provisioner error │ │ with module.mission_link_event.module.mission_link_success_lambda.null_resource.archive [0], │ on. terraform/modules/mission_link_event.mission_link_success_lambda/package.tf line 63, in resource "null_resource" "archive": │ 63: provisioner "local-exec" { 
...

이 오류는 처음에 terraform이 apply 하여 lambda를 만들 때, 파일을 이용하여 zip으로 압축을 하는데, 찾을 수 없어서 나오는 오류였다.
하지만 그다음에 apply를 하면 처음에 만들어진 zip를 이용하여 lambda를 만들 수 있다.

 

lambda와 dynamodb 연결

위의 과정을 겪어 lambda는 만들었다. 하지만 lambda에서 dynamodb의 데이터를 받은 과정에서  access denied라는 오류가 나왔다
위의 오류를 해결하기 위해서 lambda에서 다른 aws 리소스에 접근 위해서는 lambda의 실행 역할에 관련 aws 리소스 접근 정책을 연결해 주어야 한다는 생각이 들었다
또한 eventbridge에 있는 이벤트로 lambda가 작동하기 때문에 eventbridge의 데이터를 받을 수 있도록 정책을 설정해 주었다.

 

 

Code


WAS를  만들 때 제일 먼저 한 생각은 '어떤 기술 스택을 사용할까?' 였던 것 같다.

사실 팀원들이랑 회의하기 전에는 javascript의 express나 fastify 프레임워크를 사용하려고 했다.

하지만 회의하면서 요즘 자주 트렌드가 react랑 서버를 동시에 구현할 수 있는 next.js 프레임워크를 사용하기로 했다.

 

위의 프레임워크의 장점과 차이점은 다음과 같다.

fastify
fastify는 express에 비해 커뮤니티가 활성화되지 않아 정보가 부족함.
성능이 가장 좋음.

express
express는 커뮤니티가 많이 활성화되어 정보가 많다.
진입장벽이 낮다.

nextjs - 채택
현재 점유율이 가장 좋은 프레임워크 중 하나이며,
커뮤니티가 많이 활성화되어 정보가 많다.

중요러닝커브 높아서, 진입장벽이 조금 있다.
리액트의 개념도 포함되어 있기 때문 프런트엔드 작업도 같이 할 수 있다.

 

 

lambda code 구성 중 javascript ES 모듈, CommonJS 모듈 작업

사용할 서버 스택도 정했으니, 바로 WAS 구성으로 넘어갔다
사실 WAS는 리팩토링이나 그런 개념을 잘 몰라서 좀 복잡하게 code를 구성했고 실수로 몇 가지 기능을 놓쳤으나,
code 리뷰를 받고 계속 고쳐야겠다는 생각을 해서, Lambda의 기반이 되는 source code 작성으로 넘어갔다.

작성한 후 code 가 잘되는지 확인하기 위해서 console.log를 찍어봤다.
하지만 밑에 오류가 발생했다.

"require is not defined in ES module scope, you can use import instead\nThis file is being treated as an ES module because it has a '. js' file extension and '/var/task/package.json' contains \"type\": \"module\". To treat it as a CommonJS script, rename it to use the '. cjs' file extension.",
...

발생한 원인은 ES 모듈을 사용하는데 모듈에 require가 정의되지 않았다는 것 같았다.
확인해 보니  JavaScript에서 모듈 시스템을 구현할 때, CommonJS와 ES 모듈 두 가지 방식을 쓰는데, package.json에 type: module로 정의해서 import문을 써야 했다. type: module로 정의된 것을 지워서 require문을 쓸 수 있도록 해결했다.

 

 

lambda의 데이터 변환 작업

code 실행 까지는 넘어갔다, ALB로 데이터를 보내고 데이터베이스 내용이 변화되는지 확인하는 작업이 필요했다.
하지만 실행 결과, 밑에 오류가 발생했다.

[Object: null prototype] {'{"Items":[
{"action":"missionCreate","user_id":2,"mission_id":1,"amount":3000,"streamer_id":1,"id":"2"}
], "Count":1 "ScannedCount":2}': ''}
...

원인은 서버가 lambda가 보낸 데이터 형식에서 데이터를 읽지 못하는 것이었다
즉 Lambda에서 axios를 보낼 때 JSON.strigify를 써서 json 형식으로 데이터를 보냈는데, 서버에서 그 안에 데이터를 알려면, 다시 객체로 파싱해 주어야 한다는 것이다.
 ->  그래서 json으로 보내지 않고 객체 형태로 보내서 데이터를 읽을 수 있게 하였다.

 

 

final project를 하면서 느낀 점

 -> 팀원들과 협업 프로젝트를 진행하면서 github에서 많은 conflict가 있었다. 하지만 이런 것을 해결하고 수정하면서 느낀 점도 상당히 많았다. 기업에 들어가면 협업으로 많은 것을 진행하게 될 텐데, 급격하게 고민이 많아졌다. 

  -> 또한 로컬에서 되던 code가 프로덕션 환경에서는 안 되는 경우가 있었다. 내가 코드를 잘못 짜서 생긴 실수였다. 앞으로는 충분히 여러 테스트를 돌리거나, 테스트 코드를 작성해야 함을 여실히 느끼는 계기가 되었다.

  -> 이러한 과정들을 겪으면서 앞으로 여러 가지 배울 점이 많이 있겠구나라는 생각이 들었는데, 몇 년 후엔 자신이 어떻게 바뀌어 있을지 정말 기대된다.

'DevOps BootCamp' 카테고리의 다른 글

day 2 발표 자료  (0) 2023.03.08
(CodeStates) DevOps - day2  (0) 2023.03.08
(CodeStates) DevOps - day1  (0) 2023.03.07