
발자국 2회차: 너 살아 있니?
너 살아 있니?
해당 글은 인프런 워밍업 클럽 스터디 4기 - DevOps (쿠버네티스)를 진행하며 깊게 고민해 보려고 한 내용을 담습니다. 개괄적 정리보다 아는 것과의 비교를 통한 이해를 추구합니다.
서버가 죽었다. 새벽 3시에 알람이 울렸다.
뭘 생각할 정신도 없이 노트북을 켜고 터미널에 접속한다. 등골이 서늘하다.
음. 옛날에는 그랬겠지. 그런데 이제 그럴 필요가 적어졌다.
쿠버네티스를 만나고 나의 꿀잠 시대 시작됐다.
그런데 정말 든든한 꿀잠을 자기 위해서는 어떻게 [설정]해야 하는지가 중요한 영역이 된다.
이번 주는 그 부분에 대해서 더 깊이 파 보는 시간이었고, 나는 지금까지 애플리케이션의 '살아있음'을 너무 단순하게 생각한 것이 아닌가? 회고했다.
살아있다는 게 뭐야?
처음 Probe를 접했을 때, 그냥 서버 헬스체크 정도로만 생각했다.
그런데 강사님의 매우 중요 별 100개를 들으면서 아 이거 집중해야겠네... 라는 긴장 상태 (ㅋㅋ) 에 돌입했다.
각 Probe는 다음 세 가지 결과 중 하나를 가진다.
Success : 컨테이너가 진단을 통과함.
Failure : 컨테이너가 진단에 실패함.
UnKnown : 진단 자체가 실패하였으므로 아무런 액션도 수행되면 안됨.
이 판단에 따라 쿠버네티스는 액션하고, 우리의 통신 시도를 서버와 연결한다. 이 판단 중에는 단적인 평가만 있지 않다.
Probe에는 세 가지 종류가 있다.
Startup Probe는 "너 아직 준비 중이니?"라고 묻는다.
애플리케이션이 무거워서 시작하는 데 시간이 오래 걸린다면, 이 친구가 기다려준다. 덕분에 아직 준비도 안 된 애플리케이션을 쿠버네티스가 죽었다고 판단해서 재시작시키는 불상사를 막을 수 있다.
Liveness Probe는 "너 진짜 살아있어?"라고 묻는다.
애플리케이션은 돌고 있는데 데드락에 걸렸다거나, 무한루프에 빠졌다면? 겉으로는 멀쩡해 보여도 실제로는 아무 일도 못하고 있는 좀비 프로세스가 되어버린다. 이럴 때 liveness Probe가 판단해서 재시작을 명령한다.
Readiness Probe는 "너 일할 준비 됐어?"라고 묻는다.
살아있는 것과 트래픽을 받을 준비가 된 것은 다르다. DB 커넥션 풀이 아직 준비 안 됐다면? 캐시 워밍업이 필요하다면? readiness Probe가 실패하면 Service의 엔드포인트에서 제외된다.
Startup Probe가 활성화되어 있으면, 이 프로브가 성공할 때까지 다른 프로브는 실행되지 않음
Liveness Probe가 실패하면, 쿠버네티스는 컨테이너를 재시작함.
Readiness Probe가 실패하면, 해당 파드를 서비스의 엔드포인트에서 제외함
그럼 판단은 언제 내려야 할까? 그건 우리가 정해 줘야 하는 영역이다.
Probe도 리소스를 먹는다. 당연하다. 호출하는 것도 리소스다.
따라서 Probe를 설정할 때의 고민은 [무엇을 책임지는가] 로 간다는 생각을 했다.
Startup Probe: 넉넉하게. 특히 JVM 워밍업이 필요한 애플리케이션은 더더욱.
Liveness Probe: 보수적으로. 함부로 재시작하면 안 되니까.
Readiness Probe: 민감하게. 사용자 경험을 위해서.
설정은 코드다 - ConfigMap과 Secret
"환경변수는 어디에 둬?"
Spring 베이스로 개발을 하던 나는 자연스럽게 yml과 properties로 생각이 향했다.
그러나 K8S 환경에서의 Config는 조금 달랐다. 런타임의 등장이다.
런타임에서 고려해야 하는 설정 값들은 단순하게 local / dev 등으로 분기할 수 없다.
빌드 타임에 결정되는 것: application.yaml
런타임에 결정되는 것: ConfigMap
절대 노출되면 안 되는 것: Secret (어쩌면 Base64일 뿐이지만)
Kubentes는 기본적으로 Secret 값을 ETCD에 저장하는데, Base64 인코딩을 한다.
사실 Secret이라는 단어를 봤을 때 드는 생각은, 오. 민감한 정보 다 맡기면 되겠는데.
였는데... 안타깝게도 Base64로만 암호화가 되는 불상사(?)가 존재했다.
즉 ETCD에 접근권한이 있다면 Secret을 읽는 게 어려운 일이 아니다.
물론 이제 클라우드 서비스에서 제공하는 KMS(EKS)와 같은 encrypt 수단이 있긴 하지만,
결국 중요한 것은 [그 Secret 오브젝트에 대한 읽기 권한을 누가 가지게 할지]가 된다.
Secret의 진짜 가치는 암호화가 아니라 '관리'에 있는 것.
RBAC으로 접근을 제어하고, audit log로 누가 언제 접근했는지 추적하고, 필요하면 암호화 솔루션과 연동할 수도 있고.
음, 이제 설정 파일까지 완료가 됐다.
뼈대를 잡았다면 좋은 브레이크도 필요하다.
준비시키는 것도 중요하지만 얼마나 잘 죽는지가 더 중요하다.
Graceful Shutdown에 대해 생각해 볼 시간이다.
PV/PVC, 뭘 저장해?
파드는 죽는다. 언제든지, 아무 때나.
파드가 죽으면 컨테이너도 죽고, 컨테이너 안의 데이터도 날아간다.
로그 파일이든, 업로드한 이미지든, 세션 데이터든 다 사라진다.
그래서 PV(Persistent Volume)가 필요하다. 파드가 죽어도 살아남는 저장소.
hostPath
노드의 경로를 그대로 마운트
파드가 다른 노드로 스케줄링되면 데이터를 못 찾음 -> nodeSelector로 노드를 고정
쿠버네티스 공식 문서에서 사용하지 않는걸 권장하고 있음
local PV
nodeAffinity로 어느 노드에 있는 스토리지인지 명시
쿠버네티스가 스케줄링할 때 이를 고려
그런데 이름에서 보여지는 것처럼 해당 저장소들은 전부 로컬이겠지.
로컬이면? 노드가 죽으면? 데이터도 같이 죽는다.
동시에 개발자가 직접 스토리지를 관리해야 할까?
그래서 프로덕션에서는 EBS, NFS, Ceph 같은 네트워크 스토리지를 쓴다고 한다.
PVC(Persistent Volume Claim)가 등장함과 함께.
"나 100GB 스토리지 필요해"라고 요청하면, 쿠버네티스가 알아서 적절한 PV를 찾아서 연결해준다.
인프라 팀은 PV를 미리 준비해두거나 StorageClass로 동적 프로비저닝을 설정한다. 역할 분리. 깔끔하다.
안전하게 죽이는 방법은 확인했다. 그럼 이제 살리는 [시점]에 어떻게 할지를 생각해 볼 시간이다.
어떻게 살리지? 옛날에는...
Deployment: 배포를 새벽에 하라고요?
... 였다!
하지만 배포할 때마다 서비스가 죽는다면, 그건 2025년의 방식이 아니다.
우리에게 무중단 배포는 이제 고려할 것이 아니라 필수의 영역이다.
쿠버네티스의 Deployment는 여러 전략을 제공한다.
Rolling Update
기본 배포 전략. 새 버전을 하나씩 올리고 구 버전을 하나씩 내리는 방식
maxSurge와 maxUnavailable을 잘 조절하면 리소스 사용량과 안정성 사이에서 균형을 맞출 수 있다.
Recreate
모든 구 버전을 내리고 새 버전을 올린다.
다운타임이 발생하지만, 두 버전이 동시에 돌면 안 되는 경우에는 답이다.
Blue-Green
근데 Recreate만 쓸 수 있는 건 아니지.
두 개의 완전한 환경을 준비하고 트래픽을 한 번에 전환. 롤백이 쉽다.
단점은? 리소스가 두 배.
Canary
새 버전에 트래픽의 일부만 보내서 테스트. 문제없으면 점진적으로 늘려간다.
Istio나 Flagger 같은 도구와 함께 쓰면 자동화도 가능하다.
Rolling Update는 가장 기본적인 쿠버네티스의 전략이다.
그런데 Rolling Update 중에 readiness Probe가 너무 빨리 성공해서, 아직 캐시 워밍업이 안 된 Pod에 트래픽이 들어갔다면? 이는 응답 지연이나 에러 발생의 원인이 된다.
이것을 방지하기 위해서는 [점진적 헬스 체크] 설정이 중요하다고 한다.
initialDelaySeconds를 충분히 확보
livenessProbe가 10초 뒤에 최초 실행되는 등
readinessProbe에서 진짜 준비 상태를 판단하도록 구현
단순히 HTTP 200이 아닌, 내부적으로 캐시, DB, 외부 API 등의 상태가 정상인지 점검하여 응답하게 만듦
오케이, 살리는 방법까지 확인했다.
그러면 [언제] 다시 살리는가?
우리는 꿀잠 메타를 노리고 있다. 알람 울리기 전에 알아서 처리하게 해 보자.
그러려면 k8s가 수치를 쳐다보게 해야 한다.
모니터링은 사치가 아니다
"로그 어디서 봐?"
답은 사실 1주차부터 나와 있기는 했다. 설치했으니까. ㅎㅎ
Prometheus + Grafana + Loki.
쿠버네티스는 기본적으로 메트릭을 노출한다.
별도의 에이전트 설치 없이도 CPU, Memory, Network 사용량을 수집할 수 있다.
하지만 진짜 중요한 건 애플리케이션 메트릭이다.
비즈니스 메트릭: 주문 수, 가입자 수, 결제 금액
성능 메트릭: 응답 시간, 처리량, 에러율
시스템 메트릭: DB 커넥션 수, 캐시 히트율, 큐 길이
이러한 3가지 메트릭에 따라 고려해 둘 것은 다음과 같은 것들이 있었다.
단순 임계값이 아닌 rate() 함수로 변화율 감지
여러 조건을 AND로 묶어서 false positive 줄이기
알람 피로도를 줄이기 위한 억제(inhibition) 규칙
이와 같이 모니터링을 토대로 트래픽을 식별하는 이유는 뭘까? 역시 스케일링도 그 꼭지 중 하나일 것이다.
트래픽이 몰릴 때 서버를 늘릴 수 있도록. (꿀잠)
HPA - 수직적 스케일링의 두등장
"트래픽이 몰리면 서버를 늘리면 되지 않나?"
맞는 말이다. 그런데 '언제' 늘릴 것인가?
얼추 CPU 사용률 80% 넘으면 Pod 추가, 얼추 30% 이하면 Pod 제거?
만약 그렇게 간단했다면 DevOps 의 연봉 풀이... 더보기
팩터 수집가 - 여러 가지 스케일링 팩터를 가지고 있기
Pod 추가 - 빠른 스케일 아웃 방지하기: Behavior 설정
Pod 제거 - 빠른 스케일 다운 방지하기: scaleDown의 stabilizationWindowSeconds
그 기준에 대해서는 여러가지 스케일링 팩터를 가지고 있는 것이 중요하고,
너무 빠르게 스케일 아웃을 하게 하지 않는 게 중요하고,
빠르게 서버를 낮추지 않게 하는 것이 중요하다.
어떠한 기준에 도달했을 때 스케일 아웃을 할지 / 스케일 다운을 할지에 대한 팩터를 서비스에 맞춰 조절할 수 있어야 한다.
그리고 해당 기준에 도달했을 때 너무 빠른 속도로 서버가 올라가거나 내려가지 않도록 설정해 두는 것이 중요하다.
마무리하며
"서버, 너 살아 있니?"
이 질문은 어쩌면 [통신 가능]의 여부로 단순해 보이지만, 쿠버네티스 환경에서는 훨씬 깊은 의미를 담고 있는 것 같다.
Probe로 건강을 체크하고, ConfigMap/Secret으로 설정을 관리하고, 적절한 배포 전략으로 무중단을 달성하고, 모니터링으로 상태를 추적하고, HPA로 탄력적으로 대응한다.
이 모든 게 맞물려 돌아갈 때, 우리는 비로소 서버의 "나 살아있다!"라는 대답을 신뢰할 수 있게 된다. (그리고 꿀잠 잔다)
그래서, 다음 주는?
차주에는 실제 젠킨스 파이프라인과 배포 시 유의점, Helm과 Kustomize에 대해 알아본다고 한다.
뭘 알게 될까? 궁금증이 들어서 GPT에게 스포일러를 요청했다.
아, 정말 어떻게 [배포]할지에 대해서 보다 디테일하게 배워 보는 시간인가 보다.
여러 pod를 어떤 식으로, 서비스 운영에 문제가 없도록 배포할 수 있는지.
이 오케스트레이션을 알게 되는 것이 아닌지,
어쩌면 수동으로 하던 행위를 바로 일임하는 것인 만큼 가장 중요한 것이 아닌가... 하는 생각이 든다.
다음 주에도 재밌게!
댓글을 작성해보세요.