Jenkins, Docker를 활용한 CI/CD 환경 구축

minyeob·2024년 2월 20일
0

aws

목록 보기
4/18
post-thumbnail

이전에는 GitHub Actions와 Elastic Beanstalk을 이용한 CI/CD에 대해 구축해보았지만 이번 프로젝트에는 다른 방법으로 도전해보았습니다. 저는 Jenkins와 Docker를 활용해서 CI/CD를 구축하는 방법에 대해 학습했는데, 그 과정과 결과를 기록하고자 합니다.


프로젝트 구성도


1. 젠킨스 서버 인스턴스 생성

프리티어인 t2.micro EC2를 생성

default 용량인 8기가는 부족했던 경험이 있으므로 프리티어 최대 스토리지인 30기가로 설정!

EC2 에 적용할 보안그룹을 만들어 인바운드 규칙에 8080 포트 및 22(ssh) 포트를 열어주어야 한다.


2.도커 설치

도커를 설치하기 전 도커는 최소 4기가의 램이 필요하기 때문에 메모리 스와핑을 먼저 진행해야 한다.

메모리 스와핑 : 실제 Memory(Ram)가 가득 찼지만 더 많은 Memory 가 필요할때 디스크 공간을 이용하여 부족한 메모리를 대체할 수 있는 공간을 만드는 것

https://repost.aws/ko/knowledge-center/ec2-memory-swap-file

sudo dd if=/dev/zero of=/swapfile bs=128M count=16
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
sudo swapon -s
sudo vi /etc/fstab

fstab 파일 끝에 다음 줄을 새로 추가하고 파일을 저장한 다음 종료
/swapfile swap swap defaults 0 0

도커 설치

https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository

Install using the apt repository를 참고하였다.

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
  
sudo apt-get update

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

sudo docker run hello-world

도커 권한 부여

sudo usermod -aG docker $USER

docker 를 sudo 명령어 없이 사용하기 위해 권한을 부여해 준다.


3. Jenkins 실행

Jenkins 이미지 실행

docker run -d --name jenkins -p 8080:8080 -v /jenkins:/var/jenkins_home -v /usr/bin/docker:/usr/bin/docker -v /var/run/docker.sock:/var/run/docker.sock -u root jenkins/jenkins:lts

주소창에 {ec2 퍼블릭 ip 주소}:8080 url 입력하면 해당 화면이 나오게 된다.

docker exec -it jenkins bash

cat /var/jenkins_home/secrets/initialAdminPassword  << 네모박스 경로

→ 값 복사하여 password 에 집어넣기

Install Suggested plugin 클릭 및 회원가입을 완료한다.


4.Github Token 발급

github 메인 페이지 -> Settings -> Developer Settings -> Personal Access Tokens에 접근하여 토큰 발급


5.Github WebHook 설정

배포할 프로젝트 -> Settings -> Webhooks로 이동 -> Payload URL에는 {Jenkins 서버 주소}/github-webhook/을 입력


6.젠킨스 설정

Jenkins 메인 화면 -> Jenkins 관리 -> Credentials

Username -> github 아이디
Password -> access token(Github Token)
ID -> 식별자 (jenkinsfile 에 넣어야 함)

pipeline 생성

7.Docker Hub 설정

Docker Hub -> My Account로 이동 → Security로 이동 → New Access Token을 통하여 토큰을 생성

토큰이 생성 후에, 이를 복사하여 Jenkins에 등록해줍니다.

젠킨스 환경변수 설정
환경변수(system) → Environment variables
Docker User Name(docker hub Id) , Docker Password(docker hub 엑세스 토큰 값) 설정

Dockerfile

FROM openjdk:17-oracle

VOLUME /tmp

ARG JAR_FILE=/build/libs/*.jar

COPY ${JAR_FILE} app.jar

ENTRYPOINT ["java", "-jar", "app.jar"]

Jenkinsfile

pipeline {
    agent any


        stage('Clone Repository') {
            steps {
                git branch: 'main',
                url: "https://github.com/alsduq1117/gameplanner.git",
                credentialsId: 'minyeob'   << 식별자 값
            }
        }

        stage('Build Project') {
            steps {
                sh 'chmod +x gradlew'
                sh './gradlew clean build'
            }
        }

        stage('Docker Login') {
            steps {
                sh 'echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin'
            }
        }

        stage('Build Docker Image') {
            steps {
                sh 'docker build -t alsduq1117/jenkins:test .'
            }
        }

        stage('Push Docker Image') {
            steps {
                sh 'docker push alsduq1117/jenkins:test'
            }
        }
    }
}

트러블 슈팅

Dockerfile에서 ARG JAR_FILE=/build/libs/.jar 이렇게 설정했는데, /build/libs/ 디렉토리에 jar 파일이 2개 이상(plain jar, jar)이라 와일드카드()를 사용하여 특정 jar 파일을 선택할 수 없는 문제

build.gradle

jar {
enabled = false  
}

해당 코드를 추가해 주어 plain jar 가 생성되지 않도록 하였다.

9.어플리케이션 서버 생성

9-1 EC2 생성

9-2 메모리 스왑

sudo dd if=/dev/zero of=/swapfile bs=128M count=16
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
sudo swapon -s
sudo vi /etc/fstab

fstab 파일 끝에 다음 줄을 새로 추가하고 파일을 저장한 다음 종료
/swapfile swap swap defaults 0 0

9-3 도커 설치하기 진행

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
  
sudo apt-get update

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

sudo docker run hello-world

10 Jenkins Plugin 설치

Jenkins 관리 -> Plugins 이동 -> Public Over SSH, Post build task 플러그인 설치

Public Over SSH 설정

System에 들어가서 Public over SSH 를 설정해 준다.

SSH 서버를 만들어 줍니다.

HostName : Application Sever IP
Username : ubuntu

test configure 을 눌렀을때 Success 가 나오면 Jenkins 서버에서 Application Server로의 연결이 완료된 것이다!

DockerDeploy.sh 파일 추가

프로젝트에 scripts/DockerDeploy.sh 파일을 만들어준다.

#!/bin/bash

DOCKER_REPOSITORY=alsduq1117/jenkins
DOCKER_TAG=test

echo "> 현재 실행 중인 Docker Container ID 확인"
DOCKER_ID=$(docker ps -aqf "name=Jenkins")

echo "> 현재 실행 중인 Docker Container ID == $DOCKER_ID"
if [ -z "$DOCKER_ID" ]; then
  echo "> 현재 실행 중인 Docker Container가 존재하지 않습니다."
else
  echo "> 현재 실행 중인 컨테이너를 종료하겠습니다."
  docker stop $DOCKER_ID
  docker rm $DOCKER_ID
  sleep 10
fi

echo "> 실행 중인 Docker Container를 종료하였습니다."

echo "> Docker Image를 삭제하겠습니다."
docker rmi $DOCKER_REPOSITORY:$DOCKER_TAG

echo "> Docker Image를 가져옵니다."
docker pull $DOCKER_REPOSITORY:$DOCKER_TAG

echo "> Docker Image를 실행합니다."
docker run -d -p 8080:8080 --name Jenkins $DOCKER_REPOSITORY:$DOCKER_TAG

Jenkinsfile 수정

ssh 통신으로 jar 파일과 쉘 스크립트 파일을 전달 후, 이를 실행하여 Application Server에서 Docker 이미지를 띄우도록 하였다.

stage('Push Docker Image') {
            steps {
                sh 'docker push alsduq1117/jenkins:test'
            }
        }

stage('Deploy') {
            steps {
                sshPublisher(
                    continueOnError: false, failOnError: true,
                    publishers: [
                        sshPublisherDesc(
                            configName: 'ApplicationServer',
                            verbose: true,
                            transfers: [
                                sshTransfer(
                                    sourceFiles: '$JAR_FILE_PATH,$SCRIPT_FILE_PATH',
                                    removePrefix: '',
                                    remoteDirectory: '/',
                                    execCommand: '''
                                    echo "JAR_FILE_PATH: ${JAR_FILE_PATH}"
                                    echo "SCRIPT_FILE_PATH: ${SCRIPT_FILE_PATH}"
                                    echo "Starting deployment..."
                                    sudo chmod 777 /home/ubuntu/app/$SCRIPT_FILE_PATH
                                    sudo chmod 777 /home/ubuntu/app/$JAR_FILE_PATH
                                    echo "Running DockerDeploy.sh..."
                                    /home/ubuntu/app/$SCRIPT_FILE_PATH
                                    echo "Deployment finished."
                                    '''
                                )
                            ]
                        )
                    ]
               X )
            }
        }

...

System에 들어가 Jenkins 환경변수에 추가해 줍니다.

JAR_FILE_PATH -> jar 파일이 존재하는 위치 ex) build/libs/myproject-0.0.1-SNAPSHOT.jar
APPLICATION_SERVER -> Application Server의 IP주소
SCRIPT_FILE_PATH -> 스크립트 파일이 존재하는 위치 ex ) scripts/DockerDeploy.sh


11. RDS 연결

MySql RDS 생성

application.yml 파일

spring:
  datasource:
    url: jdbc:mysql://${RDS_HOSTNAME}:${RDS_PORT}/${RDS_DB_NAME}
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: ${RDS_USERNAME}
    password: ${RDS_PASSWORD}

DB를 연결하여 Database를 하나 생성해줍니다 >> RDS_DB_NAME

System에 들어가 Jenkins 환경변수에 추가해 준다.

RDS_HOSTNAME : RDS 엔드포인트 값
RDS_PORT : 3306
RDS_DB_NAME : 생성해준 Database 이름
RDS_USERNAME : 마스터 사용자 이름
RDS_PASSWORD : 마스터 암호

추가적으로 Application Server 에 연결하여 /home/ubuntu/app 경로에 env 파일을 하나 생성해 준다.

vim .env << 명령어 실행
RDS_HOSTNAME : RDS 엔드포인트 값 
RDS_PORT : 3306 
RDS_DB_NAME : myDatabase   생성해준 Database 이름
RDS_USERNAME : admin       마스터 사용자 이름 
RDS_PASSWORD : 마스터 암호

해당하는 값들을 집어넣어 준다.

DockerDeploy.sh 수정

Docker가 환경 변수를 참조하여 명령어를 실행할 수 있도록 다음처럼 쉘 스크립트를 다음과 같이 수정해준다.

... ... 

echo "> Docker Image를 실행합니다."
docker run -d -p 8080:8080 --name Jenkins --env-file /home/ubuntu/app/.env $DOCKER_REPOSITORY:$DOCKER_TAG

다음과 같이 정상적으로 배포가 완료된다.


트러블 슈팅

Jenkins 컨테이너에서 Docker 명령어 사용 issue

처음에는 Jenkins 컨테이너를 아래와 같이 간단하게 실행했습니다.

docker run -d --name Jenkins -p 8080:8080 jenkins/jenkins:lts-jdk17

docker run -d --name Jenkins -p 8080:8080 jenkins/jenkins:lts-jdk17
그러나 이렇게 설정하고 진행하다 보니, Jenkins 내부에서 Docker 이미지를 빌드하고 푸시하는 과정에서 Docker의 명령어를 사용해야 하는 상황이 발생했습니다. 이럴 경우 Jenkins 내부에 Docker를 추가로 설치해야 하는 문제가 생기는데, 이는 불필요한 리소스 낭비를 초래할 수 있습니다.

이를 해결하기 위해, Jenkins 이미지를 실행할 때 Docker socket을 사용하도록 설정했습니다. 아래의 명령어는 Jenkins 컨테이너를 실행하면서 Docker socket을 연결하는 방법을 통해 문제를 해결하였습니다

docker run -d --name jenkins -p 8080:8080 -v /jenkins:/var/jenkins_home -v /usr/bin/docker:/usr/bin/docker -v /var/run/docker.sock:/var/run/docker.sock -u root jenkins/jenkins:lts

Github의 priavate repository에 jenkins가 접근하지 못하는 이슈 발생

jenkins가 private repository 에 접근하지 못하여 초기에 Clone Repository를 제대로 하지 못했습니다. 따라서 Credencials 을 만들때 다음 값들을 넣어주고

Username -> github 아이디
Password -> access token
ID -> 식별자를 지정

pipeline {
    agent any

    stages {
        stage('Clone Repository') {
            steps {
                git branch: 'main', 
                    url: "https://github.com/alsduq1117/gameplanner.git",
                    credentialsId: 'your-credentials-id'
            }
        }
    }
}

credentialsId 를 위 코드와 같이 제공해줘서 읽어올 수 있도록 하였습니다.

마치며

이렇게 Jenkins와 Docker를 활용한 CI/CD 구축 방식을 적용해보니, 기존에 사용하던 GitHub Actions와 Elastic Beanstalk을 이용한 방식에 비해 빌드 속도가 크게 단축된 것을 경험할 수 있었습니다. 이는 프로젝트의 전반적인 개발 효율성을 높이는 데 크게 기여하였습니다.이 글을 통해 저와 같이 새로운 CI/CD 방식에 관심이 있는 분들에게 도움이 되었으면 좋겠습니다.

참고:
https://velog.io/@chrkb1569/CICD-GitHub-Jenkins-Docker%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-CICD-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95-4-ix6aj5yk

profile
백엔드 개발자를 꿈꾸며 공부한 내용을 기록하고 있습니다.

0개의 댓글