이번 포스팅에서는 이전에 구매한 도메인으로 백엔드 서버를 리버스 프록시 적용해보고, 젠킨스와 Docker Hub를 활용하여, SpringBoot API 백엔드 서버를 자동 배포해보도록 하겠습니다. 우선 리버스 프록시가 무엇인지 알아보도록 하겠습니다.
리버스 프록시는 클라이언트(사용자)의 요청을 받아 다른 서버로 전달하고, 그 서버로부터 받은 응답을 다시 클라이언트에게 반환하는 역할을 하는 서버입니다. 쉽게 말해, 웹 서버가 외부의 클라이언트로부터 직접적인 요청을 받는 대신, 리버스 프록시가 그 요청을 가로채어 내부 서버로 전달해주는 중간 매개체 역할을 합니다.
이러한 리버스 프록시는 여러 가지 장점을 제공합니다.
보안 강화
: 내부 서버의 IP 주소와 구조를 외부에 노출하지 않아 보안을 강화할 수 있습니다.로드 밸런싱
: 여러 대의 서버로 들어오는 트래픽을 균등하게 분배하여 서버의 부하를 줄일 수 있습니다.SSL 종료
: SSL/TLS 처리를 리버스 프록시가 대신함으로써, 내부 서버는 복잡한 SSL 설정 없이 HTTP로만 통신할 수 있습니다.8080
포트를 사용하기 때문에, 백엔드는 임의의 포트인 8084
포트를 사용하도록하겠습니다.sudo vi /etc/nginx/sites-available/default
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name 구매한도메인주소 www.구매한도메인주소;
location /api/ {
proxy_pass http://localhost:8084; # 백엔드 서버 포트번호
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
다음으로 젠킨스를 설치하고, 기본 설정 및 Docker를 사용한 자동배포 설정을 진행해보도록 하겠습니다.
sudo docker pull jenkins/jenkins:lts
localhost:8080/jenkins
이런식으로 접속하기 원하기 때문에 prefix
지정을 해줍니다.docker run -d -p 8080:8080 -p 50000:50000 --name jenkins -u root \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /jenkins:/var/jenkins_home \
-e JENKINS_OPTS="--prefix=/jenkins" \
jenkins/jenkins:lts
d
: 백그라운드에서 컨테이너를 실행합니다.p 8080:8080
: 호스트의 8080 포트를 컨테이너의 8080 포트에 매핑합니다.p 50000:50000
: 호스트의 50000 포트를 컨테이너의 50000 포트에 매핑합니다 (Jenkins 에이전트 연결용).-name jenkins
: 컨테이너 이름을 jenkins
로 지정합니다.v jenkins_home:/var/jenkins_home
: 볼륨을 마운트하여 Jenkins 데이터를 호스트의 jenkins_home
디렉토리에 저장합니다. sudo ufw allow 8080
sudo ufw reload
Docker 컨테이너가 실행 중이면, 웹 브라우저에서 http://<your_server_ip>:8080/jenkins
으로 접속합니다. 그럼 다음과 같은 화면이 나오는데, 초기 비밀번호를 확인하려면 다음 명령어를 사용합니다.
sudo docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
비밀번호 입력 후, Install suggested plugins
를 선택하면 웬만한 플러그인은 미리 다 설치됩니다.
ID, Password, Name, Email을 등록하여 아이디를 만듭니다.
Jenkins URL로는 일단 http://www.내도메인:8080/jenkins/
로 작성해줍니다. (이후, SSL 인증서를 통해 https 처리를 한 후, 변경해줄 예정입니다.)
설정이 완료되면 다음과 같은 화면을 볼 수 있습니다.
sudo vi /etc/nginx/sites-available/default
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name 구매한도메인 www.구매한도메인;
location /api/ {
proxy_pass http://localhost:8084; # 백엔드 서버의 포트 번호
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /jenkins {
proxy_pass http://localhost:8080/jenkins;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
sudo nginx -t
sudo systemctl restart nginx
💡 prefix 설정이 잘못되어 리버스 프록시에서 오류가 난 것이기 때문에 설정을 다시 한번 확인하시면 됩니다.
아래 명령어를 사용하여 SSH 키를 생성합니다.
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
여기서 "your_email@example.com"
을 자신의 이메일 주소로 바꿉니다.
이 과정을 마치면 id_rsa
와 id_rsa.pub
두 파일이 생성됩니다.
Jenkins 컨테이너 내부에서 SSH 키 파일의 소유자와 권한을 올바르게 설정하여 Jenkins가 GitHub 리포지토리에 접근할 수 있도록 하는 과정입니다. 이를 통해 SSH 키 파일이 Jenkins 사용자에 의해 읽히고 사용될 수 있게 설정합니다.
문제를 해결하기 위해 다음 단계를 시도해 보세요:
.ssh
디렉토리를 생성합니다. docker exec -it jenkins /bin/bash
mkdir -p /var/jenkins_home/.ssh
# 호스트 시스템에서 실행
docker cp /root/.ssh/id_rsa jenkins:/var/jenkins_home/.ssh/id_rsa
docker cp /root/.ssh/id_rsa.pub jenkins:/var/jenkins_home/.ssh/id_rsa.pub
docker exec -it jenkins /bin/bash
# Jenkins 컨테이너 내부에서 실행
chown jenkins:jenkins /var/jenkins_home/.ssh/id_rsa
chown jenkins:jenkins /var/jenkins_home/.ssh/id_rsa.pub
chmod 600 /var/jenkins_home/.ssh/id_rsa
chmod 644 /var/jenkins_home/.ssh/id_rsa.pub
# SSH 연결 테스트
ssh -i /var/jenkins_home/.ssh/id_rsa -T git@github.com
Deploy 키 즉, 해당 레퍼지토리에만 접근할 수 있는 SSH 키를 설정해야 합니다.
연결할 Github Repository에서 Setting > Deploy keys > Add deploy key
를 클릭합니다.
Title은 마음대로 적어도 되지만, Jenkins
로 지어 주고, Key 부분에 id_rsa.pub
에 들어있는 public key 값을 넣어 주고, Add key
버튼을 클릭합니다. 이때, public key 값은 다음 명령어를 통해 확인할 수 있습니다.
cat ~/.ssh/id_rsa.pub
Settings > Developer settings > Personal access tokens
Settings > Developer settings > Personal access tokens
을 선택합니다.Jenkins에 로그인한 후, Manage Jenkins
> Manage Credentials
를 클릭합니다.
<div style="margin-top: -20px;">
(global)
범위를 선택하고 Add Credentials
를 클릭합니다.
그럼 다음과 같은 화면이 나오는데 다음과 같이 설정 내용을 적어줍니다.
SSH Username with private key
로 설정합니다.Username
필드에 GitHub 사용자 이름을 입력합니다. Private Key
섹션에서 Enter directly
를 선택하고, 개인 키(id_rsa
파일)의 내용을 붙여넣습니다.cat ~/.ssh/id_rsa
ID
와 Description
을 적절히 입력하고 create
버튼을 클릭합니다. ID
필드는 Jenkins 내부에서 이 자격 증명을 참조할 때 사용할 식별자입니다. 이 필드는 다른 자격 증명과 구분하기 위해 입력해두는 것이 좋습니다.Username with password
로 설정하고, Username에는 Github 사용자 이름, Password에는 발급 받은 Access Token을 입력하고, 해당 자격 증명을 식별하기 위해 ID를 부여합니다.Docker Hub에 로그인:
새 리포지토리 생성:
오른쪽 상단의 프로필 아이콘을 클릭하고 Repositories
를 선택합니다.
Create Repository
버튼을 클릭합니다.
Repository Name
을 입력합니다. 예를 들어, your-username/your-repository
.
Visibility
를 선택합니다 (저는 보안상 Private
을 설정했습니다).
Create
버튼을 클릭하여 리포지토리를 생성합니다.
이제 Docker Hub에 리포지토리가 생성되었으므로 Jenkins Pipeline을 통해 이 리포지토리에 Docker 이미지를 푸시할 수 있습니다.
비밀번호 대신 Access Token을 사용하면 다음과 같은 이점이 있습니다.
보안 강화
관리 편의성
회전 가능성
Docker Hub에 로그인: Docker Hub에 로그인합니다.
오른쪽 상단의 프로필 아이콘을 클릭하고, "Account Settings"를 선택합니다.
"Security" 탭을 클릭합니다.
"New Access Token" 버튼을 클릭하고, 토큰에 대한 이름을 입력한 후, "Create"를 클릭합니다.
토큰 복사: 생성된 토큰을 복사하고 안전한 곳에 저장합니다. 이 토큰은 이후 다시 볼 수 없으므로 반드시 저장해야 합니다.
Jenkins 대시보드 > Jenkins 관리 > 플러그인 관리 > 설치 가능 > Docker 검색 > Docker
, Docker Pipeline
플러그인 설치 및 재실행
Jenkins 대시보드에서 자격 증명을 추가합니다.
Manage Jenkins
> Manage Credentials
로 이동합니다.(global)
범위를 선택하고 Add Credentials
를 클릭합니다.Kind
를 Username with password
로 설정합니다.Username
에 Docker Hub 사용자 이름을 입력합니다.Password
에 직전에 만든 Docker Hub AccessKey를 입력합니다.ID
에 자격 증명의 식별자를 입력합니다.Description
에 자격 증명의 설명을 입력합니다.OK
를 클릭하여 저장합니다.이 과정을 통해 Jenkins 컨테이너 내부에 Docker를 설치하고, Jenkins Pipeline에서 Docker 명령어를 사용할 수 있게 됩니다.
우선, Jenkins 컨테이너에 접속하여 Docker가 기존에 설치가 되어있는지 확인한 결과 설치가 되어있지 않았습니다.
Jenkins 컨테이너에 접속
먼저, Jenkins 컨테이너에 root 권한으로 접속합니다.
docker exec -it -u root 컨테이너명 bash
Docker 설치
컨테이너 내부에 필요한 패키지를 설치합니다. Jenkins 컨테이너는 보통 최소한의 패키지만 포함되어 있으므로, 추가 패키지를 설치해야 합니다.
# 필요한 패키지 설치
apt-get update
apt-get install -y sudo wget apt-transport-https ca-certificates curl software-properties-common
docker.io 설치하기
apt-get update
apt-get install -y docker.io
chmod 666 /var/run/docker.sock
Docker 명령어 정상적으로 동작하는지 확인합니다.
docker --version
이 자격증명은 Jenkins가 SSH를 통해 EC2 인스턴스나 다른 서버에 접근하여 애플리케이션을 배포하거나 명령을 실행할 수 있게 합니다.
Jenkins 대시보드에서 자격 증명 관리로 이동:
Manage Jenkins
> Manage Credentials
로 이동합니다.(global)
범위를 선택하고 Add Credentials
를 클릭합니다.SSH 키 자격 증명 추가:
Kind
를 SSH Username with private key
로 설정합니다.ID
를 입력합니다 (이 ID는 Jenkinsfile에서 참조됩니다).Username
에 ubuntu
를 입력합니다 (EC2 인스턴스의 기본 사용자 이름).Private Key
섹션에서 Enter directly
를 선택하고, 다운로드한 .pem
파일의 내용을 붙여넣습니다..pem
키는 Mac에서 터미널 열고, cat “pem키 있는 경로”
를 통해 얻을 수 있습니다.Create
를 클릭하여 저장합니다.Create a job
버튼을 클릭합니다.Pipeline
을 선택한 후 OK
버튼을 클릭합니다. Github Repository에 push/merge event가 발생했을 때 자동으로 Build가 실행되게 하기 위해 Pipeline과 Github Webhook을 연동해야 합니다.
Jenkins 대시보드 > Jenkins 관리 > 플러그인 관리 > 설치 가능 > Github Integration
플러그인을 검색하고 설치 및 재실행합니다.
Github project 설정
Pipeline 구성 화면 > General
영역에서 Github project
를 선택한다. Project url에 본인의 Github Repository Url을 입력한다. 이때, Repository Url은 Clone 시 사용하는 HTTPS Url을 입력합니다. Build Triggers 설정
Pipeline 구성 화면 > Build Triggers
영역에서 GitHub hook trigger for GITScm polling
을 선택합니다. GitHub 리포지토리에서 Settings > Webhooks > Add Webhook으로 이동합니다.
Payload URL에 <Jenkins Server URL>:<Jenkins Server 포트>/github-webhook/
을 입력합니다. 이때, 마지막에 /
붙이는거에 주의합니다.
Content type을 application/x-www-form-urlencoded
으로 설정합니다.
Which events would you like to trigger this webhook?에서 'Let me select individual events.'를 클릭합니다.
Pull requests
와 Push
이벤트를 선택합니다. Jenkins 파이프라인에서 application.yml
파일을 추가하거나 배포 환경에 전달하는 작업을 설정하는 가장 일반적인 방법은 application.yml
파일을 Jenkins의 Config File Provider
플러그인을 사용하여 관리하고, Jenkins Pipeline 스크립트에 이를 반영하는 것입니다.
Config File Provider 플러그인 설치:
Jenkins 관리 > Manage Plugins > Available
에서 Config File Provider
플러그인을 설치합니다.Config File 설정:
Jenkins 관리 > Managed files
로 이동하여 새로운 Config File
을 추가합니다.Custom file
또는 YAML
형식을 선택하고, application.yml
내용을 추가합니다. 또한, Jenkins Pipeline 스크립트에서 사용할 ID를 지정해줍니다. (만약, S3를 사용하기 위한 yml파일도 있다면, 같은 방법으로 추가해줍니다.) Jenkins Pipeline 스크립트 작성 파일 배포 추가:
백엔드 SrpingBoot server는 젠킨스와 포트번호가 겹치지 않게 8084 포트로 돌립니다.
사용자 지정 TCP
8084, 0.0.0.0/0으로 설정해줍니다.)sudo ufw allow 8084
sudo ufw enable
sudo ufw status
이전에 만든 젠킨스 파이프라인에 다음과 같이 작성해줍니다.
pipeline {
agent any
environment {
DOCKERHUB_CREDENTIALS = '직전에 만든 dockerhub 식별 ID'
REPO_URL = 'https://github.com/your-repository.git'
IMAGE_NAME = 'your-dockerhub-username/repository-name'
}
stages {
stage('Checkout') {
steps {
git url: "${env.REPO_URL}", branch: 'your-branch', credentialsId: '직전에 만든 github-accesskey 식별 ID'
}
}
stage('Prepare Config Files') {
steps {
// Config File Provider 플러그인에서 관리하는 application.yml 파일을 작업 디렉토리에 복사
configFileProvider([
configFile(fileId: 'application-yml-config', targetLocation: '프로젝트명/src/main/resources/application.yml'),
configFile(fileId: 'application-aws-yml-config', targetLocation: '프로젝트명/src/main/resources/application-aws.yml')
]
) {
sh 'echo "Config file copied to resources folder"'
}
}
}
stage('Build Gradle') {
steps {
sh './gradlew build -x test'
}
}
stage('Build Docker Image') {
steps {
script {
def image = docker.build("${env.IMAGE_NAME}:${env.BUILD_NUMBER}", "-f Dockerfile .")
docker.withRegistry('', "${env.DOCKERHUB_CREDENTIALS}") {
image.push()
}
sh """
docker images --filter=reference='${env.IMAGE_NAME}*' --format '{{.ID}}' | tail -n +4 | xargs -r docker rmi -f
"""
}
}
}
stage('Deploy to Server') {
steps {
script {
sh "docker pull ${env.IMAGE_NAME}:${env.BUILD_NUMBER}"
sh "docker stop app-container || true"
sh "docker rm app-container || true"
sh "docker run -d --name app-container -p 8084:8084 ${env.IMAGE_NAME}:${env.BUILD_NUMBER}"
}
}
}
}
post {
always {
cleanWs()
}
}
}
저는 다음과 같은 오류가 발생했었습니다.
ERROR: Error cloning remote repo 'origin
SSH 키가 제대로 설정된 것 같지만, Jenkins가 GitHub에 접근할 때 known_hosts 파일이 없어서 문제가 발생하고 있습니다.
1. Jenkins 컨테이너 내부에서 known_hosts
파일 확인
cat /var/jenkins_home/.ssh/known_hosts
만약 파일이 없다면
ssh-keyscan
명령어를 사용해 GitHub의 호스트 키를 known_hosts
파일에 추가해야 합니다.
ssh-keyscan github.com >> /var/jenkins_home/.ssh/known_hosts
파일 권한을 다시 설정
chown jenkins:jenkins /var/jenkins_home/.ssh/known_hosts
chmod 644 /var/jenkins_home/.ssh/known_hosts
Jenkins에서 Git 플러그인 설정:
/var/jenkins_home/.ssh/known_hosts
로 설정합니다.추가적으로 시도해본 사항
sudo: ./gradlew: command not found
에러로 빌드가 진행되지 않았습니다.
chmod +x ./gradlew
를 통해 권한 설정을 해주었고, ./gradlew
명령어를 사용할 수 있게 되었습니다.도커 파일을 못찾는 이슈 ⚠️
failed to read dockerfile: open /var/lib/docker/tmp/buildkit-mount2095907826/Dockerfile: no such file or directory
DockerFile
과 gradlew
이 들어 있기 때문에, pipeline 스크립트를 다음과 같이 설정해주어야 합니다. def image = docker.build("${env.IMAGE_NAME}:${env.BUILD_NUMBER}", "-f path/to/Dockerfile .")
stage('Build Gradle') {
steps {
dir('your-gradlew-folder') {
sh './gradlew build -x test'
}
}
}
결국 수정된 전체 코드는 다음과 같습니다.
pipeline {
agent any
environment {
DOCKERHUB_CREDENTIALS = '직전에 만든 dockerhub 식별 ID'
REPO_URL = 'https://github.com/your-repository.git'
IMAGE_NAME = 'your-dockerhub-username/repository-name'
}
stages {
stage('Checkout') {
steps {
git url: "${env.REPO_URL}", branch: 'your-branch', credentialsId: '직전에 만든 github-accesskey 식별 ID'
}
}
stage('Prepare Config Files') {
steps {
// Config File Provider 플러그인에서 관리하는 application.yml 파일을 작업 디렉토리에 복사
configFileProvider([
configFile(fileId: 'application-yml-config', targetLocation: '프로젝트명/src/main/resources/application.yml'),
configFile(fileId: 'application-aws-yml-config', targetLocation: '프로젝트명/src/main/resources/application-aws.yml')
]
) {
sh 'echo "Config file copied to resources folder"'
}
}
}
stage('Build Gradle') {
steps {
dir('your-gradlew-folder') {
sh './gradlew build -x test'
}
}
}
stage('Build Docker Image') {
steps {
script {
def image = docker.build("${env.IMAGE_NAME}:${env.BUILD_NUMBER}", "-f path/to/Dockerfile .")
docker.withRegistry('', "${env.DOCKERHUB_CREDENTIALS}") {
image.push()
}
sh """
docker images --filter=reference='${env.IMAGE_NAME}*' --format '{{.ID}}' | tail -n +4 | xargs -r docker rmi -f
"""
}
}
}
stage('Deploy to Server') {
steps {
script {
sh "docker pull ${env.IMAGE_NAME}:${env.BUILD_NUMBER}"
sh "docker stop app-container || true"
sh "docker rm app-container || true"
sh "docker run -d --name app-container -p 8084:8084 ${env.IMAGE_NAME}:${env.BUILD_NUMBER}"
}
}
}
}
post {
always {
cleanWs()
}
}
}
push/merge
이벤트가 들어온다.chekout
한다.build
를 진행한다.push
하고, EC2 서버에서 이 이미지를 pull
받아 8084 포트로 백엔드 서버를 컨테이너를 띄운다.다음 포스팅에서는 젠킨스 빌드 과정 가시화 및 Slack 연동을 진행해보도록 하겠습니다.