회사 테크

[haproxy] reload 는 성공했는데 설정이 안 바뀔 때

reload 가 rc=0 을 돌려줘도 설정이 적용되지 않는 경우가 있습니다. 구 프로세스가 살아남아 옛 설정을 서빙하거나, 검증용 파일에만 반영되고 승격되지 않는 두 가지 패턴입니다.

삽질하는개발자

reload 명령이 rc=0 과 OK 를 반환했는데도 설정이 반영되지 않은 상황을 보여주는 표지 이미지

설정을 고치고 reload 했습니다. 명령은 OK 를 뱉었고 종료 코드도 0이었습니다. 그런데 밖에서 보면 예전 설정 그대로였습니다.

이런 상황을 두 번 겪었는데 원인이 서로 달랐습니다. 둘 다 “명령이 성공했다”와 “설정이 적용됐다”가 다른 명제라는 것을 보여줍니다.

경우 1. 구 프로세스가 안 죽었습니다

관리 도구로 reload와 restart를 여러 번 돌렸는데도 외부 응답이 옛 설정이었습니다. 관리 화면에서는 백엔드가 전부 UP 이었습니다.

프로세스를 세어보고 원인을 알았습니다.

pgrep -c haproxy
2

하나여야 할 프로세스가 둘이었습니다. 새 프로세스는 새 설정으로 떴는데, 옛 프로세스가 죽지 않고 같은 포트를 계속 물고 있었습니다.

리눅스의 SO_REUSEPORT 때문에 이게 가능합니다. 이 옵션을 쓰면 여러 프로세스가 같은 포트에 동시에 바인딩할 수 있습니다. 원래는 무중단 재시작을 위한 기능입니다. 새 프로세스가 포트를 잡고, 옛 프로세스는 처리 중인 연결을 마친 뒤 스스로 빠집니다.

빠지지 않으면 커널이 두 프로세스에 연결을 나눠줍니다. 어떤 요청은 새 설정으로, 어떤 요청은 옛 설정으로 처리됩니다. 그래서 증상이 일관되지 않고, 몇 번 새로고침하면 되는 것처럼 보이기도 합니다.

해결은 단순합니다. 확실히 죽이고 다시 띄웁니다.

service haproxy stop
pkill -9 haproxy      # 남아 있는 것 정리
service haproxy start
pgrep -c haproxy      # 1 인지 확인

이걸 어떻게 확인하나

프로세스 개수 외에 두 가지를 더 봤습니다.

백엔드의 누적 세션 카운터(stot) 를 보면 어느 프로세스가 실제로 트래픽을 받고 있는지 드러납니다. 새 설정에만 있는 백엔드의 카운터가 계속 0이면 그 설정은 트래픽을 받지 않고 있다는 뜻입니다.

마커를 하나 심는 방법도 확실합니다. 설정에 응답 헤더를 하나 추가해두고 밖에서 요청해 그 헤더가 오는지 봅니다. 오면 새 설정이 서빙 중이고, 안 오면 옛 프로세스가 응답하고 있는 것입니다.

경우 2. 검증용 파일에만 반영됐습니다

두 번째는 더 헷갈렸습니다. 설정을 고치고 reload 했는데 이번에도 반영이 안 됐습니다. 그런데 프로세스는 하나였습니다.

이 관리 도구는 설정을 두 단계로 만듭니다.

템플릿 렌더링  ->  haproxy.conf.staging   (검증용)
reload         ->  haproxy.conf           (실제 사용)

문제는 reload 가 설정에 오류가 있어도 rc=0OK 를 돌려준다는 점이었습니다. staging 파일은 만들어졌지만 검증에서 걸려 승격되지 않았고, 명령은 성공했다고 답했습니다.

원인이 된 오류 자체는 사소했습니다. frontend에서만 쓸 수 있는 지시어를 backend에 넣었던 것입니다.

error ... 'be_xxx' has no frontend capability

이 메시지는 reload 출력이 아니라 설정 검증 명령을 따로 돌렸을 때 나왔습니다. reload만 보고 있으면 영영 못 봅니다.

중간에 캐시 문제를 의심해 관리 데몬을 재시작해 보기도 했는데 무관했습니다. 구조를 모르는 상태에서 짚은 헛다리였습니다.

그래서 reload 뒤에는 무엇을 봐야 하나

두 경우 모두 명령의 반환값만 보면 알 수 없습니다. reload 후에 확인할 것을 세 가지로 정리했습니다.

첫째, 프로세스가 몇 개인지. 무중단 재시작을 하는 서비스는 옛 프로세스가 남을 수 있습니다.

둘째, 실제로 로드된 설정 파일이 무엇인지. 편집한 파일과 서비스가 읽는 파일이 다를 수 있습니다. staging 구조를 쓰거나, 템플릿에서 생성하거나, include 경로가 여러 개인 경우가 그렇습니다. 파일의 수정 시각과 내용을 직접 확인하는 편이 빠릅니다.

셋째, 밖에서 본 동작이 바뀌었는지. 결국 이게 유일하게 확실한 증거입니다. 카운터, 응답 헤더, 로그 중 무엇이든 새 설정에서만 나타나는 흔적을 하나 정해놓고 그것을 확인합니다.

reload 이후 확인할 세 가지인 프로세스 개수, 로드된 설정 파일, 밖에서 본 동작을 정리한 도표

이 패턴은 HAProxy만의 일이 아닙니다

같은 형태의 함정을 다른 곳에서도 만납니다.

방화벽 규칙을 넣었더니 명령은 성공했는데, 저장된 규칙을 확인해보니 없어진 적이 있습니다. 장비의 관리 데몬이 규칙을 주기적으로 다시 쓰고 있었습니다. 그 장비에서는 OS 명령이 아니라 장비 자체의 관리 도구를 써야 했습니다.

이중화 구성에서는 한쪽에만 반영되는 경우도 있습니다. 설정 파일을 직접 편집하면 동기화가 자동으로 돌지 않아 대기 노드는 옛 설정을 그대로 들고 있습니다. 평소에는 아무 문제가 없다가 페일오버가 일어나는 순간 옛 설정이 되살아납니다. 가장 늦게 발견되고 가장 곤란한 형태입니다.

공통점은 하나입니다. 성공을 알리는 주체와 실제로 설정을 적용하는 주체가 다르다는 것입니다. 그 사이에 검증 단계, 동기화 단계, 재작성 데몬이 끼어 있으면 성공 신호는 앞쪽에서 나오고 적용은 뒤쪽에서 실패합니다.

정리

  1. rc=0 은 “명령을 접수했다”까지만 보장합니다
  2. reload 후 프로세스 개수를 셉니다. 무중단 재시작 구조에서는 옛 프로세스가 남을 수 있습니다
  3. 편집한 파일과 서비스가 읽는 파일이 같은지 확인합니다
  4. 새 설정에서만 나타나는 흔적을 하나 정해두고 밖에서 확인합니다
  5. 이중화라면 짝 노드까지 확인합니다. 동기화는 자동이 아닐 수 있습니다

설정 변경 작업에서 가장 위험한 순간은 실패했을 때가 아닙니다. 실패했는데 성공했다고 답을 받았을 때입니다.