fos-blog/study
01 / 홈02 / 카테고리03 / 시리즈
01 / 홈02 / 카테고리03 / 시리즈

카테고리

  • AI 페이지로 이동
    • RAG 페이지로 이동
    • agent 페이지로 이동
    • claude-code 페이지로 이동
    • harness 페이지로 이동
    • llm 페이지로 이동
    • ops 페이지로 이동
    • practice 페이지로 이동
  • algorithm 페이지로 이동
    • Alias Method: 가중치 랜덤 선택을 전처리 O(n), 추출 O(1)로 바꾸기
    • Welford's Online Algorithm: 값을 저장하지 않고 평균과 분산 계산하기
  • architecture 페이지로 이동
    • distributed-systems 페이지로 이동
    • domain 페이지로 이동
    • evolution 페이지로 이동
    • patterns 페이지로 이동
  • database 페이지로 이동
    • milvus 페이지로 이동
    • mysql 페이지로 이동
    • opensearch 페이지로 이동
    • postgresql 페이지로 이동
    • qdrant 페이지로 이동
    • redis 페이지로 이동
    • vespa 페이지로 이동
    • DB Connection Pool Saturation과 Thread Pool 격리
    • 커넥션 풀 크기는 얼마나 조정해야 할까?
    • gaps and islands: SQL 창 함수로 이어진 구간 찾기
    • MyBatis 기본기 — XML Mapper, resultMap, 동적 SQL, 운영 패턴 정리
    • MyBatis와 JPA/Hibernate 트레이드오프 — 레거시 백엔드를 다루는 시니어 관점
    • 한국어 형태소 분석기 Nori vs Lindera — OpenSearch에서 Milvus로 갈 때 어휘 검색은 유지되나
    • 정규화와 역정규화: 무결성과 조회 비용 사이의 선택
    • 벡터 DB 5종, 아키텍처는 어떻게 다른가
    • 벡터 DB 5종을 실제로 벤치마크했다 — 같은 recall에서 QPS는 얼마나 갈리나
    • 벡터 DB 어떻게 고를까 — OpenSearch · Milvus · Qdrant · Vespa · pgvector 비교
    • 벡터 DB를 실제로 도입한 사례 — 빅테크 프로덕션
  • devops 페이지로 이동
    • docker 페이지로 이동
    • k8s 페이지로 이동
    • Envoy Proxy
    • Graceful Shutdown
    • 종료 신호는 어디서 멈추는가
  • http 페이지로 이동
    • HTTP Connection Pool
    • HTTPS와 TLS: 핸드셰이크, 인증서, 종료 지점
  • java 페이지로 이동
    • concurrency 페이지로 이동
    • jdbc 페이지로 이동
    • spring 페이지로 이동
    • spring-batch 페이지로 이동
    • testing 페이지로 이동
    • Alpine 이미지의 Java 에서 링크 바꿔치기(TOCTOU)를 막지 못하는 이유
    • Java 동시성 락 정리 — 커머스 메뉴/프로모션 정책 캐시 갱신 관점
    • JVM 튜닝 실전: 메모리 구조부터 Virtual Threads, GC 튜닝, 프로파일링까지
    • 로그에 traceId 남기기 — MDC 부터 OpenTelemetry 까지
    • Java StampedLock — 읽기 폭주에도 쓰기가 밀리지 않는 락
    • Virtual Thread와 Project Loom
  • javascript 페이지로 이동
    • typescript 페이지로 이동
    • AbortController
    • Async Iterator와 제너레이터
    • CommonJS와 ECMAScript Modules
    • 제너레이터(Generator)
    • Http Client
    • Node 백엔드 운영 패턴 — Streams 백프레셔, pipe/pipeline, 멱등성 vs 분산 락
    • Node.js
    • `setImmediate()`
  • kafka 페이지로 이동
    • Kafka 클러스터 아키텍처: Broker, KRaft Controller와 복제
    • Kafka 기본 개념: Topic, Partition, Offset과 Segment
    • Kafka 실전 설계: 파티션 전략, 컨슈머 그룹, 전달 보장, 재시도, 순서 보장 트레이드오프
    • Kafka 파티션·리밸런스·컨슈머 지연 운영
    • Kafka를 로그로 이해하기: 복제, 재처리, 상태와 데이터 통합
    • Spring Kafka 컨슈머 오프셋 커밋과 트랜잭션 정렬: AckMode, manual ack, 멱등 처리
  • linux 페이지로 이동
    • fsync — 리눅스 파일 동기화 시스템 콜
    • SSH forced command 로 배포 키가 실행할 수 있는 명령 제한하기
  • mlops 페이지로 이동
    • llm-serving 페이지로 이동
    • model-router 페이지로 이동
    • Python CUDA 버전 생태계 — nvidia-smi, nvcc, pip, conda가 다 다른 버전을 말하는 이유
    • GPU 컨테이너의 CUDA 버전 호환성 — nvidia-smi부터 이미지 다이어트까지
    • Kubernetes GPU 노드에서 /run tmpfs가 꽉 차서 Pod가 안 뜰 때
    • GPU·CUDA·MPS 기초 — 자바 백엔드 개발자가 처음 만나는 그림
    • Multi-process GPU 워크로드 — 자바 ThreadPool 사용자가 만나는 모델 차이
    • ML 서비스 성능 분석 워크플로 — 자바 백엔드 트러블슈팅과 다른 점
    • 한 GPU 를 여러 프로세스가 나눠 쓰기 — Time-Slicing 과 MPS
  • network 페이지로 이동
    • Connection reset by peer는 누가 보낸 걸까 — 리버스 프록시 홉마다 TCP 연결은 따로 논다
    • L2(스위치)와 L3(라우터)의 역할 차이
    • L4와 VIP(Virtual IP Address)
    • IP Subnet
  • observability 페이지로 이동
    • Observability 입문: 시니어 백엔드가 장애를 탐지하고 대응하는 방식
    • K8s 위 Spring Boot 앱의 메트릭 수집
  • python 페이지로 이동
    • Python async/await — CompletableFuture·Reactor 와 다른 점, 그리고 blocking I/O 함정
    • Python 의존성 관리 — Java Maven/Gradle 사용자가 만나는 첫 충격
    • FastAPI 기초 — Spring Boot 사용자가 빠르게 익히는 법
    • Java 개발자를 위한 Python 심화 — OOP·데코레이터·컨텍스트 매니저
    • PyTorch 기초 — 텐서, 디바이스, 그리고 모델 로딩이 무거운 이유
    • Java 개발자를 위한 Python 문법 핵심
    • ThreadLocal 에서 contextvars 로 — Python 의 요청 컨텍스트 전파
    • OCR 동작 원리 — Layout · Text · Post-process 3단계
    • Python 서버의 RSS가 줄지 않는 이유: CPython, gc.collect(), malloc_trim
  • rabbitmq 페이지로 이동
    • RabbitMQ Basics — 실전 백엔드 관점에서 정리하는 메시지 브로커 기본기
    • RabbitMQ vs Kafka — 백엔드 메시징 선택 기준과 실전 운영 관점
  • resume 페이지로 이동
    • 경험 및 경력기술서
    • 김병태 경력기술서
    • 김병태 포트폴리오
  • task 페이지로 이동
    • ai-service-team 페이지로 이동
    • nsc-slot 페이지로 이동
    • personal-projects 페이지로 이동
    • sb-dev-team 페이지로 이동
    • the-future-company 페이지로 이동
  • thinking 페이지로 이동
    • 자기주도 학습: 질문에서 시작해 글로 검증하기
    • 좋은 일을 넘어 중요한 일을 하는 법
FOS-BLOG · FOOTERall systems normal·v0.1 · 2026.04.27·seoul, kr
Ffos-blog/study

개발 학습 기록을 정리하는 블로그입니다. 공부하면서 기록하고, 기록하면서 다시 배웁니다.

visitors
01site
  • Home↗
  • Posts↗
  • Categories↗
  • Glossary↗
  • About↗
02policy
  • 소개/about
  • 개인정보처리방침/privacy
  • 연락처/contact
03categories
  • AI↗
  • Algorithm↗
  • DB↗
  • DevOps↗
  • Java/Spring↗
  • JS/TS↗
  • React↗
  • Next.js↗
  • System↗
04connect
  • GitHub@jon890↗
  • Source repositoryjon890/fos-study↗
  • RSS feed/rss.xml↗
  • Newsletter매주 1 회 · 한 편의 글→
© 2026 FOS Study. All posts MIT-licensed.
built with·Next.js·Tailwind v4·Geist·Pretendard·oklch
fos-blog/devops/쿠버네티스 Admission 단계 — 리소스…
devops

쿠버네티스 Admission 단계 — 리소스가 etcd에 저장되기 직전에 무슨 일이 일어나는가

kubectl apply를 하면 리소스가 클러스터에 "생긴다". 그런데 그 사이에 뭐가 있는지는 오래 몰랐다. 공인 LoadBalancer를 붙이면서 ingress controller를 내부용·외부용으로 나눴는데, "새 Ingress를 만들면 admission webhook이 검증한다"는 문장이 계속 나왔다. admission이 정확히 어느 단계고, 왜 하...

2026.07.16·5 min read·86 views·SERIES · 백엔드 개발자를 위한 쿠버네티스 기본기 · 5/8

kubectl apply를 하면 리소스가 클러스터에 "생긴다". 그런데 그 사이에 뭐가 있는지는 오래 몰랐다. 공인 LoadBalancer를 붙이면서 ingress controller를 내부용·외부용으로 나눴는데, "새 Ingress를 만들면 admission webhook이 검증한다"는 문장이 계속 나왔다. admission이 정확히 어느 단계고, 왜 하필 "저장 직전"인지 짚고 넘어가지 않으면 self-lock 같은 사고를 이해할 수 없었다.

한 줄로 먼저 말하면 — admission은 kube-apiserver가 요청받은 객체를 etcd에 저장하기 직전에 검문·변형하는 단계다. 여기서 거부되면 객체는 etcd에 저장조차 안 되고, 그래서 controller도 그 객체를 영영 보지 못한다. 이 "저장 직전"이라는 위치가 핵심이라, 이 글은 apiserver 요청 파이프라인에서 admission이 어디 앉아 있는지부터 정리한다.

kube-apiserver 요청 파이프라인

kubectl이든 ArgoCD든, 클러스터에 뭔가를 바꾸는 요청은 전부 kube-apiserver 하나를 거친다. 그 요청이 처리되는 순서는 이렇다.

요청(kubectl · ArgoCD · controller)이 kube-apiserver에 들어오면 순서대로 처리된다.

  1. Authentication — 너 누구냐. 인증서·토큰·서비스어카운트로 신원 확인
  2. Authorization — 이거 할 권한 있냐. RBAC 검사
  3. Admission Control — 이 글의 주제. 안에서 또 세 단계로 나뉜다
    1. Mutating admission — 객체를 변형한다 (기본값 주입, sidecar 삽입)
    2. Object schema 검증 — OpenAPI 스키마에 맞는지 본다
    3. Validating admission — 객체를 검증하고, 통과 못 하면 거부한다
  4. etcd 저장 — 이제서야 클러스터에 존재하게 된다
  5. controller가 감지 — 실제 반영. nginx reload, Pod 생성 등

인증(누구냐)과 권한(되냐)을 통과한 다음, 저장되기 직전에 admission이 낀다. 그리고 실제로 뭔가가 동작하는 건(nginx가 설정을 다시 읽거나, Pod가 뜨거나) 그보다도 뒤인 5번, controller가 etcd에 저장된 객체를 보고 나서다.

이 순서를 눈에 넣어두면 두 가지가 분명해진다.

  • admission에서 거부되면 객체는 4번(etcd)에 못 간다. 클러스터에 존재한 적이 없으니 5번 controller도 못 본다. kubectl은 그 자리에서 에러를 받는다.
  • 반대로 이미 5번까지 가서 돌고 있는 기존 객체(예: 지금 트래픽을 받고 있는 Ingress)는 admission과 무관하다. admission은 "새로 들어오는 요청"만 검문하는 문지기지, 이미 안에 있는 것들을 건드리지 않는다.

두 번째가 실무에서 특히 중요했다. 운영 중인 서비스에 새 Ingress를 하나 추가할 때 "기존 트래픽이 끊기나?"를 걱정했는데, admission은 신규 객체의 등록 여부만 판정할 뿐 돌고 있는 것에 손대지 않는다. 그래서 순수하게 추가만 하는 변경은 기존 경로에 영향이 없다.

Mutating vs Validating — 변형이냐 검증이냐

admission은 두 종류로 갈린다. 순서대로 mutating이 먼저, validating이 나중이다.

  • Mutating — 저장 전에 객체를 변형한다. 비어 있는 필드에 기본값을 넣거나, Pod에 sidecar 컨테이너를 자동으로 끼워 넣는 식이다(Istio가 이렇게 한다). 요청을 바꿔서 통과시킨다.
  • Validating — 객체를 바꾸지 않고 규칙에 맞는지만 본다. 통과하면 그대로, 아니면 거부한다.

둘을 나눠 놓은 이유는 순서 때문이다. 변형이 다 끝난 최종 형태를 놓고 검증해야 하니, mutating을 먼저 돌리고 그 결과를 validating이 본다. 중간에 mutating이 넣은 기본값까지 포함해서 검증하는 것이다.

쿠버네티스에는 이 admission을 수행하는 내장 플러그인이 여럿 있다(예: NamespaceLifecycle, ResourceQuota). 그리고 여기에 직접 만든 검문 로직을 꽂는 확장 지점이 admission webhook이다.

admission webhook — apiserver가 외부에 물어보는 확장점

내장 플러그인만으로는 "우리 팀 규칙"을 강제할 수 없다. 그래서 apiserver는 admission 단계에서 외부 HTTP 엔드포인트에 물어보는 기능을 연다. 이게 admission webhook이다.

  • MutatingWebhookConfiguration — apiserver가 "이 객체 이렇게 바꿔도 돼?"를 외부에 물어 변형 결과를 받는다.
  • ValidatingWebhookConfiguration — apiserver가 "이 객체 받아도 돼?"를 외부에 물어 admit/deny를 받는다.

OPA/Gatekeeper, Kyverno, Pod Security 같은 정책 도구가 전부 이 위에서 돈다. 그리고 우리가 쓰는 ingress-nginx도 여기 하나 얹혀 있었다.

실제로 마주친 예 — ingress-nginx의 ValidatingWebhook

ingress-nginx를 설치하면 ValidatingWebhookConfiguration이 같이 깔린다. 하는 일은 이렇다.

  • 누군가 Ingress를 CREATE/UPDATE 한다.
  • apiserver가 저장 직전에 ingress-nginx controller의 webhook을 호출한다.
  • controller는 그 Ingress를 반영한 nginx 설정을 렌더해서 nginx -t(문법 검사)를 돌린다.
  • 문법이 깨지면(잘못된 annotation, 망가진 정규식 등) admission 단계에서 거부한다.

효과는 명확하다. 잘못된 Ingress 하나가 실제 nginx config로 반영돼서 controller 전체를 망가뜨리는 사고를, "저장되기 전에" 막는다. 깨진 설정은 etcd에 들어가지도 못하니 controller가 그걸 로드할 일이 없다.

실제 클러스터에서 확인한 webhook은 이런 모양이었다.

plaintext
ValidatingWebhookConfiguration: ingress-nginx-admission
  webhook: validate.nginx.ingress.kubernetes.io
  failurePolicy: Fail
  rules: ingresses (CREATE, UPDATE)   # class 구분 없이 전부

여기서 두 가지를 눈여겨봐야 했다. failurePolicy: Fail과, rules에 class 필터가 없다는 것.

함정 — webhook은 controller와 달리 격리되지 않는다

controller는 IngressClass로 자기 것만 처리한다. nginx class controller는 nginx Ingress만, nginx-external class controller는 nginx-external Ingress만 본다.

그런데 webhook은 그렇지 않다. ValidatingWebhookConfiguration은 cluster-scoped 리소스라, class로도 namespace로도 안 갈라지고 클러스터 전체의 Ingress 요청을 전부 가로챈다. 위 rules에 class 필터가 없는 게 그 증거다.

여기에 failurePolicy: Fail이 겹치면 사고 시나리오가 나온다. controller를 내부용·외부용 둘로 나누면서 외부 controller의 webhook을 그냥 켜두면 —

  1. 외부 controller의 webhook Pod가 죽는다.
  2. 그런데 그 webhook은 (class 무관) 모든 Ingress 요청을 검문하도록 등록돼 있다.
  3. 검문에 응답 못 하면 failurePolicy: Fail이라 요청을 거부한다.
  4. 결과적으로 내부 Ingress를 수정하려 해도 "webhook 응답 없음"으로 막힌다.
  5. 배포가 줄줄이 실패한다 — self-lock이다.

그래서 우리는 외부 controller에는 admissionWebhooks.enabled: false로 webhook 자체를 안 만들었다. 검증 한 겹을 포기하는 대신(전역 검증은 기존 내부 controller의 webhook이 어차피 해준다) 새 단일 장애점을 안 만든 것이다. 이 self-lock을 실제로 어떻게 피했는지, whitelist·affinity 같은 다른 운영 디테일과 함께 다룬 글은 따로 있다 — ingress-nginx 운영에서 부딪힌 디테일들.

가져갈 것

admission을 이해하고 나서 남은 판단 기준은 이렇다.

  • 기존 트래픽이 끊기나 물을 때 — admission은 신규 객체 등록만 게이트한다. 이미 돌고 있는 객체엔 손대지 않는다. 순수 추가(새 Ingress·새 Secret) 변경이면 admission 검증만 통과하면 되고, 기존 경로는 무관하다.
  • admission webhook을 늘릴 때 — controller는 class로 격리되지만 webhook은 안 된다. failurePolicy: Fail + 전역 rules 조합이면, 그 webhook Pod의 장애가 클러스터 전체 Ingress 변경을 막을 수 있다. controller를 여러 개 둘 거면 webhook을 하나로 유지하거나, 추가되는 쪽 webhook을 끄는 걸 먼저 검토한다.
  • 거부와 런타임을 분리해서 보기 — admission 거부는 "저장 실패"지 "서비스 장애"가 아니다. kubectl이 에러를 받을 뿐, 돌고 있는 워크로드는 그대로다. 반대로 잘못된 게 admission을 통과해 버리면 그때부터가 진짜 위험이다.

관련 글

  • ingress-nginx 운영에서 부딪힌 디테일들 — webhook, whitelist, affinity, 리소스 사양 — 이 글의 self-lock을 실제로 어떻게 피했는지
  • 쿠버네티스 핵심 객체 4종 — Pod, Service, Ingress, Namespace의 관계
  • API Gateway를 제거한 자리 채우기 — path rewrite, 요청 크기 병목 4개, 그리고 HTTPS

참고 링크

  • Kubernetes — Dynamic Admission Control
  • Kubernetes — Admission Controllers Reference
  • Kubernetes — Controlling Access to the Kubernetes API
on this page
  • 01kube-apiserver 요청 파이프라인
  • 02Mutating vs Validating — 변형이냐 검증이냐
  • 03admission webhook — apiserver가 외부에 물어보는 확장점
  • 04실제로 마주친 예 — ingress-nginx의 ValidatingWebhook
  • 05함정 — webhook은 controller와 달리 격리되지 않는다
  • 06가져갈 것
  • 07관련 글
  • 08참고 링크
tags
#study📚 백엔드 개발자를 위한 쿠버네티스 기본기
← PREVIOUSDeployment · ReplicaSet · Pod — 배포가 굴러가는 3층 구조NEXT →Helm — 쿠버네티스 YAML 의 패키지 매니저이자 템플릿 엔진

이런 글도

  • devops

    종료 신호는 어디서 멈추는가

    2026.09.02
  • devops

    파드가 1개면 minAvailable 이 노드 교체를 막는다

    2026.08.20
  • devops

    ArgoCD는 초록불인데 서비스는 죽어 있었다 — 불변 필드와 재조정의 경계

    2026.08.11
  • devops

    파드가 서비스 이름을 못 푸는 이유 — dnsPolicy 4종과 ndots

    2026.08.03

댓글 (0)