싸피 마지막 자율 프로젝트를 진행하면서 infra담당을 처음 맞게 되었다.
근데 이거 하면서 느끼는게 빡통이라 내가 뭘 하는건지 무슨과정을 진행중인지 까먹고 봤던거 계속 보고 물어보고 그냥 시간 걸리더라도 기록하면서 할란다.

ssh -i 키파일 ubuntu@서버주소
키파일의 경로와 현재 cmd 경로를 잘 확인하자!!

이때 pem키의 권한을 400으로 주지않으면 접속이 안된다.
chmod 400을 통해 pem키의 권한을 변경해주자.
외부에서 nginx로 접속할 수 있도록 우분투 방화벽에서 포트를 열어준다.
- 외부에서 nginx로 접속할 수 있도록 우분투 방화벽(UFW)에서 포트를 열어주었다.
1. 처음 ufw 설정 시 실수로 ssh접속이 안되는 경우를 방지하기 위해
ssh 터미널을 여유있게 2~3개 연결해 놓는다.
2. ufw 상태 확인
$ sudo ufw status
Status : inactive
3. 사용할 포트 허용하기 (ufw inactive 상태)
$ sudo ufw allow 22
3-1 등록한 포트 조회하기 (ufw inactive 상태)
$ sudo ufw show added
Added user rules (see 'ufw status' for running firewall):
ufw allow 22
4. ufw 활성화 하기
$ sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
4.1 ufw 상태 및 등록된 rule 확인하기
$ sudo ufw status numbered
Status: active
ufw에 허용된 포트 확인
sudo ufw status numbered
80번 포트를 추가로 설정
sudo ufw allow 80
numbered에 등록된 번호4번 포트를 삭제(연습용)
sudo ufw delete 4
(중요) 삭제한 정책은 반드시 enable을 수행해야 적용된다.
$ sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y입력
sudo ufw allow 443 (https)
sudo ufw allow 9090 (젠킨스)
스왑 메모리 설정
현재 메모리는 16GB로 굉장히 넉넉하지만, (연습+만약을 대비)를 이유로 가상 메모리 기법을 적용하였다. (다음에 freetier 사용할때 적용하면 좋을듯 싶다)
아마존 공식 추천 가이드에 맞추어 16GB의 절반인 8GB를 스왑메모리로 적용하였다.
나는 스왑메모리도 적용안했고, ssh 접속이 안되는 것을 방지하기위해 터미널을 여유있게도 안했다 ㅎㅎ..
http - 80
https - 443
portainer - 9000
jenkins - 5000
springboot(blue) - 8080
springboot(green) - 8081
fast api - 8000
mysql - 3306
redis - 6397
react - 3000
kafka - 9092
zookeeper - 2181
이렇게 설정 해두었다
사실 블루그린은 이후에 설정할 예정이다!

나는 jenkins를 도커 컴포즈로 관리한다.
정확히는 도커컴포즈이기때문에 도커컨테이너로 관리하는것이고
spirng은 따로 도커 컨테이너로 관리한다.
docker in docker 문제가 있다고는 하나.. 더편리하다고하니 이렇게 따라갔다
그래서
1. 도커설치
2. 도커 컴포즈 설정파일 작성
시스템 업데이트 및 도커 설치
sudo apt-get update
sudo apt-get install apt-transport-https
sudo apt-get install ca-certificates
sudo apt-get install curl
sudo apt-get install software-properties-common
도커 공식 GPG(오픈 소스 암호화 도구 GNU Privacy Guard)
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add
도커 저장소 추가
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
sudo apt-get update
도커설치
sudo apt-get install docker-ce docker-ce-cli containerd.io
시스템 실행시 도커 자동 실행
sudo systemctl start docker
sudo systemctl enable docker
도커 버전 확인
docker --version
서버 홈 이동
- cd ~
폴더 생성 및 이동
mkdir fairtail
cd fairtail
도커 컴포즈 설정 파일 설정
vi docker-compose.yml
꼭꼭 도커 컴포즈 설정 끝나면 컴포즈 파일 내렸다가 다시 올리기!
ubuntu@ip-172-26------:~/fariytale$ cat docker-compose.yml
version: '3'
services:
jenkins:
build:
context: .
dockerfile: Dockerfile.jenkins
container_name: jenkins
restart: unless-stopped
privileged: true
user: "0:0"
ports:
- "5000:8080"
- "50000:50000"
volumes:
- jenkins_home:/var/jenkins_home
- /var/run/docker.sock:/var/run/docker.sock
- /usr/bin/docker:/usr/bin/docker
environment:
- JAVA_OPTS=-Djenkins.install.runSetupWizard=false -Xmx2g
networks:
- jenkins-network
mysql:
image: mysql:8.0
container_name: mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ssafy202
MYSQL_DATABASE: donghwasoop
MYSQL_USER: user
MYSQL_PASSWORD: ssafy202
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
networks:
- jenkins-network
redis:
image: redis:7
container_name: redis
restart: unless-stopped
ports:
- "6379:6379"
networks:
- jenkins-network
zookeeper:
image: confluentinc/cp-zookeeper:7.6.0
container_name: zookeeper
restart: unless-stopped
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
ports:
- "2181:2181"
networks:
- jenkins-network
kafka:
image: confluentinc/cp-kafka:7.6.0
container_name: kafka
restart: unless-stopped
depends_on:
- zookeeper
ports:
- "9092:9092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
networks:
- jenkins-network
portainer:
image: portainer/portainer-ce
container_name: portainer
restart: unless-stopped
ports:
- "9000:9000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
networks:
- jenkins-network
networks:
jenkins-network:
driver: bridge
volumes:
jenkins_home:
external: true
name: fariytale_jenkins_home
mysql_data:
portainer_data
# 난 vim이 좋아서
$ sudo apt-get install vim
jenkins안에서 도커실행을 위한 권한 주기
sudo usermod -aG docker jenkins
$ sudo systemctl start jenkins
jenkins 권한 주기
$ sudo cat /var/lib/jenkins/secrets/initialAdminPassword
jenkins 포트 번호 변경
기본적인 시스템 젠킨스 포트 변경 (이것만 변경했을때는 적용이 되지 않았다)
sudo nano /etc/default/jenkins
젠킨스 서비스에서 포트 변경
sudo nano /usr/lib/systemd/system/jenkins.service
젠킨스 재시작(이 명령어 실행전에 데몬 재실행을 하라고 뜬다 해주자 sudo systemctl daemon-reload )
sudo systemctl restart jenkins
확인
sudo systemctl status jenkins
도커 컴포즈라서 설치하고나면 도커 컴포즈로 내렸다가 다시 올려야했다.
1. docker-compose.yml 작성후
도커 컴포즈 적용 (컨테이너 시작)
2. docker-compose up -d
-d 백그라운드 detached
3. 도커 정상작동확인
dokcer ps
-- * 종료
docker compose down
볼륨으로 명시한 외부 볼륨은 삭제 x
주의! 그리고 볼륨으로 연결이 안되어있으면 내렸다가 다시 키면 젠킨스 설정했던거 다 날아간다.
이렇게 하면 젠킨스가 도커 컨테이너로 띄워진다.
(나는 근데 도커 컴포즈는 도커 컨테이너를 뭔가 관리해주는거라 생각했는데 바로 뛰워지길래 신기했다 -> 조금더 공부해야함 )
이후 난 젠킨스 포트번호가 5000번 이니까 ip주소:5000들어가고
suggested plugins하면
대박인게 웬만하면 다 못깜
이게 이유가 정확히 모르겠는데
젠킨스 lts여도 버전이 호환되지 않는게 많다고함
일일이 설치해야함
이게 또 여러번 플러그인 설치하면 되는게 있음
왼쪽에 jenkins관리 페이지가 있는데 설치안된 플러그인들이 error창으로 나옴
그걸
System Configuration plugins -> Available plugins에서 하나씩 입력후 설치하면 된다.

다설치하면 추가로 필요한 플러그인을 설치
Generic Webhook Trigger
Gitlab
Gitlab API
Gitlab Authentication
Mattermost Notification
Docker pipeline
나는 일단 frontend랑 backend 브랜치에 각각 웹훅을 걸어야하는 상황임
프로젝트 들어감 -> Settings -> Access Tokens에서 만들기

설정함. 그리고 생성된 토큰 바로 복사해두기
Jenkins관리 -> System -> Gitlab Connections 탭에서

+add 눌러서

jenkins
1. Jenkins에서 '새로운 Item' -> 'Pipeline'
2. 'Build Triggers'에서 'Build when a change is push to Gitlab~'
3. 뒤에 URL 기록해두고
4. 고급 버튼 에서 Secret token에서 Generate해서 토큰 저장

밑에 또 설정을

참고글
https://velog.io/@big9810/CI-CD
https://velog.io/@skb0516/ec2-Gitlab-Docker-Jenkins-https-Vue-Springboot-CICD-%EB%B0%B0%ED%8F%AC
아마 근데 branch 특정은 여기서 안하고 gitlab에서 설정해도 충분함 근데 그냥햇음
webhook탭에서 기록한 url과 secret toekn 넣음
branch는 정규식 브랜치 선택해서 ^backend 넣음 (배포 대상이 될 branch 넣기


frontend는 웹훅 트리거에 정규식을 제외하곤 다 같다!
gitlab credential 설정
숨기려는 파일 업로드
Jenkins 관리 -> Credentials -> Stores scoped to Jenkins의 (glabal) ->Add Credentails
Secret file선택 후 업로드

백 프 나누진 않고 하나의 env파일로 관리했다.
젠킨스 파일 작성부분
stages {
stage('Secrets Setup') {
steps {
withCredentials([
file(credentialsId: 'env-file', variable: 'EnvFile'),
]) {
sh '''
cp "$EnvFile" .env
chmod 644 .env
'''
}
}
}
BE
FROM bellsoft/liberica-openjdk-alpine:17
WORKDIR /app
COPY build/libs/FairyTale-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","app.jar"]
BE - jenkinsfile
pipeline {
agent any
environment {
CONTAINER_NAME = 'backend'
DOCKER_IMAGE = 'backend-image:latest'
}
stages {
stage('Secrets Setup') {
steps {
withCredentials([
file(credentialsId: 'env-file', variable: 'EnvFile'),
]) {
sh '''
cp "$EnvFile" .env
chmod 644 .env
'''
}
}
}
stage('Build Backend') {
steps {
//백엔드 빌드
sh '''
cd ./server
chmod +x ./gradlew
# gradle 빌드 시 테스트 스킵 (-x test)
# 데몬 비활성화 (--no-daemon)
./gradlew clean build -x test --no-daemon
'''
}
}
stage('Docker Build & Deploy') {
steps {
sh '''
# Docker 이미지 빌드
docker build -t backend-image:latest ./server
# 기존 컨테이너 중지 및 삭제
docker stop backend || true
docker rm backend || true
# 새 컨테이너 실행
docker run -d \
--name backend \
--env-file .env \
-p 8080:8080 \
backend-image:latest
'''
}
}
}
}
젠킨스 workspace에 들어가서 젠킨스의 작업경로를 통해 fe dist밑에있는 빌드 파일들도 확인할 수 있다.
관리자 권한
sudo -s
젠킨스 워크스페이스 경로
/var/lib/docker/volumes/fariytale_jenkins_home/_data/workspace/
당시 젠킨스파일에 cp -r 옵션을 통해 빌드된결과물을 옮기고 적용하는 부분이 있었는데 젠킨스를 도커로 뛰워놓고 볼륨으로 마운트를 하지 않아서 로그상으로는 잘뛰워지는데 해당 경로를 통해서 들어가보면 확인이 안되는 상태가 있었다.

fe는 문제가 jenkins가 실행되는 컨테이너 내부에 npm이 설치가 안되어서 오류가 났다
따라서 우리 서버안에서 dockerfile.jenkins 파일을 만들고 매핑했다.
FROM jenkins/jenkins:lts-jdk17
USER root
# Node.js 설치 (NodeSource LTS 기준)
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y nodejs \
&& npm install -g npm
USER jenkins
이렇게 작성하고 docker-compose.yml에
dockerfile: Dockerfile.jenkins 추가
빌드 및 실행
docker-compose down
docker-compose build-
docker-compose up -d
# 1단계: Node.js로 빌드
FROM node:18 AS build
WORKDIR /app
COPY . .
RUN npm install && npm run build || true
# 2단계: Nginx로 정적 파일 서빙
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
당연히 프론트는 nginx로 설정을 하기 때문에 nginx로 마운트한다.
pipeline {
agent any
stages {
stage('Secrets Setup') {
steps {
withCredentials([
file(credentialsId: 'env-file', variable: 'EnvFile'),
]) {
sh '''
cp "$EnvFile" ./frontend/.env
chmod 644 ./frontend/.env
'''
}
}
}
stage('Build Frontend') {
steps {
sh '''
cd frontend
npm install
npm run build || true
'''
}
}
stage('Deploy to Nginx') {
steps {
sh '''
mkdir -p /home/ubuntu/nginx
rm -rf /home/ubuntu/nginx/*
cp -r ./frontend/dist/* /home/ubuntu/nginx/
'''
}
}
}
}
본 프로젝트에서는 민감변수를 따로 처리하지 않았으나, 처리한다면 상단과 같은 방식으로 env파일 을 Jenkins Credential에서 관리하면 됨
프론트에서 빌드한 결과물을 Nginx의 정적파일로 마운트하여 사용.
Nginx에 https 적용, /api uri는 8080포트로 프록시하도록 적용
순서
nginx 설치 -> nginx 설정 수정 -> Https 발급(Let's Encrypt) -> nginx 중지 -> Docker로 nginx 생성 후 실행(기존 nginx설정, https 파일 마운트)
# Nginx 설치
sudo apt update
sudo apt install nginx
# Nginx 상태 확인
sudo systemctl status nginx
nginx 설정 수정
sudo vi /etc/nginx/sites-available/default
(vim 설치 안되어있으면 설치하고!)
nginx 설정
server {
listen 80 default_server;
listen [::]:80 default_server;
root /home/ubuntu/nginx;
index index.html;
server_name donghwasoop.com www.donghwasoop.com;
location / {
try_files $uri /index.html;
}
location /api/ {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
nginx 설정 문법 체크
sudo nginx -t
nginx 시작
sudo systemctl start nginx
sudo systemctl enable nginx
근데 원래는
✅ "프론트에서 빌드한 결과물을 Nginx의 정적파일로 마운트하여 사용"이란?
프론트엔드 앱 (React, Vue 등) 은 개발 서버에서는 npm run dev로 동작하지만,
배포 시에는 npm run build 명령어를 사용하여 HTML, CSS, JS 파일을 /dist 폴더에 빌드합니다.
이 /dist 폴더의 내용을 Nginx가 제공하는 /usr/share/nginx/html 같은 정적 루트에 복사하거나 마운트해서,
사용자가 http://example.com/에 접속하면 Nginx가 이 정적 파일을 응답
즉, React 앱 전체를 Nginx가 서빙하는 HTML 파일들로 제공하게 되는 것
근데 여기 들어가기 귀찮아서 홈파일에 nginx설정파일 두었다. (이설정은 Nginx의 설정파일에 root로 변경가능하다)
# certbot 설치
sudo apt install certbot python3-certbot-nginx
# 인증서 발급 (자동으로 Nginx 설정도 수정됨)
sudo certbot --nginx -d 도메인(ex.tripggukgguk.site)
# 갱신 스크립트 생성
sudo vim /home/ubuntu/nginx/renew-cert.sh
# 아래 적용
#!/bin/bash
docker stop nginx
certbot renew --quiet
docker start nginx
# 스크립트 권한 설정
chmod +x /home/ubuntu/nginx/renew-cert.sh
# crontab 설정
sudo crontab -e
# 아래 라인 추가 (매월 1일 새벽 3시에 실행)
0 3 1 * * /home/ubuntu/nginx/renew-cert.sh
도커 컴포즈로 해당 스크립트를 작성했기 때문에!
<ip 포트번호>:9000 으로 들어가면
비밀번호를 간단하게 입력하면 들어오게 된다.
해당 컨테이너들을 GUI로 로그 및 관리를 편하게 할 수 있어서 좋다.
nginx
/var/log/nginx/access.log
Spring Boot
/app/logs/app.log (logback 등으로 경로 지정 필요)
도커 컴포즈 환경설정에 추가로 작성한 부분!
version: '3.7'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.12
container_name: elasticsearch
environment:
- discovery.type=single-node
- xpack.security.enabled=false
ports:
- "9200:9200"
volumes:
- esdata:/usr/share/elasticsearch/data
networks:
- elk
kibana:
image: docker.elastic.co/kibana/kibana:7.17.12
container_name: kibana
ports:
- "5601:5601"
depends_on:
- elasticsearch
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
networks:
- elk
logstash:
image: docker.elastic.co/logstash/logstash:7.17.12
container_name: logstash
volumes:
- ./logstash/pipeline:/usr/share/logstash/pipeline
- ./logs:/logs
ports:
- "5044:5044"
depends_on:
- elasticsearch
networks:
- elk
volumes:
esdata:
networks:
elk:
elk를 하면서 신기하게 엘라스틱 서치 컨테이너를 뛰우면
서버가 터지는 현상이 발생했다.
지금 시도한 방법은
1. JVM의 힙메모리에 제한
을 뒀더니! 확실히 8g -> 1.3g로 낮아졌다.
힙메모리란게 java프로그램이 객체를 저장하고 데이터를 처리하는데 사용하는 주요 메모리 공간에 제약을 두는것
엘라스틱서치가 java위에서 돌아가기 때문에 설정해주었다.
docker-compose.yml
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms1g -Xmx1g" # 힙 메모리 제한 설정
# 메모리 사용량 확인
free -h
# Docker 컨테이너 리소스 확인
docker stats