서비스 상태
사용 도구
- 이슈 관리 : Jira
- 형상 관리 : GitLab
- 커뮤니케이션 : Notion, MatterMost
- 디자인 : Figma
- 빌드 도구 : Jenkins
개발 도구
- Visual Studio Code : ver 1.90.2
- IntelliJ IDEA Ultimate : 2024.1.4
- Pycharm : 2024.1.6
- DataGrip : 2024.1.4
외부 서비스
- Kakao Oauth2.0 카카오 로그인
💡시작하기 전에
서버는 EC2 하나를 지급받았다.
AWS 콘솔에 따로 접속할 수는 없고 ssh 접속으로 EC2에만 접속할 수 있는 상태.
현재 EC2에 Docker, Java 설치 되어 있는 상태
젠킨스 설치
# 도커 소켓 마운트 하기
docker run -itd --name jenkins -p 8005:8080 -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker jenkins/jenkins:jdk17
명령어를 살펴보면 /var/run/docker.sock:/var/run/docker.sock 이라는 볼륨 마운트가 있다.
이것의 경우 DinD방식이 아닌 DooD방식을 사용하기 위해 설정해주는 것이다.
DinD(Docker in Docker) vs DooD(Docker out of Docker)
현재 EC2상에서 젠킨스는 도커로 동작을 하고 있다.
GitLab과 연동하여 젠킨스에서는 곧 소스코드를 가져와서 도커를 이용해 이미지를 빌드할 것이다.
이를 위해서는 젠킨스 컨테이너는 Docker 명령어가 사용이 가능해야한다.
그럼 이를 위해서 젠킨스 안에서 Docker를 설치하고 실행할 수 있어야한다.
DinD는 단어 그대로 도커 컨테이너 속에 도커 컨테이너를 실행한다.

위 그림을 보면 C1, C2, Dind라는 세개의 컨테이너가 호스트 머신 위에서 동작한다.
그리고 Dind 컨테이너 안에서 C3, C4 컨테이너가 동작한다.
다른 블로그 들에서 DinD방식의 장점과 단점을 충분히 많이 설명하고 있기 때문에 간단하게 정리만 하자면
DinD방식의 경우 외부 도커와 내부 도커의 충돌 등의 이슈도 있고 리눅스 보안 문제가 발생할 수도 있다.
=> 아무튼 그래서 권장되지 않는 방식이다!
DooD의 경우 호스트 머신의 도커 소켓을 공유하여 사용하는 방식이다.
소켓을 마운트 해줌으로써 젠킨스 컨테이너에 도커 엔진을 설치할 필요 없이 호스트 머신의 도커를 사용할 수 있게 된다.

위와 같은 그림처럼 동작하게 된다. 물론 DooD라고 해서 무조건 좋은 것은 아니고 각각의 장단점이 있다.
하지만 공식적으로도 DinD는 권장되지 않는 방식이라고 하니 DooD로 구현을 했다.
docker exec -it jenkins /bin/bash
위 명령어를 통해 젠킨스 내부에서 docker명령어가 잘 실행 되는지 한번 확인 해보면 된다.
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
이제 다시 호스트로 빠져나와서 위 명령어를 입력하면 젠킨스 초기 접속 비밀번호를 확인할 수 있다.
그리고 서버의 8005번 포트(혹은 설정한 다른 포트)로 접속해보면 젠킨스 gui로 접속할 수 있다.

여기에다가 위에서 알아낸 비밀번호를 입력 해주면 된다.


뒤에 나오는 과정들을 따라 플러그인 설치를 하고 admin계정을 만들어 주면 된다.
하지만 우리팀의 경우도 그렇고 주변 많은 사람들이 플러그인 설치에서 고생을 좀 했다.

플러그인 설치에 계속해서 실패하는 경우가 발생한다면 일단 계속 리트라이해보다 보면 갑자기 되기도 한다(?)
아무튼 여기서 너무 스트레스 받지 말고 그냥 넘긴다음에 접속해서 플러그인 설치를 위한 미러링 사이트 url을 변경하기도 해보고 여러번 시도를 하다보면 풀리게 된다.

쟌. 아무튼간 이 화면이 나오면 정상적으로 설치까지 완료한 모습이다.
플러그인 및 Credentials설정
Jenkins 관리 → Plugins → Available plugins
에 접속해서 필요한 플러그인을 설치하면 된다. 내가 설치했던 플러그인들은 다음과 같다.
- SSH Agent Plugin
- GitLab
- Docker plugin
- NodeJS
- Mattermost Notification Plugin
필요에 따라 설치하면 될 듯하다.
Jenkins 관리 → Credentials → Add credentials
필요한 credentails 정보는 다음과 같다.
- GitLab Token 등록
- Docker hub 로그인 정보 등록
- Docker image push를 위한 repo 정보 등록
- SSH 접속을 위해 EC2 IP 정보와 .pem키 정보 등록
- .env 파일 등록
다음과 같이 등록을 해주면된다..

- Kind : GitLab API token
- Scope : Global
- API token : GitLab 토큰
- ID : 원하는 걸로
GitLab 로그인 정보 설정
- Kind : Username with password
- Scope : Global
- Username : gitlab id
- Password : gitlab password
- ID : 원하는 걸로
Docker Hub 설정
- Username : Docker Hub ID
- Password : Docker Hub PW
- ID : pipline Script 에서 사용할 이름 = DOCKER_USER
Docker Repository 설정
- Username : Docker Hub nameSpace
- Password : Docker Hub RepositoryName
- ID : pipline Script 에서 사용할 이름 = DOCKER_REPO
EC2 IP 설정
- Secret : 서버 주소
- ID : pipline Script 에서 사용할 이름 = EC2_SERVER_IP
ssh 접속을 위한 pem키는 다음과 같이 등록하면 된다.

밑에 private key칸
맥북이라면 vi ***.pem 하면 되고 윈도우라면 키 파일을 메모장으로 열어서
나오는 내용을 모두 복사해서 넣어주면 된다.
configure 설정


- connection name : 원하는 걸로
- GitLab host URL : ***
- Credentials : 앞에서 만든 토큰 credentials 선택
test connection이 성공하면 준비 끝
파이프라인 만들기
Dashboard → 새로운 item

freesytle로 하는 방법도 있다. 둘다 해봤는데 나는 pipeline이 조금 더 이해가 잘 돼서 pipeline으로 진행했다.

왼쪽에서 구성 탭 클릭

CI/CD를 적용할 깃 레포지토리 주소를 넣어준다.

빌드 트리거는 원하는방법으로 하고 고급 탭 안에 있는 secret token generate 후 복사 해두기
파이프라인 스크립트는 다음과 같이 작성했다.
또 윗쪽에 모자이크 된 부분인 GitLab webhook URL 또한 뒤쪽에서 할 깃랩 설정에서 사용된다,
pipeline {
agent any
environment {
ENV_FILE = credentials('BACK_ENV')
}
stages {
stage('Git Clone') {
steps {
git branch: 'dev-back', credentialsId: '***', url: '***'
}
post {
failure {
echo 'Repository clone failure !'
}
success {
echo 'Repository clone success !'
}
}
}
stage('Prepare .env File') {
steps {
script {
writeFile file: './Backend/***/.env', text: "${ENV_FILE}"
writeFile file: './Backend/***/.env', text: "${ENV_FILE}"
sh 'cat ./Backend/***/.env'
}
}
}
stage('Build API Project') {
steps {
script {
// 프로젝트 권한 변경
sh 'chmod +x ./Backend/***/gradlew'
// 프로젝트 빌드
dir('./Backend/***') {
sh './gradlew build -x test'
}
}
}
post {
failure {
echo 'API project build failure !'
}
success {
echo 'API project build success !'
}
}
}
stage('Build Chat Project') {
steps {
script {
// 프로젝트 권한 변경
sh 'chmod +x ./Backend/***/gradlew'
// 프로젝트 빌드
dir('./Backend/***') {
sh './gradlew build -x test'
}
}
}
post {
failure {
echo 'Chat project build failure !'
}
success {
echo 'Chat project build success !'
}
}
}
// Docker
stage('Docker Hub Login') {
steps {
withCredentials([usernamePassword(credentialsId: 'DOCKER_USER', passwordVariable: 'DOCKER_PASSWORD', usernameVariable: 'DOCKER_USERNAME')]) {
sh 'echo "$DOCKER_PASSWORD" | docker login -u $DOCKER_USERNAME --password-stdin'
}
}
}
stage('Build Docker Image for Backend') {
steps {
script {
docker.build("***/***:latest", "./Backend/***")
}
}
}
stage('Build Docker Image for Chat') {
steps {
script {
docker.build("***/***:latest", "./Backend/***")
}
}
}
stage('Tag and Push Docker Images') {
steps {
script {
// api 이미지 푸시
sh 'docker tag ***'
sh 'docker push s***'
// 채팅 이미지 푸시
sh 'docker tag ***'
sh 'docker push ***'
}
}
}
stage('Deploy') {
steps {
sshagent(credentials: ['ssh-credentials']) {
withCredentials([string(credentialsId: 'EC2_SERVER_IP', variable: 'IP')]) {
script {
sh 'ssh -o StrictHostKeyChecking=no ubuntu@$IP "cd /home/ubuntu && sudo ./deploy.sh"'
}
}
}
}
}
}
// MatterMost Noti
post {
success {
script {
def Author_ID = sh(script: "git show -s --pretty=%an", returnStdout: true).trim()
def Author_Name = sh(script: "git show -s --pretty=%ae", returnStdout: true).trim()
mattermostSend (color: 'good',
message: "백엔드 배포 성공: ${env.JOB_NAME} #${env.BUILD_NUMBER} \n(<${env.BUILD_URL}|Details>)",
endpoint: '***',
channel: '***️'
)
}
}
failure {
script {
def Author_ID = sh(script: "git show -s --pretty=%an", returnStdout: true).trim()
def Author_Name = sh(script: "git show -s --pretty=%ae", returnStdout: true).trim()
mattermostSend (color: 'danger',
message: "백엔드 배포 실패: ${env.JOB_NAME} #${env.BUILD_NUMBER}\n(<${env.BUILD_URL}|Details>)",
endpoint: '***',
channel: '***️'
)
}
}
}
}
우리 프로젝트 관련된 부분은 모두 *** 처리했다.
백엔드 파이프라인인데 우리는 일반 api 프로젝트와 채팅 프로젝트가 분리 되어 있는데 한 파이프라인 안에서 같이 처리를 한다.
깃 클론 -> 빌드 -> 도커 허브에 푸시 -> 서버에 있는 deploy.sh 실행
순이다.
마지막에 있는 post의 경우 빌드 성공/실패 알림을 위해 mattermost와 연동한 내용이다. 해당 부분에 대해서는 나중에 따로 글을 쓰는게 목표.
deploy.sh의 경우
기존 compose파일을 모두 down하고 기존 이미지를 삭제 한 후 새로운 latest이미지를 pull 받아서 실행 시키는 내용을 쉘 스크립트로 작성해두었다.
이렇게 하면 젠킨스 쪽 설정은 끝.
이제 깃랩설정을 해보겠다.
GitLab 설정

깃랩 Settings에서 Webhooks에 들어간다.
add new webhook 후에

아까 위에서 복사 해두었던 jenkins url과 secret token을 넣어준다.

트리거 설정은 원하는데로...
이렇게 하면 모든 설정이 끝나게 된다!
프론트엔드의 경우 젠킨스 플러그인으로 node를 설치 후 파이프라인 구성 과정에서 버전을 맞춰주고
같은 방식으로 진행하면 된다.
'프로젝트기록' 카테고리의 다른 글
| Jenkins 용량 부족 문제 해결해보기 (0) | 2025.02.19 |
|---|---|
| 클라우드 프론트에 https달기 (0) | 2025.02.17 |
| 서브도메인을 등록하기 위해 AWS Route 53, CloudFront, S3, ACM 설정하기 (0) | 2025.02.14 |
| private subnet에 Github와 Jenkins로 CI 구축하다가 ALB때문에 다시 한 이야기 (0) | 2025.02.13 |
| 퍼블릭 액세스를 허용하지 않은 AWS RDS에 접근하는 방법 (0) | 2025.02.12 |