배포 스크립트가 갑자기 실패했는데 어제까지는 아무 문제 없이 되던 것이었다.
./deploy.sh: line 12: docker: command not found
Table of contents
Open Table of contents
증상 — 있는데 안 보였다
docker 파일이 실제로 있는지부터 봤다.
$ which docker
(없음)
$ ls -l /usr/bin/docker
-rwxr-xr-x 1 root root ... /usr/bin/docker
/usr/bin/docker 는 있는데 which 가 못 찾는다.
$ echo $PATH
/bin:/usr/local/bin
PATH 에 /usr/bin 이 빠져 있었다.
그런데 ssh 로 직접 접속해서 쳤을 때는 정상이었고 같은 서버인데 결과가 다르면 실행 환경이 다르다는 뜻이다.
원인 — 어디서 도는지가 달랐다
접속해서 확인한 PATH 와 스크립트가 보는 값이 달랐다.
$ ssh server 'echo $PATH'
/usr/local/bin:/usr/bin:/bin
배포 쪽은 그와 다른 방식으로 명령을 넘기고 있었다.
빌드 도구 → ssh 로 명령 실행 → PATH 가 다르다
로그인 셸과 ssh 명령 실행이 읽는 설정 파일이 서로 다르다.
명령을 인자로 바로 넘기면 로그인 쪽 설정을 아예 안 읽는다. 같은 서버인데 어떻게 들어가느냐에 따라 환경이 달랐고 옮긴 사람은 자기 환경에서만 확인했던 것이다.
조치 — 볼 순서
이런 일이 반복돼서 순서를 적어 뒀다.
"명령을 못 찾겠다"가 나오면
1. 파일이 실제로 있는가 ls -l <경로>
2. 실행 권한이 있는가 ls -l 결과의 x
3. PATH 에 그 경로가 있는가 echo $PATH
4. 어떤 셸에서 도는가 ps -p $$ / echo $0
5. 어떤 사용자로 도는가 whoami / id
6. 어떤 방식으로 들어왔는가 로그인인가 명령 실행인가
앞의 셋이 대부분이고 뒤의 셋이 이번 같은 경우를 잡는다.
이 순서를 정해 두니 그다음부터는 몇 분 만에 원인이 어디인지 갈렸다. 무엇을 볼지 그때그때 떠올리는 것과 적힌 것을 따라가는 것은 걸리는 시간이 달랐다.
설정 — 스크립트에서 전제를 명시
스크립트가 환경을 가정하고 있었다.
#!/bin/sh
docker build -t app .
docker 가 PATH 에 있다는 전제가 아무 데도 안 적혀 있다.
전체 경로를 쓰거나 확인하게 바꿨다.
#!/bin/sh
set -eu
DOCKER=${DOCKER:-/usr/bin/docker}
[ -x "$DOCKER" ] || { echo "docker 를 찾을 수 없습니다: $DOCKER"; exit 1; }
"$DOCKER" build -t app .
DOCKER 를 -x 로 확인해서 없으면 무엇이 없는지 말하고 끝난다.
command not found 는 무엇을 어디서 찾았는지를 안 알려 준다. 스크립트에 전제를 적어 두면 실패 메시지가 그 전제를 그대로 가리킨다.
사전 준비 — 앞에서 한 번에 확인
하나씩 걸리는 것보다 처음에 다 보는 편이 나았다.
check_requirements() {
for c in docker git rsync jq; do
command -v "$c" >/dev/null 2>&1 || { echo "없음: $c"; missing=1; }
done
[ -z "${missing:-}" ] || exit 1
}
check_requirements
check_requirements 가 넷을 한 번에 보고 없는 것을 모아서 낸다.
넷 중 셋이 없으면 세 번 실패하는 것이 아니라 한 번에 다 나온다. 중간에 실패하면 그때까지 진행한 작업이 어중간하게 남는데 시작조차 안 하면 그 상태가 안 생긴다.
재발 방지 — 환경 기록과 형상 관리
어느 환경에서 도는지도 로그 앞에 남기게 했다.
echo "실행 정보: user=$(whoami) shell=$0 pwd=$(pwd) path=$PATH" >> "$LOG"
whoami 와 $PATH 가 함께 남아 두 환경을 비교할 수 있다.
내 자리에서는 되는데 하는 말이 나오면 이 줄을 서로 비교했다. 실제로 이것으로 한 번 찾았는데 사용자가 달라서 다른 설정 파일을 읽고 있었다.
PATH 가 애초에 왜 바뀌었는지도 찾아봤다.
$ ls -la /etc/environment /etc/profile /etc/profile.d/
/etc/profile 수정 시각이 전날이었는데 변경 기록이 없어 누가 했는지는 몰랐다.
설정 파일을 형상 관리에 넣자는 이야기가 그때 나왔는데 전부는 못 했고 배포에 관련된 것부터 넣었다.
정리
- 명령을 못 찾을 때 파일 존재와 권한과
PATH를 먼저 본다 - 그다음 셸과 사용자와 접속 방식을 본다
- 같은 서버여도 어떻게 들어가느냐에 따라 환경이 다르다
- 명령을 바로 넘기면 로그인 설정을 안 읽는다
- 한 환경에서 되는 것으로는 충분하지 않다
- 스크립트에서 전제를 명시하고 없으면 무엇이 없는지 말하고 끝낸다
- 필요한 것을 앞에서 한 번에 확인한다
- 중간 실패는 어중간한 상태를 남긴다
- 실행 환경을 로그에 남겨 두 자리를 비교한다
- 환경 설정 파일을 형상 관리에 넣는다