브랜치 이름을 정리하면서 dev 를 develop 으로 바꿨다. 며칠 뒤 고친 게 안 올라간다는 말이 나왔는데 배포 이력은 전부 성공이었다.
Table of contents
Open Table of contents
증상 — 성공 이력과 안 바뀐 결과
배포 로그를 열었다.
Started by user ...
Fetching changes from the remote Git repository
Checking out Revision 3c81f0a (origin/dev)
...
Finished: SUCCESS
origin/dev 를 받고 있었다.
dev 는 이제 안 쓰는데 원격에 참조가 남아 있어서 받아지긴 했다. 받아지는데 옛 것이 받아지고 그 결과가 SUCCESS 로 남는다.
실패했으면 그 자리에서 알았을 것이다. 성공했기 때문에 며칠 동안 아무도 못 알아챘다.
원인 — 원격에 남은 옛 이름
이름을 바꿀 때 새 것을 만들고 옛 것을 안 지웠다.
$ git ls-remote --heads origin
3c81f0a refs/heads/dev
8f2a1c3 refs/heads/develop
refs/heads/dev 와 refs/heads/develop 이 둘 다 있으니 옛 것을 가리키는 설정이 계속 돈다.
$ git push origin --delete dev
--delete dev 로 지우고 나니 그 이름을 가리키는 것은 실패한다.
남겨 두는 것이 안전해 보였는데 실제로는 잘못된 상태를 조용히 유지하게 했다. 없어져야 드러나고 실패로 드러나는 편이 성공한 척 지나가는 것보다 낫다.
이름을 바꾸는 작업은 옛 이름을 지우는 것까지가 한 묶음이었다.
조치 — 가리키는 자리 전수 탐색
그 이름이 어디에 박혀 있는지 파일에서 찾았다.
$ grep -rn '\bdev\b' --include=*.xml --include=*.groovy /var/lib/jenkins/jobs/
/var/lib/jenkins/jobs/api-deploy/config.xml: <name>*/dev</name>
/var/lib/jenkins/jobs/api-test/config.xml: <name>*/dev</name>
/var/lib/jenkins/jobs/batch-deploy/config.xml:<name>*/dev</name>
config.xml 셋에 있었고 pull.sh 에도 있었다.
$ grep -rn 'dev' /srv/deploy/*.sh
/srv/deploy/pull.sh:12: git pull origin dev
관리 화면만 봤으면 pull.sh 쪽을 놓쳤을 것이다.
화면으로 찾으면 화면에 나오는 것만 찾게 된다. 같은 값이 어디에 박혀 있는지는 파일에서 뒤져야 전부 나온다.
설정 — 이름을 한 자리에
각 설정에 문자열로 박혀 있으면 다음에 또 바꿀 때 또 흩어진다.
// Jenkinsfile
environment {
DEPLOY_BRANCH = 'develop'
}
# /etc/deploy.env
DEPLOY_BRANCH=develop
스크립트는 DEPLOY_BRANCH 를 읽어 간다.
. /etc/deploy.env
git pull origin "$DEPLOY_BRANCH"
이제 이름이 바뀌면 deploy.env 한 곳만 고친다.
이번에도 config.xml 셋 중 하나만 고쳤으면 같은 증상이 다시 났을 것이다. 값을 한 자리로 모으는 것이 이 종류의 사고를 없애는 방법이었다.
검증 — 무엇을 배포했는지 남긴다
성공인데 안 바뀌는 상황을 다시 만나고 싶지 않았다.
REV=$(git rev-parse --short HEAD)
BR=$(git rev-parse --abbrev-ref HEAD)
echo "$BR@$REV $(date -Is) by ${BUILD_USER:-jenkins}" > /srv/app/REVISION
$ curl -s http://web01/health
{"rev":"develop@8f2a1c3","at":"2018-06-24T12:10:33+09:00"}
REVISION 과 health 응답으로 도는 것에서 본다.
기대한 것과 다르면 멈추게도 했다.
EXPECT=$(git rev-parse "origin/$DEPLOY_BRANCH")
ACTUAL=$(git rev-parse HEAD)
if [ "$EXPECT" != "$ACTUAL" ]; then
echo "받은 것이 기대와 다릅니다: expect=$EXPECT actual=$ACTUAL"; exit 1
fi
일부러 다른 커밋으로 체크아웃해 놓고 expect 와 actual 비교가 멈추는지 봤고 멈췄다.
교훈 — 종료 코드는 결과를 말하지 않는다
이 건에서 남은 것은 종료 코드의 뜻이었다.
받는 것에 성공했다 → 무엇을 받았는지는 안 말한다
스크립트가 끝났다 → 대상이 바뀌었는지는 안 말한다
성공은 명령이 끝났다는 뜻이지 의도한 상태가 됐다는 뜻이 아니다.
BEFORE=$(curl -s http://web01/health | ...)
# 배포
AFTER=$(curl -s http://web01/health | ...)
[ "$BEFORE" = "$AFTER" ] && { echo "배포됐는데 표시가 그대로입니다"; exit 1; }
배포 전후의 rev 를 비교하는 확인을 따로 붙였다.
바뀐 것을 보는 확인이 없으면 성공만 쌓인다. 이번에도 며칠치 성공 이력이 있었지만 그중 무엇도 실제로는 아무 일을 하지 않았다.
정리
- 배포 설정이 옛 이름을 가리키면 성공한 채로 아무 일도 안 한다
- 옛 이름을 안 지우면 계속 받아진다
- 지워야 실패로 드러난다
- 실패로 드러나는 편이 성공한 척 지나가는 것보다 낫다
- 이름을 바꾸는 작업은 옛 이름을 지우는 것까지가 한 묶음이다
- 가리키는 자리를 화면이 아니라 파일에서 전부 찾는다
- 설정과 스크립트 양쪽에 박혀 있다
- 이름을 한 자리에 두고 나머지는 읽어 가게 한다
- 무엇이 도는지 표시를 남기고 도는 것에서 확인한다
- 기대한 커밋과 받은 커밋이 다르면 멈추게 한다
- 종료 코드는 결과를 말하지 않는다
- 바뀐 것을 보는 확인이 없으면 성공만 쌓인다