CodePipeline - 배포 오류 해결 (S3 거치지 않고 ECR에서 이미지 가져오기)
지난 게시글에서 GitHub main 브랜치에 push가 되면 CodePipeline이 trigger 되고 CI/CD가 적용되는 모습을 봤다. 중간에 Build 단계에서 오류가 났던걸 해결했다. 그리고 이제 Deploy 단계에서 발생하는 에러들을 해결해 보자.
1. 배포 Deploy 오류 발생 (S3 관련 오류)
1-1. Build 단계가 잘 넘어가더니 이번엔 Deploy 단계에서 에러가 발생했다.
1-2. 아래와 같은 오류가 발생했다.
Unable to access the artifact with Amazon S3 object key 'recipia-member-git-n/BuildArtif/IejGhqN' located in the Amazon S3 artifact bucket 'codepipeline-ap-northeast-2-555139989952'. The artifact object key is not found.
1-3. 에러 원인
- 이 오류는 AWS CodePipeline의 Deploy 단계에서 발생하며, 특정 Amazon S3 객체 키에 대한 액세스를 실패했음을 나타낸다.
- AWS CodePipeline은 빌드 결과물을 S3 버킷에 저장하고, 이후 단계에서 이 버킷에서 아티팩트를 검색한다.
- 그러나 지정된 객체 키('recipia-member-git-n/BuildArtif/IejGhqN')에 해당하는 아티팩트를 찾을 수 없기 때문에 이 오류가 발생했다.
1-4. 에러 분석
- AWS CodePipeline의 Deploy 단계에서 발생한 오류는 S3 버킷에서 아티팩트를 검색하려고 하는 과정에서 발생했다. 일반적인 워크플로우에서는 빌드 단계의 출력물을 S3 버킷에 저장하고, 그 저장된 아티팩트를 배포 단계에서 사용한다.
- 그런데 난 여기서 의문이 들었다. 애당초에 나는 S3에 빌드 결과물을 저장하지 않고 소스 스테이지에서 GitHub에서 소스 코드를 가져와 CodeBuild에서 자체적으로 도커 이미지를 만들고, 그것을 ECR에 푸시하는 형태로 워크플로우를 설정했는데 왜 S3에 저장하지?라는 생각이 들었다.
- 찾아보니까 내가 원하는 워크플로우로 진행하려면 아예 다른 방법으로 접근했었어야 했다.
- 배포 단계의 설정을 변경하여 CodePipeline이 S3 아티팩트 대신 ECR 리포지토리에서 이미지를 가져오도록 할 수 있는 방법으로 전면 수정했다.
1-5. CodeBuild에서 S3 거치지 않고 ECR에서 이미지 가져오는 방법
- 이미지 정의 파일(image-definitions.json)을 생성하여 ECS 서비스의 컨테이너에 대한 이미지 및 태그 정보를 지정한다.
- 이 파일을 GitHub 프로젝트 레포지토리의 루트 디렉토리에 저장하고 변경 사항을 커밋하여 푸시한다.
- AWS CodePipeline의 배포 단계를 수정하여 이미지 정의 파일의 위치를 지정한다.
- 이 설정을 사용하면 CodePipeline이 S3를 거치지 않고 직접 ECR에서 이미지를 가져와 ECS에 배포한다.
- 이 방법으로 중간 단계(S3)를 줄이고 배포 프로세스를 단순화할 수 있다.
2. 배포 단계에 '이미지 정의 파일' 추가하기 - S3 오류 해결
2-1. 진행하기 전에 SpringBoot 프로젝트의 root 경로에 이미지 정의 파일을 json 형태로 만들어준다.
[
{
"name": "<ECS 서비스의 task 정의 내에서 설정한 컨테이너의 이름>",
"imageUri": "<AWS 계정 ID>.dkr.ecr.<리전>.amazonaws.com/<리포지토리 이름>:<태그>"
}
]
2-1-1. container의 이름을 알아보기 위해 ECS의 클러스터 개요 하단에서 “태스크” 이름을 클릭해서 들어간다.
2-1-2. 들어와서 제일 하단을 보면 컨테이너 목록이 있고 여기에 있는 컨테이너 이름을 복사해서 "name"의 key값으로 넣어주면 된다.
2-1-3. SpringBoot 프로젝트에 이미지 정의 json 파일 생성하기
- 아래와 같이 root경로에 json파일을 만들어 줬다. 이후 Github repository에 push 한다.
- AWS CodePipeline은 자동으로 이 파일을 찾지 않는다. 따라서 이미지 정의 파일의 정확한 위치를 지정해야 한다.
- 파일을 레포지토리의 루트 디렉토리에 두는 경우, 단순히 파일 이름만 작성해 줘도 된다. (예: image-definitions.json).
- 그러나 파일이 레포지토리의 서브 디렉토리에 있는 경우, 파일의 상대 경로를 포함해야 한다 (예: path/to/image-definitions.json).
- 루트 디렉토리에 파일을 두면 참조하기가 더 간단해질 수 있으며, AWS CodePipeline의 배포 단계에서 파일의 위치를 쉽게 지정할 수 있다.
2-2. 이제 수정하고자 하는 Pipeline으로 들어온 다음 상단에 있는 [편집] 버튼을 누른다.
2-3. 그럼 아래와 같은 페이지로 이동할 것이다. 여기서 단계를 찾아서 수정하면 된다.
2-4. 맨 하단으로 스크롤을 내려서 "편집: Deploy"를 찾는다. 그리고 우측의 [스테이지 편집]을 클릭한다.
2-5. 그러면 편집창이 아래와 같이 나올 것이다.
2-6. 여기서 하단의 Amazon ECS라고 쓰여있는 곳 오른쪽 아래에 있는 종이에 적는 것 같이 생긴 버튼을 누른다.
2-7. 그럼 아래의 작업 편집창이 나온다. (사진은 미리 내용들을 작성해 놓은 상태다.)
- 작업 공급자는 'Amazon ECS'를 선택한다.
- 입력 아티팩트는 'SourceArtifact'를 선택한다.
- 클러스터 이름은 프로젝트를 배포할 클러스터를 선택하고 서비스도 해당 클러스터에 속해있는 서비스 이름을 선택한다.
- 배포 제한 시간과 변수 네임스페이스는 선택 사항으로 작성해도 되고 안 해도 된다.
2-7-1. 그리고 여기서 "이미지 정의 파일" 항목이 비어있었을 텐데 내용을 적어줬다.
- 중요한 건 내가 SpringBoot 프로젝트에 생성한 JSON 이름과 동일한 파일명을 작성해줘야 한다.
2-7-2. 다 수정하고 상단으로 가서 [저장] 버튼을 눌러줘서 수정을 완료했다.
2-8. 다시 빌드를 시작했다. Deploy단계에서 이전과는 다르게 시간이 오래 걸렸다. (제발 성공해라…)
2-9. 계속 진행 중이 떠서 답답해서 ECS 클러스터에 들어가서 확인해 봤다.
- 아래와 같이 배포에서 실패한 작업이 3번이었다.
2-10. 이벤트의 메시지를 확인해 봤더니 다음과 같았다.
- 위 오류 메시지는 Amazon ECS가 recipia-member-service 서비스의 요구 사항을 충족하는 컨테이너 인스턴스를 찾지 못했음을 나타낸다. 특히, 가장 근접한 매치인 컨테이너 인스턴스 15ce592c569442ec8c9437a689ed8191는 필요한 메모리가 부족하여 태스크를 배치할 수 없다.
- 즉, 메모리가 부족하다… 프리티어인 t2.micro를 사용 중인데 이것 때문인 것 같다.
3. ECS 클러스터(EC2) 메모리 사양 올리기 - 배포 오류 해결 (1)
3-1. 메모리 부족을 해결하기 위해서 ECS 클러스터를 아예 새롭게 만들었다.
- 이번에는 t2.micro가 아닌 t2.small로 메모리만 올렸다.
3-2. 이후 다시 Codepipe를 실행했다.
- 우측 상단에 있는 [변경 사항 릴리스]를 클릭한다.
3-3. [릴리스] 버튼을 클릭하면 다시 Codepipe가 실행된다.
3-4. 근데 또 계속 Deploy에서 무한로딩이 되었다.
3-5. ECS 클러스터로 들어가서 배포를 확인했는데 실패한 작업이 5였다.
3-6. 서비스 진행 메시지 확인하기
- [ECS클러스터 > 실행 중인 서비스]로 들어가서 상단의 '이벤트' 탭을 눌러서 메시지를 확인했다.
- 이번엔 조금 다른 이벤트 메시지가 있었다.
service recipia-member-service was unable to place a task because no container instance met all of its requirements. The closest matching container-instance f2af4a66bcce497d9c29d441598d2831 is already using a port required by your task. For more information, see the Troubleshooting section of the Amazon ECS Developer Guide.
- 지금 이 상황에서는 기존에 실행 중인 ECS 태스크와 새로 배포하려는 태스크 사이에 포트 충돌이 발생하고 있는 것으로 보인다.
- 이 에러 메시지는 기존에 실행 중인 컨테이너 인스턴스가 이미 8081 포트를 사용하고 있어서, 새로운 작업을 같은 포트로 배포할 수 없다는 것을 의미한다.
- 즉, 새로 배포하려는 서비스 또는 작업이 8081 포트를 사용하려고 했지만, 이미 ECS 클러스터 내의 상용으로 떠있는 컨테이너 인스턴스에서 해당 포트가 이미 사용 중이기 때문에 새 작업을 배치할 수 없다는 에러다.
- AWS ECS에서는 기본적으로 동일한 호스트 인스턴스에서 동일한 포트를 사용할 수 없으므로, 이 문제를 해결하기 위해 동적 포트 매핑을 사용해서 해결해 보도록 하겠다.
4. 상용 중인 태스크와 새로 배포되는 태스트의 포트 충돌 문제 해결 - 배포 오류 해결 (2)
4-1. 동적 포트 맵핑
- ECS 태스크 정의에서 동적 포트 매핑을 활성화하여 ECS가 사용 가능한 호스트 포트를 자동으로 선택하도록 할 수 있다.
4-2. ECS의 서비스를 삭제하고 다시 생성해 봤다.
- 서비스를 등록하기 전에 태스크 정의를 다시 만들었다. (포트 설정을 다시 하기 위해)
- 포트 매핑을 호스트:0, 컨테이너:8081로 해줬다.
- (호스트 포트)
- AWS ECS에서 호스트 포트를 0으로 설정하면, ECS는 동적 포트 매핑 기능을 활성화한다.
- 이것은 ECS가 작업을 실행할 때 사용 가능한 호스트 포트를 자동으로 찾아 할당한다는 것을 의미한다.
- 이렇게 하면 하나의 호스트에서 여러 개의 동일한 작업(또는 서비스) 인스턴스를 실행할 수 있게 된다.
- 왜냐하면 각 인스턴스가 동적으로 할당된 고유의 호스트 포트를 사용할 것이기 때문이다.
- (컨테이너 포트)
- 컨테이너 포트를 8081로 설정한 경우, 애플리케이션 내부에서는 8081 포트로 트래픽을 수신하게 된다.
- 다시 말해서, 컨테이너 내부에서 실행되는 애플리케이션은 8081 포트에서 들어오는 요청을 처리한다.
- 호스트 포트를 0으로 설정하고 컨테이너 포트를 8081로 설정하면 AWS ECS는 동적 포트 매핑을 사용하여 사용 가능한 호스트 포트를 자동으로 선택하고 할당한다.
- 이 설정을 사용하면 이전에 발생했던 포트 충돌 문제를 해결할 수 있다.
4-3. 이제 ECS 서비스를 만들 때 방금 만든 태스크 정의를 "패밀리"에서 선택하고 다시 서비스를 만들고 Deploy를 확인했다.
4-4. 결국에는 성공했다. (결론은 호스트 포트를 0번으로 해줘서 동적 포트로 설정해줘야 하는 것이다.)
4-5. CodePipeline 대시보드에는 아래와 같이 표시되었다. (성공!!)
5. ECS 배포 검증하기
5-1. 내가 ECS 생성할 때 로드 밸런서를 설정해 줬기 때문에 배포를 확인하기 위해선 로드 밸런서의 DNS 주소로 접속을 해야 한다.
- ECS > 로드 밸런서로 들어가서 DNS 주소를 복사한다.
5-2. Task 정의할 때 호스트 포트에 “0”을 넣어줬기 때문에 이제 로드밸런스로 연결할 때 포트를 입력하지 않아야 “8081” 포트로 연결된다
5-2-1. 스프링에 만들어둔 api에 요청을 한다.
5-2-2. 만약에 DNS 주소 뒤에 8081 포트를 붙이면 연결이 안 된다. (원래 이렇다. 리스너로 80 포트만 열어줬기 때문이다.)
5-2-3. 동적 포트와 ALB의 동작 원리에 대한 자세한 설명은 다음 포스팅에서 설명하겠다.
6. 마무리
긴 여정이었다.
AWS에 대한 지식이 EC2를 약간 사용해 본 정도였던 나는, 이번 프로젝트를 통해 그 범위를 넓혀나갔다.
ECR, ECS, CodePipeline—이름만 들어봤던 이 기술들을 직접 생성하고 실행해 보면서 마침내 CI/CD 파이프라인을 구축했다.
나는 이제 클라우드 네이티브의 세계에 첫발을 내디뎠다. 그리고 여기서 그치지 않고 더 깊이 파고들 예정이다.
프로젝트의 원 사이클을 AWS 서비스를 활용해 완성하면서 느낀 그 뿌듯함은, 앞으로 더 큰 도전을 위한 발판이 될 것이다.
[AWS] CodePipeline을 이용한 CI/CD (3) - 빌드 오류 해결 (IAM 역할에 권한 정책 연결)
'AWS' 카테고리의 다른 글
[AWS] ECS와 RDS 연동하기 (2) - SpringBoot와 PostgreSQL 연동하기 (0) | 2023.11.03 |
---|---|
[AWS] ECS와 RDS 연동하기 (1) - PostgreSQL 생성하기 (3) | 2023.11.03 |
[AWS] CodePipeline을 이용한 CI/CD (3) - 빌드 오류 해결 (IAM 역할에 권한 정책 연결) (0) | 2023.11.01 |
[AWS] CodePipeline을 이용한 CI/CD (2) - 빌드 오류 해결 (S3 접근 권한) (2) | 2023.10.31 |
[AWS] CodePipeline을 이용한 CI/CD (1) - 파이프 라인 생성 (0) | 2023.10.31 |