- CI/CD는 지속적인 통합과 지속적인 배포라 합니다.
- Continious Integration
- Continious Delivery(Deployment)
- CI Process
- 코드작성
- 자동빌드
- 자동 테스트
- 자동화된 통합
- CD Process
- CI를 통한 자동화
- 자동 배포 준비
- 수동 승인
- 자동 배포
Gradle
- Gradle은 JVM 기반 프로젝트를 빌드하고 관리하기 위한 오픈소스빌드 자동화 도구
- 플러그인 시스템 :
- 다양한 플러그인을 제공하며, 다양한 프로젝트 유형 지원
- 의존성 관리: Gradle은 프로젝트의 의존성 관리
- 멀티 프로젝트 빌드: Gradle은 여러 프로젝트를 하나의 빌드로 관리 가능.
- 빌드 캐시 : 빌드 시간을 최적화하기 위해 Gradle은 이전 빌드에서 생성된 산출물을 캐시하고 빌드 시간을 단축
- 유연한 태스크: Gradle은 태스크 단위로 빌드 작업을 정의하며, 이를 활용하여 사용자 정의 빌드 프로세스를 만들 수 있음.
- reference : https://docs.gradle.org/current/userguide/writing_settings_files.html#writing_settings_files
Jenkins
- Jenkins는 오픈소스 CI/CD 도구입니다.
- 소프트웨어 개발 및 배포 프로세스를 자동화하고 개선하기 위해 사용되며, 개발자 및 팀이 소프트웨어 품질과 안정성을 관리 할 수 있도록 도와줍니다.
- 기능
- 지속적 통합
- 자동화 빌드
- 자동화 테스트
- 지속적 배포
- 플러그인 생태계
- 스케쥴링과 모니터링
아키텍처
주요 구성 요소 및 흐름
- Front End:
- 사용자 인터페이스(UI)를 담당하는 프런트엔드 애플리케이션입니다.
- 사용자는 이 프런트엔드를 통해 게시판 CRUD(생성, 읽기, 수정, 삭제) 작업을 수행합니다.
- Spring 애플리케이션:
- 중심에는 Spring 프레임워크를 사용한 애플리케이션이 있습니다.
- 이 애플리케이션은 Tomcat 서버 위에서 구동되며, EC2 인스턴스에서 호스팅됩니다.
- 프런트엔드에서 발생하는 모든 CRUD 요청을 처리하고, MySQL 또는 AuroraDB와 상호작용하여 데이터베이스 작업을 수행합니다.
- MySQL 또는 AuroraDB:
- 애플리케이션의 데이터베이스 역할을 합니다.
- 게시판 관련 데이터, 사용자 정보 등을 저장하고, Spring 애플리케이션에서 요청 시 데이터를 제공하거나 갱신합니다.
- Redis 또는 Elastic Cache:
- 캐시 레이어로, 데이터베이스의 정보를 캐싱하여 빠르게 제공하는 역할을 합니다.
- Spring 애플리케이션은 주로 DB 조회 전에 Redis에서 캐시된 데이터를 확인하여 성능을 최적화할 수 있습니다.
- Amazon S3:
- 게시판의 이미지 또는 파일을 저장하는 클라우드 스토리지입니다.
- 애플리케이션은 S3에 이미지를 업로드하고, 필요시 이미지를 조회합니다.
- CloudWatch:
- 애플리케이션의 로깅 및 모니터링을 담당하는 AWS 서비스입니다.
- 애플리케이션의 로그를 저장하고, 오류나 성능 문제를 감지하여 모니터링할 수 있습니다.
- Amazon SNS (Simple Notification Service):
- 예외, 오류, 모니터링 경고 등을 처리하는 알람 시스템입니다.
- CloudWatch에서 발생한 경고나 Spring 애플리케이션에서 발생한 이벤트를 SNS를 통해 전달하여 관리자가 인지할 수 있도록 합니다.
- CI/CD 파이프라인 (Jenkins):
- GitHub에서 코드가 변경되면, Webhook을 통해 Jenkins가 트리거됩니다.
- Jenkins는 새로운 코드를 빌드, 테스트, 배포하며 CI/CD 파이프라인을 자동화합니다.
- 배포된 코드는 EC2 인스턴스에서 구동되는 Spring 애플리케이션에 반영됩니다.
- Locust:
- 성능 테스트 도구로, 애플리케이션의 부하 테스트를 수행합니다.
- 개발자들은 Locust를 사용하여 시스템의 성능 및 확장성을 테스트하고, 결과를 분석하여 애플리케이션을 최적화할 수 있습니다.
- 개발자:
- 개발자는 코드를 작성하고 GitHub에 커밋하여 프로젝트를 관리합니다.
- 코드 리뷰, 테스트, 모니터링을 통해 애플리케이션을 유지보수하고 개선합니다.
- Jenkins를 통해 자동화된 빌드 및 배포 과정을 통해 빠르게 변경 사항을 반영할 수 있습니다.
아키텍처의 주요 특징
- 확장성: AWS의 다양한 서비스(S3, CloudWatch, SNS, EC2 등)를 사용하여 확장 가능한 아키텍처를 구축하였습니다. 필요에 따라 리소스를 확장하거나 축소할 수 있습니다.
- 자동화: CI/CD 파이프라인을 통해 코드 변경이 자동으로 배포되며, 이로 인해 개발 및 운영 효율성이 높아집니다.
- 성능 최적화: Redis 또는 Elastic Cache를 사용해 데이터베이스 부하를 줄이고, S3를 통해 대용량 데이터를 관리하며, 성능 테스트 도구인 Locust를 통해 시스템 성능을 지속적으로 모니터링 및 개선합니다.
- 모니터링 및 알림: CloudWatch와 SNS를 사용하여 시스템 상태를 실시간으로 모니터링하고, 문제 발생 시 즉각적으로 알림을 받을 수 있습니다.
이 아키텍처는 클라우드 기반 애플리케이션의 표준적인 구성 요소를 포함하고 있으며, 자동화된 배포와 성능 최적화, 실시간 모니터링을 통해 안정적이고 확장 가능한 시스템을 구축하려는 목적에 잘 맞습니다.
Jenkins 컨테이너 설치 및 기동
version: '3.8'
services:
jenkins:
image: jenkins/jenkins:lts-jdk17
container_name: jenkins
environment:
- TZ=Asia/Seoul
user: root
privileged: true
ports:
- 9090:8080
- 50000:50000
volumes:
- jenkins_data:/var/jenkins_home
- docker_sock:/var/run/docker.sock
volumes:
jenkins_data:
docker_sock:
이 compose.yml
파일은 Jenkins를 Docker 컨테이너로 실행하기 위한 설정 파일입니다. 이 파일의 각 섹션을 설명해드리겠습니다.
1. version: '3.8'
- Docker Compose 버전을 지정합니다.
3.8
은 Docker Compose 파일의 문법 및 기능을 정의하는 버전입니다.
2. services:
- Docker Compose에서 정의된 서비스 목록을 지정하는 섹션입니다. 이 예제에서는
jenkins
라는 서비스가 정의되어 있습니다.
3. jenkins:
jenkins
서비스의 설정을 정의하는 부분입니다.
3.1 image: jenkins/jenkins:lts-jdk11
- Jenkins 이미지를 지정합니다.
jenkins/jenkins:lts-jdk11
이미지는 LTS(장기 지원) 버전의 Jenkins를 사용하며, JDK 11이 포함된 이미지를 사용한다는 의미입니다.
3.2 container_name: jenkins
- 컨테이너의 이름을
jenkins
로 지정합니다. 이를 통해 컨테이너를 쉽게 식별할 수 있습니다.
3.3 environment:
- 컨테이너 내의 환경 변수를 설정하는 섹션입니다.
- TZ=Asia/Seoul
: 컨테이너의 시간대를 아시아/서울로 설정하여, 한국 표준시(KST)를 사용하도록 합니다.
3.4 user: root
- Jenkins 컨테이너가 root 사용자로 실행되도록 지정합니다. 이는 Jenkins가 시스템 리소스에 대해 더 높은 권한을 가지고 동작할 수 있도록 합니다.
3.5 privileged: true
- 이 옵션을
true
로 설정하면 컨테이너가 호스트 머신의 커널 기능에 더 넓은 접근 권한을 가지게 됩니다. - Jenkins가 Docker를 사용해 다른 컨테이너를 관리할 수 있도록 하기 위해 필요합니다.
3.6 ports:
- 컨테이너의 포트를 호스트의 포트와 연결(포트 매핑)합니다.
9090:8080
: 로컬 머신의 9090 포트를 Jenkins 컨테이너의 8080 포트에 매핑합니다. Jenkins의 웹 인터페이스에 접근하려면 웹 브라우저에서http://localhost:9090
을 사용하면 됩니다.50000:50000
: 로컬 머신의 50000 포트를 Jenkins 컨테이너의 50000 포트에 매핑합니다. 이는 Jenkins 마스터와 에이전트 간의 통신에 사용됩니다.
3.7 volumes:
- 호스트와 컨테이너 간의 파일 시스템을 공유하는 설정입니다.
jenkins_data:/var/jenkins_home
: Docker가 관리하는 명명된 볼륨(jenkins_data
)을 컨테이너 내부의/var/jenkins_home
디렉터리에 마운트합니다. 이 디렉터리는 Jenkins의 모든 설정, 플러그인, 작업 기록 등을 저장하는 경로입니다. 이 볼륨을 사용하면 Jenkins의 데이터를 영구적으로 저장할 수 있습니다.docker_sock:/var/run/docker.sock
: Docker가 관리하는 명명된 볼륨(docker_sock
)을 컨테이너 내부의/var/run/docker.sock
에 마운트합니다. 이를 통해 Jenkins가 호스트의 Docker 엔진과 상호작용할 수 있게 됩니다.
4. volumes:
- 명명된 볼륨을 정의하는 섹션입니다. 이 섹션에서 정의된 볼륨은 Docker에 의해 관리되며, 호스트 시스템의 특정 디렉터리에 저장되지 않고 Docker가 관리하는 영역에 저장됩니다.
jenkins_data
: Jenkins 데이터를 저장하는 명명된 볼륨입니다.docker_sock
: Docker 소켓 파일을 공유하기 위한 명명된 볼륨입니다.
요약
이 compose.yml
파일은 Jenkins를 Docker 컨테이너로 실행하기 위한 설정 파일입니다. 주요 기능은 다음과 같습니다:
- Jenkins 이미지로 LTS 버전의 Jenkins(JDK 11 포함)를 사용합니다.
- Jenkins의 데이터를 영구적으로 보존하기 위해 Docker가 관리하는 명명된 볼륨을 사용하여
/var/jenkins_home
디렉터리를 호스트와 공유합니다. - Jenkins가 호스트의 Docker 엔진과 상호작용할 수 있도록 Docker 소켓을 공유합니다.
- Jenkins 웹 UI는 포트 9090을 통해 접근할 수 있습니다.
- Jenkins는 루트 사용자 권한으로 실행되며, 호스트 시스템의 리소스에 더 높은 접근 권한을 가집니다.
이 설정은 Jenkins를 Docker 환경에서 안정적으로 실행하고, 지속적인 통합/배포(CI/CD) 작업을 수행하기 위한 모든 요소를 갖추고 있습니다.
실행 명령어
docker-compose up
명령어 실행 결과
[+] Running 13/1
✔ jenkins 12 layers [⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿] 0B/0B Pulled 19.1s
[+] Running 4/4
✔ Network jenkins_default Created 0.0s
✔ Volume "jenkins_jenkins_data" Created 0.0s
✔ Volume "jenkins_docker_sock" Created 0.0s
✔ Container jenkins Created 0.3s
Attaching to jenkins
jenkins | Running from: /usr/share/jenkins/jenkins.war
jenkins | webroot: /var/jenkins_home/war
jenkins | 2024-09-02 05:30:46.559+0000 [id=1] INFO winstone.Logger#logInternal: Beginning extraction from war file
jenkins | 2024-09-02 05:30:47.155+0000 [id=1] WARNING o.e.j.s.handler.ContextHandler#setContextPath: Empty contextPath
jenkins | 2024-09-02 05:30:47.194+0000 [id=1] INFO org.eclipse.jetty.server.Server#doStart: jetty-10.0.20; built: 2024-01-29T20:46:45.278Z; git: 3a745c71c23682146f262b99f4ddc4c1bc41630c; jvm 11.0.24+8
jenkins | 2024-09-02 05:30:47.377+0000 [id=1] INFO o.e.j.w.StandardDescriptorProcessor#visitServlet: NO JSP Support for /, did not find org.eclipse.jetty.jsp.JettyJspServlet
jenkins | 2024-09-02 05:30:47.408+0000 [id=1] INFO o.e.j.s.s.DefaultSessionIdManager#doStart: Session workerName=node0
jenkins | 2024-09-02 05:30:47.708+0000 [id=1] INFO hudson.WebAppMain#contextInitialized: Jenkins home directory: /var/jenkins_home found at: EnvVars.masterEnvVars.get("JENKINS_HOME")
jenkins | 2024-09-02 05:30:47.798+0000 [id=1] INFO o.e.j.s.handler.ContextHandler#doStart: Started w.@6778aea6{Jenkins v2.462.1,/,file:///var/jenkins_home/war/,AVAILABLE}{/var/jenkins_home/war}
jenkins | 2024-09-02 05:30:47.810+0000 [id=1] INFO o.e.j.server.AbstractConnector#doStart: Started ServerConnector@5829e4f4{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}
jenkins | 2024-09-02 05:30:47.820+0000 [id=1] INFO org.eclipse.jetty.server.Server#doStart: Started Server@63fdab07{STARTING}[10.0.20,sto=0] @1558ms
jenkins | 2024-09-02 05:30:47.823+0000 [id=24] INFO winstone.Logger#logInternal: Winstone Servlet Engine running: controlPort=disabled
jenkins | 2024-09-02 05:30:48.010+0000 [id=32] INFO jenkins.InitReactorRunner$1#onAttained: Started initialization
jenkins | 2024-09-02 05:30:48.038+0000 [id=41] INFO jenkins.InitReactorRunner$1#onAttained: Listed all plugins
jenkins | 2024-09-02 05:30:48.509+0000 [id=30] INFO jenkins.InitReactorRunner$1#onAttained: Prepared all plugins
jenkins | 2024-09-02 05:30:48.512+0000 [id=30] INFO jenkins.InitReactorRunner$1#onAttained: Started all plugins
jenkins | 2024-09-02 05:30:48.518+0000 [id=30] INFO jenkins.InitReactorRunner$1#onAttained: Augmented all extensions
jenkins | 2024-09-02 05:30:48.644+0000 [id=32] INFO jenkins.InitReactorRunner$1#onAttained: System config loaded
jenkins | 2024-09-02 05:30:48.644+0000 [id=32] INFO jenkins.InitReactorRunner$1#onAttained: System config adapted
jenkins | 2024-09-02 05:30:48.644+0000 [id=32] INFO jenkins.InitReactorRunner$1#onAttained: Loaded all jobs
jenkins | 2024-09-02 05:30:48.646+0000 [id=32] INFO jenkins.InitReactorRunner$1#onAttained: Configuration for all jobs updated
jenkins | 2024-09-02 05:30:48.658+0000 [id=54] INFO hudson.util.Retrier#start: Attempt #1 to do the action check updates server
jenkins | WARNING: An illegal reflective access operation has occurred
jenkins | WARNING: Illegal reflective access by org.codehaus.groovy.vmplugin.v7.Java7$1 (file:/var/jenkins_home/war/WEB-INF/lib/groovy-all-2.4.21.jar) to constructor java.lang.invoke.MethodHandles$Lookup(java.lang.Class,int)
jenkins | WARNING: Please consider reporting this to the maintainers of org.codehaus.groovy.vmplugin.v7.Java7$1
jenkins | WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
jenkins | WARNING: All illegal access operations will be denied in a future release
jenkins | 2024-09-02 05:30:48.945+0000 [id=40] INFO jenkins.install.SetupWizard#init:
jenkins |
jenkins | *************************************************************
jenkins | *************************************************************
jenkins | *************************************************************
jenkins |
jenkins | Jenkins initial setup is required. An admin user has been created and a password generated.
jenkins | Please use the following password to proceed to installation:
jenkins |
jenkins | 1bf45f09281d4e5baef7933163723dd6
jenkins |
jenkins | This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
jenkins |
jenkins | *************************************************************
jenkins | *************************************************************
jenkins | *************************************************************
jenkins |
jenkins | 2024-09-02 05:30:52.841+0000 [id=38] INFO jenkins.InitReactorRunner$1#onAttained: Completed initialization
jenkins | 2024-09-02 05:30:52.853+0000 [id=23] INFO hudson.lifecycle.Lifecycle#onReady: Jenkins is fully up and running
jenkins | 2024-09-02 05:30:53.796+0000 [id=54] INFO h.m.DownloadService$Downloadable#load: Obtained the updated data file for hudson.tasks.Maven.MavenInstaller
jenkins | 2024-09-02 05:30:53.797+0000 [id=54] INFO hudson.util.Retrier#start: Performed the action check updates server successfully at the attempt #1
실행 로그를 확인하면 비밀번호
1bf45f09281d4e5baef7933163723dd6
를 확인 할 수 있습니다.http://localhost:9090 으로 접속하여 언급된 비밀번호를 입력해줍니다.
Customize Jenkins에서는
Install suggested plugins
버튼을 눌러 기본적으로 가장 많은 빈도로 추천되고 사용되는 플러그 인들이 설치되도록 합니다.Create First Admin User
웹화면에서 관리자 계정 정보를 입력합니다.아이템 출가 -> 아이템 이름 입력, free style project 선택 -> 구성, 설정
github repository url 설정
board-server
레포지토리 >settings
>webhooks
>Add Webhook
>- Payload URL : Jenkins 서버 URL을 입력(ngrok를 이용 Local에 서버를 띄워도 인터넷 연결 가능) - Content type : application/json
Build Steps 구성
- 사전 구성
- Jenkins 관리 -> Tools -> Gradle installations -> Add Gradle
- name 입력 : board-server
- Install automatically 체크 [v]
- Version 입력: Gradle 8.2.1
- Jenkins 관리 -> Tools -> Gradle installations -> Add Gradle
- Invoke Gradle
- Gradle Version :
board-server
(위 사전 구성을 통해 설정된 정보를 선택)board-server Configuration 마무리
- Gradle Version :
- 잘못된 부분은 없는지 다시 검토하고 저장 버튼을 누른다.
빌드 이후 jar파일
- build now 혹은 지금 빌드 버튼을 클릭
- 성공한다면,
/var/jenkins_home/workspace/board-server/build/libs/
여기서 project가 board-server 이므로 디렉토리 이름이 그렇게 명명되었다. 만약 다른 job(project)일 경우 이 부분은 동적으로 변경된다.
AWS EC2 설정
0.EC2 인스턴스 생성
- 프리티어 기준으로 생성합니다.
1. SSH 키 페어 준비
AWS에서 EC2 인스턴스를 생성할 때 사용한 키 페어 (예: my-key.pem
)가 필요합니다. 이 키 페어는 EC2 인스턴스에 접속하기 위한 보안 인증서 역할을 합니다.
만약 키 페어가 없다면, 새로 인스턴스를 생성할 때 새로운 키 페어를 만들 수 있습니다. 기존 키를 분실한 경우에는 기존 인스턴스에 대한 SSH 접속을 복구할 수 없으므로, 인스턴스를 복구하는 절차가 필요합니다.
2. EC2 인스턴스 정보 확인
- 퍼블릭 IP 주소 또는 퍼블릭 DNS: EC2 인스턴스의 퍼블릭 IP 또는 퍼블릭 DNS를 확인해야 합니다. 이는 EC2 대시보드에서 인스턴스의 세부 정보에서 확인할 수 있습니다.
- 유저 이름: 인스턴스의 운영 체제에 따라 접속할 때 사용할 유저 이름이 다릅니다.
- Amazon Linux 또는 RHEL:
ec2-user
- Ubuntu:
ubuntu
- CentOS:
centos
- Debian:
admin
또는root
- SUSE:
ec2-user
또는root
- Windows: RDP (리모트 데스크탑 프로토콜)를 사용
- Amazon Linux 또는 RHEL:
3. SSH 권한 설정
.pem 파일은 SSH 접속 시 중요한 인증서이므로 적절한 권한을 설정해야 합니다. .pem
파일에 올바른 권한이 설정되지 않으면 SSH 접속이 거부됩니다.
다음 명령어로 권한을 수정합니다:
chmod 400 /path/to/your-key.pem
4. SSH로 접속하기
SSH 명령어를 사용해 EC2 인스턴스에 접속할 수 있습니다. 다음 명령어를 터미널에서 실행하세요:
ssh -i /path/to/your-key.pem ec2-user@<EC2-퍼블릭-IP>
예를 들어, my-key.pem
파일이 ~/.ssh/my-key.pem
에 있고, EC2 인스턴스의 퍼블릭 IP가 203.0.113.25
라면 명령어는 다음과 같습니다:
ssh -i ~/.ssh/my-key.pem ec2-user@203.0.113.25
5. SSH 접속에 성공했을 경우
성공적으로 접속되면, EC2 인스턴스의 터미널에 접속하게 됩니다. 여기서 원격 서버에서의 작업을 수행할 수 있습니다.
6. 문제가 발생할 경우
- 보안 그룹 확인: EC2 인스턴스의 보안 그룹에서 인바운드 규칙을 확인하세요. SSH 접속(포트 22)이 열려 있어야 합니다.
- 규칙 예시:
TCP
,Port 22
,Source: 0.0.0.0/0
(모든 IP에서 접속 가능하게 설정)
- 규칙 예시:
- 프라이빗 인스턴스: 인스턴스가 퍼블릭 IP를 가지고 있지 않다면, 퍼블릭 IP가 있는 인스턴스를 중간 서버(bastion host)로 설정해 프라이빗 인스턴스로 접속해야 합니다.
7. 예시 명령어 요약
- Amazon Linux 또는 RHEL:
ssh -i /path/to/your-key.pem ec2-user@<EC2-퍼블릭-IP>
- Ubuntu:
ssh -i /path/to/your-key.pem ubuntu@<EC2-퍼블릭-IP>
이 단계를 따라 EC2 인스턴스에 성공적으로 SSH로 접속할 수 있을 것입니다.
8. Jenkins를 이용하여 빌드 이후 자동 배포
- ~/manage/pluginManager/available URL로 접속합니다.
- publish over ssh 를 검색 후 나오는 해당 플러그인을 다운로드 합니다.
- System 변수 설정
- ~/manage/configure 로 접속하여 환경 변수 설정을 해줍니다.
- SSH Servers 란에 갑니다.
- Name
- Hostname
- Username
- Remote Directory
- SSH Servers 란에 갑니다.
- ~/manage/configure 로 접속하여 환경 변수 설정을 해줍니다.
- "~/job/board-server/configure" URL로 이동합니다.
주요 설정 항목은 다음과 같습니다:
1.빌드 환경 섹션:
- "Send files or execute commands over SSH after the build runs" 옵션이 체크되어 있습니다. 이는 빌드가 완료된 후에 SSH를 통해 서버로 파일을 전송하거나 명령을 실행하도록 설정한 것입니다.
2.SSH 서버 설정:
- SSH Server: 이름이
board-server-ec2
로 설정되어 있으며, 이는 EC2 인스턴스를 대상으로 설정된 SSH 연결을 의미합니다. - Transfer Set:
- Source files:
C:\ProgramData\Jenkins\workspace\board-server\build\libs\board-server-0.0.1-SNAPSHOT.jar
파일을 전송하려고 설정되어 있습니다. 이 경로는 Jenkins 워크스페이스 내에서 빌드된 파일의 경로를 나타냅니다. - Remove prefix:
C:\ProgramData\Jenkins\workspace\board-server\build\libs
경로는 원본 경로에서 제외하여 전송할 때는 파일 이름만 남기겠다는 설정입니다. - Remote directory:
/home/ec2-user/
로 설정되어 있으며, 이는 EC2 서버의 사용자 홈 디렉터리로 해당 파일이 전송된다는 것을 의미합니다.
- Source files:
이 설정은 Jenkins가 빌드를 완료한 후, EC2 서버로 빌드된 JAR 파일을 자동으로 전송하는 작업을 수행하도록 구성된 것입니다.
'프레임워크 > 자바 스프링' 카테고리의 다른 글
Webflux 소개 (0) | 2024.09.17 |
---|---|
접속자 대기열 시스템 #1: 시스템 설계와 Spring WebFlux, Redis (3) | 2024.09.09 |
대규모 트래픽 게시판 구축 시리즈 #13: 알림 서비스 구현과 통합 - AWS SNS 및 Slack (0) | 2024.09.07 |
대규모 트래픽 게시판 구축 시리즈 #12: 성능 테스트 (5) | 2024.09.07 |
대규모 트래픽 게시판 구축 시리즈 #11: 로깅, 예외처리 (0) | 2024.09.07 |