Jenkins가 Build할 때 메모리를 많이 잡아 먹기 때문에 EC2인스턴스 2개를 사용해서 하나는 Jenkins를 실행시키고, 하나는 SpringBoot 애플리케이션을 사용하여 구상하였습니다.
지속적 통합(Continuous Integration, CI) 및 지속적 배포(Continuous Deployment, CD)를 위한 오픈 소스 자동화 도구입니다. 개발자들은 Jenkins를 사용하여 소스 코드의 변경 사항을 자동으로 테스트하고 빌드하여 통합하는 것으로, 소프트웨어 개발 프로세스를 자동화할 수 있습니다.
다양한 플러그인을 통해 다른 도구 및 서비스와의 통합을 지원하며, 확장성과 유연성이 뛰어고 오픈 소스(가장 큰 장점이라 생각)라서 라이선스 비용이 없이 사용이 가능합니다.
기본적인 AWS 환경구축(인스턴스 생성방법, 보안 그룹)은 되어있다고 가정하고 정리하였습니다.
- Swap 메모리는 하드디스크의 일부를 RAM처럼 사용하는 메모리입니다. 프리티어로 EC2인스턴스를 사용할 경우 jenkins에서 build를 진행하면 메모리 양이 RAM 용량을 초과하는 상황이 자주 생기기 때문에 일부 데이터를 하드디스크의 Swap 영역으로 옮겨서 메모리 부족 문제를 해결합니다.
- Swap 메모리는 RAM보다 속도가 느리기 때문에, 프로그램이 Swap을 사용하는 경우 시스템 성능이 저하될 수 있습니다.
스왑 파일이나 파티션이 존재하는지 확인
sudo free -m
sudo swapon -s
swap 관련 내용이 존재하지 않으면 넘어가고 swap이 작동중이라면, 아래 명령어를 통해 작동을 중지합니다.
sudo swapoff -a
swap을 하기위한 swapfile을 생성합니다.
sudo fallocate -l 2G /swapfile
-l 2G : 메모리 외에 추가로 2G의 가상 메모리를 사용가능
swapfile의 권한 수정 및 활성활
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
재부팅 해도 계속 사용하기 위해 /etc/fstab 파일을 수정
sudo vi /etc/fstab
# 내용 추가
/swapfile swap swap defaults 0 0
먼저 Docker환경에서 Jenkins를 기동시킬 것이기 때문에 Docker를 설치해줍니다.
sudo apt update
sudo apt install docker.io
sudo systemctl start docker
sudo systemctl enable docker
jenkins 이미지를 pull해줍니다.
docker pull jenkins/jenkins:jdk17
jenkins 볼륨을 위한 폴더를 생성 해줍니다. /home/jenkins 경로로 생성해주시면 됩니다.
mkdir jenkins
sudo chown -R 1000 ./jenkins
해당 폴더에 권한을 부여해줍니다.
sudo chown -R 1000 ./jenkins
jenkins 컨테이너를 실행 시킬 때 docker명령어를 사용해야하므로 아래와 같이 명령어를 작성해줍니다.
sudo docker run -u 0 --privileged --name jenkins -d -p 9090:8080 -p 50000:50000 -v /var/run/docker.sock:/var/run/docker.sock -v $(which docker):/usr/bin/docker -v /home/jenkins:/var/jenkins_home jenkins/jenkins:jdk17
--privileged : 컨테이너에 특권을 부여하여 호스트 시스템과 상호 작용을 허용
--name jenkins : 컨테이너의 이름을 jenkins로 지정
-d : 컨테이너를 백그라운드에서 실행
-p 9090:8080 : 호스트의 9090포트를 컨테이너의 8080포트와 연결
-p 50000:50000 : 호스트의 5000 포트와 컨테이너의 5000 포트를 연결 이 옵션은 Jenkins의 에이전트와의 통신을 위해 사용
-v /var/run/docker.sock:/var/run/docker.sock : 호스트의 Docker 소켓을 컨테이너 내부의 Docker 소켓과 연결하여 Docker 명령어를 실행할 수 있도록 함
-v $(which docker):/usr/bin/docker : 호스트의 Docker 실행 파일을 컨테이너 내부의 /usr/bin/docker에 연결하여 Docker 명령어를 실행할 수 있게 함
-v /home/jenkins:/var/jenkins_home : 호스트의 /home/jenkins : 디렉토리를 컨테이너 내부의 /var/jenkins_home에 연결하여 Jenkins 데이터를 영속화
jenkins/jenkins:jdk17 : 사용할 이미지지정
EC2에 jenkins 내부에 Docker를 띄우기 위해 docker socket를 빌리기 위한 Dockerfile과 docker_install.sh 파일을 작성해야 합니다.
FROM jenkins/jenkins:jdk17
USER root
COPY docker_install.sh /docker_install.sh
RUN chmod +x /docker_install.sh
RUN /docker_install.sh
RUN usermod -aG docker jenkins
USER jenkins
FROM jenkins/jenkins:jdk17: 이 Dockerfile은 기존에 존재하는 Jenkins 이미지를 기반으로 합니다. JDK 17 버전의 Jenkins 이미지를 사용.
USER root: 이후 명령어를 root 권한으로 실행합. Docker를 설치하기 위해 root 권한이 필요.
COPY docker_install.sh /docker_install.sh: 호스트의 docker_install.sh 파일을 Jenkins 컨테이너 내부로 복사.
RUN chmod +x /docker_install.sh: 복사된 스크립트 파일에 실행 권한을 부여.
RUN /docker_install.sh: Docker 설치 스크립트를 실행하여 Docker를 설치.
RUN usermod -aG docker jenkins: Jenkins 사용자가 Docker 명령을 실행할 수 있도록 Jenkins 사용자를 docker 그룹에 추가.
USER jenkins: 마지막으로 Jenkins 사용자로 변경하여 Jenkins 서버를 실행.
#!/bin/sh
apt-get update && \
apt-get -y install apt-transport-https \
ca-certificates \
curl \
gnupg2 \
zip \
unzip \
software-properties-common && \
curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg > /tmp/dkey; apt-key add /tmp/dkey && \
add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") \
$(lsb_release -cs) \
stable" && \
apt-get update && \
apt-get -y install docker-ce
- 패키지 관리자를 업데이트하고 필요한 패키지들을 설치.
- Docker의 GPG 키를 다운로드하고 인증.
- Docker의 저장소를 시스템에 추가.
- Docker를 설치.
- 이제 Dockerfile과 docker_install.sh 스크립트를 사용하여 Jenkins 컨테이너를 구성하고 Docker를 내부에 띄울 수 있음.
docker.sock의 파일 권한을 변경해줍니다.
sudo chmod 666 /var/run/docker.sock
{젠킨스 실행한 EC2서버ip}:9090 로 들어가서 jenkins의 초기 비밀번호를 입력해줍니다. 아래 명령어를 입력하시면 비밀번호를 확인할 수 있습니다.
docker exec -it jenkins /bin/bash
cat /var/jenkins_home/secrets/initialAdminPassword
이후 Install suggested plugins 로 플러그인을 설치해주고 계정정보도 생성해줍니다.
CI/CD를 구축하기 위해서 추가로 설치해야하는 Plugins이 있습니다.
Dashboard > Jenkins 관리 > Plugins > Available plugins
아래 Plugins을 설치해줍니다.
gradle, github integration, post build task, publish over ssh
jenkins에서 github과 연동하기 위햇 token을 발급 받아줍니다.
Github > Settings > Developer Settings > Personal access toekns > Tokens(classic)
아래와 같이 선택을 하고 토큰을 생성해줍니다.
Github의 push 이벤트를 받아서 Jenkins의 build 유발을 하기위해 Webhooks 설정을 해줍니다.
Github > 배포할 respository > Settings > Webhooks > Add webhook
Jenkins 관리 > Credentials
2개의 Credentials를 생성해야하는데 하나는 github 계정에 접근하기 위함이고, 하나는 github webhook을 사용하기 위함입니다.
kind : Username with password
Scope : Global (Jenkins, nodes, items, all child items. etc)
Username : Github계정의 아이디
Password : Github에서 생성한 Token값
ID : credentials을 식별하기 위한 값으로 원하는 값 입력
Description : 원하는 값 입력
kind : Secret text
Scope : Global (Jenkins, nodes, items, all child items. etc)
Secret : Github에서 생성한 Token값
ID : credentials을 식별하기 위한 값으로 원하는 값 입력
Description : 원하는 값 입력
Jenkins 관리의 System 버튼을 클릭합니다.
Docker hub에 푸쉬하기 위해서 docker login명령어를 통해 login을 해야하는데 이때 사용할 docker hub의 username과 password를 Global properties에 등록해줍니다.
Github Servers에 값을 입력 후 Test connect를 클릭하여 연결이 잘 되었는지 확인해봅니다.
Credentials verified for user {Github 아이디}, rate limit: 4999가 나오면 성공입니다.
Name : 원하는 값 입력
API URL : 원래 있는 값 그대로 사용(수정x)
Credentials : Github token으로 생성한 Credentials(Secret text)
배포할 EC2 인스턴스와 연결을 위해 SSH Server를 등록해줍니다.
Name : deploy
Hostname : EC2 인스턴스 IP
Username : 서버의 username으로 리눅스의 경우 ubuntu
고급 버튼을 눌러 추가로 EC2 인스턴스에 접속할 때 사용하는 pem키를 Key란에 입력해줍니다.
Test Configuration을 클릭하여 Success가 나오면 성공적으로 배포할 EC2 인스턴스 서버에 접근할 수 있습니다.
Dashboard에서 New Item을 클릭하여 Freestyle project를 생성합니다.
생성한 Item의 구성을 클릭하여 Project url에 github 경로를 입력합니다.
소스 코드 관리의 git을 클릭하여 Respository URL에 배포할 github URL을 입력하고, Credentials를 선택해줍니다.
Github의 push 이벤트를 webhook으로 받기 위한 설정입니다.
빌드 유발에 GitHub hook trigger for GITScm polling을 클릭합니다.
Add build step 에서 Invoke Gradle script 를 선택하여 build를 진행합니다.
Build Steps에서 Addbuild step을 클릭한 후 Execute shell을 클릭하여 아래 내용을 작성해줍니다.
Dockerfile을 기반으로 이미지를 생성한 뒤 docker login을 하여 push 후 해당 이미지를 삭제하는 명령어 입니다.
docker build -t {Docker Hub계정}/{Docker repository명}:{태그} .
echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin
docker push {Docker Hub계정}/{Docker repository명}:{태그}
docker rmi {Docker Hub계정}/{Docker repository명}:{태그}
참고로 Dockerfile은 아래와 같습니다.
FROM amazoncorretto:17 AS builder
WORKDIR /app
COPY build/libs/*-SNAPSHOT.jar app.jar
FROM amazoncorretto:17
WORKDIR /app
COPY --from=builder /app/app.jar app.jar
ENTRYPOINT java -jar app.jar
빌드조치가 끝난 뒤 배포 서버에 접근한 뒤 Docker를 실행 시켜야합니다.
빌드 후 조치 추가에서 send build artifacts over SSH를 클릭하여 SSH Server를 선택한 뒤 고급 탭에서 Source files, Remove prefix, Exec command를 아래와 같이 입력해줍니다.
echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin
sudo docker ps -a -q --filter "name=spring-boot" | grep -q . && docker stop spring-boot && docker rm spring-boot | true
sudo docker rmi onetaekoh/jenkins-test:1
sudo docker pull onetaekoh/jenkins-test:1
docker run -d -p 8080:8080 --name spring-boot onetaekoh/jenkins-test:1
docker rmi -f $(docker images -f "dangling=true" -q) || true
배포가될 EC2서버에 접근해서 docker 그룹에 사용자를 추가합니다. ubuntu를 사용할 경우 user명이 ubuntu입니다.
sudo usermod -aG docker ubuntu
여기까지 왔으면 CI/CD 환경 구축을 위한 설정은 완료된 것입니다.
push 이벤트가 발생하면 Build History에 Build가 생성됩니다.
Console Output에 들어가면 Build Steps과 Build 후 조치에 입력한 과정들을 거치고 마지막에 Finished: SUCCESS 가 나오면 완료입니다.
Build가 되던 중 ./gradlew파일 접근 권한 문제가 발생하여 Dockerfile에 권한부여 명령어 추가
// 생략...
RUN chmod +x ./gradlew && \
sed -i 's/\r//' ./gradlew && \
./gradlew bootJar
// 생략...
프리티어 사양이 낮아서 발생하는 문제로 Swap 설정으로 해결
docker login 명령어에서 Username 과 Password값이 틀려서 발생한 문제
회사에서 CI/CD 환경을 구축할 때 Windows 서버를 사용하고 Docker를 사용하지 않아서 레퍼런스 부족으로 많은 어려움을 겪었습니다. 그러나 리눅스 서버를 사용하여 CI/CD 환경을 구축할 때는 레퍼런스가 풍부하여 구축이 수월했습니다.
다음에는 CI/CD 파이프라인을 구축하고, 각각의 브랜치에 따라 다르게 Build를 유발하여 체계적으로 운영할 계획입니다. 이를 통해 프로젝트를 더 효율적으로 관리하고, 개발과 배포 과정을 더욱 체계적으로 돌아가도록 구상해보겠습니다.