서비스가 안 된다는 얘기를 들었다. 서버에 들어가서 확인했다.
$ ps -ef | grep java
deploy 3821 ... java -jar app.jar
떠 있어서 정상이라고 답했다. 그래도 안 된다고 했다.
Table of contents
Open Table of contents
떠 있는데 안 받고 있었다
포트를 봤다.
$ netstat -tlnp | grep 8080
아무것도 안 나온다. 프로세스는 있는데 포트를 안 열고 있었다. 로그를 보니 시작하다 데이터베이스 접속에서 멈춰 있었다.
2014-03-13 09:12:03 Connecting to database...
여기서 더 안 나간다. 접속을 무한정 기다리고 있었다.
프로세스가 살아 있어도 요청을 받는다는 뜻은 아니다. 감시는 앞의 것만 보고 있었으니 정상이라는 답이 틀린 것은 아니었지만 쓸모가 없었다.
검증 — 단계를 나눠 확인했다
이 뒤로 확인을 단계로 나눴다.
1. 프로세스가 있나 ps
2. 포트를 열었나 netstat
3. 연결이 되나 nc -z
4. 응답을 주나 curl
5. 정상 응답인가 응답 내용 확인
각 단계가 다른 것을 본다. netstat 까지는 나오는데 curl 에서 멈추는 경우도 있었다. 연결은 받는데 처리가 안 되는 상태다.
어느 단계에서 끊기는지에 따라 볼 곳이 달라진다. 단계를 나누니 같은 안 된다는 말이 다른 조사로 갈렸다.
상태 확인 경로를 만들었다
5번을 위해 간단한 경로를 하나 뒀다.
@RequestMapping("/health")
public Map<String, Object> health() {
Map<String, Object> m = new HashMap<String, Object>();
m.put("status", "ok");
m.put("db", checkDb() ? "ok" : "fail");
m.put("cache", checkCache() ? "ok" : "fail");
m.put("version", APP_VERSION);
return m;
}
$ curl -s http://localhost:8080/health
{"status":"ok","db":"ok","cache":"fail","version":"1.4.2"}
checkDb 와 checkCache 를 각각 담으니 캐시가 안 된다는 것이 바로 보인다. 의존하는 것을 하나씩 확인해야 어디가 문제인지 나온다.
상태 확인이 무거우면 그것 때문에 부하가 생긴다.
private boolean checkDb() {
try {
jdbcTemplate.queryForObject("SELECT 1", Integer.class);
return true;
} catch (Exception e) {
return false;
}
}
SELECT 1 처럼 가장 가벼운 쿼리를 쓴다. 실제 업무 쿼리를 돌리면 안 된다. 시간 제한도 뒀는데 확인이 오래 걸리는 것 자체가 이상 신호이기 때문이다.
애매한 생존보다 확실한 종료
시작 중인 것과 준비된 것을 구분했다.
private volatile boolean ready = false;
@PostConstruct
public void onStart() {
// 초기화
ready = true;
}
ready 가 아직 거짓이면 상태 경로가 503을 준다. 앞단이 그것을 보고 요청을 안 보낸다. 이걸 넣기 전에는 시작 중인 서버로 요청이 가서 오류가 났다.
무한정 기다리는 것도 고쳤다.
dataSource.setConnectionTimeout(10000);
정해 둔 시간 안에 접속이 안 되면 시작이 실패하고 프로세스가 죽는다. 죽으면 감시가 알아채고 다시 뜬다.
애매하게 살아 있는 것보다 확실하게 죽는 게 대응하기 쉽다. 살아 있으면 아무도 손대지 않지만 죽으면 알림이 간다.
감시도 고쳤다.
# 전
pgrep -f app.jar >/dev/null || alert "프로세스 없음"
# 후
CODE=$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 http://localhost:8080/health)
if [ "$CODE" != "200" ]; then
alert "상태 확인 실패 (HTTP ${CODE})"
fi
pgrep 이 아니라 curl 의 응답 코드를 본다. 떠 있는데 안 받는 상태를 이걸로 잡는다.
정리
- 프로세스가 살아 있어도 요청을 받는다는 뜻은 아니다
- 확인을 단계로 나눈다.
ps→netstat→nc -z→curl→ 응답 내용 - 어디서 끊기는지에 따라 볼 곳이 달라진다
- 상태 확인 경로를 만들고 의존하는 것을 각각 확인한다
- 확인은
SELECT 1처럼 가볍게 하고 시간 제한을 둔다 - 시작 중과 준비됨을 구분해 앞단이 요청을 안 보내게 한다
- 시작에 시간 제한을 둔다. 애매하게 살아 있는 것보다 확실하게 죽는 게 낫다
- 감시가
pgrep만 보면 이 상태를 못 잡는다