DevOps

[AWS] ECS 이용하여 ECR에 수동으로 배포해보기 (+ ECS, ECR 개념 정리)

코딩 기록하는 애기 개발자 2025. 11. 5. 01:52

목차

    개념정리 

    일단 제목에서 알다시피 지금 내가 할 것은 "ECS를 이용하여 ECR에 수동으로 배포해보기" 이다. 

    즉, 내 로컬 (Mac)에서 만든 웹 애플리케이션을, AWS 클라우드 위 컨테이너인 Fargate로 배포해볼 것이다. 

     

    ECS과 ECR의 개념에 대해서 먼저 알아보고, 실습을 해보도록 하자. 

    ECS (Amazon EC2 Container Service)

    ECS는 컨테이너 실행, 배치, 확장, 상태 관리를 자동화하는 오케스트레이션 서비스이다. 쿠버네티스와 유사한 역할을 하지만, AWS에 최적화되어 있고, AWS에서 플랫폼으로 통합할 수 있다는 것이 강점이다. 

     오케스트레이션
    : 여러 개의 컴퓨터 시스템, 애플리케이션 및/또는 서비스를 조율하고 관리하는 것으로, 여러 개의 작업을 함께 연결하여 크기가 큰 워크플로나 프로세스를 실행하는 방식을 취한다. 이러한 프로세스는 여러 개의 자동화된 작업으로 구성될 수 있으며 관련되는 시스템도 여러 개일 수 있다.

     

    주요 구성 요소

    ECS는 Task를 실행할 때, 스케줄러가 클러스터 상태를 기반으로 리소스 사용량배치 전략가용 영역 (AZ) 등을 고려해 자동으로 배치한다. 
    • Task Definition : 어떤 컨테이너를 어떤 환경에서 실행할지 정의하는 설계도와 같은 역할을 한다. 
    • Task : Task Definition을 기반으로 실제로 구동되는 컨테이너 인스턴스이다. 
    • Service : 특정 수의 Task를 항상 유지시키는 ECS의 관리 계층이다. 
    • Cluster : 컨테이너가 실행될 리소스의 묶음을 일컫는다.
    • Container Instance : EC2 Launch Type일 경우, Task가 배포되어 있는 실제 서버이다.
    • Schedular : Task가 어떤 인스턴스에 배치될지를 정하는 ECS 내부 스케줄러이다. 

     

    동작 과정

    ECS Schedular는 클러스터 안의 리소스 (메모리, CPU, 네트워크 등)의 상태를 모니터링 하고, Task Definition에 지정된 리소스 요구사항을 확인한다. 이후, 지정된 배치 전략 (binpack, spread, random)이나 제약 조건을 고려하여 가장 적합한 인스턴스 (EC2)나 Fargate 위에 Task를 자동으로 배치한다. 

     

    배치 전략

    Task Placement Strategy (배치 전략)

    배치 전략은 ECS가 여러 Task를 어떤 인스턴스에 어떻게 분산할지 결정하는 정책이다.

    하나 하나 간단히 살펴보도록 하자.

          • Binpacking
            : Task를 가장 여유가 적은 인스턴스부터 채워서, 리소스 낭비를 최소화하는 전략이다. ECS가 인스턴스들의 CPU나 메모리 여유 공간을 게산해서 Task가 들어갈 수 있는 가장 꽉 찬 인스턴스에 배치한다. 즉, 리소스 밀집도를 높이는 전략이다. 

            만약, EC2 인스턴스가 A,B,C 이렇게 3개가 있을 때, A,B,C에는 각각 200MB, 2GB, 4GB 가 남아있고, CPU의 공간이 각각 50%, 70%, 90% 남아있다.  이때 Binpacking 전략을 선택하면, A 인스턴스처럼 여유가 상대적으로 적은 인스턴스부터 Task를 채워넣는다. 

            그럼 메모리와 CPU 와 Memory 의 사용량 중 어떤 리소스가 더 중요시될까 ?
            배치 전략을 설정할 때에는 `type`과 `field`를 설정할 수 있는데, 이때 `type`은 `binpack`을 의미하고, `field`에 `cpu`또는 `memory`를 작성하여 사용자가 직접 설정하면 된다.
          • Spread
            : Task를 특정 속성(attribute)을 기준으로 균등하게 분산시키는 전략이다. 보통 가용영역 기준 (`"field" : "attribute:ecs.availability-zone"`)인스턴스 타입 (`"field": "attribute:ecs.instance-type"`) 그리고 인스턴스 자체를 기준으로 (`"field": "instanceId"`)을 기준으로 분산한다. 
          • Distinct Instance
            : 같은 Task Definition을 가진 여러 Task가 같은 인스턴스에 배치되지 않도록 하는 제약 조건이다. 이때는 별도의 `field` 변수를 넣지 않아도 된다. 
          • Affinity
            : 특정 속성을 가진 인스턴스 또는 가용영역에만 Task를 배치하도록 제한하는 설정이다. 이는 `expression` 변수에 조건을 적어주면 된다.  

    Launch Type 

    ECS는 컨테이너를 실행하는 두 가지 런타임 환경을 지원한다. 바로, Fargate와 EC2이다. 표를 이용하여 두 서비스를 비교해보자.

    구분 Fargate EC2
    실행 방식 서버리스 (인프라 자동 관리) 직접 EC2 클러스터 구성
    인프라 관리 필요 없음 사용자가 직접 관리
    스케일링 자동 (서버 단위 X) EC2 인스턴스 기반
    과금 컨테이너 리소스 단위(vCPU/Memory) EC2 인스턴스 단위
    사용 사례 간단한 웹앱, API 서버 커스텀 OS, Daemon 필요 환경
    단순한 서비스이고, 서버 관리가 싫다면 Fargate를 사용하고, 세밀한 제어가 필요하면 EC2를 사용하면 된다 ! 

     

     

     

    ECR (Amazon EC2 Container Registry)

    ECR은 컨테이너 이미지를 저장, 관리, 배포할 수 있는 완전관리형 레지스트리 서비스이다. ECS와 자연스럽게 통합되어 있어 개발자는 로컬에서 이미지를 빌드한 뒤 ECR에 Push 하고, ECS에서 Pull하여 실행할 수 있다. 

     

     

     

    ECR은 단순한 이미지 저장소를 넘어 보안, 통합성, 성능, 관리, 기능 면에서도 완성도 높은 컨테이너 레지스트리 서비스를 제공한다.

     

    ECR은 보안 측면에서 IAM 리소스 기반 정책을 활용하여 접근 제어를 세밀하게 관리할 수 있고, 이미지 전송 과정에서 HTTPS 프로토콜을 사용해 암호화된 통신을 보장하며, 저장 시에도 암호화하여 저장된 이미지가 안전하게 보호된다. 

     

    또한, AWS의 다른 서비스들과 밀접하게 통합되어 있는데, 특히 ECS, EKS와 연동되기 때문에 이미지를 빌드하고 배포하는 전체 파이프라인을 AWS 내부에서 일관되게 구성할 수 있다. 이 덕분에 개발자는 별도의 외부 레지스트리 설정 없이 CI/CD 환경을 빠르고 안전하게 구축할 수 있다.

     

    ECR은 Docker CLI나 AWS CLI를 통해 기존 Docker Hub와 동일한 방식으로 이미지를 Push/Push 할 수 있어 편리성 측면에서 이점을 보인다. 

     

     

    실습 과정

    1. 로컬에서 개발 후, 만든 Docker 이미지를 생성한다.
    2. Docker 이미지를 push 하여 ECR에 업로드한다.
    3. ECS Task Definition을 생성하여 Task를 어떻게 실행할지 정의한다.
    4. Fargate로 실행하여 서버리스로 배포한다. 이때, Fargate가 ECR에서 Docker 이미지를 Pull 한다.
    5. Security Group/VPC를 외부에서 접속 가능하도록 구성한다.
    6. Public IP로 웹페이지에 접근한다. 

     

     

    00 AWS 초기 설정

    AWS CLI 설치 확인

    일단 AWS에 연동하여 사용할 것이기 때문에 AWS CLI 설치가 되어있는지 확인해보자. 

    aws --version

     

     

    이렇게 aws-cli/2.xxx가 나오면 성공이다.

     

    AWS 자격 증명 등록

    AWS CLI가 AWS 계정과 통신하려면 Access Key가 필요하다. 

    나는 Mac 환경에서 진행할 것이기 때문에 `~/.aws/credentials` 경로에 자격증명파일이 있을 것이다. 

     

    아래 명령어를 통해 나의 `aws_access_key_id`와 `aws_secret_key`를 출력할 수 있다. 

    자격증명등록을 할 때 필욯하기 때문에 `aws_access_key_id`와 `aws_secret_key`는 복사해두도록 하자.

    cat ~/.aws/credentials

     

     

    이제 자격증명을 등록해보자.

    aws configure

     

     

    이전에 복사해둔 `aws_access_key_id`와 `aws_secret_key`를 입력하면 된다.

     

     

    아래 명령어를 통해 제대로 설정되었는지 확인해보자.

    aws sts get-caller-identity

    나는 이렇게 오류가 뜨는데, 

    이는 Access Key 나 Secret ID가 유효하지 않다는 뜻이다. 

     

     

    IAM 콘솔에서 사용자의 Access Key와 Secret ID를 다시 확인해보도록 하자.

    이전에 생성해두었던 사용자들을 다 삭제했기 때문에 해당 Access Key와 Secret ID가 유효하지 않았던 것이다. 

     

     

    나는 사용자를 생성해주었고, Access Key와 Secret ID를 발급해주었다. 

    이후, 위 과정들을 통해 자격증명등록을 하였다. 

    이렇게 출력되면 성공한 것이다 !! 

    이때, root 사용자보다 IAM 사용자를 이용하는 것이 보안적으로 좋다.

     

     

     

    01 도커 (Docker) 이미지 만들기

    이제 도커 이미지를 만들 것이다. 일단, 도커 데몬의 상태를 확인해보자. 

    만약 도커 설치가 안 되어 있으면, 아래 사이트에서 자신의 OS에 맞는 도커를 설치해주면 된다.

     

    Docker: Accelerated Container Application Development

    Docker is a platform designed to help developers build, share, and run container applications. We handle the tedious setup, so you can focus on the code.

    www.docker.com

     

     

    아래 명령어를 통해 도커 설치가 잘 되었는지 확인한다. 

    docker info

     

     

    아래 명령어를 통해 프로젝트 폴더를 생성하고,

    mkdir ~/ecs-demo
    cd ~/ecs-demo

     

     

    폴더 내에 `index.html`을 생성한다.

    <!DOCTYPE html>
    <html lang="ko">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Amazon ECS Demo</title>
      <style>
        body {
          font-family: 'Segoe UI', 'Noto Sans KR', sans-serif;
          background: linear-gradient(135deg, #0056b3, #00a8e8);
          color: #ffffff;
          margin: 0;
          padding: 0;
          display: flex;
          flex-direction: column;
          align-items: center;
          justify-content: center;
          height: 100vh;
          text-align: center;
        }
        h1 {
          font-size: 2.4em;
          margin-bottom: 12px;
          font-weight: 600;
        }
        h2 {
          font-size: 1.1em;
          font-weight: normal;
          margin-bottom: 25px;
          color: #e8f0ff;
        }
        .info-box {
          background: rgba(255, 255, 255, 0.12);
          padding: 25px 50px;
          border-radius: 14px;
          box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
          backdrop-filter: blur(6px);
        }
        p {
          margin-bottom: 12px;
          line-height: 1.6em;
          color: #f5f7fa;
        }
        button {
          background-color: #ffffff;
          color: #0056b3;
          border: none;
          border-radius: 8px;
          padding: 10px 22px;
          cursor: pointer;
          font-size: 1em;
          font-weight: 600;
          transition: all 0.3s ease;
          margin-top: 15px;
        }
        button:hover {
          background-color: #0056b3;
          color: #ffffff;
        }
        .footer {
          position: absolute;
          bottom: 12px;
          font-size: 0.9em;
          color: #dce4ef;
        }
      </style>
    </head>
    <body>
      <div class="info-box">
        <h1>Amazon ECS 웹 애플리케이션</h1>
        <h2>이 웹 페이지는 Fargate 기반 컨테이너에서 실행 중입니다.</h2>
        <p>AWS의 <strong>ECR</strong>에서 이미지를 가져와 <strong>ECS</strong>로 배포했습니다.</p>
        <p>이 애플리케이션은 Docker + Nginx + ECS로 구성되어 있습니다.</p>
        <button onclick="alert('ECS 컨테이너에서 실행 중입니다!')">확인하기</button>
      </div>
    
      <div class="footer">
        © 2025 ECS Demo | Powered by AWS ECR & ECS
      </div>
    </body>
    </html>

    위 `index.html` 파일은 아래 명령어로 생성할 수 있다.

    cat > index.html <<'EOF'

     

     

    이제 Dockerfile을 생성하자.

    이 파일 또한 아래 명령어로 생성할 수 있다. 

    cat > Dockerfile <<'EOF'
    FROM nginx:1.27-alpine
    COPY index.html /usr/share/nginx/html/index.html
    EXPOSE 80
    CMD ["nginx", "-g", "daemon off;"]
    EOF

     

    여기까지 에러없이 성공했으면, `~/ecs-demo` 폴더에 `index.html` 파일과 `Dockerfile`이 존재하는 것을 확인할 수 있다. 

     

     

     

    성공한 것을 확인했으면,

    아래 명령어를 통해 도커이미지를 빌드한다. 

    docker buildx build --platform linux/amd64 -t ecs-demo-img .

     

    이렇게 저장되어 있는 내 도커이미지에 `ecs-demo-img`가 빌드되어 있는 것을 확인할 수 있다. 

     

     

     

    02 Public ECR 리포지토리 생성하기 

    aws 콘솔에 로그인 후, `Elastic Container Registry` > `Public registry` > `Repositories` > `Create repository` 선택

    그리고 다음과 같이 설정한다.

    로고 이미지와 설명은 생략해도 되기 때문에 생략해주도록 한다.

     

     

    이렇게 생성되었으면 URI를 복사해둔다. 

     

     

     

    03 Public ECR에 로그인 및 이미지 푸시하기

    `ecs-demo-repo` > `View push commands` 선택

     

    아래와 같은 화면이 뜨는데, 해당 명령어를 1번부터 4번까지 순서대로 실행한다.

    1번 명령어에 리전이 us-east-1로 되어있는 것을 확인할 수 있는데, 이는 Public ECR의 리전이 us-east-1로 고정되어있으므로, 서울 리전에서 작업하더라도 화면에 나온 명령어 그대로 실행하도록 한다. 즉, 리전을 ap-northeast-2로 바꾸지 않도록 한다.

     

    IAM 권한 확인 필요 (Trouble Shooting)

    첫번째 명령어를 실행하면, 다음과 같은 에러가 발생할 것이다.  이는 현재 로그인한 IAM 사용자에게 Public ECR에 로그인할 권한이 없다는 것을 의미한다. 

     

    Public ECR은 누구나 접근 가능한 저장소지만, 이미지를 업로드 하려면 IAM 권한이 필요하다.

    콘솔에서 IAM 권한을 추가해주도록 하자. 

    `IAM` > `Users` > `사용자 이름` > `Permissions` > `Add permissions` 선택 

     

    `Attach policies directly` > `AmazonElasticContainerRegistryPublicFullAccess` 선택하여 권한을 추가해주도록 한다.

     

    이렇게 권한을 추가해주면 로그인에 성공하는 것을 확인할 수 있다.

     

    이후, 나머지 명령어들도 실행시켜준다.

    다음과 같이 `ecs-demo-repo` > `Images` 에 latest 가 보이면 성공한 것이다. 

     

    `latest` > 'URI' 복사하기

     

     

    04 ECS Cluster 생성하기

    `Elastic Container Service` > `Clusters` > `Create cluster` 선택

     

    나는 이름만 `ecs-demo-cluster`로 설정해주고, 나머지는 초기값을 유지한 채 생성해주었다.

    ECS 초기 설정 문제 (Trouble Shooting)

    생성하게 되면, 다음과 같은 오류가 발생할 수 있는데, 이 에러는 정말 자주 나오는 ECS 초기 설정 문제이다.

     

    왜 이러한 문제가 발생할까 ?
    ECS 콘솔에서 처음 클러스터를 만들게 되면, 백그라운드에서 CloudFormation 스택이 자동으로 생성된다.
    해당 스택은 ECS 클러스터를 생성하고, 필요한 IAM 역할을 자동으로 생성하며, VPC 및 Subnet을 설정하는데,
    이 과정 중 IAM 권한이 부족하거나 ECS 서비스 연결 역할이 없으면, 스택 생성이 중간에 실패한 상태로 멈추게 된다.

    즉, ECS 클러스터를 새로 만들려고 해도, 기존에 실패했던 스택이 잠깐 남아 있어 ECS가 다시 역할을 만들지 못하는 상태가 되는 것이다. 위 과정 중 IAM 권한이 없어 실패했던 스택이 남아있었던 것이다.

     

    `CloudFormation` > `CREATE_FAILED 상태인 스택` 선택 > 'Delete'

     

    삭제가 완료되었으면, cluster를 재생성해보도록 하자.

     

    그럼 이렇게 완료한 것을 확인할 수 있다. 

     

     

    05 Task Definition 생성하기

    `Elastic Cotainer Service` > `Task definitions` > `Create new task definition`선택

     

    다음과 같이 정보를 기입해준다. 

    이때 CPU와 Memory는 자신이 원하는대로 해주면 되는데, 나는 실습만 해볼 것이기 때문에 가장 낮은 사양을 선택했다. 

    또, Task execution role 은 `ecsTaskExecutionRole` 를 선택해줘야 하는데, 없으면 `Create new role`을 선택하면 자동으로 생성된다.

     

    Container 설정도 해주어ㅑ 하는데, 

    Image URI는 이전에 복사해뒀던 Public ECR ecs-repo-demo URI를 붙여넣어주면 된다. 

    나머지 값들은 기본값을 유지하고 생성해준다. 

     

    생성하고 나면 이렇게 Task execution role이 `ecsTaskExecutionRole` 로 변경됨을 확인할 수 있다. 

     

    06 Task 실행하기

    Task 를 실행하기 전, VPC, Public Subnets 2개, Security Group (Inbound 규칙에 HTTP 80 PORT 설정) 이 있는지 확인하고 진행하도록 하자. 

     

    `Elastic Container Service` > `Clusters` > `ecs-demo-cluster` > `Tasks` > `Run new task`선택

     

    아래와 같이 Task details, Environment, Networking 부분에 기존에 만들어뒀던 리소스들을 선택한다.

    빨간색 상자 부분만 선택해주고, 나머지는 기본값을 유지한다. 

     

     

    `Create`를 누르면 Task가 생성되었을 것이다.

     

    07 브라우저에서 접속 확인

    `Tasks` 탭에서 위에서 생성한 Task를 선택한다.

    `Configuration` > Configuuration 부분의 Public IP 주소 복사

     

    브라우저에 다음 주소를 입력한다. 

    http://<Public-IP>

     

    나 같은 경우, `http://43.201.152.55/`를 입력하면 된다. 

    그럼 이렇게 뜨는 것을 확인할 수 있다 !! 

     

    사실 나는 아키텍처 불일치 에러 때문에 도커 이미지를 다시 빌드했다.
    컨테이너 이미지는 ARM 환경 (M2 맥) 환경에서 빌드된 걸 ECS (Fargate, x86_84 기반)에서 실행하려고 했기 때문에 에러가 발생했던 것이다.

    그래서 도커 이미지를 재빌드 후, 다시 위 과정을 실행했다 .. . .  
    성공한 게 어디냐 !!

     

     

    (중요) 환경 정리  !!

    이러한 실습을 했다면 환경 정리는 필수이다 ........

    돈이 아깝기 때문 !! 아래 절차에 따라 사용한 리소스들을 정리해주자. 돈 아낍시다 !!

    1. ECS > Clusters > ecs-demo-cluster (내가 생성한 cluster 이름) > Tasks > Running중인 Task선택 > Stop
      이때 실행중인 Task가 없으면 Stop 버튼이 비활성화 되어 있을 것이다. 그럼 건너뛰기 !
    2. ECS > Task definitions > ecs-demo-task (내가 생성한 task 이름) >  전체 선택 > Action > Deregister
    3. ECRRepositories > ecs-demo-repo (내가 생성한 repo 이름) > Delete
    4. ECS →Clusters → ecs-demo-task (내가 생성한 task 이름) → Delete cluster
    5. CloudWatchLog groups → 모두 선택 → ActionDelete log group(s)