CI/CD
GitHub Actions 배포에서 SSH known_hosts 때문에 멈춘 날
CI에서는 서버 접속이 처음이라는 사실을 놓쳐 배포가 멈췄던 과정을 정리했다.
증상은 빌드 성공 뒤에 나왔다
로컬에서 ssh deploy@서버는 잘 들어가졌고, GitHub Actions에서도 빌드와 테스트는 통과했다. 그런데 마지막 배포 단계에서만 멈췄다. 로그에는 익숙한 에러가 짧게 남았다.
Host key verification failed.
rsync error: unexplained error (code 255)
처음에는 SSH 키 권한 문제라고 생각해서 chmod 600만 계속 확인했다. 그런데 키가 틀렸다면 Permission denied가 나와야 했다. 이번에는 서버가 믿을 수 있는 호스트인지 확인하는 단계에서 막힌 것이었다.
로컬 성공이 CI 성공을 보장하지 않았다
내 노트북에는 이미 ~/.ssh/known_hosts에 서버 지문이 저장되어 있었다. 반면 GitHub Actions runner는 매번 새 환경으로 시작한다. 그래서 서버 지문을 모르는 상태에서 바로 접속하려고 하니 안전하게 실패한 것이다.
임시로 StrictHostKeyChecking=no를 넣으면 지나가긴 한다.
ssh -o StrictHostKeyChecking=no [email protected] "pwd"
하지만 배포 자동화에 이 옵션을 습관처럼 넣고 싶지는 않았다. 중간자 공격을 막기 위해 있는 확인을 그냥 꺼 버리는 셈이라서, 적어도 내가 관리하는 서버 지문을 명시적으로 넣는 방식으로 바꿨다.
내가 고친 배포 단계
Actions에서 private key를 만들고, 바로 ssh-keyscan으로 서버 지문을 등록했다.
mkdir -p ~/.ssh
printf '%s
' "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
rsync -az -e "ssh -i ~/.ssh/deploy_key" ./out/ "$DEPLOY_USER@$DEPLOY_HOST:/var/www/app/"
여기서 헷갈렸던 부분은 ssh-keyscan이 인증을 하는 명령이 아니라는 점이다. 서버의 공개 지문을 가져와 파일에 적을 뿐이다. 그래서 처음 한 번은 서버 콘솔에서 실제 지문을 확인해 두고, Actions 로그에 찍힌 값과 맞는지 비교했다.
체크한 순서
- SSH 개인 키가 secret에 줄바꿈 포함 형태로 들어갔는지 확인했다.
~/.ssh디렉터리와 키 파일 권한을 먼저 맞췄다.- 에러가
Permission denied인지Host key verification failed인지 구분했다. - 서버를 재설치한 뒤라면 known_hosts의 예전 지문을 그대로 믿지 않았다.
- 배포 계정이 실제 배포 경로에 쓸 수 있는지도 별도로 확인했다.
다음부터 남겨 둘 기준
CI 배포에서 SSH가 실패하면 키부터 의심하기 쉽다. 나는 이제 에러 문구를 먼저 둘로 나눈다. 인증 실패면 키와 계정 문제, 호스트 확인 실패면 known_hosts 문제다. 이 구분만 해도 배포 로그를 붙잡고 시간을 꽤 줄일 수 있었다.