Certified Kubernetes Application Developer (CKAD) #19 Ingress와 NetworkPolicy

#18에서 Service로 Pod 묶음에 안정적인 진입점을 만들었습니다. 그러나 Service만으로는 두 가지가 부족합니다. 하나는 여러 서비스를 하나의 외부 진입점에서 호스트와 경로로 갈라 보내는 L7 라우팅이고, 다른 하나는 클러스터 안 Pod 사이의 통신을 어느 Pod가 어느 Pod에 접속할 수 있는지를 기준으로 제어하는 일입니다. 앞쪽이 Ingress, 뒤쪽이 NetworkPolicy입니다.

이번 글에서는 외부에서 들어오는 트래픽을 호스트와 경로 기준으로 라우팅하는 Ingress, 그리고 Pod 간 통신을 화이트리스트로 잠그는 NetworkPolicy를 실기 관점에서 다루겠습니다. 둘 다 CKAD의 Services and Networking도메인에 속하며, 매니페스트 필드를 손에 익히는 것이 핵심입니다. 네트워킹의 기초가 헷갈린다면 K8s 중급 #3을 먼저 보고 오면 좋습니다.

Ingress: 하나의 진입점에서 L7 라우팅 #

Service의 LoadBalancer 타입은 서비스마다 외부 IP를 하나씩 잡습니다. 서비스가 늘어날수록 외부 IP가 늘어나고 비용도 늘어납니다. Ingress는 단 하나의 진입점에서 HTTP/HTTPS 요청을 호스트 이름과 URL 경로로 갈라 내부 Service로 전달합니다. L4에서 동작하는 Service와 달리 Ingress는 L7에서 HTTP 헤더의 Host와 경로를 보고 라우팅합니다.

host와 path 기반 라우팅 #

Ingress의 핵심 필드는 rules입니다. 각 규칙은 host(생략 가능)와 http.paths 배열을 가지며, 각 path는 path 문자열, pathType, 그리고 트래픽을 받을 backend를 지정합니다. backend는 대상 Service의 이름과 포트를 가리킵니다.

rules:
- host: shop.example.com
  http:
    paths:
    - path: /api
      pathType: Prefix
      backend:
        service:
          name: api-svc
          port:
            number: 8080
    - path: /
      pathType: Prefix
      backend:
        service:
          name: web-svc
          port:
            number: 80

shop.example.com/api로 들어온 요청은 api-svc로, 그 밖의 경로는 web-svc로 갑니다. 하나의 호스트 안에서 경로별로 다른 서비스에 트래픽을 나눠 보내는 구조입니다.

pathType: Prefix와 Exact #

pathType은 경로를 어떻게 매칭할지 정합니다. CKAD에서 비워 두기 쉬운 필수 필드입니다.

pathType매칭 방식
PrefixURL 경로 요소 단위 접두사 매칭. /api/api, /api/v1에 매칭
Exact경로가 정확히 일치할 때만 매칭. 대소문자 구분
ImplementationSpecific컨트롤러 구현에 위임

실무에서는 대부분 Prefix를 씁니다. pathType을 누락하면 매니페스트가 거부되거나 의도와 다르게 동작하므로, path마다 반드시 채워야 합니다.

IngressClass와 defaultBackend #

클러스터에 Ingress Controller가 여러 종류 있을 수 있으므로, 어떤 컨트롤러가 이 Ingress를 처리할지 spec.ingressClassName으로 지정합니다. 예전에는 kubernetes.io/ingress.class 애너테이션을 썼지만, 지금은 ingressClassName 필드가 표준입니다.

spec:
  ingressClassName: nginx

어떤 규칙에도 매칭되지 않는 요청을 받을 기본 백엔드는 spec.defaultBackend로 둘 수 있습니다. 지정하지 않으면 매칭되지 않는 요청은 컨트롤러의 기본 404로 떨어집니다.

TLS: secret 참조로 HTTPS 종료 #

Ingress는 spec.tls로 HTTPS를 종료합니다. 인증서와 키는 kubernetes.io/tls 타입의 Secret에 담고, Ingress는 그 Secret 이름을 참조합니다.

spec:
  tls:
  - hosts:
    - shop.example.com
    secretName: shop-tls

hosts에 적은 호스트로 들어오는 HTTPS 요청에 shop-tls Secret의 인증서를 적용합니다. Secret 자체는 kubectl create secret tls shop-tls --cert=tls.crt --key=tls.key로 미리 만들어 둡니다.

Ingress Controller가 있어야 동작한다 #

Ingress는 규칙을 적은 명세일 뿐, 그 자체로는 아무 일도 하지 않습니다. 실제로 트래픽을 받아 라우팅하는 것은 ingress-nginx, Traefik 같은 Ingress Controller입니다. 컨트롤러는 Ingress 오브젝트를 감시하다가 그 규칙대로 리버스 프록시를 구성합니다. 따라서 클러스터에 Ingress Controller가 설치되어 있지 않으면 Ingress를 아무리 만들어도 외부 트래픽이 들어오지 않습니다. CKAD 환경에는 보통 컨트롤러가 미리 설치되어 있으니, kubectl get ingressclass로 사용 가능한 클래스 이름을 먼저 확인하고 그 이름을 ingressClassName에 넣겠습니다.

NetworkPolicy: Pod 간 통신을 화이트리스트로 잠근다 #

기본 상태의 쿠버네티스 클러스터는 모든 Pod가 서로 자유롭게 통신합니다. 어떤 Pod 든 다른 Pod의 IP로 연결할 수 있는 all-allow가 기본값입니다. NetworkPolicy는 이 열린 통신에 방화벽 규칙을 거는 리소스입니다.

핵심 동작은 한 문장으로 요약됩니다. NetworkPolicy가 하나도 선택하지 않은 Pod는 여전히 all-allow지만, 어떤 NetworkPolicy 라도 선택한 Pod는 그 정책에 명시된 트래픽만 허용합니다. 즉 정책이 붙는 순간 해당 Pod는 화이트리스트 방식으로 바뀝니다.

podSelector와 policyTypes #

NetworkPolicy의 적용 대상은 spec.podSelector로 고릅니다. 이 셀렉터에 매칭되는, 같은 네임스페이스의 Pod가 정책의 적용 대상입니다.

spec.policyTypes는 이 정책이 들어오는 트래픽(Ingress)을 통제하는지, 나가는 트래픽(Egress)을 통제하는지, 둘 다인지 정합니다.

spec:
  podSelector:
    matchLabels:
      app: db
  policyTypes:
  - Ingress
  - Egress

policyTypesIngress가 있으면 들어오는 연결은 spec.ingress 규칙에 명시된 출처만 허용되고, 명시되지 않은 출처는 모두 차단됩니다. Egress도 같은 방식으로 나가는 연결을 통제합니다.

ingress.from과 egress.to #

허용할 출처와 목적지는 세 가지 셀렉터로 표현합니다.

셀렉터의미
podSelector같은 네임스페이스 안에서 label로 고른 Pod
namespaceSelectorlabel로 고른 네임스페이스 전체
ipBlockCIDR 범위(cidr)와 예외(except)로 지정한 IP 대역

ingress.from 아래에 이 셀렉터들을 나열하면 그 출처에서 오는 트래픽만 허용됩니다. 여기서 YAML 들여쓰기 한 칸이 의미를 바꾸는 함정이 있습니다. 하나의 from 항목 안에 podSelectornamespaceSelector를 함께 두면 **둘 다 만족(AND)**해야 하고, -로 항목을 나누면 **둘 중 하나만 만족(OR)**해도 됩니다.

# AND: 지정 네임스페이스 안의, 지정 label Pod만
ingress:
- from:
  - namespaceSelector:
      matchLabels:
        team: backend
    podSelector:
      matchLabels:
        app: web
# OR: 지정 네임스페이스 전체 또는 같은 ns의 지정 label Pod
ingress:
- from:
  - namespaceSelector:
      matchLabels:
        team: backend
  - podSelector:
      matchLabels:
        app: web

포트 제한 #

ingressegress 규칙에는 ports를 더해 허용 포트를 좁힐 수 있습니다. from이나 to로 출처를 고르고, ports로 그 출처가 접근할 포트까지 제한하는 식입니다.

ingress:
- from:
  - podSelector:
      matchLabels:
        app: web
  ports:
  - protocol: TCP
    port: 5432

app: web Pod에서 오는 TCP 5432 연결만 허용하고 나머지는 막습니다.

default deny 패턴 #

가장 자주 출제되는 패턴이 네임스페이스 전체에 기본 차단을 거는 default deny입니다. podSelector를 빈 값({})으로 두면 네임스페이스의 모든 Pod가 대상이 되고, ingress 규칙을 비우면 들어오는 트래픽이 전부 차단됩니다.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: prod
spec:
  podSelector: {}
  policyTypes:
  - Ingress

policyTypesIngress만 있고 spec.ingress가 없으므로 prod 네임스페이스의 모든 Pod로 들어오는 연결이 전부 차단됩니다. 여기에 개별 허용 정책을 추가로 붙여 화이트리스트를 넓혀 가는 것이 표준 운영 방식입니다. 나가는 트래픽까지 막으려면 policyTypesEgress를 더하고 egress를 비웁니다.

CNI가 NetworkPolicy를 지원해야 한다 #

NetworkPolicy도 Ingress처럼 명세일 뿐이며, 이를 실제로 시행하는 것은 클러스터의 CNI 플러그인입니다. Calico, Cilium 같은 CNI는 NetworkPolicy를 지원하지만, 일부 단순한 네트워크 플러그인은 정책을 무시합니다. 즉 NetworkPolicy를 만들어도 CNI가 지원하지 않으면 통신이 그대로 열려 있을 수 있으니, 시험 환경에서는 정책 적용 후 실제로 막히는지 반드시 확인하겠습니다.

실전 YAML 예제 #

Ingress: host + path + TLS #

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shop-ingress
  namespace: shop
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - shop.example.com
    secretName: shop-tls
  rules:
  - host: shop.example.com
    http:
      paths:
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: api-svc
            port:
              number: 8080
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-svc
            port:
              number: 80

shop.example.com으로 들어오는 HTTPS 요청을 shop-tls 인증서로 종료하고, /apiapi-svc:8080으로, 나머지는 web-svc:80으로 라우팅합니다.

NetworkPolicy: 특정 label에서만 ingress 허용 + default deny #

먼저 네임스페이스 전체를 막는 default deny를 깔고, 그 위에 app: web Pod에서 오는 DB 접근만 여는 정책을 올립니다.

# 1) 기본 차단
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: shop
spec:
  podSelector: {}
  policyTypes:
  - Ingress
---
# 2) web에서 db로 5432만 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-web-to-db
  namespace: shop
spec:
  podSelector:
    matchLabels:
      app: db
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: web
    ports:
    - protocol: TCP
      port: 5432

app: db Pod는 두 정책 모두에 선택되지만, NetworkPolicy는 합집합으로 동작하므로 결과적으로 app: web Pod에서 오는 TCP 5432만 허용되고 그 밖의 모든 ingress는 차단됩니다.

디버깅: 정책이 실제로 막는지 확인한다 #

NetworkPolicy는 적용해도 즉시 결과가 눈에 보이지 않으므로, 직접 연결을 시도해 차단 여부를 확인해야 합니다. 임시 Pod를 띄워 대상 Service나 Pod로 연결을 거는 방식이 가장 빠릅니다.

# 정책 적용 전: 연결 성공해야 정상
k run probe --image=busybox --rm -it --restart=Never -- \
  wget -qO- --timeout=3 db-svc:5432

# 정책 적용 후: 허용되지 않은 출처면 타임아웃으로 막힘
k run probe --image=busybox --rm -it --restart=Never -- \
  wget -qO- --timeout=3 db-svc:5432

허용된 label을 가진 Pod에서 보내면 통하고, 그렇지 않은 Pod에서 보내면 타임아웃이 납니다. 정책이 의도대로 동작하지 않을 때 확인할 지점은 다음과 같습니다.

  • kubectl get networkpolicy -n <ns>로 정책이 올바른 네임스페이스에 있는지 확인
  • kubectl describe networkpolicy <name>으로 podSelector가 의도한 Pod를 고르는지 확인
  • 대상 Pod와 출처 Pod의 label이 셀렉터와 정확히 일치하는지 확인
  • from 항목의 들여쓰기로 AND/OR가 뒤바뀌지 않았는지 확인
  • 통신이 끝까지 안 막히면 CNI가 NetworkPolicy를 지원하는지 확인

Ingress 쪽이 동작하지 않을 때는 kubectl describe ingress <name>으로 규칙과 backend Service가 올바르게 연결됐는지 보고, kubectl get ingressclass로 지정한 클래스가 실제로 존재하는지 확인합니다. backend Service 이름이나 포트 오타가 가장 흔한 원인입니다.

시험 포인트 #

  • Ingress는 L7 라우팅입니다. rules.hosthttp.paths로 호스트와 경로별로 backend Service에 트래픽을 나눠 보냅니다.
  • pathType은 필수입니다. 대부분 Prefix, 정확 매칭이 필요할 때만 Exact를 씁니다.
  • ingressClassName으로 컨트롤러를 지정합니다. kubectl get ingressclass로 이름을 먼저 확인합니다.
  • TLS는 kubernetes.io/tls Secret을 spec.tls.secretName으로 참조합니다. Secret은 kubectl create secret tls로 만듭니다.
  • Ingress는 Ingress Controller가 있어야 동작합니다. 명세만으로는 트래픽이 흐르지 않습니다.
  • NetworkPolicy가 선택한 Pod는 화이트리스트로 전환됩니다. 정책이 없으면 all-allow가 기본입니다.
  • **default deny는 podSelector: {} + policyTypes**로 만듭니다. ingress/egress를 비워 전부 차단합니다.
  • from 항목 안의 셀렉터는 AND, -로 나눈 항목은 OR입니다. 들여쓰기 한 칸이 의미를 바꿉니다.
  • NetworkPolicy는 CNI가 지원해야 시행됩니다. 적용 후 실제로 막히는지 확인합니다.

정리 #

이번 글에서 잡은 것:

  • Ingress. host/path 라우팅, pathType(Prefix/Exact), rules/backend(service+port), ingressClassName, defaultBackend, TLS Secret 참조, 그리고 Ingress Controller 의존성
  • NetworkPolicy. all-allow 기본값, 정책이 붙은 Pod의 화이트리스트 전환, podSelector/policyTypes, ingress.from/egress.to의 세 셀렉터, 포트 제한
  • default deny 패턴과 그 위에 허용 정책을 쌓는 운영 방식, CNI 의존성과 디버깅 절차

Service에서 시작해 Ingress로 외부 진입을, NetworkPolicy로 내부 통신 제어를 마쳤습니다. 두 리소스의 셀렉터 문법을 한 번 더 다지고 싶다면 K8s 중급 #7에서 label과 셀렉터의 동작을 복습하면 좋습니다.

다음: 시험 팁과 시간 관리 #

도메인별 지식은 #18과 #19로 마무리됐습니다. 이제 남은 것은 그 지식을 2시간 안에 점수로 바꾸는 운영 능력입니다.

#20 시험 팁과 시간 관리, 자주 틀리는 패턴에서는 작업당 시간 배분, 부분 점수를 극대화하는 풀이 순서, kubectl explain과 dry-run을 활용한 속도 전략, 그리고 context 전환 누락이나 pathType 누락처럼 응시자들이 반복해서 틀리는 패턴을 모아 정리하겠습니다.

X