CodeDeploy 사용 시, "failed to run command 'java'" 에러 발생하는 경우

김유정·2024년 10월 13일
2
post-thumbnail

문제 상황

EC2 서버에 JAVA를 설치하고 환경 설정을 모두 했는데도 아래와 같은 에러가 발생했다.

[ 에러 로그 ]

[ 환경 설정 ]
위치: /etc/profile.d/jdk.sh

export JAVA_HOME=/opt/jdk-17
export PATH=$PATH:$JAVA_HOME/bin
export CLASSPATH=/opt/jdk-17/jre/lib:/opt/jdk-17/lib/tools.jar

[ appspec.yml ]

version: 0.0
os: linux
files:
  - source: /
    destination: /home/ec2-user/backend
file_exists_behavior: OVERWRITE

permissions:
  - object: /home/ec2-user/backend
    owner: ec2-user
    mode: 755
    type:
      - file

hooks:
  ApplicationStop:
    - location: scripts/stop_was.sh
      timeout: 60
      runas: ec2-user
  ApplicationStart:
    - location: scripts/run_new_was.sh
      timeout: 60
      runas: ec2-user

[ run_new_was.sh ]

# run_new_was.sh

#!/bin/bash

# JAVA_HOME과 PATH 설정을 명시적으로 로드
if [ -f /etc/profile.d/jdk.sh ]; then
    source /etc/profile.d/jdk.sh
fi

echo "start run_new_was"
echo "current user: $USER"
echo "JAVA_HOME: $JAVA_HOME"
echo "PATH: $PATH"
shopt -q login_shell && echo "Login shell" || echo "Non-login shell"
PROJECT_ROOT="/home/ec2-user/backend"
JAR_FILE="$PROJECT_ROOT/build/libs/backend-0.0.1-SNAPSHOT.jar"

TARGET_PORT=8080

nohup java -jar -Dspring.profiles.active=prod -Dserver.port=${TARGET_PORT} ${JAR_FILE} > /home/ec2-user/nohup.out 2>&1 &
echo "> Now new WAS runs at ${TARGET_PORT}."
exit 0

로그파일 위치 정리

  • codedeploy agent 로그: /var/log/aws/codedeploy-agent
  • codedeploy가 실행한 스크립트에서 발생한 로그: /opt/codedeploy-agent/deployment-root/deployment-logs
  • 애플리케이션 실행 로그: ~/nohup.out

해결 방법

CodeDeploy는 /etc/profile이나 ~/.bashrc에 있는 쉘스크립트를 읽어와 환경변수를 로딩하지 못한다. 따라서 아래의 2가지 방법을 사용할 수 있다.

  • CodeDeploy를 통해 실행하는 쉘스크립트에서 환경변수 로딩
  • AWS Parameter Store 를 이용해 변수 정의하기

서버를 이관해야한다거나 새로운 프로젝트를 시작할 때 기존 프로젝트에 남아있는 코드를 최대한 활용할 수 있으면 좋겠다고 생각해서, 쉘스크립트에서 변수를 로딩하는 방식을 선택했다.

(Parameter Store 이용하는 방식 참고: https://blog.zooneon.dev/aws-codedeploy-environment-variables/)

아래 코드를 쉘 스크립트 초반에 추가해서 환경변수를 로딩하도록 했다. 경로는 JAVA_HOME 을 설정한 스크립트로 해주면 된다.

# JAVA_HOME과 PATH 설정 로드
if [ -f /etc/profile.d/jdk.sh ]; then
    source /etc/profile.d/jdk.sh
fi

[ /etc/profile.d/jdk.sh ]

export JAVA_HOME=/opt/jdk-17
export PATH=$PATH:$JAVA_HOME/bin

문제를 해결하며 알게된 것들

로그인 여부에 따라 다른 파일을 참조한다?


bash 공식 문서를 보면, 로그인 여부에 따라 시작할 때 다른 파일을 참조한다고 나와있다.
interactive login shell의 경우, /etc/profile 파일을 읽은 후, ~/.bash_profile, ~/.bash_login, ~/.profile 차례대로 읽는다고 한다. 그리고 /etc/profile 에서는 /etc/profile.d 안에 있는 스크립트도 읽는다.

그리고 interactive non-login shell 의 경우 ~/.bashrc 파일을 읽는다.

로그인 여부는 알겠는데, interactive shell은 뭐지?

로그인 여부에 따라 시작할 때 다른 쉘을 참조하는 것 맞지만, 이 모든 건 interactive 즉, 대화형 쉘인 경우에만 해당한다. 그럼 그 반대는 뭐냐면, Non-interactive, 비대화형 쉘이다.

대화형 쉘은 사용자와 상호작용하는 걸 말하는데, 사용자에게 a와 b를 입력받아서 더한 값을 출력하는 걸 예로 말할 수 있을 것 같다.

비대화형 쉘이 좀 더 흔히 접하는 방식일 것 같은데, 배포 스크립트를 실행한다거나 로그 수집, 모니터링을 위해 주기적으로 스크립트를 실행하는 것들을 예로 들 수 있다.

[ 대화형 쉘로 판단되는 경우 ]

  • bash 명령어를 입력해서 쉘을 시작하는 경우
  • bash -i 를 통해 대화 모드로 쉘을 시작할 때
  • 사용자로부터 응답을 받아 출력을 하는 프로그램

[ 비대화형 쉘로 판단되는 경우 ]

  • ./test.sh 이런식으로 스크립트를 실행할 때
  • 원격으로 명령을 실행할 때 (예: ssh user@host "command")

[ 대화형 쉘 여부 파악하기 ]

[[ $- == *i* ]] && echo ‘Interactive’ || echo ‘not-interactive’

그럼 비대화형일 경우, 환경변수를 어떻게 로드하지?


비대화형일 경우 /etc/environment에서 변수를 읽어온다고한다. 거기에 export BASH_ENV = "비대화형 시작 시 로딩하고 싶은 스크립트 위치" 이런식으로 써오면 읽어오는 것 같은데, 비대화형에 대한 정보는 많이 없어서 잘 모르겠다.

그렇다면, /etc/environment를 따로 설정하지 않은 경우에서
"비대화형 비로그인 상태라면 환경변수를 로딩하지 못할까?"
이 부분이 궁금해서 테스트를 해봤다.

우선 sudo su root로 비로그인 상태로 전환한 후에 쉘스크립트를 직접 수행해봤다.


비대화형 쉘에 비로그인 상태인데도 JAVA_HOME과 PATH 에 대한 값을 잘 읽어왔다. 왜 그런가 했는데, 부모 프로세스로부터 상속받을 수도 있다고 한다. 상속이 언제 어떤 경우에 되는지 정확한 건 잘 모르겠다.

CodeDeploy는 어떤 환경에서 스크립트를 실행할까?

case $- in
  *i*) echo "Interactive shell" ;;
  *) echo "Non-interactive shell" ;;
esac
shopt -q login_shell && echo "Login shell" || echo "Non-login shell"

이렇게 로그를 찍어서 확인해본 결과, 비대화형에 비로그인 상태로 쉘을 실행하고 있는 걸 알 수 있었다. 위에서 실험한 것처럼 비대화형에 비로그인 상태라도 환경변수 값을 상속받을 수도 있지만, CodeDeploy의 경우, 독립된 다른 환경에서 제한된 권한으로 스크립트를 실행하기 때문에 .bashrc 파일을 읽어오지 못할 수도 있다.

참고

0개의 댓글

관련 채용 정보