From 5a1b6b2d1612726dff3d8928fb627d4da282e280 Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Tue, 16 Jun 2026 21:31:39 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20Docker=20=EA=B8=B0=EB=B0=98=20CI=20?= =?UTF-8?q?=EC=9B=8C=ED=81=AC=ED=94=8C=EB=A1=9C=EC=9A=B0=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 47 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..28108e5b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,47 @@ +name: CI + +on: + push: + branches: [ "main" ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: main 체크아웃 + uses: actions/checkout@v4 + + - name: 자바 설정 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - name: gradlew 실행 권한 부여 + run: chmod +x gradlew + working-directory: ./Joonseok + + - name: gradle 세팅 + uses: gradle/actions/setup-gradle@v4 + + - name: gradle 빌드 + run: ./gradlew clean build -x test + working-directory: ./Joonseok + + - name: Docker Hub 로그인 + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Docker Buildx 설정 + uses: docker/setup-buildx-action@v3 + + - name: Docker 이미지 빌드 및 푸시 + uses: docker/build-push-action@v7 + with: + context: ./Joonseok + push: true + tags: ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGENAME }}:latest + platforms: linux/amd64, linux/arm64 From c3c94f696a0816116cdfe7a52da16efe8619bf95 Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Tue, 16 Jun 2026 21:31:56 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20Spring=20Boot=20=EC=95=B1=EC=9D=84?= =?UTF-8?q?=20=EC=9C=84=ED=95=9C=20Dockerfile=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- Joonseok/Dockerfile | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Joonseok/Dockerfile diff --git a/Joonseok/Dockerfile b/Joonseok/Dockerfile new file mode 100644 index 00000000..cd86d8ab --- /dev/null +++ b/Joonseok/Dockerfile @@ -0,0 +1,7 @@ +FROM eclipse-temurin:21-jdk +WORKDIR /app + +ARG JAR_FILE=build/libs/*.jar +COPY ${JAR_FILE} project.jar + +ENTRYPOINT ["java", "-Dspring.profiles.active=docker", "-jar", "project.jar"] From 77e96b0e3c89a9df5b494e65a7abcf4e2d3fe6fe Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Tue, 16 Jun 2026 21:32:22 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20Nginx=20=EB=A1=9C=EB=93=9C=EB=B0=B8?= =?UTF-8?q?=EB=9F=B0=EC=8B=B1=20=EC=84=A4=EC=A0=95=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- Joonseok/nginx/conf.d/default.conf | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 Joonseok/nginx/conf.d/default.conf diff --git a/Joonseok/nginx/conf.d/default.conf b/Joonseok/nginx/conf.d/default.conf new file mode 100644 index 00000000..c8e7f5c5 --- /dev/null +++ b/Joonseok/nginx/conf.d/default.conf @@ -0,0 +1,22 @@ +upstream backend { + least_conn; + server app1:8080 weight=3; + server app2:8080 weight=1; +} + +server { + listen 80; + server_name _; + + location / { + proxy_pass http://backend; + 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; + + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } +} From 552e17b7b1f6f4390eded27256acf7620faadf30 Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Tue, 16 Jun 2026 22:07:51 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=EB=A1=9C=EB=93=9C=EB=B0=B8?= =?UTF-8?q?=EB=9F=B0=EC=8B=B1=20=EC=8B=9C=EA=B0=81=EC=A0=81=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20API=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../study/global/config/SecurityConfig.java | 3 ++- .../web/controller/ServerInfoController.java | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 Joonseok/src/main/java/com/umc/study/global/web/controller/ServerInfoController.java diff --git a/Joonseok/src/main/java/com/umc/study/global/config/SecurityConfig.java b/Joonseok/src/main/java/com/umc/study/global/config/SecurityConfig.java index bc43701b..2eb9e558 100644 --- a/Joonseok/src/main/java/com/umc/study/global/config/SecurityConfig.java +++ b/Joonseok/src/main/java/com/umc/study/global/config/SecurityConfig.java @@ -26,7 +26,8 @@ public class SecurityConfig { "/swagger-ui/**", "/swagger-resources/**", "/v3/api-docs/**", - "/auth/**" // sign-up, login request allow + "/auth/**", // sign-up, login request allow + "/server/**" // load balancing check }; @Bean diff --git a/Joonseok/src/main/java/com/umc/study/global/web/controller/ServerInfoController.java b/Joonseok/src/main/java/com/umc/study/global/web/controller/ServerInfoController.java new file mode 100644 index 00000000..47cc3bea --- /dev/null +++ b/Joonseok/src/main/java/com/umc/study/global/web/controller/ServerInfoController.java @@ -0,0 +1,24 @@ +package com.umc.study.global.web.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Map; + +@RestController +@RequestMapping("/server") +public class ServerInfoController { + + @GetMapping("/info") + public ResponseEntity> getServerInfo() throws UnknownHostException { + InetAddress host = InetAddress.getLocalHost(); + return ResponseEntity.ok(Map.of( + "hostname", host.getHostName(), + "ip", host.getHostAddress() + )); + } +} From 861a4a14ccfa718278e2d5ed0ebd71731f3fe7a5 Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Tue, 16 Jun 2026 22:09:36 +0900 Subject: [PATCH 5/6] =?UTF-8?q?docs:=20ci/cd=20=EA=B4=80=EB=A0=A8=20yaml?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Joonseok/.github/workflows/cd.yml | 59 +++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Joonseok/.github/workflows/cd.yml diff --git a/Joonseok/.github/workflows/cd.yml b/Joonseok/.github/workflows/cd.yml new file mode 100644 index 00000000..5b7d6839 --- /dev/null +++ b/Joonseok/.github/workflows/cd.yml @@ -0,0 +1,59 @@ +name: CD + +on: + workflow_run: + workflows: ["CI"] + types: [completed] + branches: [main] + +permissions: + contents: read + +env: + BACKEND_IMAGE: ${{ secrets.DOCKER_USERNAME }}/kkumteul-backend + FASTAPI_IMAGE: ${{ secrets.DOCKER_USERNAME }}/kkumteul-ai + +jobs: + deploy: + name: Deploy to EC2 + if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Copy deploy files to EC2 + uses: appleboy/scp-action@v0.1.7 + with: + host: ${{ secrets.SERVER_HOST }} + username: ${{ secrets.SERVER_USER }} + key: ${{ secrets.SERVER_SSH_KEY }} + port: 22 + source: "docker-compose.yml,nginx/conf.d/default.conf" + target: "/home/${{ secrets.SERVER_USER }}/app" + + - name: Deploy over SSH + uses: appleboy/ssh-action@v1.2.0 + with: + host: ${{ secrets.SERVER_HOST }} + username: ${{ secrets.SERVER_USER }} + key: ${{ secrets.SERVER_SSH_KEY }} + port: 22 + envs: BACKEND_IMAGE + script: | + cd /home/${{ secrets.SERVER_USER }}/app + echo "BACKEND_IMAGE=${BACKEND_IMAGE}:latest" > .env + echo "FASTAPI_IMAGE=${FASTAPI_IMAGE}:latest" >> .env + + echo "[1/4] Pull latest images..." + docker pull ${BACKEND_IMAGE}:latest + docker pull ${FASTAPI_IMAGE}:latest + + echo "[2/4] Restart with docker compose..." + docker compose up -d --remove-orphans + + echo "[3/4] Force-recreate nginx for config changes..." + docker compose up -d --force-recreate nginx + + echo "[4/4] Cleanup unused images..." + docker image prune -f From 6825dd8fea725d4d4a2bcf8b727b27dbf1a8739f Mon Sep 17 00:00:00 2001 From: Joonseok-Lee Date: Tue, 16 Jun 2026 22:15:03 +0900 Subject: [PATCH 6/6] =?UTF-8?q?docs:=20=ED=95=B5=EC=8B=AC=ED=82=A4?= =?UTF-8?q?=EC=9B=8C=EB=93=9C=20=EC=A0=95=EB=A6=AC=20md=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Joonseok/keyword_summary/ch10.md | 131 +++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 Joonseok/keyword_summary/ch10.md diff --git a/Joonseok/keyword_summary/ch10.md b/Joonseok/keyword_summary/ch10.md new file mode 100644 index 00000000..c2920b1b --- /dev/null +++ b/Joonseok/keyword_summary/ch10.md @@ -0,0 +1,131 @@ + + +- 클라우드 컴퓨팅이란? + + + + 인터넷을 통해 필요할 때마다 컴퓨팅 리소스, 즉 물리적 또는 가상 서버, 데이터 스토리지, 네트워킹 기능, 애플리케이션 개발 툴, 소프트웨어, AI 기반 분석 플랫폼 등에 사용한 만큼만 접근할 수 있는 서비스 + + 온-프레미스 방식과 대조되는 방식으로, 보안성이 다소 떨어질 수 있으나, 유연성과 확장성이 매우 뛰어나다. + + - 비용 및 효율성 + - 속도 및 민첩성 + - 무제한 확장성 + - 전략적 가치 향상 + + 현대적인 클라우드 아키텍처는 이하의 항목을 포함한다. + + - 데이터 센터 + - 네트워킹 기능 + - 가상화 +- AWS? GCP? + + + + ### AWS + + Amazon Web Service의 약자. + 클라우드 컴퓨팅에 필요한 거의 모든 서비스를 제공하며, 사용한 만큼의 비용만 지불하면 되는 형태이다. + + EC2라고 불리는 다양한 가상 컴퓨터를 제공한다. 리소스 선택폭도 굉장히 넓은 편 + + 저장공간 역시 넓다 + + 네트워크 인프라 역시 더 크다. + + ### GCP + + Google Cloud Platform + AI 및 기계 학습 부문에서 빠르게 주목받고 있다. + 가격 측면에서 더욱 직관적이지만, 솔루션이나 가격 유연성이 부족하다. + + AWS 만큼의 선택폭은 없지만, 사용하면 할 수록 자동 할인이 있다. + + AI 학습이나, Google Cloud와 연결할 수 있다는 장점이 있다. + + 프라이빗, 데이터 집약적 운영에 더 강하다. + +- 환경변수 처리 방법과 왜 환경변수로 민감 정보를 가려야 하는가? + 1. 서버에 직접 등록한다. + Github가 제공하는 CI/CD 등 기능 사용 불가 + 2. Git Secrets에 등록한다 + CI/CD 사용 가능 + + 환경변수는 대부분 비용과 직결되는 문제로, 해커에 의한 악의적 요청으로 지불해야 할 대금이 폭증하지 않도록 가린다. + +- yml 환경 분리 방법 + + 각 환경마다 빌드해야 할 것과 빌드 하지 말아야 할 것들을 분리하여 + `@Profile(value = NAME )` 으로 분기한다 + + 메인 설정 파일인 application.yml(applicaiton.properties)에 환경변수로 + `spring.profiles.active=${이름}` 으로 환경마다 다른 설정 파일을 주입하도록 한다. + +- Docker와 .jar vs Docker 이미지 + + Docker는 개발자와 개발자, 개발 환경과 운영 환경의 차이를 없애기 위해 프로젝트를 애플리케이션 단위로 포장하는 툴이다. + + .jar : java 빌드 산출물. JVM에 의해 읽히는 바이트 파일 등 + + Docker 이미지 : Dockerfile에 의해 생성되는 산출물. 애플리케이션 실행에 필요한 모든 것들을 담고 있다. +--- +- Sticky Session vs Session Clustering + + stateful 서버의 한계점. 서버를 확장할 때, 세션 저장소 의존성이 생기므로, 확장성과 유연성이 떨어진다. + + ### Stick sesison + + 세션은 각 서버에 저장되어 종속적인 방식. + + 인프라가 단순해지고, 로컬 메모리 접근이므로 빠르다. + + - 세션이 서버 종속이므로 서버가 다운되면 세션도 전부 사라진다. + - 한 서버에 무거운 세션이 pinned → 부하 불균등 문제 + + ### Session Clustering + + 모든 서버가 세션을 공유한다. + + 1. 세션 복제식 + 2. 외부 저장 서버 + + 장점 : 스티키 세션의 단점을 해결 + + 단점 : 접속마다 네트워킹 왕복 필요 → 오버헤드 상승 + +- blue-green 배포 + + 서버를 2개 띄우고, 트래픽을 통째로 전환하는 무중단 배포 전략 + + - Blue : 현재 트래픽을 받는 서버 + - Green : 유휴 상태에 있는 서버 + - 새 버전 업로드 + - 스모크 테스트 + - 헬스 체크… + + 모든 테스트가 종료되면 모든 트래픽을 현재 Blue → Green으로 이동 후 Green을 Blue 서버로 지정 + + 장애 발생 시, 기존 Blue로 즉시 전환하면 되므로 문제 없음 + +- Gzip 압축 및 캐싱 + + ### Gzip 압축 + + HTTP 응답 본문을 DEFLATE 알고리즘으로 압축하고, wire로 보내는 본문 인코딩 방법. + HTTP 응답 본문은 반복 패턴이 많아서 크게 이득볼 수 있다. + + - 클라이언트가 `Accept-Encoding: gzip, deflate, br`로 압축 해제 가능함을 알림 + - 서버가 압축 후 `Content-Encoding: gzip` + `Vary: Accept-Encoding` \ No newline at end of file