[CI/CD] DockerHub에서 AWS ECR 전환하기

LDB·2026년 2월 11일

❓작성계기

예전에 DockerHub로 배포를 하려고 했는데 DockerHub에 접속이 안되는 이슈가 있었습니다. 그래서 확인해보니 DockerHub자체가 마비되서 일어난 이슈였고 그래서 현재 서비스가 AWS EC2위에서 동작하는점이 반영되어 이미지 업로드 저장소를 소프트웨어 이미지를 DockerHub에서 AWS ECR로 변경하기로 결정했고 그 역할을 제가 맏기로 했습니다. 해당 작업을 마치고 나중에 해당 에피소드를 써야지 했다가 여러가지 이슈로 인해 미루어졌다가 이제와서야 쓰게 됩니다.


🗒️ 기존 CI/CD 구조

이미지로 그리자면 대충 이러한 형태가 됩니다. 작업자가 GitHub에 Push 및 Merge를 하면 Jenkins에서는 해당 GitHub계정에 접근해 해당하는 Repository를 이미지로 만들고 그거를 DockerHub에 올린 후 지정된 서버로 업로드 하는 그런 형식입니다.


🗒️ 개선한 CI/CD 구조

구조는 단순히 DockerHub에 올리는 방식에서 AWS ECR에 업로드하는 방식으로 변경한 것 뿐이었습니다만 의외로 괜찮은 결과를 얻을 수 있었습니다.

  • 얻은 것들
    • 개발서버 배포시간 51% 감소
    • 스크립트 과정 간결화
    • 기존 DockerHub 인증서 노출 보안 이슈 해결

🔗 젠킨스 인증서 등록

해당 작업을 하기전에 인증서를 등록해야하는데 그거는 하단의 주소를 참고하면 됩니다.
https://fwani.tistory.com/29


🧑‍💻 기존 젠킨스 스크립트

pipeline {
    agent any
    
    environment {
        APP_NAME = '[프로젝트 명]' // 저장소 명
        DOCKER_CREDENTIALS = credentials('[Jenkins에 등록된 아이디와 비밀번호가 DockerHub 로그인 정보와 같은 인증키 이름]')
        DOCKER_IMAGE = '[이미지 명]' // Docker Image 이름
        SPRING_PROFILE = '[운영설정]'  // 운영 환경 프로필
        REMOTE_HOST = '[배포서버 IP]'  // 운영 서버 IP 또는 호스트명
        REMOTE_USER = '[접속 계정 명]' // SSH 접속 계정
        REMOTE_CREDENTIALS = credentials('[Jenkins에서 발급한 인증키 이름]')  // 비밀번호 자격증명 ID
    }
    
    stages {
        stage('Git Clone') {
            steps {
                git branch: '[브랜치 이름]',  // 운영 환경은 main 브랜치 사용
                    credentialsId: '[Jenkins에서 발급한 토큰 Key]',
                    url: "[깃허브 주소]/${APP_NAME}.git"
            }
        }

        // 가져온 chat-Repository에서 gradle에 등록된 application version 가져오기
        stage('get application version') {
            steps {
                script {
                    // Gradle 실행권한 부여
                    sh 'chmod +x ./gradlew'

                    // Gradle의 callVersion 태스크를 실행하여 버전 값 가져오기
                    def appVersion = sh(returnStdout: true, script: './gradlew -q callVersion').trim()

                    // 가져온 버전을 환경 변수로 설정합니다.
                    env.DOCKER_TAG = appVersion
                }
            }
        }

        stage('Build and Push Docker Image') {
            steps {
                // Docker Hub 로그인
                sh '''
                    echo $DOCKER_CREDENTIALS_PSW | docker login -u $DOCKER_CREDENTIALS_USR --password-stdin
                '''
                
                // Jib을 사용하여 도커 이미지 빌드 및 푸시 (태그와 프로필 전달)
                sh """
                    ./gradlew jib \
                        -Djib.to.tags=${DOCKER_TAG} \
                        -Djib.container.environment.SPRING_PROFILES_ACTIVE=${SPRING_PROFILE}
                """
            }
        }

        stage('Deploy to Production') {
            steps {
                withCredentials([
                    sshUserPrivateKey(credentialsId: '[Jenkins에서 발급한 인증키]', keyFileVariable: 'KEY_FILE'),
                    usernamePassword(credentialsId: '[Jenkins에 등록된 아이디와 비밀번호가 DockerHub 로그인 정보와 같은 인증키 이름]', passwordVariable: 'DOCKER_PASSWORD', usernameVariable: 'DOCKER_USERNAME')
                ]) {
                    sh '''
                        # 배포 스크립트 생성
                        cat > deploy.sh << EOF
#!/bin/bash
# Docker Hub 로그인
echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin
# 기존 컨테이너가 있으면 중지 및 삭제
if docker ps -a | grep -q ${APP_NAME}; then
    docker stop ${APP_NAME}
    docker rm ${APP_NAME}
fi
# 이미지 풀링
docker pull ${DOCKER_IMAGE}:${DOCKER_TAG}

# 컨테이너 실행
[컨테이너 실행 설정 및 명령어]

# Docker Hub 로그아웃
docker logout
EOF
                        
                        # 스크립트에 실행 권한 부여
                        chmod +x deploy.sh
                        
                        # 원격 서버로 스크립트 전송
                        scp -i $KEY_FILE -o StrictHostKeyChecking=no deploy.sh ${REMOTE_USER}@${REMOTE_HOST}:~/
                        
                        # 원격 서버에서 스크립트 실행
                        ssh -i $KEY_FILE -o StrictHostKeyChecking=no ${REMOTE_USER}@${REMOTE_HOST} "chmod +x ~/deploy.sh && ~/deploy.sh"
                    '''
                }
            }
        }
        
        // 클린업을 위한 별도 스테이지 추가
        stage('Cleanup') {
            steps {
                sh 'docker logout || true'  // Docker Hub 로그아웃
                cleanWs()  // 워크스페이스 정리
            }
        }
    }
    
    post {
        success {
            echo 'Production deployment succeeded!'
        }
        failure {
            echo 'Production deployment failed!'
        }
    }
}

기존에는 DockerHub로그인 및 로그아웃 과정이 있어서 시간이 약간 오래걸렸던거 같습니다. 이 글을 쓰면서 새삼 보니까 어째서 DockerHub로그아웃을 2번 요청하는지는 모르겠습니다.


🧑‍💻 개선한 젠킨스 스크립트

pipeline {
    agent any

    environment {
        AWS_REGION = '[현재 서비스를 실행하는 리전]'
        ECR_REGISTRY = "[AWS 계정 12자리 일련번호].[ECR Repository 주소]"
        APP_NAME = ''

        CONTAINER_NAME = '[등록될 컨테이너 이름]'
        DOCKER_IMAGE = "${ECR_REGISTRY}/[ECR_Repository 이름]" // ECR에 등록된 Repository
        SPRING_PROFILE = '[운영설정]'  // 운영 환경 프로필
        REMOTE_HOST = '[배포서버 IP]'  // 운영 서버 IP 또는 호스트명
        REMOTE_USER = '[접속 계정 명]' // SSH 접속 계정
        SSH_CREDENTIALS_ID = '[Jenkins에서 발급한 인증키 이름]' // 비밀번호 자격증명 ID
    }

    stages {
        stage('Git Clone') {
            steps {
                git branch: 'develop',
                    credentialsId: 'github-lch-token1',
                    url: "https://github.com/store-labs/${APP_NAME}.git"
            }
        }

        stage('Get Version') {
            steps {
                script {
                    sh 'chmod +x ./gradlew'
                    def tag = sh(script: './gradlew -q callVersion', returnStdout: true).trim()
                    env.DOCKER_TAG = tag
                }
            }
        }

        stage('Build & Push to ECR') {
            steps {
                // 문자열 보간법을 사용하여 docker 명령어가 젠킨스 변수로 오해받지 않게 함
                sh "aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${ECR_REGISTRY}"
                sh "./gradlew jib -Djib.to.image=${DOCKER_IMAGE} -Djib.to.tags=${env.DOCKER_TAG} -Djib.container.environment.SPRING_PROFILES_ACTIVE=${SPRING_PROFILE}"
            }
        }

        stage('Deploy to Production') {
                    steps {
                        withCredentials([
                            sshUserPrivateKey(credentialsId: "${SSH_CREDENTIALS_ID}", keyFileVariable: 'KEY_FILE')
                        ]) {
                            sh """
                            cat > deploy.sh << EOF
#!/bin/bash
# 1. ECR 로그인
aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${ECR_REGISTRY}

# 2. 기존 컨테이너가 있다면 무조건 삭제
if docker ps -a | grep -q "${APP_NAME}"; then
    docker stop ${APP_NAME} || true
    docker rm ${APP_NAME} || true
fi
# 3. 이미지 작업
docker pull ${DOCKER_IMAGE}:${env.DOCKER_TAG}
# 4. 컨테이너 실행
[컨테이너 실행 명령어 및 설정]
EOF
                        chmod +x deploy.sh
                        scp -i \$KEY_FILE -o StrictHostKeyChecking=no deploy.sh ${REMOTE_USER}@${REMOTE_HOST}:~/
                        ssh -i \$KEY_FILE -o StrictHostKeyChecking=no ${REMOTE_USER}@${REMOTE_HOST} "bash ~/deploy.sh"
                    """
                }
            }
        }

        // 클린업을 위한 별도 스테이지 추가
        stage('Cleanup') {
            steps {
                cleanWs()  // 워크스페이스 정리
            }
        }
    }

    post {
        success {
            echo 'Production deployment succeeded!'
        }
        failure {
            echo 'Production deployment failed!'
        }
    }
}

전체적으로 스크립트가 간결해졌고 추가적인 로그인 및 로그아웃 과정이 생략되었습니다.


🖼️ 필요한 추가작업

단순히 스크립트만 바꾸면 안되고 AWS에서 추가작업이 필요합니다.

  • 배포하는 서버에 AWS CLI설치
  • ECR Repository 등록
  • ECR이 접근가능하도록 IAM Role 생성
  • EC2에 IAM 역할 연결

(AWS CLI 설치방법은 구글링으로 찾으면 방법은 쉽게 알 수 있습니다.)

ECR Repository 등록

  1. 리포지토리 생성을 클릭합니다.

  2. ECR Repository이름을 입력하고 생성합니다.

(저의 경우에는 임시로 tester라고 이름을 지었습니다.)


ECR이 접근가능하도록 IAM Role 생성

  1. IAM 역할을 추가하는 방법은 일단 IAM > 역할 > 역할 생성 이렇게 진행합니다.

  2. 역할 > 권한 추가 에서는 2가지만 필요합니다.

    • AmazonEC2ContainerRegistryReadOnly (ECR Pull)
    • AmazonEC2ContainerRegistryPowerUser (ECR Push/Pull)

(하나의 역할에 2개의 권한을 줘서 배포서버, 젠킨스 서버에 동시에 지정해도 되고 배포 서버는 AmazonEC2ContainerRegistryReadOnly 이것 젠킨스 서버는 AmazonEC2ContainerRegistryPowerUser를 가진 역할을 주면 됩니다.)

  1. 역할 이름을 지정하고 역할 생성 합니다.

(이렇게 ECR_Test 이름의 역할이 만들어집니다.)

EC2에 IAM 역할 연결

EC2 > 인스턴스 > [인스턴스 아이디] > IAM 역할 수정

이렇게 넘어가면 방금 만든 ECR_Test라는 역할을 확인할 수 있고 지정한 후 IAM 역할 업데이트를 클릭하여 등록하면 됩니다.


📋 참고 사이트

https://fwani.tistory.com/29

profile
가끔은 정신줄 놓고 멍 때리는 것도 필요하다.

0개의 댓글