본문 바로가기
DevOps/CICD & Automation

[Github Action] appleboy 사용 시 남을 수 있는 Drone SSH 임시 환경변수 삭제

by persi0815 2025. 6. 14.

기존 배포(CI/CD) 로직

GitHub Actions 에서 main 브랜치로의 push를 감지

→ jar build

→ dockerfile을 통해 docker image 생성

→ 해당 이미지를 AWS ECR에 업로드 (latest 태그)

→ AWS EC2에 SSH Connection

→ .env 파일 echo로 설정하고 ./deploy.sh 실행

  • docker compose pull: ECR에서 최신 이미지 pull
  • docker compose down: 기존 컨테이너 종료 및 삭제
  • docker compose up -d —build: pull 받은 최신 이미지를 기반으로 백그라운드 실행
  • (docker image rm $(docker images -q) || true): 안 쓰는 이미지 제거

위와 같이 배포가 되는데,

해당 과정에서 GitHub Actions Settings에 저장된 환경변수 파일(ENV) 내용을 그대로 출력(echo)한 후, 

EC2의 /home/ubuntu/.env.user파일에 새로 저장(덮어쓰기)하도록 했습니다. 

  - name: SSH to EC2 and restart container
    uses: appleboy/ssh-action@v1.0.3
    with:
      host: ${{ secrets.EC2_HOST }}
      username: ${{ secrets.EC2_USERNAME }}
      key: ${{ secrets.EC2_PRIVATE_KEY }}
      script_stop: true
      script: |
        echo '${{ secrets.ENV }}' > /home/ubuntu/.env.user
        chmod +x /home/ubuntu/deploy.sh
        /home/ubuntu/deploy.sh

*기존 파일이 있어도 '>'연산자로 내용을 덮어씌우고 있었습니다.

 

문제 상황

GitHub Actions에서 EC2에 SSH(appleboy/ssh-action)로 접속해 생성한 .env.user 파일에 Drone CI 관련 환경변수가 남아 경고가 발생하는 이슈가 있었습니다.

`DRONE_SSH_PREV_COMMAND_EXIT_CODE=$? ; if [ $DRONE_SSH_PREV_COMMAND_EXIT_CODE -ne 0 ]; then exit $DRONE_SSH_PREV_COMMAND_EXIT_CODE; fi;'

 

이로 인해 .env.user 파일의 길이가 비정상적으로 길어졌고,
환경변수 인자(argument) 개수가 초과된다는 argument list too long 에러가 발생,

Spring 서버가 배포되지 않는 문제가 있었습니다.

 

이 때문에, 자동 배포라는 CICD의 이점을 얻지 못하고, 

배포 할때마다(=문제 발생할때마다) EC2에 직접 접속해 .env.user 파일을 수동으로 재생성해야 했습니다.

 

해결 방안

이때 제가 세운 가설과 이에 대한 검증은 다음과 같습니다. 

 

1. docker run 명령어 길이가 ARG_MAX를 초과했다.

= docker-compose가 env에 있는 환경변수를 docker run -e로 넘겨 리눅스 프로세스 환경변수로 설정하는데, 그 docker run 명령어 길이가 ARG_MAX를 초과했다. 

검증) getconf ARG_MAX 명령어로 docker run 명령어 최대 길이를 확인했더니 2097152 bytes (약 2MB. 기본값)임을 확인할 수 있었고, env | wc -c 명령어로 컨테이너에 주입된 리눅스 프로세스 환경변수의 크기를 봤더니 1127 byte 밖에 되지 않았습니다.

결론) 환경 변수 양 자체가 많은건 아니지만, 이미지 이름 + 모든 실행 인자의 양이 충분히 많을 순 있겠다. 

* ARG_MAX = docker run 명령어의 길이 + -e KEY=VALUE 옵션들 + 이미지 이름 + 모든 실행 인자 

 

1-2. 환경변수를 제외한 docker run 명령어가 충분히 길다.

검증) docker inspect 실행하고 docker run 복원했는데, 환경변수가 대부분의 길이를 차지했고, 길이도 계산해보니 1858 bytes였다.. 2,097,152 bytes에는 한참 못 미치는 수준이다. 

결론) Drone 없는 상황에서의 docker run 명령어는 충분히 짧다. 

1. docker inspect 실행
docker inspect <컨테이너 ID> > container.json

2. docker run 복원
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock assaflavie/runlike <컨테이너 ID>

3. docker run 명령어 길이 확인
echo '복사한 docker run 명령어' | wc -c

 

2. 다른 서버들도 Spring Server와 똑같이 인수 길이와 관련하여 에러가 난다. 

검증) EC2 들어가서 확인해보니 다른 컨테이너들은 잘 작동되고 있었다. 다만, Spring 서버와 다른 점은 환경변수의 개수가 약 5개 정도로, spring 서버에 비해 1/3 정도였습니다.

결론) Drone 명령어가 붙었다고 해서 무조건 문제가 생기는 것은 아니다. Drone 명령어로 그만큼 .env 파일에 더미 데이터가 많아져서 인자 개수 관련 에러가 났구나. (근데 2097152 byte를 넘겼다는 건 이해가 안됨..)

 

-> Drone명령어가 붙지 않는다면, argument list too long 에러가 안 생기겠다! 생각이 들었고, 우선 에러 자체보다 drone 명령어가 생성되는 이유에 대하여 알아보는 것을 목적으로 잡았습니다. 

 

3.  GitHub Actions settings의 ENV에 drone 명령어가 포함되어 있다.

검증) GitHub Actions secrets (settings)에 key=value 리스트로 잘 등록해도 같은 이슈가 발생했습니다. 

결론) GitHub secrets 자체 문제는 아니다.

 

4. Github Actions Settings의 ENV가 EC2의 .env.user로 올라갈때 drone 명령어가 포함된다. 

검증) Github Actions trigger로 .env.user가 새로 생성되었을 때 매번 파일에 drone 명령어가 포함됨을 확인했습니다. 

결론) ssh-action을 사용하여 EC2에 접속해서 .env.user 파일을 생성하여 값을 넣을때 drone 명령어가 추가된다. 

 

-> Drone이란? 

컨테이너 기반의 CI로, 모든 단계에서 Docker 컨테이너를 활용해 빌드 파이프라인이 시간이 덜 걸리고 설정과 실행이 쉽다고 합니다. Drone 자체가 중요한 것은 아니니, 자세한 내용은 https://newdeal123.tistory.com/101 참고하면 좋을 것 같습니다..!

 

5. 내가 Drone을 사용하고 있다.

검증) Drone CI 설정을 따로 하지 않았는데..? 생각이 들어 사용한 ssh connection 툴인 appleboy/ssh-action에 대해 서치를 하다가 https://github.com/appleboy/ssh-action의 Dockerfile에서 entrypoint.sh에서 사용하는 쉘이 drone-ssh를 기반으로 동작하고 있다는 것을 발견했습니다. 

결론) Drone을 사용하고 있다. appleboy/ssh-action은 Drone 플러그인으로 개발된 ssh-action이고, Drone 기반으로 동작한다.

 

찾았다..!! 그러면 이제 해결책을 찾아봅시다. 

 

먼저 궁금하니! 왜 이런 사단이 났나부터 알아봅시다.ㅎㅎ

echo '${{ secrets.ENV }}' > /home/ubuntu/.env.user

를 통해 secrets.ENV의 "문자열 그대로" .env.user 파일에 한 줄로 그대로 저장완료해야했습니다. 

그런데 appleboy/ssh-action은 명령어를 ssh 세션으로 넘길 때 Drone 시절의 ssh 플러그인 코드를 그대로 가져오면서 모든 명령어 끝에 붙이는 것만이 아니라 아예 환경변수에 해당 내용을 집어 넣어 전달을 하게 된 것입니다. 

 

 

그러면 DRONE_SSH_PREV_COMMAND_EXIT_CODE이 뭔가? 

Drone에서 SSH 명령어를 실행할 경우, 내부적으로 항상 DRONE_SSH_PREV_COMMAND_EXIT_CODE라는 환경변수를 남겨 명령어 종료 상태를 추적합니다. 즉, Drone이 SSH가 성공했는지 실패했는지 관리할 수 있도록 만들어 놓은 장치입니다. 

DRONE_SSH_PREV_COMMAND_EXIT_CODE=$?
-> 방금 실행한 명령어의 종료 코드를 저장

if [ $DRONE_SSH_PREV_COMMAND_EXIT_CODE -ne 0 ]; then exit $DRONE_SSH_PREV_COMMAND_EXIT_CODE; fi;
-> 만약 명령어가 실패했으면 (exit code != 0) → 실패한 상태 그대로 ssh 세션 종료.


그러면, GitHub Actions에서 Drone SSH 플러그인 흔적이 남아 있을 경우 이를 자동으로 삭제하도록 수정하면 되겠다!는 생각을 했고, 스트림 에디터를 사용하여 파일에서 해당 문자열을 찾아 삭제하도록 했습니다. 

sed -i '/DRONE_SSH_PREV_COMMAND_EXIT_CODE/d' .env.user
  • sed: Stream Editor - 텍스트 파일에서 특정 파일을 찾거나, 수정, 삭제하는 명령어
  • -i: in-place - 파일을 직접 덮어쓰고 수정
  • '/DRONE_SSH_PREV_COMMAND_EXIT_CODE/d': 해당 문자열에 포함된 줄을 찾아서 삭제한다. 
  • .env.user: 명령어 적용할 대상 파일

 

결과

배포하자마자 깨끗한 .env.user를 만날 수 있었습니다!!

-> 이제 배포할때마다 수동으로 파일 안만들어줘도 되겠당 ㅎㅎ

 

추가로.. 나만 겪은 이슈가 아니다!

https://github.com/appleboy/drone-ssh/issues/175

 

using script_stop, there is a problem when using cat in script to output to a file. · Issue #175 · appleboy/drone-ssh

example file: DRONE_SSH_PREV_COMMAND_EXIT_CODE=0 ; if [ 0 -ne 0 ]; then exit 0; fi; version: '3.5' DRONE_SSH_PREV_COMMAND_EXIT_CODE=0 ; if [ 0 -ne 0 ]; then exit 0; fi; services: DRONE_SSH_PREV_COM...

github.com

22년에 발행된 Issue인데, 아직까지도 해결이 안된 것으로 보였습니다.

재현은 해봤으니, 이슈 더 파보다가 해결될 기미가 보이면 컨트리뷰트 도전해보겠습니다..ㅎㅎ

 

추가로 왜 argument list too long 에러가 났을까? (미해결)

길었던 가설과 검증 과정.. 언젠간 꼭 다시 해결해서 업데이트 하겠습니다. 

 

1. export $(cat .env.user | xargs)가 실행되었다. 

검증) EC2 서버 전체에서 export $(cat .env.user | xargs) 명령어를 grep으로 탐색하고, deploy.sh 파일, bashrc, bash_profile, dockerfile, docker-compose.yml 모두 점검했는데, export 명령어가 전혀 사용되고 않고 있었습니다... 확인할 수 있었던 건 사용되었다는 로그 뿐.. 

결론) bash export는 원인이 아니다. 

 

2. docker-compose.yml에서 잘못된 명령어 override했다. 

검증) docker-compose.yml 전체 확인하고, docker compose up 시 command/entrypoint override 여부 grep으로 확인했는데, override 없었습니다. Dockerfile의 entrypoint도 그대로 사용되고 있었습니다. 

결론) docker-compose 명령어 문제가 아니다. 

 

3. GitHub Actions에서 docker run을 직접 실행하며 xargs를 사용하고 있다. 

검증) GitHub Actions workflow 전체 코드 검토했는데, 사용하지 않고 있었습니다. 

결론) GitHub Actions docker run 직접 실행 아니다. 

 

4. build.gradle에서 bootRun or bootJar 실행 시 모든 환경변수를 Spring boot argument로 강제 전달하고 있다. 

검증) build.gradle 전체 검토했고, bootRun args 사용 여부, bootJar launchScript 사용 여부 확인했는데, build.gradle 내 args 주입, bootRun 커스텀 설정, launchScript 설정 전혀 없었다. 

결론) Gradle 빌드 설정 문제 아니다. 

 

....