코드스테이츠 DevOps #Final-Project 회고
파이널 프로젝트
코드스테이츠 데브옵스 부트캠프를 진행하며, 파이널 프로젝트를 마무리 지었다.
기존 설계과정과 구현과정은 데일리회고로 남겨두었지만, 이 글에서 다시 한 번 정리하며 후기를 작성하려한다.
프로젝트 기획
기존의 프로젝트에서는 어떤 서비스를 써야하는지, 어떤 서비스를 만들어야 하는지 주어졌다면, 파이널 프로젝트는 최소한의 큰 주제를 받고, 거기에 맞춰 직접 시스템을 설계하는 방식으로 진행되었다.
우리가 고를 수 있는 시나리오는 총 3개였고, 그 중 우리는 첫번째 시나리오인 마라톤 대회 기록 시스템 시나리오를 선택했다.
마라톤 대회 기록 시스템
시나리오 선정이유
- 기능 요구사항이 명확
- 기존의 배웠던 서비스들을 최대한 활용 가능
- 인프라에 집중할 수 있고, 백엔드 쪽에 비중을 크게 두지 않아도 됨
프로젝트 기간 및 인원
- 설계 및 구현 기간
- 2023-06-12 ~ 2023-06-24 (약 2주)
- 구성원
- 팀장 : 김민주
- 팀원: 김한수, 문한성, 이상윤
- 구현 항목
- VPC 구성 및 연결
- 백엔드 구현
- 회원 서비스
- 대회, 기록, 점수 서비스
- DB 구축
- 회원 서비스의 DynamoDB
- 대회, 기록, 점수 서비스의 RDS
- 각 서비스 Auto Scaling Group 설정
- ECS, ALB 구축
- Lambda 구성 및 Route Table 구성
- 모니터링 프로세스 구축
- IaC(코드형 인프라) 작성
- 내가 구현한 부분
- CI/CD 파이프라인
- VPC 구성
- RDS 구축
- 비용관리 프로세스
- 기술 스택
기획 및 설계 단계에서 아쉬웠던 점
파이널 프로젝트를 시작하며 진행에는 문제가 없을 것이라 생각했다.
하지만 마지막 프로젝트를 진행하며 서로 반대되는 의견으로 소소하게 다투는 일들이 생겼고,
팀원들이 부트캠프 시작부터 마무리까지 함께하며 서로 친밀하게 지내온 사람들로 그 중에서도 나는 막내였다는 점에서 팀원들이 서로 다른 의견으로 충돌이 발생하더라도 팀장으로서 팀원들을 잘 이끌어주지 못한 것 같다.
게다가 우리 팀은 에이스 조 중 하나로 꼽힐만큼 다들 각각 잘하는 분야가 있고, 열정적이고 자기주관이 또렷하기에 그 사이에서 한명의 손을 들어주기도 힘들었다.
팀원들간의 불화로 힘들어하며 프로젝트에 대한 애정도 식어갔고, 나는 이 사실을 알고 있었음에도 마음을 고쳐먹기 쉽지 않았다. 이런 내 모습에 실망하고 자책하며 스트레스로 아프기도 했다.
하지만, 계속 이렇게 프로젝트를 진행해선 안됐기에 정신을 차려야했기에 설계 기간이 마무리되고 주말동안은 내 마음가짐을 재정비하는 것에 집중했고 이후엔 의견 충돌 시 조금 힘들더라도 강하게 중재를 하려 애쓰게 되었다.
이번 일을 통해 협업 방식과 마인드 세팅에 대해 다시 생각해 볼 수 있었고, 내 감정이 프로젝트에 영향을 주지 않기 위해 노력해야겠다고 생각했다.
기획 및 설계 단계에서 잘한 점
칸반보드를 활용하여 미팅 내용을 잘 정리했다고 생각한다.
나는 사소한 것들을 잘 잊어버리는 성격이라, 회의를 하거나 문제가 생겼을 땐 늘 기록하는 습관이 있는데, 이번 프로젝트를 진행하며 덕을 많이 본 것 같다.
무언가를 결정하거나 의논할 땐 늘 이슈에 잘 적어두어 이후에 착오나 의견충돌을 줄이고 원활한 진행을 할 수 있도록 노력했다.
기존의 이런 형태의 일정관리 및 내용 정리는 한번 해본 적 있지만, Github Project에서 이슈로 연동시켜 작성한 칸반보드는 이번에 처음해본 것으로 굉장히 편리하다고 생각했다.
특히 업무 시간 외에 이슈가 생겨도 메일로 알람이 오기 때문에 빠르게 처리할 수 있다는 점에서 잘 사용한 것 같다.
진행에 바빠 기록하는 것을 잊지 않도록 계속 의식하며 작업을 했고, 업무 효율이 좋아졌음을 느꼈다.
프로젝트 구현
인프라 아키텍처
서비스 및 리소스 선정 이유
- ECS, Fargate
- ECS와 Fargate는 오토 스케일링과 필요한 만큼의 컴퓨팅 리소스 용량 선택 가능 및 손쉬운 배포 부분에 강점을 가져 선택하게 되었다.
- 또한 Fargate를 사용함으로 서버 프로비저닝, 확장 또는 패치에 대한 걱정 없이 컨테이너 실행 및 관리에만 집중할 수 있었다.
- Lambda
- Lambda의 경우 실행되는 시간만큼만 비용을 지불한다는 비용효율적 장점을 가지고 있고, 콜드 스타트의 단점이 있지만 빠른 응답시간을 주지 않아도 되는 시스템에 적용함으로 문제가 되지 않았다.
- 특히, 점수 추가 시스템을 람다로 구현하였을 때, 사용자가 응답을 기다리지 않아도 되는 프로세스이기에 아직 트래픽이 많이 발생하지 않는 프로젝트에 적용하기 적합하다고 생각해 사용하였다.
- DB
- (NoSQL) DynamoDB: 회원의 정보가 담길 DB로, 유연한 스키마로 미리 정의된 구조 없이 회원 데이터를 저장 및 검색할 수 있고 이후 확장 및 발전 가능성을 생각하여 다양한 속성이 추가될 것을 고려하여 DynamoDB를 선택하게 되었다.
- (SQL) RDS: 대회정보, 개인 대회 시간 기록, 점수의 정보가 담길 DB로, 많은 유저가 동시에 조회하는 경우를 고려하여 데이터 일관성을 보장해주고, 데이터 무결성을 보장해주어 일관성이 없거나 유효하지 않은 데이터가 삽입 및 업데이트되는 것을 방지해줄 수 있는 RDS로 선택하게 되었다.
- SQS
- SQS를 통해 각 서비스 간의 통신 과정에서 메세지가 유실될 수 있는 상황을 방지하고자 했다.
- 비동기 처리가 가능하게끔 설계하여 메세지 전달에 문제가 생기더라도 DLQ로 전달되어 시스템 중지를 방지할 수 있다는 점에서 선택하게 되었다.
- Cloud Watch, Grafana
- 모니터링 대상이 ECS 및 RDS의 CPU 사용률과 반응시간이기에 클라우드 와치를 통해 데이터를 획득하였고, 그라파나를 통해 시각화하여 관리자가 원하는 정보의 데이터를 간편하게 확인하고 관리할 수 있어 사용하였다.
CI/CD
각 서비스의 자동배포를 위해서 CI/CD 파이프라인을 구축하였다.
- 지정된 브랜치에 Push 트리거를 걸어, 브랜치의 소스가 업데이트 될 시 Github Actions의 workflow의 정의되어 있는 Job들이 순차적으로 실행된다.
- Actions가 실행되면 소스를 도커이미지로 만든 다음 ECR에 업로드된다.
- 이 때, 이미지의 태그는 Github commit id로 설정됨
- 도커 이미지가 ECR에 업로드 되면, 지정된 ECS의 최신 task-definition.json 파일을 불러와 업로드 한 도커 이미지로 교체를 한다.
- 이를 통해 ECS가 새로 개정된 task 정의에 맞춰 자동으로 배포가 진행된다.
workflow.yml
name: Race source Deploy to Amazon ECR & ECS
on:
push:
branches:
- release/ecs-race-record
env:
AWS_REGION: ap-northeast-2
ECR_REPOSITORY: race
ECS_SERVICE: race-record-sv
ECS_CLUSTER: race-records-cluster
CONTAINER_NAME: tf-race-record-task
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
environment: production
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Set short git commit SHA
id: vars
run: |
calculatedSha=$(git rev-parse --short ${{ github.sha }})
echo "::set-output name=short_sha::$calculatedSha"
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ steps.vars.outputs.short_sha }}
working-directory: ./backend/race-backend
run: |
# Build a docker container and
# push it to ECR so that it can
# be deployed to ECS.
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . || true
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG || true
echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
- name: Retrieve most recent ECS task definition JSON file
id: retrieve-task-def
run: |
aws ecs describe-task-definition --task-definition tf-race-record-task-inserted-console-rds --query taskDefinition > task-definition.json
cat task-definition.json
echo "::set-output name=task-def-file::task-definition.json"
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: ${{ steps.retrieve-task-def.outputs.task-def-file }}
container-name: ${{ env.CONTAINER_NAME }}
image: ${{ steps.build-image.outputs.image }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: ${{ env.ECS_SERVICE }}
cluster: ${{ env.ECS_CLUSTER }}
wait-for-service-stability: true
구현 단계에서 아쉬웠던 점
제일 아쉬웠던 점은 많은 것을 해보지 못했다는 것이다.
나는 전체적으로 되돌아봤을 때, CI/CD 부분만 깊게 파고 있었다.
물론 그 외의 것들은 대부분 이전에 배우고 써봤던 서비스라 이해함에 있어 부족함은 없지만, CI/CD 파이프라인을 구축하며 보낸 시간이 너무 길었던 것 같다.
짧게 변명이라도 해보자면, 팀원들이 코드형 인프라를 위해 테라폼을 사용하며 전체적인 인프라의 구축과 삭제가 매우 쉬워져서 CI/CD 파이프라인을 구축하는 와중에도 계속 조금씩 인프라의 변동사항이 생기거나, 클러스터명 등이 바뀌어 에러가 난다거나, 클러스터 자체가 지워져있어 테스트를 할 수 없다던가 등의 일들로 파이프라인 구축의 마무리가 계속 늦어졌다.
하지만 그런걸 감안하고 보더라도 내가 프로젝트 과정 내내 CI/CD에 꽂혀있었다는 사실은 변함없다.
특히 나는 백엔드를 공부하며 프로젝트 배포를 위해 CI/CD 파이프라인을 만들다가 데브옵스에 관심을 가졌기 때문에 더욱 CI/CD에 관심이 많이 가는 것 같다.
그렇다고 해서 다른 일들에 조금 더 신경쓰지 못한 점은 아쉽다고 생각한다.
다음에 프로젝트를 하게 된다면 조금 더 큰 숲을 볼 수 있도록 노력해야겠다.
구현 단계에서 잘한 점
잘한 점 역시 CI/CD 파이프라인이다.
물론 그 외의 업무도 담당했지만, 애정이 가득 담긴 쪽은 역시 배포자동화이다.
각 서비스의 CI/CD 파이프라인을 효율적으로 관리하기 위해 이런저런 고민 또한 많이 했고,
어떻게 하면 완성도 있는 CI/CD 파이프라인을 구축할 수 있을까에 대해 많이 생각했다.
ECS에 태스크를 변경하기 위해선 태스크 정의의 설정파일이 필요한데, 그 파일이 소스파일에 같이 담겨있어야 한다는 점이 내내 마음에 들지 않았고, 결국 집착 끝에 최신 ECS 태스크 정의 설정파일을 불러올 수 있는 방법을 알아냈다.
물론 이 과정에서 이상한 에러가 발생해 삽질 또한 많이 했지만, 태스크 정의 설정파일을 자동으로 불러올 수 있게 성공했을 땐 기뻤다.
또한 CI/CD 파이프라인을 통해 업로드 된 이미지의 태그가 굉장히 길다는 점에서도 불만을 가지고 있었고,
구글링을 통해 Github의 표시되는 짧은 커밋 id로 바꾸어 태그를 달아주는 방법에 대해서도 알아냈다.
확실히 태그의 길이가 줄어들어 이미지 관리가 쉬워졌고, 뿌듯했다.
비록 프로젝트 일정에 영향이 가지 않을 정도지만 Github Actions에 대해 더욱 깊게 공부해볼 수 있는 시간이었던 것 같다.
우당탕탕 에러해결
내가 가장 해결하기 어려웠던 에러 또한 Github Actions 부분이다.
CI/CD 파이프라인은 처음 구축된 이후 총 2번 수정되었는데, 이유를 설명하자면 먼저, 초기 아키텍처에는 Public Subnet에 위치한 ECS 클러스터가 연결된 외부 서비스가 없기 때문에 보안 강화를 위해 Private Subnet에 위치하도록 변동되었고, 그 과정에서 환경변수들을 바꿔주며 task-definition.json 파일에 민감 정보가 들어가게 되어 더이상 레포지토리에 올려놓을 수 없었다.
기존의 CI/CD 파이프라인에 task-definition.json 파일을 자동으로 불러오는 과정을 추가하며 금방 마무리 될 것 같았던 업데이트 작업은...
알 수 없는 arn MISSING 에러로 배포가 되지 않았다.
기존의 yml 코드와 비교도 하고, task-definition.json 파일을 직접 넣어 배포 시도도 해보고, json 파일을 하나하나 비교하며 무슨 arn이 문제인가 싶어 다 찾아봤지만... 도저히 원인을 알 수 없었다.
심지어 이전 yml 파일로 롤백을 하여 배포시도를 해봤지만 그것마저 되지 않았고, 나는 엉뚱한 곳에서 헤매기 시작했다.
무슨 ARN이 없는건지 파악을 할 수 없어 업데이트 된 인프라를 쥐잡듯 뒤져봤지만, 없는건 없었고 심지어 날 미치게 한 사실은 태스크 정의까지는 업로드가 되었다는 사실이다.
게다가 수동으로 클러스터의 태스크를 교체해주어도 아무 이상이 없었다!!!
헛다리를 제대로 짚은 나는, task-definition.json 파일을 불러오며 태스크 정의의 태그가 바뀌지 않아 에러가 발생했다고 생각했고, 멍청한 짓을 시작했다.
workflow 내의 태스크 정의를 1씩 증가해주어 새로 올라갈 태스크 정의의 태그와 맞춰주면 그만 아닐까! 라는 바보같은 짓을 말이다.
결론부터 말하자면, 모든 게 헛수고...까지는 아니긴 했지만 시간을 많이 허비했다.
왜냐면, 그놈의 arn MISSING 에러는 죄가 없었기 때문이다.
근본적인 원인부터 다시 보자면, Public Subnet에 있던 ECS 클러스터를 Private Subnet으로 옮겼다.
그렇기 때문에 CI/CD 파이프라인을 다시 구축하고 있었던 것이고, 에러가 나서 롤백을 했을 때도 똑같은 에러가 발생하는 거라면?
문제가 뭔지 알아버리고 난 뒤 난 기쁘지 않았다.
제일 마지막의 클러스터명과 서비스명을 환경변수로 받아오는 과정에서 잘못 받아오는게 아니냐는 팀원의 조언에 평문으로 작성하려하는 도중 머리 속에서 번쩍 드는 생각이 있었다.
문제는 정말 간단하고 허무할 정도로 사소한 문제였다.
바로 환경변수로 넣어주던 클러스터명이 알맞지 않았기 때문이었다.
맞지 않는 클러스터명으로 배포를 시도하고 있으니, 서비스를 당연히 찾을 수 없고 그렇기 때문에 arn MISSING 에러가 발생하던 것이었다.
당시에는 이해할 수 없었지만, 해결을 하고 머리를 식힌 뒤 차분히 생각해보니 팀원 간 소통이 미흡해 발생한 제일 큰 문제였다는 걸 깨닫게 되었다.
분명 초기 배포테스트 당시에는 정확한 클러스터 명으로 적혀져 있었고 그렇기에 테스트에도 문제가 없었으나, 클러스터를 Private Subnet으로 옮기는 과정에 팀원의 실수로 클러스터 명이 변경 되었던 것이다.
클러스터 명이 바뀌었을 거란 걸 상상조차 못하고 있었기에 문제원인을 코 앞에 두고도 먼 길로 빙빙 돌게 되었던 것 같다.
앞으로 배포자동화 작업을 하며 이것만큼은 잊을 수 없을 것이다.
다음에는 기본적인 것들이더라도 한 번씩 더 체크해보자.
비용 관리
먼저 비용관리를 위한 프로세스에 대해 먼저 설명하겠다.
프로젝트가 정해진 예산 내에서 진행되는 것이 중요하기에 AWS Budget을 사용하여 예산 요금 초과 시 이메일로 알림을 받는 프로세스를 구축했다.
예상 비용이 예산의 100%가 넘었을 때, 실제 금액이 예산의 85%가 넘었을 때, 사용 금액이 예산의 100%가 넘었을 때.
총 3개의 경우 이메일로 알림을 받을 수 있다.
So?
초기 설계를 하며 예상했던 프로젝트의 비용은 약 74달러(예상기간 2주, 월 148달러) 내외로 견적서를 제출하였지만,
실제 프로젝트 진행을 한 결과 약 51달러(실제기간 1주, 월 196달러)를 지출하였다.
이는 구현을 시작하며 다양한 요인이 지출 비용에 영향을 주었기 때문인데, 그 중 2가지 요인에 대해 공유하려 한다.
첫번째 이유로는 테라폼을 통한 인프라 구성이다.
테라폼을 통해 인프라를 구성해두어 테스트 시간을 제외하면 리소스를 생성해놓고 있지 않아도 됐다.
제출한 견적서에 의하면, 우리는 하루에 8시간 동안 리소스를 켜놓는 것을 가정하고 계산한 예상금액으로, 테라폼을 이용하게 되며 실제로 하루에 8시간을 켜놓지 않아도 됐고, 2~3시간 정도만 켜놓았기 때문에 비용 지출이 줄어들었다.
두번째 이유로는 프로젝트 초기에 고려하지 못한 인프라 리소스의 추가이다.
고려하지 못했던 리소스는 VPC endpoint로, ECS를 Private Subnet으로 변경하게 되며 구성상 필요에 의해 추가가 되었다.
이 때, 추가된 리소스의 예상 비용이 대략 월 48달러로 비용 지출 증가에 큰 영향을 끼쳤다.
아쉬웠던 점
프로젝트를 시작하며 예상 비용 자체를 지원받은 금액을 넘지 않게 측정되어서 미리 이런 상황을 대비했던 것과, 테라폼을 이용함으로서 실제 비용 발생 시간이 확연히 줄었다는 점에서 실제 비용 지출은 예상비용을 넘지 않았지만,
실제로 업무 진행 시에는 처음부터 다양한 경우의 수를 생각하여 비용에 대해 좀 더 세심하게 신경 쓸 필요가 있다고 느꼈다.
아래의 그림은 실제 비용 지출 그래프로, 총 51달러가 사용되었다.
프로젝트 후기
프로젝트를 진행하며 실제로 구현을 하는 것에 있어서는 큰 어려움이 없었다.
가장 힘들었던 건 팀원들과 의견을 맞추는 것으로, 구현하는 시간보다 더 많은 시간을 할애한 것 같다.
또한 앞서 언급했듯, 한가지 일에만 너무 매몰되어 있어 다른 부분들이 빠르게 완성될 동안 같이 참여하지 못했다는 사실이 너무 아쉽게 느껴진다.
그래도 나름 뿌듯했던 건, 최종 발표 자료로 만든 PPT와 발표대본이 꽤나 많이 아쉽다는 생각에 슬퍼하고 있었는데, 막상 다른 팀들의 발표를 보고 우리팀 발표를 보니 꽤 괜찮게 했구나 싶었다.
가끔은 내가 나에게 너무 높은 기준을 들이밀고 있나 싶은 생각도 들었다.
CI/CD 파이프라인 구축에 흥미가 생겨 인프라 쪽으로 눈을 돌리며 데브옵스 부트캠프에 신청서를 넣은게 바로 어제같은데, 이제 마무리라니 실감이 나지 않는다.
아직 부족한 부분이 많이 있다고 생각들지만, 늘 최선을 다했다고 생각하기에 불안하진 않다.
수료까지 이제 일주일정도 남았는데, 남은 시간동안 배웠던 것을 다시 한 번 보며 정리를 해야겠다.