Troubleshooting 2026.11.08 6 min read

Ready Pod 두 개가 같은 Worker에 몰린 롤아웃의 분산 조건 수정

목표는 Worker 두 대에 Platform을 한 개씩 배치하는 것이었다. replica 수와 요청 응답은 정상이었지만, 의도한 장애 분산은 이루어지지 않았다. 다음 이미지 복귀 시험을 진행하기 전에 이 문제부터 고치기로 했다.

첫 신규 이미지 배포에서 Argo CD의 Sync는 성공했다. RCA Platform의 새 Pod 두 개가 Ready였고 재시작 횟수도 0이었다. HTTPS와 로그인, API, DB 연결, 기존 보고서와 Prometheus 검사까지 통과했다. 그런데 노드 배치를 확인하니 두 Pod가 모두 Worker-02에 있었다.

목표는 Worker 두 대에 Platform을 한 개씩 배치하는 것이었다. replica 수와 요청 응답은 정상이었지만, 의도한 장애 분산은 이루어지지 않았다. 다음 이미지 복귀 시험을 진행하기 전에 이 문제부터 고치기로 했다.

배포 성공 판정에서 빠질 뻔한 노드 배치

이번 배포는 GitOps 저장소의 Platform image digest를 바꾼 PR을 병합한 뒤 시작했다. Jenkins가 PR을 만들고, 내가 변경 내용을 검토해 병합했다. Argo CD에서는 승인한 Git revision을 지정해 Deployment를 수동 Sync했다.

첫 operation은 Succeeded였다. 새 이미지가 실행되고 실제 요청도 성공했으므로 이미지 전달과 서비스 시작은 확인할 수 있었다. 남은 검사는 Platform Pod가 어디에 배치됐는지였다.

두 Pod가 같은 Worker에 올라간 결과는 이 검사를 통해 드러났다. 운영 중 한 Worker가 영향을 받을 때 두 Platform Pod가 함께 영향을 받을 수 있는 배치였다. HTTP 200과 Ready 수만 확인했다면 이 지점을 놓쳤을 것이다.

기존 조건은 old와 new revision을 함께 세고 있었다

Deployment에는 이미 topologySpreadConstraints가 있었다. maxSkew: 1, minDomains: 2, DoNotSchedule을 지정해 두었기 때문에 처음에는 이 조건이 신규 Pod 두 개도 서로 나눠 배치할 것이라고 생각했다.

실제 문제는 롤링 업데이트 중 어떤 Pod를 함께 계산하는지에 있었다. 기존 조건은 old revision과 new revision을 함께 세었다. Worker-01에 old Pod가 남아 있는 동안에는 그 Pod가 한쪽 분산 수를 채우고, 새 Pod 두 개가 Worker-02에 올라갈 수 있었다.

이후 old Pod가 사라진 상태를 보면 새 Pod 두 개만 한쪽에 남았다. 이미 배치된 Pod가 자동으로 옮겨져 최종 분산을 맞출 것이라고 가정하지 않았다. 앞으로의 롤아웃에서도 필요한 배치 조건이 적용되도록 선언 자체를 바꿔야 했다.

revision별로 계산하도록 변경 범위를 좁혔다

수정은 최초 image 변경과 분리한 PR로 진행했다. 추가한 핵심 값은 다음과 같다. 전체 Deployment가 아니라 spread 조건 안의 변경 부분만 발췌했다.

matchLabelKeys:
  - pod-template-hash

pod-template-hash를 사용해 같은 revision의 Pod를 기준으로 분산을 계산하도록 했다. 기존 maxSkew, minDomains, DoNotSchedule, nodeTaintsPolicy: Honor와 rolling 정책은 유지했다.

Platform의 pod-template-hash 기반 topology spread 설정
Platform의 pod-template-hash 기반 topology spread 설정.
topology 분산 조건을 보정한 GitOps PR #4
GitOps PR #4의 Merged 표시와 matchLabelKeys 수정 내용.

원하는 결과를 만들기 위해 노드를 drain하거나 Pod를 수동 삭제하지는 않았다. 변경할 설정을 한 가지로 좁히고, 그 변경이 다음 롤아웃의 배치 결과에 반영되는지 확인했다. 수동 조작으로 한 번 위치를 맞추는 것과 반복할 배포 조건을 고치는 것은 확인할 대상이 달랐다.

설정 변경과 실제 실행 결과를 연결했다

별도 검토와 승인 후 Codex를 통해 해당 revision을 수동 적용했다. Sync operation만 확인하고 끝내지 않고, Deployment의 generation과 observedGeneration, updated·available·ready 수를 확인했다. 이어 각 Pod의 실제 imageID와 Worker 배치를 살펴봤다.

수정 후 Deployment는 generation 6과 observedGeneration 6이 일치했다. Platform Pod는 Worker-01과 Worker-02에 한 개씩 배치됐고, 두 개 모두 Ready이며 재시작은 0이었다. 실행 이미지는 검증하던 신규 이미지로 유지됐고 Application은 Healthy/Synced였다.

Worker별 Platform Pod 한 개씩 Ready·restart 0
Worker별 Platform Pod 한 개씩 Ready·restart 0.

이미지와 배치만 보지 않고 서비스와 기존 데이터도 다시 확인했다. 다른 관리 리소스와 DB 및 PVC, Helm 이력도 보존했다. 분산 조건을 바꾸는 수정이 다른 운영 상태에 영향을 주지 않았는지 확인하기 위해서였다.

다음 이미지 전환의 기준을 먼저 만들었다

첫 배포에서 확인한 것은 서비스가 실행됐다는 사실과 배치 검사가 실패했다는 사실이었다. 이후 설정을 수정하고 두 Worker 분산을 확인한 시점에야 다음 시험으로 넘어갔다.

이 과정에서 배포 완료 기준을 더 구체적으로 잡았다. Sync 결과와 Ready 수, 실제 imageID, Worker별 배치, 사용자 요청과 데이터 확인을 각각 남기기로 했다. 여러 검사가 같은 결과를 말해줄 때 운영 변경을 마무리할 수 있었다.

다음 글에서는 이 분산 조건을 유지한 채 이전 애플리케이션 이미지로 복귀하고, 같은 신규 이미지를 다시 적용한 과정을 다룬다. 설정을 고쳤다는 결과에서 한 단계 더 나아가 실제 이미지 전환 경로를 확인했다.

2026년 10월 19일 재조회에서도 kubectl의 nodeName, Ready, restartCount와 imageID를 대조해 Worker01·Worker02에 한 개씩 Ready, restart 0임을 확인했다. 수정 전 편중은 보존한 당시 JSON, 수정은 GitOps PR #4의 diff로 구분했다.

변경 이력은 GitOps PR #4에서 확인할 수 있다.

SEARCH JOURNAL

기록 검색

글 제목, 요약, 태그에서 찾습니다.

검색을 준비하고 있어요.

검색어를 입력해 주세요.

    Ctrl/⌘ K 검색 Esc 닫기 ↑ ↓ 결과 이동