[Kubernetes] 21. 서비스와 네트워크 (7편) - Ingress (L7 로드밸런싱)

JIWON·2025년 7월 22일

Kubernetes

목록 보기
21/32
post-thumbnail

Kubernetes Ingress: L7 로드밸런싱과 라우팅

지금까지 다룬 대부분의 서비스(ClusterIP, NodePort, LoadBalancer)는 L4(TCP/UDP) 계층에서 동작하는 로드밸런싱을 제공한다. 하지만 실제 웹 서비스 환경에서는 도메인 이름이나 URL 경로에 따라 트래픽을 나누는 정교한 규칙이 필요하다.

이번 포스트에서는 L7(HTTP/HTTPS) 계층에서 동작하며, 단일 진입점으로 여러 서비스를 라우팅할 수 있는 인그레스(Ingress)에 대해 정리한다.

참고: 인그레스는 서비스(Service)의 한 종류가 아니라, 독립적인 kind: Ingress 리소스이다. 또한 NetworkPolicy의 Ingress(수신 트래픽) 규칙과는 다른 개념이므로 혼동하지 말아야 한다.


1. Ingress의 필요성 및 개념

1) 왜 Ingress인가? (NodePort vs Ingress)

애플리케이션을 외부에 노출하기 위해 NodePortLoadBalancer 서비스를 사용할 수 있다. 하지만 이 방식은 서비스가 늘어날 때마다 포트를 관리해야 하거나, 비싼 클라우드 로드밸런서 비용이 증가하는 단점이 있다.

인그레스(Ingress)는 클러스터 외부에서 내부로 들어오는 HTTP/HTTPS 트래픽을 처리하는 규칙들의 집합이다. 이를 통해 단 하나의 외부 IP 주소만으로 호스트나 경로에 따라 여러 서비스로 트래픽을 분산할 수 있다.

구분Service (NodePort/LB)Ingress
계층L4 (TCP/UDP)L7 (HTTP/HTTPS)
기능"단순 부하 분산, 포트 포워딩""URL 기반 라우팅, SSL/TLS 종료"
특징서비스마다 IP/포트 할당 필요단일 IP로 여러 서비스 연결 가능

2) 핵심 아키텍처: 리소스와 컨트롤러

인그레스를 이해하기 위해서는 쿠버네티스의 리소스(Resource)컨트롤러(Controller) 모델을 알아야 한다. 인그레스 리소스를 생성한다고 해서 바로 접속이 되는 것이 아니라, 이를 실행해 줄 구현체가 필요하기 때문이다.

  1. 인그레스 리소스 (Ingress Resource):

    • 사용자가 작성하는 YAML 파일(설계도).

    • "어떤 도메인(example.com)의 어떤 경로(/api)로 들어오면 A 서비스로 보내라"는 라우팅 규칙을 정의한다.

  2. 인그레스 컨트롤러 (Ingress Controller):

    • 실제 작업을 수행하는 시스템(작업자).

    • 리소스의 규칙을 감시하고 있다가, 실제 리버스 프록시 서버(Nginx, HAProxy 등)의 설정을 변경하고 리로드하여 트래픽을 처리한다.


2. Ingress 구현 방식

인그레스 컨트롤러를 구성하는 방식은 크게 두 가지로 나뉜다.

1) 클라우드 관리형 (AWS, GCP 등)

  • 사용자가 Ingress 리소스를 만들면, 클라우드 제공업체의 L7 로드밸런서(AWS ALB 등)가 자동으로 생성되어 연결된다.

  • 특징: 간편하지만 클라우드 종속적이다.

2) 클러스터 내부 설치형 (Nginx Ingress 등)

클러스터 내부에 Nginx, Traefik, HAProxy 등 오픈소스 L7 로드밸런서를 파드(Pod) 형태로 직접 배포하여 사용한다.

  • 트래픽 흐름: ClientExternal LB (Service)Ingress Controller PodTarget Pod

  • 특징: 컨트롤러 파드가 목적지 파드로 트래픽을 보낼 때, 파드 IP로 직접 전송하므로 효율적이다. 또한, 트래픽 부하에 따라 인그레스 컨트롤러 파드의 수를 조절하는 오토스케일링(HPA) 사용을 고려해야 한다.


3. Nginx 인그레스 컨트롤러 배포

가장 널리 사용되는 Nginx Ingress Controller를 실습 환경에 배포해 본다. 공식적으로 제공되는 매니페스트를 사용하면 쉽게 설치할 수 있다.

# Nginx 인그레스 컨트롤러 설치
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.6.4/deploy/static/provider/cloud/deploy.yaml

설치 후 ingress-nginx 네임스페이스를 확인하여 컨트롤러 파드가 실행 중인지, 그리고 외부 접속을 위한 서비스(LoadBalancer 혹은 NodePort)가 생성되었는지 확인한다.

kubectl get all -n ingress-nginx

확인 사항: ingress-nginx-controller 서비스의 EXTERNAL-IP 또는 포트 번호.


4. 실습 1: 기본 Ingress 라우팅

간단한 웹 서버를 띄우고 인그레스를 통해 접속하는 기본 실습을 진행한다.

1) 애플리케이션 및 서비스 배포

  • Deployment (deploy-test.yaml): Python을 이용한 간단한 HTTP 서버

  • Service (svc-test.yaml): 위 디플로이먼트를 80번 포트로 노출

#deploy-test.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
  name: service-test
spec:
  replicas: 3
  selector:
    matchLabels:
      app: service_test_pod
  template:
    metadata:
      labels:
        app: service_test_pod
    spec:
      containers:
      - name: simple-http
        image: python:2.7
        imagePullPolicy: IfNotPresent
        command: ["/bin/bash"]
        # -c "..." 안에 전체 명령을 넣고, SimpleHTTPServer에 포트 8080을 명시합니다.
        args: ["-c", "echo '<p>Hello from $(hostname)</p>' > index.html; python -m SimpleHTTPServer 8080"]
        ports:
        - name: http
          containerPort: 8080
# svc-test.yaml
apiVersion: v1
kind: Service
metadata:
  name: service-test
spec:
  selector:
    app: service_test_pod
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080

2) Ingress 리소스 작성 (ingress-test.yaml)

ingress.test.com 이라는 도메인의 /test 경로로 들어오는 요청을 service-test 로 연결한다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx
  annotations:
    # 요청 경로를 재작성하는 규칙. /test로 들어온 요청의 /test 부분을 제거하고 백엔드로 전달한다.
    nginx.ingress.kubernetes.io/rewrite-target: /
    # 이 Ingress 규칙을 "nginx" 클래스의 컨트롤러가 처리하도록 지정한다.
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host: "ingress.test.com" # 이 도메인으로 들어오는 요청에 대해
    http:
      paths:
      - pathType: Prefix
        path: /test # 이 경로로 시작하는 요청을
        backend:
          service:
            name: service-test # 'service-test' 서비스의
            port:
              number: 80 # 80번 포트로 전달한다.

3) 접속 테스트

실제 도메인이 없으므로 로컬 호스트 파일이나 curl을 이용해 테스트한다.

1. 로컬 PC 호스트 매핑:

실제 도메인이 없으므로, 테스트를 위해 로컬 PC의 /etc/hosts 파일에 워커 노드의 IP와 Ingress에 설정한 호스트 이름을 매핑한다.

# /etc/hosts 파일

192.168.11.101 ingress.test.com

2. 포트번호 확인

Ingress 컨트롤러 서비스가 외부로 노출한 포트(NodePort)를 확인 (예: 80:30402/TCP ).

kubectl get svc -n ingress-nginx

NAME                                 TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)                      AGE
ingress-nginx-controller             LoadBalancer   10.101.89.165   192.168.11.101   80:30402/TCP,443:31940/TCP   40m
ingress-nginx-controller-admission   ClusterIP      10.101.203.26   <none>           443/TCP                      40m

3. 최종 요청:

curl을 사용하여 Ingress를 통해 서비스에 접근되는지 확인한다.

# <NodePort>는 2번 단계에서 확인한 포트 번호
curl http://ingress.test.com:30402/test

# 정상적으로 응답이 오면 성공!
<p>Hello from $(hostname)</p>

5. 실습 2: 고급 라우팅 (Host 기반 vs Path 기반)

실무에서 가장 많이 쓰이는 두 가지 라우팅 패턴을 실습한다. 이를 위해 backend1backend2 라는 두 개의 서로 다른 애플리케이션을 미리 배포해 둔다.

1) 테스트용 백엔드 애플리케이션 배포

먼저 라우팅 테스트를 위한 두 개의 다른 백엔드 애플리케이션(Deployment)과 이를 연결하는 서비스(Service)를 배포한다.

ingress-resource.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend1
spec:
  replicas: 2
  selector:
    matchLabels:
      app: backend1
  template:
    metadata:
      labels:
        app: backend1
    spec:
      containers:
      - name: backend1
        image: nginxdemos/nginx-hello:plain-text
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: backend1-svc
spec:
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
    name: http
  selector:
    app: backend1
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: backend2
  template:
    metadata:
      labels:
        app: backend2
    spec:
      containers:
      - name: backend2
        image: nginxdemos/nginx-hello:plain-text
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: backend2-svc
spec:
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
    name: http
  selector:
    app: backend2

위 파일을 적용하여 리소스를 생성한다.

kubectl apply -f ingress-resource.yaml

# 배포 확인
kubectl get pod,service
NAME                                       READY   STATUS    RESTARTS       AGE
pod/backend1-656f7c6885-949hn              1/1     Running   0              25s
pod/backend1-656f7c6885-s4pfw              1/1     Running   0              25s
pod/backend2-6c84bb867-w8kb2               1/1     Running   0              24s

NAME                    TYPE           CLUSTER-IP       EXTERNAL-IP      PORT(S)          AGE
service/backend1-svc    ClusterIP      10.110.34.95     <none>           80/TCP           6s
service/backend2-svc    ClusterIP      10.101.54.17     <none>           80/TCP           5s

2) Host 기반 라우팅 (Name-based Virtual Hosting)

클라이언트가 요청한 도메인 이름(Host Header)에 따라 서로 다른 서비스로 연결한다.

  • example1.combackend1-svc

  • example2.combackend2-svc

# ingress-host-based.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host: "example1.com"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: backend1-svc
            port:
              number: 80
  - host: "example2.com"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: backend2-svc
            port:
              number: 80

실행 및 확인

1. Ingress 리소스 배포:
kubectl apply -f ingress-host-based.yaml

2. Ingress 상태 확인:
kubectl get ingress

NAME            CLASS    HOSTS                       ADDRESS          PORTS   AGE
ingress-nginx   <none>   example1.com,example2.com   192.168.11.101   80      20m

3. Ingress Controller의 NodePort 확인:
kubectl get svc -n ingress-nginx (e.g., 80:30402/TCP)

NAME                                 TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)                      AGE
ingress-nginx-controller             LoadBalancer   10.101.89.165   192.168.11.101   80:30402/TCP,443:31940/TCP   59m
ingress-nginx-controller-admission   ClusterIP      10.101.203.26   <none>           443/TCP                      59m

4. 로컬 PC의 /etc/hosts 파일에 워커 노드 IP와 도메인 매핑 추가 (테스트 목적).

192.168.11.101 example1.com
192.168.11.101 example2.com

5. curl로 요청하여 라우팅 확인.

# backend1-svc로 라우팅되어야 함
curl example1.com:30402

# 실행결과
Server address: 10.244.2.20:8080
Server name: backend1-656f7c6885-s4pfw
Date: 22/Jul/2025:02:44:56 +0000
URI: /
Request ID: ec53759064d14a9d3c032cc624c4a98c

# backend2-svc로 라우팅되어야 함
curl example2.com:30402

# 실행결과
Server address: 10.244.1.23:8080
Server name: backend2-6c84bb867-w8kb2
Date: 22/Jul/2025:02:45:02 +0000
URI: /
Request ID: 05d53f02a3b4a0d2b7cc6e242f206701

테스트 결과: /etc/hosts 에 두 도메인을 모두 노드 IP로 등록한 후 curl을 보내면, 도메인에 따라 서로 다른 파드(backend1 또는 backend2)가 응답하는 것을 확인할 수 있다.

3) Path 기반 라우팅(Fanout)

동일한 도메인 내에서 URL 경로(Path)에 따라 서비스를 분기한다.

  • example.com/backend1backend1

  • example.com/backend2backend2

# ingress-path-based.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host: "example.com"
    http:
      paths:
      - path: /backend1
        pathType: Prefix
        backend:
          service:
            name: backend1-svc
            port:
              number: 80
      - path: /backend2
        pathType: Prefix
        backend:
          service:
            name: backend2-svc
            port:
              number: 80

실행 및 확인

1. Ingress 리소스 배포:

kubectl apply -f ingress-path-based.yaml

2. 로컬 PC의 /etc/hosts 파일에 매핑 추가.

192.168.11.101 example.com

3. curl로 요청하여 라우팅 확인.


# backend1-svc로 라우팅
curl example.com:30402/backend1

# 실행결과
Server address: 10.244.2.20:8080
Server name: backend1-656f7c6885-s4pfw
Date: 22/Jul/2025:02:53:31 +0000
URI: /
Request ID: cc22bbe3351412e5c8bd3891711a2b0c

# backend2-svc로 라우팅
curl example.com:30402/backend2

#실행결과
Server address: 10.244.1.23:8080
Server name: backend2-6c84bb867-w8kb2
Date: 22/Jul/2025:02:53:33 +0000
URI: /
Request ID: b3d18e917c68811ddcb136136247832f

테스트 결과: curl example.com:port/backend1 요청 시 backend1 파드가, /backend2 요청 시 backend2 파드가 응답한다. 이를 통해 마이크로서비스 아키텍처에서 API 게이트웨이와 같은 역할을 수행할 수 있다.


6. 마치며

인그레스(Ingress)는 쿠버네티스 네트워크 환경에서 외부와 내부를 연결하는 가장 표준적인 방법이다.

  • 효율성: 단일 IP/포트로 수많은 서비스를 관리한다.

  • 유연성: 도메인 및 경로 기반의 섬세한 트래픽 제어가 가능하다.

  • 확장성: SSL/TLS 보안 설정, 세션 고정 등 다양한 부가 기능을 인그레스 레벨에서 통합 관리할 수 있다.

0개의 댓글