오늘 배운 것

오늘 오후까지도 ECS 배포 이슈가 이어지고 있다. 어제 fargate 옵션을 통해 서버를 띄우는 데까지는 성공했다. 장고 서버가 무사히 시작했다는 로그도 보았다. 이 이후에 다른 문제가 있었다. 

어딘가에서 서버가 실행되었던 흔적

1. 어딘가에서 잘 실행되고 있는 이 서버에 어떤 엔드포인트로 접속해야 하는지 모르는 문제

2. 마이그레이션 명령어(python manage.py migrate)를 실행하지 않고 바로 실행시켰기 때문에, 분명 RDS에 마이그레이션이 적용되지 않았을 것이다...

-> 이렇게 생각했었는데, 사실 이미 우리 서비스는 원격 RDS를 사용하고 있다. 그렇다는 것은 이미 로컬에서 RDS 연결 테스트를 할 때 명령어로 마이그레이션을 실행해 주었으니, 사실상 이 문제에 대해서는 걱정하지 않아도 되겠다. 

 

어쨌든 1번 문제는 아직 남아있다. 게다가 방금 또 다른 문제 상황을 알게 되었다. 분명히 어제 밤까지는 서비스 내부의 태스크 로그에서 python manage.py runserver 명령어가 잘 실행된 로그를 확인했었다. 그런데 오늘 다시 확인해 보니 실행 중인 태스크가 없는 거였다..!

 

어제의 경험을 바탕으로 CloudFormation 서비스에 들어가 로그를 살펴보니, ECS에서 failure가 났다는 로그가 남아있었다. 

상세 사유를 확인해 보니, fargate 인스턴스에서 ECS 서비스를 생성하는 데 실패했다고 나온 것 같았다. 무슨 문제일지는 더 살펴봐야 알 것 같다. 

아무래도 기존에 'ECR ECS'로 검색하면서 나온 여러 블로그와 영상들 중, EC2가 아니라 fargate 옵션이라서 보지 않았던 자료들을 다시 참고해야 할 것 같다. 


fargate로 서버 띄워서 접속했다!!!

위의 상황에서 문제가 없는지(로드밸런서 구성은 잘 되었는지, 퍼블릭 서브넷에는 잘 연결되어있는지 기타등등)를 한번 더 점검하고 ECS 클러스터 서비스를 업데이트했다. 

 

역시나 위에처럼 서버는 또 내가 모르는 어딘가에서 잘 실행되는 것처럼 보였다. 사용한 로드밸런서의 퍼블릭 DNS에서도 접속이 안 되고, 태스크의 ENI에 나오는 퍼블릭 주소로도 접속이 안 되길래 도대체 뭐지 싶었다. (여기까지는 어제랑 똑같은 상황이다)

냉큼 도움요청

GPT에게 ENI와 로드밸런서의 퍼블릭 주소는 멀쩡한데 접속이 안 된다고 물어보니 보안그룹 설정을 확인해보라고 했고, ECS 태스크의 ENI에서 사용하고 있는 보안그룹 설정을 보니 Anywhere IPv4 설정이 안 되어있는 게 아닌가. 사실 이것도 안드로이드 엡에서 접속하는거면 분명 특정 도메인이나 IP나 프로토콜 접속만을 허용해야 하는 무언가의 룰이 있을 텐데... 일단 이것은 개발서버이니 전부 열어두었다. 

 

그랬더니 이렇게 잘 접속되었다. 

 

로그도 잘 찍히고 있나 싶어서 ECS>태스크>로그를 확인해보았다. 

Not Found는 기본 URI에 매핑된 API가 없어서 그런 것이고, 404 response를 리턴하는 이유는 왜인지 모르겠다. 

404라니 뭔가 찜찜

 

암튼 급한 불은 껐다. 

 

이제 이어서 해야 할 일은 github action으로 이 모든 과정을 자동화하는 yaml 파일을 만들고, 그 과정을 통해 컨테이너가 무사히 ECR>ECS까지 잘 등록되는지를 확인하는 것이다. 


평화와 성공의 기쁨도 잠시, 또 Timeout이 났다. 

 

GPT에게 질문해서 오류를 파악해 보았다. 

 

GPT가 제시한 방안은 다음과 같았다.

1. Fargate의 태스크가 중지되었을 수 있으니 이벤트 로그를 확인해라. 

2. 로드밸런서의 헬스 체크 설정에 문제가 있는지 확인해라. 

3. 리소스 부족 때문에 태스크가 중지된 건 아닌지 확인해라. 

4. 애플리케이션 자체의 오류일 수도 있다. 

5. 보안그룹이나 네트워크 ACL 같은 네트워크 설정에 문제가 있을 수 있다. 

 

1번의 경우 이벤트를 확인하니 에러가 났다는 로그가 있었다. 

 

2번의 경우 헬스 체크 설정에 문제가 있는 게 맞았다. 사용하고 있는 로드밸런서(onestep_alb) 설정에 들어가니, 1개의 인스턴스에서는 요청이 타임아웃 되었고, 다른 하나의 인스턴스에서는 헬스체크가 fail 되었다.

 

3번의 경우 CPU 사용량 등의 리소스 그래프가 확 튀는 모양은 없었으므로 이것은 아닌 것 같았다. 

4번의 경우 현재 접속한 것은 swagger API 뿐이고 별다른 오류 로그가 없었으므로 아닌 것 같다. 

5번의 경우 그게 원인이었다면 5분 뒤에 접속이 끊길 리 없으니 가능성이 낮다. 

 

오류의 원인이 1-2번이라면 오류가 설명이 된다. 

즉 로드밸런서의 헬스체크가 fail로 간주되기 전에 계속해서 시도를 하던 동안에는 서버 접속이 잘 되었는데, 로드밸런서의 헬스체크 기준이 잘못된 것 같았다. 그래서 헬스체크를 fail로 간주하고, 이 태스크가 실패한 것으로 간주되면서 서버에 접속이 안 된다고 추측하고 있다. 그렇다면 로드밸런서의 헬스체크 기준을 바꿔줘야 하겠다. 

 

그래서 바꿔줬다. 

정확히는 EC2의 로드밸런서에서 사용 중인 로드밸런서를 클릭하고, 이 로드밸런서가 어떤 대상 그룹으로 요청을 보내주는지를 확인했다. 

나의 경우 onestep_alb라는 로드밸런서가 onestep_dev라는 대상 그룹으로 요청을 보내준다. 

 

그런데 onestep_dev 대상그룹의 헬스 체크 기준을 보니 기본 URI(/)로 요청을 보내서 200을 받는 것이 헬스체크의 기준으로 되어있었다. 그런데 어플리케이션 서버의 경우 / 에는 맵핑된 URI가 없었기 때문에 404 에러가 나서 헬스체크에 실패했던 거였다. 그래서 이 URI를 다시 /swagger(모든 API 명세를 볼 수 있는 URI로 상태코드 200을 리턴한다)로 바꿔주었다. 만약 이게 문제였다면 이제는 성공할 것이고, 아니면 또 다른 원인을 찾아야 하겠다. 

 

일단 다시 서비스를 업데이트 하니 서버가 또 잘 뜨긴 한다. 이제 몇 분 뒤에 헬스체크가 다 되었을 시점에 다시 결과를 보면 되겠다. 

 

헬스체크 규칙을 바꾸니 10분이 넘어가도 서버가 살아있다! 원인이 이것 때문이었던 것 같다. 그런데 나머지 1개의 인스턴스는 왜 timeout이 뜨는지는 잘 모르겠다. 

원래는 2개 다 fail이었는데 이제 하나는 정상이 뜬다.


로드밸런서가 계속해서 /swagger를 호출하면서 헬스체크를 계속하는 이유는 뭘까? 로그를 봤더니 거의 1000개가 있어서 놀랐다. 

찾아보니 로드밸런서의 헬스체크 기준에 원인이 있는 것 같았다. 헬스체크 기준은 로드밸런서가 가리키는 대상 그룹(target group)에서 정하는데, 현재 로드밸런서는 10초에 한 번씩 /swagger로 API 요청을 보내고 있었던 것이다. 이러니 부하가 있을 수 밖에! 그래서 10초 대신 maximum인 300초로 바꿔주었다.

 

그리고 깃허브액션으로 위 작업을 자동화하는 데 성공했다! 여기에는 GPT의 공이 컸지만, 그래도 나도 이 작업을 얼추 이해했기에 내 이해도 돕고 지식도 정리할 겸 남겨본다. yml 파일만 잘 작성되어 있다면 생각보다는 과정은 간단했다. 

 

우선 yml 파일을 GPT의 도움을 받아 작성해준다. 이 yml 파일이 제대로 동작하려면 그 전에 완료되어야 하는 사전 작업들이 있다. 

 

우선은 ECR에 도커 이미지를 올릴 수 있는 레포지토리가 있어야 하고, ECS 클러스터가 생성되어 있어야 하며, 그 클러스터 내에서 실행될 서비스가 생성되어 있어야 하고, ECS 클러스터에서 생성될 작업에 대한 정의(태스크 정의)가 작성되어 있어야 한다. 그리고 여기에 관련된 모든 변수들(ECR 레포지토리의 이름, ECS 클러스터의 이름, ECS 클러스터 안의 서비스의 이름 등)이 github secret에 변수로 등록되어 있어야 한다. 그래야 yml 파일에서 이 값들을 그대로 노출시키지 않고 변수를 통해 할당할 수 있다. 

 

ECS 클러스터에서 생성한 태스크 정의는 변수로 등록할 필요는 없다. 그럼에도 이 태스크 정의가 필요한 이유는 yml 파일을 작성할 때, ECS의 어떤 클러스터의 어떤 서비스에서 어떤 작업(태스크)를 실행할지를 명시해줘야 하는데, 이때 태스크 정의를 생성하면 나오는 JSON 파일이 필요하기 때문이다. 

 

사실 이 JSON 파일도 원래는 프로젝트 루트 디렉토리에 두려고 했었다. 그런데 JSON 파일에서는 .env 파일의 환경변수를 바로바로 등록해주지 못하고 별도로 파이썬 스크립트 파일을 하나 만들어서 그 파일에서 JSON 파일에 환경변수를 집어넣어주는 작업을 해야 한다. 굳이 싶어서 그냥 yml 파일에 그 작업을 넣어주기로 했다. 

 

그리고 GPT의 도움을 받아 생성된 yml 파일을 넣어주면, 잘 동작할 수도 있고 오류를 반환할 수도 있다! 나의 경우는 ECS 클러스터의 값이 예전에 생성된 값이었어서 해당 클러스터가 없다는 로그가 떴었다. 이처럼 깃허브 액션의 로그를 잘 보고, 알아서 잘 딱 깔끔하고 센스있게 GPT의 도움을 받아 고쳐주면 머지않아 이런 감동적인 화면을 볼 수 있다. 

 

궁금한 점

1. ENI는 또 어떤 녀석이며 왜 필요할까

2. 로드밸런서의 DNS 주소와, ECS 태스크의 ENI에 나오는 퍼블릭 주소는 각각 어떤 개념이며 무엇이 어떻게 다른가? 

3. 네트워크 ACL은 어떤 녀석인가

4. 로드밸런서에서 헬스체크가 fail이 나면 어떤 일이 발생하나? 설마 태스크의 실패로 간주되어 서비스가 종료되나?

5. 로드밸런서에서 대상그룹으로 요청을 보내준다(포워딩인 듯)는 개념이 어떤 의미일까? 이 개념이나 원리가 잘 이해가 안 된다. 

 

참고한 블로그

https://velog.io/@eunocode/%EC%A1%B0%EA%B0%81%EC%A1%B0%EA%B0%81-json-.env

 

'개발 일기장 > SWM Onestep' 카테고리의 다른 글

20240731 TIL  (0) 2024.07.31
20240730 TIL: RN에서 UI Kitten으로 모달에서 캘린더 띄우기  (0) 2024.07.30
20240728 TIL  (2) 2024.07.28
20240727 TIL: ECR ECS 적용기  (0) 2024.07.27
20240726 TIL  (0) 2024.07.26

 오늘 배운 것

오늘은 멘토님과 멘토링하면서 CI/CD 자동배포 및 도메인 연결에 대해서 작업했다. 

오늘 다룰 것으로 예상되는 부분은 다음과 같다: 

1. ECS에서 EC2 컨테이너 실행

2. 실행한 EC2 컨테이너를 Route53 등을 이용해서 사전에 구매한 도메인 주소 할당

 

멘토링에서 배운 것

이전에 블로그에 올린 질문 관련

EBS, 그리고 이와 관련된 개념인 RAID (raid 0, 1, 5, 10 전략 & hotswap(빈 disk 하나 둬서 장애에 대비하는 전략))를 말씀해주셨다. EBS는 Elastic Block Store의 약자로, 찾아보니 EC2 인스턴스에 사용될 스토리지 볼륨을 제공해주는 서비스라고 한다. 그리고 RAID 중에서는 RAID 10 전략이 제일 많이 사용된다고 하셨다. 왜냐하면 데이터 스트라이핑(striping)과 미러링(mirroring)을 동시에 사용하는 전략이기 때문이다. 

 

참고로 데이터 스트라이핑과 미러링을 구성하려면 디스크가 최소 N(N>=2)개 이상 필요하다. 스트라이핑의 경우, RAID를 구성하는 모든 디스크에 데이터를 분할하여 저장하는 방식으로, 전체 디스크를 모두 동시에 사용하기 때문에 성능은 단일 디스크의 N배가 된다고 한다. 미러링은 이와는 다르게 모든 디스크에 데이터를 복제하여 기록한다. 그래서 우선은 디스크 1개에서 장애가 발생해도 데이터를 복구할 수 있다는 장점이 있으며, read 작업을 할 때 성능이 N배가 된다고 한다. 

 

RAID 전략 중에 가장 많이 사용하는 것은 RAID 10번 전략인데, 이는 4개의 디스크로 미러링과 스트라이핑을 동시에 하는 전략이다. 이렇게 하면 디스크가 1개 손상되어도 데이터를 복구할 수 있고, 또한 읽기나 쓰기의 성능도 2배가 되어서 많이 사용한다고 한다. 

 

IAM은 하나 혹은 여러 AWS 세부 서비스를 사용할 때 효과적으로 역할과 권한을 분리하기 위한 용도이다. 

ECS에서 EC2에 접근하는 게 아니었다. EC2에서 ECS에 접근하는 거였다!!

가장 많이 쓰는 EC2 스팟 인스턴스는 t3.micro이다. 이유는 가격이 싸기 때문. 그러므로 t2.micro 대신 t3.micro를 쓰자. (b/c 더 용량이 큰데 가격이 더 싸고, 여러 AWS 리전 중에서는 t3.micro 인스턴스가 있는 리전은 많은데 t2.micro 인스턴스는 리전에 따라 없는 곳도 있다. 이럴 경우 실행이 안 될 수 있기 때문에 t3.micro를 권하셨다.)

 

ECS 클러스터 네트워크 관련 내용

awsvpc로 하면 인스턴스와 컨테이너 모두가 각각 퍼블릭 ip를 할당받는다. 그래서 싼 인스턴스를 쓸 경우 하나의 인스턴스에 하나의 컨테이너밖에 올리지 못할 수 있다. 반면 bridge 모드를 쓰면 하나의 인스턴스에 여러 개의 컨테이너를 할당받을 수 있다. 반면 bridge 모드로 하면 컨테이너는 퍼블릭 ip를 할당받지는 않는다. bridge 모드는 이 인스턴스를 공유기처럼 쓰기 때문에 인스턴스 밖에서는 접근이 안 되고, 그래서 컨테이너를 띄울 때 항상 포트 번호를 지정하도록 한다. 

 

서버 지식 관련 내용

로드밸런서의 경우는 ELB, ALB 등의 관련 키워드가 있다. 로드밸런서의 역할이란 가령 가령 서버 인스턴스가 여러 개 떴을 경우, 여러 서버들에게 요청을 나눠서 보내주는, 말 그대로 서버들에게 가해지는 부하(로드)를 밸런싱해주는 역할을 한다. 

 

그 외에도 멘토링을 하면서 fargate가 아닌 EC2 인스턴스를 사용해서 ECS 클러스터에 등록하려고 했는데 여러 문제가 발생했다. 

 

가령 컨테이너의 용량이 인스턴스의 용량을 초과하는 문제도 있었다. 이 경우 EC2의 시작 템플릿에서 필요한 CPU의 개수와 메모리 용량을 기존에 3Gib로 되어 있었던 것을 400MB로 바꾸어서 해결하였다.

 

또한 오토스케일링 그룹에서 ec2 인스턴스를 1개만 띄우도록 기본으로 설정해 두었었는데, 알고보니 오토스케일링 그룹에서는 기본으로 롤링 업데이트 방식을 사용하고 있었다. 이 방식에서는 최소 2개 이상은 띄워져 있어야 서버가 시작할 수 있었다. 왜냐하면 서버를 기본적으로 2개 시작해 두고 1개를 kill 한 다음 나머지 1개만 정상 실행시켜 두고, 만약 잘 실행되고 있던 1개의 서버가 죽거나 업데이트를 해야 하는 상황이면 그때 나머지 1개를 실행시켜두는 방식이 롤링 업데이트 방식이었기 때문이다. 이것 역시 EC2의 오토스케일링 그룹에서 인스턴스의 개수를 1개에서 2개로 바꿔주니 해결되었다.

 

그리고 이 모든 상황은 cloudformation, autoscaling group 등의 로그를 통해 파악할 수 있었다. 나는 기존에는 cloudwatch를 통해서만 로그를 볼 수 있는 것인 줄 알고 cloudwatch에 로그가 안 떠서 어디를 찾아봐야하나 싶었는데, 알고보니 오토스케일링 그룹 관련 로그는 EC2>autoscaling group 부분에서 뜨고, cloudformation이라는 로그를 볼 수 있는 또 다른 서비스가 있었다. 정말 AWS의 세계는 알면 알수록 크고 심오한 것 같다...


삽질을 계속하다 문득 월요일부터는 잠시 뒤로 미뤄놓았던 프론트엔드 관련 이슈를 처리해야 한다는 판단이 들었다. 그래서 오늘 안으로는 ECS를 통해 인스턴스를 띄워야 하는데, 그러려면 EC2 옵션을 통해서는 오늘 안에 배포가 어려울 수도 있다고 생각했다. 그래서 fargate 옵션을 사용하기로 했다. 어차피 나중에 다시 바꾸면 되니까!

 

 

 

 

참고한 블로그

https://velog.io/@ghldjfldj/AWS-EBSAMISG

https://devocean.sk.com/blog/techBoardDetail.do?ID=163608

 

 오늘 배운 것

오늘은 ECS에서 EC2 서버를 실행시키는 작업을 하다가, 문득 EC2에서 사용하는 데이터베이스가 로컬 mysql DB라는 사실을 깨달았다. 그런데 EC2 인스턴스에선 기본적으로 mysql db가 설치되어있지 않기 때문에, 이렇게 코드를 올리면 로컬에서만 실행되고 원격 인스턴스에서는 실행되지 않을 게 분명했다. 결론은 현재 로컬에서 사용하는 mysql 서버를 RDS로 바꿔야 한다는 것이었다. 

 

이를 위해 AWS 콘솔을 확인해 보니 저번 주에 멘토링하면서 만들어 둔 RDS가 있었다. 그런데 유저 이름과 패스워드가 기억나지 않아서, 새로 RDS 인스턴스를 생성하기로 했다. 

 

그런데 또 생각해보니 RDS를 처음에는 로컬에서 접근 가능하게 해야 하지만, 또 이걸 깃허브 브랜치에 올린 다음 ECR에다가 도커 이미지를 올리는데 그러면 해당 EC2 서버에서만 RDS에 접근 가능하게 해야 하는 게 아닌가? 그러면 RDS의 초기 접근 설정은 Anywhere IPv4로 한 다음에 나중에 특정 VPC에서만 접근 가능하게 하도록 바꿔 줘야 하는 것인가라는 생각이 들었고, 잘 모르지만 일단 초기에 로컬에서 접속은 되어야 하니 처음에는 퍼블릭 엔드포인트를 열어서 어디서든 접속할 수 있게 설정해서 진행해 보기로 했다. 

-> RDS 인스턴스를 생성할 때 자세히 보니, 이 설정은 나중에 RDS 인스턴스에서 변경 가능하다고 했다! 그렇다면 우리는 EC2 인스턴스를 생성한 다음, RDS와 EC2를 같은 VPC에 위치시키고, 이 상태에서 ECS를 통해 EC2에서 이미지를 컨테이너로 만들어서 실행시키면 되지 않을까 싶다. 

 

우선은 새로운 RDS 인스턴스를 생성해서 로컬에서 mysql을 통해 접속한 후, 웹 어플리케이션 서버에서 사용하는 database를 생성해 주었다. 그리고 기존의 settings.py의 DATABASES 변수 관련 설정(HOST, NAME 등)을 RDS에서 설정한 대로 잠시 하드코딩해서 바꾸어주니, 무사히 잘 실행되었다. 

 

그리고 기존의 settings.py에서는 .env 파일을 사용하는 대신 AWS의 secrets manager를 사용해서 환경 변수를 관리하고 있었다. 왜냐하면 기존의 로컬 환경변수 파일은 여러 명의 팀원이 같은 .env 파일을 공유해야 하는 불편함이 있었기 때문이다. 여기서 DB의 유저 이름이나 엔드포인트 등을 불러왔었기 때문에, 이 값들도 AWS secrets manager에서 하나하나씩 바꿔주었다. 

대충 이런 값들이 있었다

 

그리고 어제 막바지에 AWS ECS에서 클러스터 생성하고, 태스크(작업) 정의 생성하고, 그걸 토대로 ECS 서비스(태스크와 서비스 2가지 종류가 있었는데, 태스크는 배치 등 1회성 작업에 적합하고 서비스는 웹 어플리케이션 등 계속해서 유지 및 실행되어야 하는 작업에 적합하다고 설명이 나와있어서 서비스를 선택했다)를 생성했다. 역시나 처음엔 어제처럼 생성 가능한 EC2 인스턴스가 없다는 오류가 떴다. 

 

그래서 또 찾아보니 ECS 클러스터 안에서 서비스를 생성하는 화면에서, 맨 처음에 오른쪽 메뉴(시작 유형)이 아니라 왼쪽 메뉴(용량 공급자 전략)을 선택해서, 사전에 미리 만들어둔 용량 공급자를 여기서 선택할 수도 있는 것 같았다. 

 

결과적으로 이 방법으로 ECS를 통해 EC2 인스턴스를 생성하고 실행시키는 데까지는 성공했다(물론 태스크가 성공하지는 못했지만 이만큼이라도 된게 다행이다). 

 

1. ECS에서 EC2 인스턴스를 생성 및 제어해야 하므로, 이는 하나의 서비스가 다른 서비스에 접근하거나 영향을 주어야 하는 상황이기 때문에 IAM 역할이 필요하다는 것 같다. 그래서 IAM 서비스로 가서 'AmazonEC2ContainerServiceforEC2Role' 이라는 역할을 생성해 주었다. 이름을 보니 ECS에서 EC2 서비스에 접근하기 위해 필요한 역할 같았다. 

 

2. 정확히 왜 그런지는 모르겠는데 EC2 스팟 인스턴스를 제대로 사용하려면 auto scaling 그룹이 필요하다고 한다. 그리고 auto scaling 그룹을 생성하려면 시작 템플릿(launch template)이 필요하단다. 그래서 EC2 메뉴로 가서 시작 템플릿을 생성해 주고, 그걸 토대로 auto scaling 그룹도 생성해 주었다. 1번에서 생성한 IAM 역할은 여기서 필요하다. 

 

3. 이제 이 autoscaling 그룹을 ECS와 연결해야 한단다. ECS에서 이미 생성한 클러스터를 누르고, 용량 공급자(capacity provider) 탭으로 이동하자. 그러면 기본으로 있던 용량 공급자 외에 새로운 용량 공급자를 선택할 수 있다. 여기서 '생성'을 누르고, autoscaling 그룹을 선택하는 영역에서 2번에서 생성한 그룹을 선택해 준다. 

이 과정을 완료하면 ECS 클러스터에서 EC2 스팟 인스턴스를 생성할 수 있는 준비가 되었다. 

 

이제 다시 위의 화면(ECS 클러스터 안에서 서비스 생성하는 화면)으로 가서, 왼쪽 메뉴(용량 공급자 전략)를 누르고 여기서 생성한 용량 공급자를 할당해 주자. 그리고 앞서 정의한 태스크 정의를 선택해 주고... 기타 등등을 하면 서비스가 생성되고, EC2에 들어가서 확인해 보면 ECS 클러스터에 의해서 못 보던 EC2 스팟 인스턴스가 생성되어 있는 걸 볼 수 있다. 뿌듯.

 

그리고 서비스 안에 하나의 태스크가 할당되었길래(이건 태스크 정의를 통해 정의된 태스크로 보인다), 이제 이게 실행만 잘 되면 되겠거니 싶었다. 그런데 태스크가 한참을 프로비저닝 상태로 있더니 실패했다. Cloudwatch를 통해 로그를 찾아보고 싶었지만 나오는 게 없어서 원인을 알 수 없는 상태이다... 

 

지금 생각하고 있는 원인으로는

1. ECS에서 EC2 인스턴스를 생성하기만 했지, 사실 클러스터에 제대로 할당된 EC2 인스턴스가 없었던 게 아닐까

2. 코드에서 루트 디렉토리에 도커파일을 별도로 두어 실행하는 커맨드를 두었어야 하는데 그게 안 되었던 게 아닐까

 

그 외에 다른 원인이 있을지는 잘 모르겠어서, 우선 도커파일을 다시 추가하고 main 브랜치를 다시 github action을 통해 도커 이미지로 만들어서 ECR에 올려봐야겠다. 그래도 안 되면 1번에 가능성을 두고 더 찾아봐야겠다. 


다시 찾아보았는데 생각한 원인 중 1번이 맞았다. 우선 2번의 경우 .Dockerfile이 루트 디렉토리에 없나? 싶었는데 멀쩡히 잘 있어서 머쓱해졌다... 대신 1번이 맞는지를 확인하고 싶어서 GPT에게 확인하는 명령어를 알려달라고 해 보았다. 

aws ecs list-container-instances --cluster onestep-production2

그랬더니 이런 명령어를 알려주었다. 이거는 ECS의 해당 클러스터(onestep-production2)에서 현재 연결된 EC2 인스턴스가 있는지를 알려주는 명령어이다. 

그렇다. 나름 기대를 갖고 실행해 보았는데 ECS 클러스터에 연결된 EC2 인스턴스가 하나도 없었던 것이다. 이러면 결국 문제는 다시 이전으로, ECS 클러스터에서 EC2 스팟 인스턴스를 연결하는 부분으로 돌아간다. 

 

또한 짚이는 문제 부분이 하나 더 생겼다! 

어제 작업했던 부분인데 ECS 서비스나 태스크를 추가할 때였나, 아니면 태스크 정의를 할 때였나 암튼 환경변수를 추가하는 부분이 있었어서 그때 보면서 이게 왜 필요한지 의아했었다. 그런데 사실 깃허브 브랜치에 코드를 올릴 때 .env 로컬 환경변수 파일을 올리지는 않지만 분명 코드 상 해당 파일을 참조해야 하는 부분이 있기 마련이다. 그 환경변수 값을 셋팅해주는 것이 이 과정이었던 것으로 예상한다. 

 

그리고 생각해보니 멘토님은 브랜치에 있는 코드를 도커 이미지로 빌드해서 ECR에 올리는 거나, ECR에 있는 이미지를 ECS에서 실행시키는 두 작업 모두 아마 깃허브 액션으로 가능할 거라고 하셨다. 그래서 아 이렇게 복잡하게 돌아가지 말고 최소로 필요한 세팅만 AWS 콘솔에서 한 다음에 나머지는 깃허브 액션에서 yaml 파일로 하는 것이 맞겠구나, 라는 생각이 들어서 노선을 또 바꿔 보았다. 


우선은 yaml 파일을 작성하기 전에 필요한 사전 작업이 있다. AWS 콘솔에서 다음과 같은 작업들을 먼저 하고, 이후에 깃허브 액션으로 ECR 이미지 빌드 + ECS 컨테이너 생성 및 실행까지 하면 되겠다. 

 

+)

GPT와 얘기하면서 삽질을 여러 번 했는데... 어쩌다 보니 다시 ECS 클러스터에 연결된 EC2 인스턴스를 확인해 보니 1개가 되어 있었다..!! 이유는 모르겠다...

 

어쨌든 ECS 클러스터에서 EC2 스팟 인스턴스를 연결하는 부분이 해결되었다! 이제 다음 단계로 가 보자. 

오늘은 GPT가 만들어준 ECR에 이미지를 빌드해서 업로드 해 주고, ECS에서 컨테이너를 생성한 뒤 EC2 인스턴스에서 실행시켜주는 yaml 파일만 테스트해보고 자야겠다. 

'개발 일기장 > SWM Onestep' 카테고리의 다른 글

20240729 TIL: ECR ECS CI/CD 적용기 with fargate & github action  (1) 2024.07.29
20240728 TIL  (2) 2024.07.28
20240726 TIL  (0) 2024.07.26
20240725 TIL  (0) 2024.07.25
20240724 TIL  (0) 2024.07.24

 오늘 배운 것

오늘 오후까지 이어지던 SZ-85 작업은 계속 길어질 것 같아서 드래그앤 드롭 구현에 이상이 없는 정도에서 잠시 머지하고, 나는 그 동안 미뤄왔던 또 다른 급한 이슈를 처리하려고 했다. 

 

바로 github action, ECR, ECS를 통해 깃허브의 메인 브랜치의 코드를 도커 이미지로 빌드해서 ECR에 업로드하고, ECS를 통해 ECR에 업로드된 도커 이미지를 실행하는 일을 했다. 

 

사실 ECR로 이미지를 업로드하는 일까지는 순탄했고, 그 이후에 ECS의 클러스터를 설정하고, fargate와 EC2 중에서 나름 고민해서 선택을 했다. fargate는 서버리스로, 좀 더 간단한 설정을 할 수 있다는 장점이 있는 반면 EC2는 설정을 커스텀해야 하는 부분이 있지만 비용 측면에서 좋다고 했다. 그러다 문득 팀원이 멘토님께서 EC2 인스턴스를 그냥 쓰지 말고 EC2 스팟 요청을 통해 더 저렴하게 사용하라고 했던 것 같다고 말해줘서, 아 그러면 어차피 EC2 스팟 인스턴스를 써야 하는 거니 EC2를 써야 하겠다는 생각이 들어 EC2 인스턴스를 선택했다. 

 

그래서 EC2를 선택했는데 클러스터에 할당된 EC2 인스턴스가 없다는 말을 듣고 EC2 스팟 요청을 해야하나 말아야하나 하다가 클러스터 설정에서 '서비스 추가' 대신에 '태스크 추가'를 발견해서 그걸 눌러보았더니 참고하던 블로그와 똑같은 UI가 떴다. 

 

그래서 태스크 추가에서도 여러 설정을 하는데, '네트워크 모드'라는 옵션이 있었다. 옆의 설명을 읽어보니 태스크에서 ECS가 실행시키는 컨테이너가 어떤 네트워크 설정을 가질지를 결정하는 것 같았다. 기본값은 awsvpr로 되어있었는데 이건 fargate에서 필요한 설정이라고 되어 있어서 내가 선택한 EC2에 해당하는 옵션은 아닌 것 같았다. 그래서 docker network ls 명령어를 치면 기본값으로 항상 보이던 bridge 네트워크가 떠올라서 bridge를 선택했다. 예상해보건대 bridge 옵션을 선택하고, 만약 여러 컨테이너를 같이 사용해야 한다면 그때 그 컨테이너들을 같은 네트워크상에 위치시키면 되는 게 아닌가 싶다. (만약 그게 아니라면 설정을 바꾸면 되니, 큰 문제는 아니다.) 

 

그리고 이렇게 환경 변수를 따로 설정하는 부분도 있었는데, 나는 이미 AWS secrets manager에서 환경 변수를 설정해주었어서 이걸 사용한다면 언제 사용해야 하는지도 궁금했다. 

 

그런데 ECS에서 EC2 옵션을 선택하니 사용 가능한 인스턴스가 없다고 떴다. 그래서 GPT의 도움을 받아 원인을 찾아보니 EC2 인스턴스에 접속해서 별도로 ECS에 연결하는 설정을 해 주어야 하는 것으로 보였다. 그래서 그 작업을 한 다음, EC2 옵션에서 다시 ECS에 연결한 인스턴스가 뜨는 것을 확인한 다음에 다음 작업으로 넘어갈 수 있을 것 같았다. 

 

EC2 (스팟) 인스턴스를 생성할 때, User Data 섹션에 이 텍스트를 붙여넣어야 하는 것 같다. 환경변수 설정을 해 주는 것이라고 생각했다. 

echo ECS_CLUSTER=onestep-production4 >> /etc/ecs/ecs.config

 

그렇게 스팟 인스턴스를 실행시키고 퍼블릭 DNS 주소를 확인해서 EC2 서버에 접속하려고 하니, 퍼블릭 DNS 주소가 없었고 프라이빗 DNS 주소만 있었다. 원래 이런건지는 모르겠다. 그래서 탄력적 IP(elastic IP) 주소를 하나 생성하고, 이를 생성한 스팟 인스턴스에 할당했다. 다시 확인해 보니 스팟 인스턴스에 퍼블릭 DNS 주소가 잘 나왔다. 

 

그리고 종종 까먹지만 처음에 ssh로 접속을 시도할 때 실패하는데, 보안 그룹에서 TCP 통신을 허용하고 22번 포트를 열어주면 무사히 접속이 된다. 그 이유는 ssh로 통신할 때 TCP 프로토콜을 쓰고 22번 포트를 통하기 때문이다. 


그런데 ECS에서 태스크를 정의하고, 서비스나 태스크를 만들 때마다 항상 클러스터에서 실행 가능한 EC2 인스턴스가 없다는 문구가 계속 떴다. 심지어 관련 유튜브 영상을 참고했는데도, 해당 영상에서는 ECS 클러스터를 만들면서 자동으로 EC2 인스턴스를 생성했는데, 내가 했을 때는 그렇게 되지가 않아서 뭔가 놓친 것이 있다는 생각이 들었다. 

 

그래서 GPT에게 도움을 요청하니, IAM 권한을 생성하라고 말해주어서 AmazonEC2ContainerServiceforEC2Role라는 권한을 생성했다. 정확히 무슨 역할인지는 모르지만, EC2 컨테이너를 다른 AWS 서비스에서도 생성할 수 있도록 하는 역할일 것으로 추측했다. 찾아보니 AWS 내부의 여러 서비스들을 같이 사용할 때, 가령 서비스A에서 서비스B에 접근해야 하는 경우, IAM에서 그 작업을 실행할 수 있도록 역할을 생성해줘야 하는 것 같았다. 

 

 궁금한 점

1. 탄력적 IP 주소의 용도는 무엇일까

2. 왜 처음에 스팟 인스턴스를 생성하면 퍼블릭 DNS 주소는 기본적으로 할당되지 않고 별도로 할당해 주어야 하는 것일까 -> 찾아보니 추가 설정에서는 '퍼블릭 주소 할당'이 있다! 불필요하게 퍼블릭 주소(리소스)를 할당하지 않고, 사용자가 원할 때 할당하도록 한 것 같다. 

3. sudo yum과 sudo apt-get 이라는 명령어를 많이 보는데 이 명령어들의 구체적인 뜻은 무엇일까

4. Amazon EBS는 무엇인가

5. IAM 서비스는 무엇을 위해서, 왜 필요한가 b/c 종종 IAM 계정이나 권한을 생성하라는 말을 많이 들어서 궁금하다. 

 

 

'개발 일기장 > SWM Onestep' 카테고리의 다른 글

20240728 TIL  (2) 2024.07.28
20240727 TIL: ECR ECS 적용기  (0) 2024.07.27
20240725 TIL  (0) 2024.07.25
20240724 TIL  (0) 2024.07.24
20240723 TIL  (2) 2024.07.23

 오늘 배운 것

오늘은 삽질해서 막히던 SZ-85번의 하위 이슈 중 하나를 팀원들의 도움으로 같이 해결했다. 세부 문제 상황은, 투두 리스트들 아래 텍스트 인풋을 받는 Input 컴포넌트를 누르면 텍스트 인풋 창이 활성화되지 않고 키보드가 활성화되었다가 바로 비활성화되는 문제였다. 

팀원들과 웹엑스로 회의를 하면서 문제가 되는 컴포넌트(DailyTodos 안의 컴포넌트)를 KeyboardAvoidingView로 한번 더 감싸 보았더니 된다고 했다. 

 

사실 어떤 원리로 문제가 해결되었는지는 모르겠다. 왜냐하면 이미 바깥 화면에서 KeyboardAvoidingView를 사용하고 있었고, 그래서 굳이 한번 더 감쌀 생각을 안 한 것이었기도 하기 때문이다. 또한 GPT한테 물어봤을 때는 컴포넌트(아마도 Input 컴포넌트)가 계속 텍스트창이 활성화될 때마다 다시 렌더링 되어서 텍스트 창 클릭 -> 활성화 -> 다시 렌더링됨 -> 초기 값인 비활성화...의 루프를 타는 것이라고 생각하였는데 결국 그도 아니었던 것이다. 

 

이 부분은 KeyboardAvoidingView에 대해서도 더 알아봐야 알 수 있을 것 같고, 그래도 모호하다면 멘토님께 이 상황을 한번 공유드려봐도 어떤 실마리가 보일 것 같다. 이제 다음 하위 이슈들을 처리할 수 있을 것 같아 마음이 조금이나마 홀가분해졌다. 

 

 

아직 SZ-85번 이슈의 완료를 위해선 몇 개의 하위 이슈들이 남아있지만, 이걸 완료해야 다른 팀원이 '드래그앤드롭 기능과 현재 컴포넌트를 연결하는 이슈' 및 'AI가 하위투두를 자동으로 생성해주는 이슈'를 처리할 수 있어서, 이걸 얼른 완료하는 게 중요하다. 그래야 나도 다음 급한 이슈인 'ECR, ECS를 통해 자동 배포하기'를 해볼 수 있을 것 같다. 암튼 파이팅이다.

 

'개발 일기장 > SWM Onestep' 카테고리의 다른 글

20240727 TIL: ECR ECS 적용기  (0) 2024.07.27
20240726 TIL  (0) 2024.07.26
20240724 TIL  (0) 2024.07.24
20240723 TIL  (2) 2024.07.23
20240721 TIL  (1) 2024.07.22

 오늘 배운 것

오늘도 SZ-85번 이슈에서 삽질을 했다. 대부분은 상태관리에 관한 문제였다. 특히 프로젝트에서는 상태관리를 위해 zustand와 useContext API를 사용하는데, zustand 라이브러리를 통해서는 useTodoStore()와 useModalStore()라는 2가지의 store를 사용한 것이 복잡성의 원인이 되었다. 

 

정확히는 selectedTodo라는 모달창에서 선택된 투두를 저장하기 위한 상태 변수가 useModalStore()에 선언되어 있었는데, 이것을 useTodoStore()에 선언된 줄 알고 useTodoStore에서 불러와서 제대로 모달창이 나타나지 않는 문제가 있었다. 

 

또한 Jwt Parse error도 있었다. GPT에게 물어보니 토큰이 발급된 시간(iat)가 컴퓨터의 로컬 시간보다 빨라서 발생하는 에러로, 컴퓨터의 로컬 시간을 점검해 볼 것을 말해줬다. 나는 맥을 쓰고 있어서, 맥의 환경설정에 들어가서 서버가 애플의 시간 서버(time.apple.com)에서 제대로 시간을 받아오고 있는지를 확인했다. 그런데 이미 애플의 서버에서 제대로 현지 시각을 잘 받아오고 있어서, 오류의 원인은 알았지만 해결은 할 수 없었다... 현재 오류는 확률적으로 발생하는데, 만약 매번 또는 빈번하게 발생한다면 그때 더 자세히 알아봐야 할 것 같다. 

 

'개발 일기장 > SWM Onestep' 카테고리의 다른 글

20240727 TIL: ECR ECS 적용기  (0) 2024.07.27
20240726 TIL  (0) 2024.07.26
20240725 TIL  (0) 2024.07.25
20240723 TIL  (2) 2024.07.23
20240721 TIL  (1) 2024.07.22

 오늘 배운 것

투두 안에 하위 투두가 있고, 각 투두의 설정 아이콘을 눌러 모달창을 열 수 있고, 그 모달창을 통해 투두를 수정 및 삭제할 수 있는 기능을 개발 중이다. 원래 어제부터 만들었었는데, 시간을 많이 썼음에도 문제가 하나 터지고 겨우 해결하면 또 다음 게 생기고 이런 식이라서 아직도 막히고 있다. 

 

오늘 배운 것은 상태 관리의 중요성이다. 투두 컴포넌트들은 크게 CategoryTodos > DailyTodos > DailyTodo > DailySubTodo의 계층 구조(사실 이 정도면 그냥 재귀 구조로 호출해도 될 것 같다...)로 되어 있는데, 문제는 TodoModal이라는 모달창 컴포넌트의 위치였다. 원래는 이 TodoModal이 DailyTodo와 DailySubTodo 컴포넌트에 있었는데, 그러다 보니 카테고리 값을 바꾸고 나서 투두를 클릭할 때 상태 관리가 제대로 되지 않아서 이전 카테고리의 투두 값을 모달에 띄운다는 문제가 있었다. 

 

요즘은 개발을 할 때 GPT와 티키타카를 많이 하는데, 내가 문제 상황을 인식해서 그걸 풀어서 설명하면 GPT가 해결책을 제시해주고, 그럼 내가 그걸 따라해보거나 찾아보면서 이해하는 식으로 작업하고 있다. 모든 투두 컴포넌트 파일들을 첨부하고 GPT에게 위의 문제를 해결할 방법을 제시해달라고 하니, GPT는 기존의 CategoryTodos > DailyTodos > DailyTodo > TodoModal의 구조 대신 TodoModal을 CategoryTodo의 바로 아래, DailyTodos와 같이 배치했다. 그리고 그 대신 useContext나 zustand를 사용해서 상태 관리를 하는 식으로 코드를 제시해줬다. 

 

그래서 이 방법을 사용해서 기존에 있었던 zustand로 투두 상태를 관리하는 useTodoStore() 대신, useModalStore()를 사용했다. 사실 상태 관리나 컴포넌트 작성에 정답은 없어서 useContext()를 사용할지 zustand를 사용할지 고민이었는데, 단순 변수와 set 함수(useState를 통해 쌍으로 생성되는 변수와 함수)만 있는 게 아니라면 zustand도 괜찮을 것 같다고 판단해서 zustand를 사용했다. 

 

아직 모달창은 시작도 못 했고, CategoryTodos 컴포넌트 안에서도 해결해야 할 과제가 많이 남아있어 막막하지만, 근데 또 시간을 많이 썼는데 안 된 거라서 조금만 더 해보고 안 되면 일단 자고 내일 일찍 일어나서 조금이라도 더 해 봐야 할 것 같다. 늘 항상 막힘없이 개발이 잘 되는 게 아니고 막힐 때가 더 많은데, 오늘은 그런 날이었던 것 같다. 그래도 그런 날의 기록이라도 남기길 잘한 것 같다:)

 

 

'개발 일기장 > SWM Onestep' 카테고리의 다른 글

20240727 TIL: ECR ECS 적용기  (0) 2024.07.27
20240726 TIL  (0) 2024.07.26
20240725 TIL  (0) 2024.07.25
20240724 TIL  (0) 2024.07.24
20240721 TIL  (1) 2024.07.22

오늘 배운 것

github actions, workflow를 이용해서 CI/CD를 구축할 때는 무작정 AWS EC2 서버에 접속해서 도커를 띄우는 것이 아니다. 

대신 AWS의 ECS(Elastic Container Service), ECR(Elastic Container Registry) 서비스를 사용한다. 즉 순서는 다음과 같다. 

1. github actions에서 도커 이미지를 만든 다음에 ECR에 올린다. 

2. ECR의 signal을 사용해서 ECS에게 ECR에 도커 이미지가 업로드되었다고 알림을 보내거나, github actions에서 직접 ECS에게 알림을 보낸다. 

3. 배포 완료

 

그리고 도메인을 처음으로 돈 주고 사 보았고, 그것을 Route53이라는 AWS의 서비스를 통해서 AWS와 연결시켜 보았다. 구체적인 스텝이 정확히는 기억이 안 나서 겨우겨우 약간이나마 복기해 보았다. 

1. 여러 사이트를 통해서 도메인 사기

2. Route53을 이용해서 도메인 연결하기

3. 서브넷 설정하기. 그래야 하나의 서버가 실행되다가 죽어도 다른 서브넷의 서버로 연결할 수 있다고 하셨던 것 같다. 

4. 로드 밸런서 설정하기. 각 지역(aws region)에 따라서 request가 들어오면 그에 맞는 region의 subnet으로 보내주는 역할이라고 이해했다. 

 

또한 우리가 있는 ap-northeast-2 aws region은 서브넷이 크게 4개(2a, 2b, 2c, 2d)이기 때문에, 총 서브넷은 기본으로 생성되어 있는 private 서브넷 4개와, 우리가 직접 생성한 public 서브넷 4개로 총 8개면 된다고 하셨다. 

 

그리고 CloudWatch로 로그도 저장해야 한다고 하셨다. 백엔드의 경우는 CloudWatch, 프론트의 경우는 잘 모르지만 알아보아야겠다. 

 

또한 배포용 DB라면 RDS를 사용하는 것이 제일 깔끔하다고 말씀해 주셨다. 

또한 LexoRank 라이브러리도, 원래는 프론트 단에서만 정렬을 하려고 했는데 그럴 수가 없다고 하셨다. 왜냐하면 프론트 단에서 정렬 관련 정보를 보내면 백엔드에서 이를 아예 validation하지 않고 저장하는 것은 거의 사실상 불가능하기 때문이다. 

 

더 궁금한 것

도커 대신 ECR, ECS를 사용하는 것이 권장되는 이유가 궁금하다. 

github workflow도 내부 소스코드를 보면 github actions를 통해 구현된 것이라고 한다. 이 원리가 궁금하다. 

 

'개발 일기장 > SWM Onestep' 카테고리의 다른 글

20240727 TIL: ECR ECS 적용기  (0) 2024.07.27
20240726 TIL  (0) 2024.07.26
20240725 TIL  (0) 2024.07.25
20240724 TIL  (0) 2024.07.24
20240723 TIL  (2) 2024.07.23

+ Recent posts