진짜 우연히 Prometheus와 연동된 Grafana 서버를 들어가서, 배포한 웹사이트가 잘 동작을 하나 확인을 했는데...
10월 9일 10시 48분 경에 cpu사용량 97퍼 가 되어있는 기이한 장면을 목격하고야 말았습니다.
또한 ram으로 데이터를 올리는 page-in 과 데이터를 ram으로 내리는 page-out 도 매우 높은 것을 발견하였습니다.
이 외에도 2틀 혹은 하루에 한번 정도 cpu사용률 및 page-in, page-out을 비롯한 network-traffic, page-fault rate역시 모두 높은 시간대가 있는 것을 확인
이 시간대에 자원 사용 패턴을 분석하여, 어떤 애플리케이션이나 프로세스가 높은 부하를 유발하는지 파악할 필요가 있다고 판단하였습니다.
왜 위처럼 cpu사용률이 급증 했을까 궁금해서 해당 시간대의 docker log를 열어보니 다음과 같습니다.
org.springframework.dao.IncorrectResultSizeDataAccessException: Query did not return a unique result: 6 results were returned
즉 채용공고가 update되는 과정에서 기존의 CompanyRepository에서 회사이름을 통해서 company를 찾는데, 이때 unique하게 반환하도록 설정되어있는데, 6개가 반환된 것입니다.
그래서 Cpu사용률이 올라간 이유를 총 3가지 정도로 생각해보았습니다.
- 예외가 터지면서 트랜잭션이 정상적으로 종료되지 않고, 누수가 생겨서 자원낭비 혹은 db락이 생겼다.
- 사람인 api를 가지고 올 때의 과정에서 네트워크 지연이 생긴다.
- CI/CD과정에서 생기는 오류(pr하고 머지한 기간이랑 비슷해서 생각)
그래서 저는 현재 도커내에 모든 파일을 그 때 그 때 다운받아서 빌드 및 cpu를 많이 사용하는듯해서, 이를 CI/CD과정에서 Github Caching을 이용해서 해결하고자 했습니다.
현재 github action을 활용한 CI/CD 코드는 다음과 같습니다
name: Docker Image 만들기
# Event : 실행되는 시점
on:
push:
branches:
- main
pull_request:
branches:
- main
# 하나의 워크플로우에 1개 이상의 잡
# 잡은 병렬 수행
jobs:
My-Deploy-Job:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: recursive
token: ${{secrets.PAT_TOKEN}}
- name: JDK 17 설치
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Spring Project 빌드하기
run: |
cd ./server/
chmod +x ./gradlew
./gradlew clean build -x test
- name: Docker 이미지 만들고 Dockerhub에 푸시
run: |
docker login -u ${{secrets.DOCKER_USERNAME}} -p ${{secrets.DOCKER_PASSWORD}}
docker build -t ${{secrets.DOCKER_REPO}}/devroute-server:${{github.sha}} .
docker push ${{secrets.DOCKER_REPO}}/devroute-server:${{github.sha}}
- name: EC2에서 이미지 pull 후, 컨테이너 실행
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_SSH_USER }}
key: ${{ secrets.PRIVATE_KEY }}
script: |
docker kill $(docker ps -qf expose=8080) 2> /dev/null || echo 'No container running on port 8080'
docker run -d -p 8080:8080 ${{secrets.DOCKER_REPO}}/devroute-server:${{github.sha}}
checkout -> JDK 17 설치 -> Spring Project 빌드하기->dockerfile을 통해서 이미지를 만들고, 이를 DockerHub에 푸시-> Ec2에서 이미지 pull후, dockerhub에 만든 컨테이너를 실행
현재 github cache를 활용한 CI/CD 코드는 다음과 같습니다
name: Docker 이미지 만들기
# 이벤트: 실행되는 시점을 정의합니다
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
My-Deploy-Job:
runs-on: ubuntu-latest # Ubuntu 환경에서 실행
steps:
- name: 코드 체크아웃 # GitHub 리포지토리의 소스 코드를 가져옵니다
uses: actions/checkout@v3 # GitHub의 공식 액션 사용
with:
submodules: recursive # 서브모듈 포함 체크아웃
token: ${{ secrets.PAT_TOKEN }} # Private Access Token 사용
# Dockerfile이 변경된 경우에만 캐시 저장 여부 확인
- name: Check for Dockerfile changes
id: dockerfileChanged
run: |
git fetch origin main
git diff --quiet origin/main -- ./dev-route/Dockerfile || echo "changed=true" >> $GITHUB_OUTPUT
- name: Docker Buildx 설치 및 설정 # Buildx 설치 및 Docker 컨테이너 드라이버 사용 설정
uses: docker/setup-buildx-action@v2
with:
install: true
driver: docker-container # docker-container 드라이버 설정
buildkitd-flags: --debug # 추가 플래그 (옵션)
- name: JDK 17 설치 # JDK 17을 설치하여 Java 환경을 설정합니다
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin' # Temurin 배포판 사용
- name: Spring 프로젝트 빌드 # Spring 프로젝트를 빌드합니다
run: |
cd ./dev-route/
chmod +x ./gradlew # gradlew 파일에 실행 권한 부여
./gradlew clean build -x test # 테스트 제외하고 빌드 실행
# 항상 캐시 복원 시도 (이전 캐시가 있고, dockerfile이 변경이 안되어있으면 복원)
- name: Docker 레이어 캐시 복원
if: steps.dockerfileChanged.outputs.changed != 'true'
uses: actions/cache/restore@v4
with:
path: /tmp/.buildx-cache # Docker 빌드 캐시를 복원할 경로
key: ${{ runner.os }}-buildx-${{ hashFiles('Dockerfile') }} # 캐시 키 설정
restore-keys: |
${{ runner.os }}-buildx-
- name: Docker 이미지 생성 및 DockerHub에 푸시 # Docker 이미지를 빌드하고 DockerHub에 푸시합니다
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} # DockerHub 로그인
cd ./dev-route/ # dev-route 디렉토리로 이동
docker buildx build --cache-from=type=local,src=/tmp/.buildx-cache \
--cache-to=type=local,dest=/tmp/.buildx-cache,mode=max \
--push \
-t ${{ secrets.DOCKER_REPO }}/devroute-server:${{ github.sha }} . # Docker Buildx 사용
- name: EC2에서 이미지 pull 후 컨테이너 실행 # EC2 서버에서 Docker 이미지 pull 후 컨테이너 실행
uses: appleboy/ssh-action@master # EC2 서버에 SSH 접속 후 명령 실행
with:
host: ${{ secrets.EC2_HOST }} # EC2 인스턴스 호스트 정보
username: ${{ secrets.EC2_SSH_USER }} # SSH 접속 사용자 이름
key: ${{ secrets.PRIVATE_KEY }} # SSH 프라이빗 키
script: | # EC2에서 실행할 스크립트
docker rm -f $(docker ps -qa) # 실행 중인 모든 컨테이너 제거
docker pull ${{ secrets.DOCKER_REPO }}/devroute-server:${{ github.sha }} # 새로운 Docker 이미지 pull
docker run -d -p 8080:8080 -e "SPRING_PROFILES_ACTIVE=prod" ${{ secrets.DOCKER_REPO }}/devroute-server:${{ github.sha }} # 새 컨테이너 실행
# Dockerfile이 변경된 경우에만 캐시 저장
- name: Docker 레이어 캐시 저장
if: steps.dockerfileChanged.outputs.changed == 'true' # Dockerfile 변경 시에만 캐시 저장
uses: actions/cache/save@v4
with:
path: /tmp/.buildx-cache # Docker 빌드 캐시를 저장할 경로
key: ${{ runner.os }}-buildx-${{ hashFiles('Dockerfile') }} # 캐시 키 설정
순서는 다음과 같습니다
1. checkout
2. Dockerfile이 변경된 경우 changed=true
3. docker buildx 설치 및 설정
4.jdk 17 저장
5.spring project 빌드
6.레이어 캐시 복원 시도 (이전 캐시가 있고, dockerfile이 변경이 안되어있으면 복원)
7.Docker 이미지 생성 및 DockerHub에 푸시
8. EC2에서 이미지 pull 후 컨테이너 실행->
9.Dockerfile이 변경된 경우(changed==true)에만 캐시 저장
깃허브에 레이어 관련 캐시가 있고, dockerfile이 변경되지 않았으면, 캐시를 복구→ dockerfile을 통해서 의존성을 다운 받고,도커 이미지를 만들 때 의존성 파일이 캐시에 있을 때, 캐시를 이용하여 이미지를 만들고 docker hub에 push→후에 이미지를 pull후에 ec2에서 컨테이너 실행→ dockerfile이 변경된 경우에는 캐시에 저장
캐싱을 도입하기 전 ci/cd 시간
캐싱을 도입하기 후 ci/cd시간
2분 57초에서 1분 39초로 개선되면서 , 약 44.07% 향상되었습니다.
그러나 CPU사용률은 아직 그대로입니다. 이에 대해서는 다음 포스팅에 다루겠습니다.