Git Bash에서 Minikube 명령어를 사용할 때 발생하는 "command not found" 문제를 해결하기 위한 상세 가이드입니다. 이 글에서는 Kubernetes Minikube를 설치하고 Git Bash 환경에서 정상적으로 동작하도록 설정하는 방법을 설명합니다.


1. Kubernetes Minikube 설치

Kubernetes Minikube는 로컬 환경에서 Kubernetes 클러스터를 실행할 수 있는 경량 툴입니다. 설치를 시작하기 전에 시스템 요구사항을 확인하세요.

1.1 시스템 요구사항

  • 운영체제: Windows 10 이상
  • CPU: 2개 이상의 코어
  • RAM: 4GB 이상
  • 가상화: BIOS에서 활성화 필요 (VT-x 또는 AMD-v)

1.2 Minikube 설치

  1. Minikube 공식 다운로드 페이지에 접속합니다.
  2. Windows용 Minikube 바이너리를 다운로드합니다:
  3. 다운로드한 파일 이름을 minikube.exe로 변경하고, 적절한 위치(예: C:\Program Files\Kubernetes\Minikube)에 저장합니다.
  4. 환경 변수에 Minikube 경로를 추가합니다:
    • Windows 검색창에 **"환경 변수 편집"**을 입력하여 시스템 속성 창을 엽니다.
    • 환경 변수 버튼 클릭 > 시스템 변수에서 Path 항목을 선택 > 편집 클릭.
    • C:\Program Files\Kubernetes\Minikube를 새 경로로 추가 후 저장.
  5. 명령 프롬프트(cmd)에서 Minikube가 정상적으로 설치되었는지 확인합니다:정상적으로 버전 정보가 출력되면 설치가 완료된 것입니다.
  6. minikube version

2. Git Bash에서 Minikube 명령어 사용 설정

Git Bash에서 Minikube 명령어를 실행하려면 환경 변수 또는 실행 경로를 추가 설정해야 합니다.

2.1 Git Bash PATH 환경 변수 확인

  1. Git Bash를 열고 다음 명령어로 현재 PATH를 확인합니다:
  2. echo $PATH
  3. Minikube 경로가 포함되어 있지 않다면, 아래 명령어로 경로를 추가합니다:
  4. export PATH=$PATH:/c/Program\ Files/Kubernetes/Minikube
  5. Minikube 명령어가 정상적으로 작동하는지 확인합니다:
  6. minikube version

2.2 실행 파일 확장자 문제 해결

Minikube 실행 파일이 .exe로 저장되어 있다면, Git Bash에서 실행 시 .exe 확장자를 명시해야 할 수도 있습니다:

./minikube.exe start

보다 간단히 사용하기 위해 심볼릭 링크를 생성할 수도 있습니다:

ln -s /c/Program\ Files/Kubernetes/Minikube/minikube.exe /usr/bin/minikube

3. Git Bash에서 Minikube 실행 테스트

  1. Minikube 클러스터를 시작합니다:
  2. minikube start
  3. 클러스터 상태를 확인합니다:
  4. minikube status
  5. Kubernetes 대시보드를 실행합니다:웹 브라우저에서 Kubernetes 대시보드가 열리면 정상적으로 작동하는 것입니다.
  6. minikube dashboard

4. Git Bash에서 "command not found" 문제 해결

4.1 Minikube 경로 설정 확인

Git Bash에서 Minikube 명령어가 작동하지 않는다면, 다음 단계를 확인하세요:

  1. where minikube 명령어로 Minikube 경로를 확인합니다:
  2. where minikube
  3. Minikube 실행 파일 경로가 없다면 환경 변수에 경로를 추가하세요.

4.2 Git Bash PATH 업데이트

Git Bash에서 환경 변수 파일을 업데이트하여 경로를 영구적으로 추가할 수 있습니다:

echo 'export PATH=$PATH:/c/Program\ Files/Kubernetes/Minikube' >> ~/.bashrc
source ~/.bashrc

4.3 Minikube 심볼릭 링크 생성

Git Bash가 Windows 실행 파일을 인식하지 못할 경우, 심볼릭 링크를 생성합니다:

ln -s /c/Program\ Files/Kubernetes/Minikube/minikube.exe /usr/bin/minikube

5. 추가 문제 해결 방법

문제 1: 가상화 지원 여부 확인

Minikube는 가상화를 사용하므로 가상화가 활성화되어 있지 않으면 작동하지 않습니다. BIOS 설정에서 VT-x 또는 AMD-v가 활성화되었는지 확인하세요.

문제 2: Docker 드라이버 설정

Minikube는 Docker 드라이버를 기본으로 사용합니다. Docker Desktop이 설치되어 있는지 확인하고, 설치되어 있지 않다면 Docker 설치 가이드를 참조하세요.

문제 3: Git Bash 관리자로 실행

일부 권한 문제가 발생할 수 있으므로 Git Bash를 관리자 권한으로 실행하세요.

최근 프로젝트에서 Gitea, AWS ECR, Jenkins, Docker-Compose, 그리고 AWS Elastic Beanstalk(EB)를 활용하여 CI/CD 파이프라인을 구성하는 작업을 진행했습니다. 이 포스팅에서는 CI/CD 환경 구축 과정과 함께 발생했던 문제 및 이를 해결한 과정을 기록하고자 합니다.


1. 프로젝트 개요

목표

Django 기반의 웹 애플리케이션과 이를 지원하는 서비스(Nginx, Redis, TensorFlow)를 Docker-Compose로 구성하고, AWS Elastic Beanstalk를 통해 배포 및 관리하며, CI/CD를 통해 코드 변경 사항이 자동으로 배포되도록 하는 환경 구축.

사용 기술 스택

  • Gitea: 코드 저장소 관리
  • AWS ECR: Docker 이미지를 저장
  • Jenkins: CI/CD 자동화
  • Docker-Compose: 컨테이너 오케스트레이션
  • AWS Elastic Beanstalk: 배포 및 운영 환경
  • Redis: 캐시 및 세션 관리
  • TensorFlow Serving: 모델 서빙
  • Nginx: Reverse Proxy와 정적 파일 제공

2. CI/CD 파이프라인 구축

2.1 Gitea 설정

Gitea를 프로젝트의 소스 코드 저장소로 사용했습니다. Gitea의 Webhook 기능을 통해 Jenkins와 통합하여 코드 변경 시 자동으로 빌드가 트리거되도록 설정했습니다.

Webhook 설정:

  1. Gitea에서 프로젝트 Repository 설정으로 이동
  2. Webhook 추가 → Jenkins URL 및 토큰 입력

 

2.2 AWS ECR 설정

AWS ECR을 Docker 이미지를 저장하는 레지스트리로 사용했습니다.

ECR 설정 과정:

  1. AWS 콘솔에서 ECR 리포지토리를 생성
  2. Jenkins의 빌드 스크립트에서 aws ecr get-login-password를 통해 ECR에 로그인
  3. Docker 이미지를 태그 및 푸시하도록 스크립트를 구성

 

2.3 Docker-Compose 구성

Docker-Compose를 사용해 다음 서비스를 정의:

  • uwsgi (Django): 주요 애플리케이션 서버
  • nginx: Reverse Proxy 및 정적 파일 서비스
  • redis: Django의 캐시 및 세션 백엔드
  • ml_models (TensorFlow Serving): 모델 서빙

Docker-Compose 파일 예시:

version: '3'
services:
  uwsgi:
    ## image: ECR 경로
    depends_on:
      - redis
      - ml_models

  nginx:
    ## image: ECR 경로
    ports:
      - 80:80
    depends_on:
      - uwsgi

  redis:
    image: redis
    container_name: redis_server
    ports:
      - 6379:6379

  ml_models:
    image: emacski/tensorflow-serving
    restart: always
    volumes:
      ## - 프로젝트의 model 경로
    command: "--model_config_file=/models/model_config.config --model_config_file_poll_wait_seconds=60"
    ports:
      - 8501:8501

 

2.4 Jenkins 설정

Jenkins는 Gitea에서 Webhook 이벤트를 받아 CI/CD를 수행합니다.

Job 구성:

  • Pipeline: 멀티 스테이지로 빌드, 테스트, 배포를 구성
  • 빌드 스크립트: 아래와 같은 Shell Script를 사용
#!/bin/bash
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
set -e  # Stop script on error

# AWS credentials setup
export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID_VAR
export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY_VAR
export AWS_DEFAULT_REGION=ap-northeast-2

# AWS ECR login
aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com

# Docker image build
docker build -t my-app-dev .

# Docker image tagging
docker tag my-app-dev:latest 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/my-app-dev:latest

# Push Docker image to ECR
docker push 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/my-app-dev:latest

# Prepare deployment for Elastic Beanstalk
zip -r my-app-eb-deploy.zip docker-compose.yml Dockerfile nginx/ app_models/

# Upload the zip file to S3
aws s3 cp my-app-eb-deploy.zip s3://my-app-s3/my-app-eb-deploy.zip

# Create a new Elastic Beanstalk application version
aws elasticbeanstalk create-application-version \
    --application-name "my-app" \
    --version-label "build-${BUILD_NUMBER}" \
    --source-bundle S3Bucket="my-app-s3",S3Key="my-app-eb-deploy.zip"

# Update Elastic Beanstalk environment
aws elasticbeanstalk update-environment \
    --environment-name "My-app-test" \
    --version-label "build-${BUILD_NUMBER}"

3. 트러블슈팅 사례

문제 1: Jenkins에서 eb 명령어 실행 불가

원인: Jenkins 계정에 awsebcli가 설치되지 않음.
해결: Jenkins 사용자 환경에서 pip install awsebcli --user로 설치 후 PATH 환경 변수 수정.

문제 2: Elastic Beanstalk 업데이트 실패 (ascii codec 에러)

원인: 환경 변수나 출력에서 비ASCII 문자가 포함됨.
해결: LANG 및 LC_ALL을 en_US.UTF-8로 설정하여 UTF-8 환경 보장.

export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8

 

Jenkins를 사용하면서 Execute Shell 단계에서 다음과 같은 오류를 만난 적이 있습니다.

FATAL: Unable to produce a script file
java.nio.charset.UnmappableCharacterException: Input length = 1
	at java.base/java.nio.charset.CoderResult.throwException(CoderResult.java:275)
	at java.base/sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:307)
	...
Caused: java.io.IOException: Failed to create a temp file on /var/lib/jenkins/workspace/...

 

이 오류는 임시 파일을 생성하는 과정에서 발생했으며, 이를 해결하기 위해 몇 가지 단계를 거쳤습니다. 이 글에서는 문제 원인과 해결 방법을 공유합니다.

 

 

1. 문제 원인

이 오류는 Jenkins가 쉘 스크립트를 실행하기 위해 임시 파일을 생성하려고 할 때 발생합니다. 주요 원인은 다음과 같습니다:

  • Jenkins가 사용하는 임시 디렉터리의 권한 문제.
  • Java 인코딩 설정이 맞지 않음.
  • Jenkins 작업 디렉터리에서 임시 파일 생성 실패.

 

2. 해결 방법: 임시 디렉터리 변경

Jenkins가 사용하는 임시 디렉터리를 명시적으로 설정하고, 적절한 권한을 부여하면 문제를 해결할 수 있습니다.

1단계: Jenkins 설정 파일 수정

Amazon Linux 2에서는 Jenkins 설정 파일이 /usr/lib/systemd/system/jenkins.service에 위치합니다.

 

설정 파일을 열어 수정합니다

sudo vi /usr/lib/systemd/system/jenkins.service

 

Environment 블록에 다음 내용을 추가합니다

Environment="JAVA_OPTS=-Djava.awt.headless=true -Djava.io.tmpdir=/tmp/jenkins_tmp -Dfile.encoding=UTF-8"

 

 

2단계: 임시 디렉터리 생성 및 권한 설정

새로 정의한 임시 디렉터리(/tmp/jenkins_tmp)를 생성하고, Jenkins 사용자에게 적절한 권한을 부여합니다.

 

디렉터리 생성 및 Jenkins 권한 부여

sudo mkdir -p /tmp/jenkins_tmp
sudo chown -R jenkins:jenkins /tmp/jenkins_tmp

 

 

3단계: Jenkins 재시작

Jenkins 설정을 적용하려면 서비스를 재시작해야 합니다.

 

sudo systemctl daemon-reload
sudo systemctl restart jenkins
sudo systemctl status jenkins

 

 Jenkins를 사용하는 도중 Jenkins 재시작 후 플러그인이 정상 동작하지 않고, 플러그인 버전이 낮아 재설치조차 불가능한 문제를 겪었습니다.

 해당 문제로 Jenkins 버전 업그레이드하면서 겪었던 JSONObject["scm"] is not a JSONObject 오류와 이를 해결한 과정을 공유합니다. 이 문제를 해결하기 위해 진행했던 과정과 원인을 분석한 결과를 바탕으로, 비슷한 문제를 겪는 분들께 도움이 되었으면 합니다.

 

 

문제 상황

Jenkins 재시작 후 플러그인 비정상 작동
Jenkins를 재시작한 뒤 일부 플러그인(Git 관련 등)이 제대로 작동하지 않았습니다. 특히 Git 플러그인을 사용하는 빌드 작업에서 아래와 같은 오류가 발생했습니다

 
JSONObject["scm"] is not a JSONObject

 

 

플러그인 버전 낮음 및 재설치 불가
플러그인 관리 메뉴에서 확인해보니 몇몇 플러그인의 버전이 매우 낮은 상태였고, 이를 재설치하려고 해도 플러그인 업데이트 서버와의 통신 오류로 인해 재설치가 진행되지 않았습니다.

java.io.FileNotFoundException: https://updates.jenkins.io/download/plugins/plain-credentials/...

 

 

Jenkins 업그레이드 후 오류 지속
Jenkins 자체의 문제가 의심되어 최신 LTS 버전(2.4 LTS)으로 업그레이드했지만, 문제가 지속되었습니다.

 

 

문제 해결 과정

업그레이드 후 플러그인 재설치

Jenkins를 업그레이드한 뒤에도 문제가 발생했으므로, 우선 플러그인 재설치를 시도했습니다.
하지만 플러그인 관리 메뉴에서 일부 플러그인은 여전히 재설치가 불가능하거나, 최신 버전임에도 불구하고 오류가 계속되었습니다.

 

Git(SCM) 플러그인 오류 분석

Git 플러그인을 사용하는 빌드에서 JSONObject["scm"] is not a JSONObject 오류가 지속적으로 발생했습니다.
이를 통해 Git 플러그인(SCM 관련) 또는 빌드 환경의 JSON 데이터 파싱에 문제가 있다고 판단했습니다.

  • 로그 분석 결과, Git 플러그인이 JSON 데이터를 읽는 과정에서 문제가 발생하고 있음을 확인했습니다.
  • 하지만 Git 플러그인을 재설치해도 문제가 해결되지 않았습니다.

Build Timeout 플러그인 문제 발견

문제를 해결하기 위해 구글링 중, Build Timeout 플러그인의 오류임을 발견했습니다:

  • 플러그인 관리 메뉴에서 해당 플러그인이 활성화되지 않은 상태로 표시.

Build Timeout 플러그인 재설치 후 문제 해결

  • Build Timeout 플러그인을 삭제한 뒤 다시 설치하고 Jenkins를 재시작했습니다.
  • 이후, Git 플러그인의 JSONObject["scm"] 오류가 사라지고 빌드가 정상적으로 진행되었습니다.

 

원인 분석

문제가 해결된 후, 왜 Build Timeout 플러그인 재설치가 Git(SCM) 관련 오류를 해결했는지 분석해본 결과, 다음과 같은 원인을 추정할 수 있었습니다:

  1. 플러그인 간 의존성
    Jenkins 플러그인들은 서로 의존 관계를 가질 수 있습니다.
    Build Timeout 플러그인은 빌드 작업 시간 제한을 설정하며, Git 플러그인이나 SCM 관련 기능과 연계될 가능성이 있습니다.
    • Build Timeout 플러그인의 손상 또는 삭제로 인해 Git 플러그인이 사용하는 API나 공통 라이브러리가 비정상적으로 작동한 것으로 보입니다.
  2. 플러그인 버전 불일치
    Jenkins 업그레이드 후, 기존 플러그인 버전이 Jenkins의 새로운 API와 호환되지 않아 문제가 발생했을 가능성이 큽니다.
    • Build Timeout 플러그인을 재설치하면서 최신 버전으로 교체되었고, Git 플러그인과의 의존성 문제도 해결되었습니다.

 

이번 경험을 통해 Jenkins 관리의 복잡성을 다시 한번 실감했습니다. 비슷한 문제를 겪는 분들에게 이 글이 작은 도움이 되길 바랍니다! 

해결 방법

아래의 단계별 가이드를 따라 문제를 해결해 보세요.

1. Docker Desktop 실행 확인

먼저 Docker Desktop이 실행 중인지 확인하세요.

  1. Windows 작업 표시줄에서 Docker Desktop 아이콘을 찾습니다.
  2. 실행되지 않았다면 Docker Desktop을 시작하세요.
  3. 실행 중에도 에러가 발생한다면 Docker Desktop을 재시작합니다.

 

에러 원인 분석

Docker가 Windows에서 작동할 때 네임드 파이프(named pipe)를 통해 Docker Daemon과 통신합니다. 그러나 다음과 같은 상황에서 이 통신이 실패할 수 있습니다:

  • Docker Desktop이 비정상적으로 종료되었거나 실행되지 않음.
  • Docker 서비스(com.docker.service)가 중지됨.
  • Docker CLI가 Docker Desktop 데몬과 연결되지 않음.
  • Windows Subsystem for Linux 2(WSL2)와의 통합이 문제를 일으킴.
  • Docker Desktop 설치 파일이 손상됨.

 

AWS EC2를 사용하다 보면 인스턴스의 성능을 최적화하거나 비용을 절감하기 위해 인스턴스 유형을 변경하는 경우가 많습니다. 하지만 이번 경험은 제게 인스턴스 유형 변경 시 CPU 아키텍처가 바뀌면 심각한 문제가 발생할 수 있다는 점을 상기시켜 주었습니다.

 

문제 상황

기존 EC2 인스턴스는 r5a.large (AMD 기반) 유형을 사용 중이었습니다. 성능 개선을 위해 r6i.large (Intel 기반)으로 인스턴스 유형을 변경했습니다. 변경 후 인스턴스를 시작했는데, 예상치 못한 문제가 발생했습니다:

  1. SSH 접근 불가: 인스턴스가 시작되었지만 SSH로 접속이 불가능했습니다.
  2. Emergency Mode: AWS 직렬 콘솔(Serial Console)을 통해 접속했을 때 인스턴스가 Emergency Mode로 부팅되어 있었습니다.

 

시도했던 해결 방법

문제를 해결하기 위해 여러 방법을 시도했지만, 결국 실패하고 다른 접근 방식을 선택해야 했습니다.

 

1. Emergency Mode에서 로그 확인

journalctl -xb

부팅 중 네트워크 관련 오류 및 sshd 서비스가 제대로 실행되지 않았다는 점을 확인했습니다.

2. 네트워크 및 SSH 복구

Emergency Mode에서 네트워크와 SSH를 복구하려고 시도했습니다:

ip link set eth0 up dhclient eth0 systemctl start sshd

그러나 네트워크와 SSH 서비스가 완전히 복구되지 않았습니다.

3. 파일 시스템 및 부트로더 복구

다음 명령을 통해 부트로더와 커널을 복구하려고 했습니다:

sudo yum reinstall kernel sudo dracut -f sudo grub2-mkconfig -o /boot/grub2/grub.cfg sudo grub2-install /dev/xvda

하지만 여전히 문제가 해결되지 않았습니다.

4. 볼륨 교체 및 AMI 복구

  • 볼륨 교체: 문제의 루트 볼륨을 스냅샷으로 생성한 뒤, 새로 생성한 인스턴스에 연결하여 복구를 시도.
  • AMI 복구: 기존 볼륨을 기반으로 새로운 AMI를 생성하고 다른 인스턴스에서 실행.

여러 시도에도 불구하고 인스턴스는 Emergency Mode를 벗어나지 못했습니다.

 

 

최종 해결 방법

모든 복구 시도가 실패하자, 다음과 같은 방법으로 문제를 해결했습니다:

  1. 애플리케이션 복제:
    • 문제의 애플리케이션과 데이터를 로컬 환경으로 복제.
  2. 새로운 서버 생성:
    • 새로운 EC2 인스턴스를 생성하고 애플리케이션을 재배포.
  3. 서비스 재개:
    • 새 인스턴스에서 정상적으로 서비스를 운영할 수 있었습니다.

 

 

결론 및 교훈

이번 경험을 통해 두 가지 중요한 교훈을 얻었습니다:

1. CPU 아키텍처 변경 시 주의

  • AMD에서 Intel로, 또는 ARM(r6g)에서 Intel(r6i)로 CPU 아키텍처가 변경될 때 부트가 실패할 가능성이 높습니다.
  • 특히 부트로더나 커널이 해당 아키텍처를 지원하지 않는 경우 문제가 발생합니다.

2. AMI를 미리 생성하자

  • 인스턴스 유형 변경 전에는 반드시 AMI를 생성해두는 것이 중요합니다.
  • AMI는 문제 발생 시 빠르게 복구할 수 있는 가장 안전한 방법입니다.

문제 상황

프로젝트 환경을 새로 설정하면서 원본 서버의 설정을 대상 서버로 복제하는 작업을 진행했습니다. 원본 서버는 Tomcat에서 SSL(443번 포트)이 활성화된 상태로 잘 동작하고 있었기 때문에, 대상 서버에서도 동일한 설정을 적용했습니다.

그러나, Tomcat을 시작하자마자 아래와 같은 에러가 발생했습니다

 

Caused by: java.net.BindException: 주소가 이미 사용 중입니다
	at sun.nio.ch.Net.bind0(Native Method)
	...

Tomcat의 로그를 확인해보니, 443번 포트가 이미 사용 중이라는 내용이었습니다. 하지만 원본 서버에서는 이런 문제가 없었기 때문에 한참 헤매게 되었습니다.

 

원인 분석

대상 서버를 자세히 살펴보니, **Apache HTTP 서버(httpd)**가 설치되어 있었습니다.

Apache HTTP 서버는 기본적으로 80번(HTTP) 포트443번(HTTPS) 포트를 사용합니다. Tomcat에서도 443번 포트를 사용하려고 설정되어 있었기 때문에, 두 서버가 충돌하면서 포트 사용 문제가 발생한 것입니다.

결국 대상 서버에 HTTP 서버가 설치된 사실을 몰랐던 것이 문제의 원인이었습니다.

 

해결 방법

이 문제를 해결하기 위해 아래 단계를 거쳤습니다:

 

포트 충돌 확인 먼저, 대상 서버에서 443번 포트를 누가 사용 중인지 확인했습니다

sudo lsof -i :443


COMMAND   PID    USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
httpd    1234    root   10u  IPv4  12345      0t0  TCP *:https (LISTEN)

 

 

  • 해결 방안 선택
    • Apache HTTP 서버를 비활성화 또는 제거
    • Tomcat의 SSL 포트를 변경
    • Apache를 리버스 프록시로 설정하여 Tomcat과 연동
    이번 환경에서는 HTTP 서버가 불필요했기 때문에, Apache HTTP 서버를 중지하는 방법을 선택했습니다.

 

  • Apache HTTP 서버 중지
sudo systemctl stop httpd
sudo systemctl disable httpd

 

교훈

  1. 환경 확인의 중요성
    새로운 서버 환경에서는 **기본적으로 설치된 서비스(예: HTTP 서버)**가 무엇인지 먼저 확인해야 합니다. 특히, 잘 알려진 포트(80, 443 등)는 다른 서비스가 사용하고 있을 가능성이 높습니다.
  2. 효율적인 디버깅 방법
    포트 충돌 문제를 해결하기 위해서는 다음 명령어를 적극적으로 활용하면 좋습니다:
    • netstat -tuln → 현재 사용 중인 포트 확인
    • lsof -i :<port> → 특정 포트를 점유하고 있는 프로세스 확인
  3. 원본 서버와 대상 서버의 차이점 확인
    원본 서버에서 아무 문제가 없더라도, 대상 서버의 환경이 다를 수 있다는 점을 항상 염두에 두어야 합니다.

 

마무리

이번 경험은 단순한 포트 충돌이었지만, 대상 서버 환경을 제대로 이해하지 않고 설정을 복제하려다 발생한 문제였습니다. 다음부터는 환경 구성 전에 기본 설정을 꼼꼼히 확인하고, 효율적인 디버깅 과정을 통해 시간을 절약할 수 있을 것 같습니다.

비슷한 문제를 겪고 계신 분들께 도움이 되었길 바랍니다! 

 

문제 상황

최근 Tomcat에서 애플리케이션을 실행하던 중, JVM 메모리 관련 에러로 인해 애플리케이션이 정상적으로 작동하지 않는 문제가 발생했습니다. 아래는 당시 발생한 에러 메시지입니다:

 

NOTE: Picked up JDK_JAVA_OPTIONS:  --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x0000000600000000, 8589934592, 0) failed; error='Not enough space' (errno=12)
#
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (mmap) failed to map 8589934592 bytes for committing reserved memory.
# An error report file with more information is saved as:
# /home/project/apache-tomcat-9.0.54/bin/hs_err_pid23479.log

 

이 메시지는 다음을 나타냅니다:

  • JVM이 8GB의 메모리를 요청했으나, 서버의 물리 메모리가 부족하여 요청이 거부되었습니다.
  • 물리 메모리(7.7GB)가 JVM이 요청한 메모리보다 적었기 때문에, 시스템이 더 이상 JVM의 메모리 요청을 처리할 수 없었습니다.

원인 분석

  1. JVM 메모리 설정 과도
    Tomcat에서 JVM 힙 메모리 설정이 기본값보다 과도하게 높게 설정되어 있었습니다:
    • 초기 힙 메모리(-Xms): 8GB
    • 최대 힙 메모리(-Xmx): 8GB
  2. 물리 메모리 초과
    서버의 물리 메모리(7.7GB)가 JVM 메모리 설정을 감당할 수 없었기 때문에, JVM이 실행 중 메모리 할당 에러를 발생시켰습니다.

문제의 기존 설정

Tomcat의 setenv.sh 파일에 다음과 같은 JVM 메모리 설정이 있었습니다

export CATALINA_OPTS="$CATALINA_OPTS -Xms8192m"
export CATALINA_OPTS="$CATALINA_OPTS -Xmx8192m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:NewSize=4096m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:MaxNewSize=4096m"

 

해결 과정

이 문제를 해결하기 위해 다음과 같이 setenv.sh 파일을 수정했습니다:

수정된 setenv.sh 내용

# 기존 JVM 옵션 (주석 처리)
# export CATALINA_OPTS="$CATALINA_OPTS -Xms8192m"
# export CATALINA_OPTS="$CATALINA_OPTS -Xmx8192m"
# export CATALINA_OPTS="$CATALINA_OPTS -XX:NewSize=4096m"
# export CATALINA_OPTS="$CATALINA_OPTS -XX:MaxNewSize=4096m"

# 새로운 JVM 메모리 옵션 설정
export CATALINA_OPTS="$CATALINA_OPTS -Xms2g"
export CATALINA_OPTS="$CATALINA_OPTS -Xmx4g"
export CATALINA_OPTS="$CATALINA_OPTS -XX:NewSize=512m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:MaxNewSize=1024m"

변경된 설정

  • 초기 힙 메모리(-Xms): 8GB → 2GB
  • 최대 힙 메모리(-Xmx): 8GB → 4GB
  • Young Generation 초기 크기(-XX:NewSize): 4GB → 512MB
  • Young Generation 최대 크기(-XX:MaxNewSize): 4GB → 1GB

 

설정 확인

JVM 메모리 옵션 확인

Tomcat이 올바르게 설정되었는지 확인하려면 아래 명령어를 사용하세요

jps -lvm

#결과 예시
15190 org.apache.catalina.startup.Bootstrap start -Xms2g -Xmx4g -XX:NewSize=512m -XX:MaxNewSize=1024m

 

결론

이 과정을 통해 Tomcat의 JVM 메모리 설정을 서버 환경에 맞게 조정하였습니다. 변경 후에는 더 이상 메모리 부족 에러가 발생하지 않았으며, 서버 리소스 사용이 안정화되었습니다.

교훈

JVM 메모리 옵션(-Xms, -Xmx)을 설정할 때는:

  1. 서버의 물리 메모리와 애플리케이션의 요구사항을 고려합니다.
  2. 필요 이상으로 높은 설정은 시스템에 오히려 부하를 줄 수 있습니다.
  3. GC 로깅 등을 통해 애플리케이션의 실제 메모리 사용량을 파악하고 설정을 최적화하세요.

'Trouble Shooting > java' 카테고리의 다른 글

Tomcat SSL 설정 중 포트 충돌 문제  (0) 2024.11.26

+ Recent posts