Service API vol. 4 - Ingress

성윤·2026년 3월 9일

[☸️Kubernetes]

목록 보기
13/13
post-thumbnail

들어가며 👋

지난 포스팅에서는 클러스터 내부의 트래픽을 세밀하게 제어하고, K8s 외부 시스템과 똑똑하게 연동하기 위한 Service API의 고급기능에 대해 알아보았습니다.

지금까지 다룬 NodePortLoad Balancer는 IP와 Port를 기반으로 트래픽을 전달하는 L4 수준의 통신이었습니다. 하지만 실무에서 MSA 환경을 구축하다 보면, 서비스가 수십, 수백개로 늘어나게 됩니다. 이때 외부 접근을 위해 서비스마다 LoadBalancer 를 하나씩 달아주면 클라우드 비용이 폭발적으로 증가하고, DNS 매핑 관리는 힘들어질 것입니다.

그래서 이번 포스팅에서는 단 하나의 공인 IP(LoadBalancer)만으로 클러스터 내의 수많은 애플리케이션에 트래픽을 정확히 라우팅해주는 Ingress에 대해 알아보겠습니다.


1. L7 로드밸런서, Ingress

Ingress는 외부에서 들어오는 HTTP/HTTPS 트래픽을 쿠버네티스 클러스터 내부의 알맞은 서비스로 인도하는 '게이트웨이' 역할을 수행합니다.

L7 로드밸런서라고 불리며 MSA 환경에서 많이 쓰이는 이유는 IP/Port 뿐만 아니라 URL 경로(Path)나 도메인 이름(Host) 을 인식하여 트래픽을 분기하는 L7 수준의 라우팅이 가능하기 때문입니다.

특징

-단일 지점 관리: 하나의 공인 IP로 전체 클러스터의 라우팅 규칙을 관리할 수 있습니다.

-비용 절감: 서비스 개수만큼 LoadBalancer를 만들 필요가 없어 퍼블릭 클라우드 비용이 크게 절감됩니다.

-SSL/TLS 터미네이션: 인그레스 단에서 인증서를 적용해 HTTPS 통신을 일괄적으로 처리할 수 있습니다.

SSL/TLS 작동원리

  1. 연결: 브라우저에 인증서를 제시해 안전한 서버임을 증명
  2. 해독: 외부에서 들어온 HTTPS 메세지를 복호화
  3. 전달: 내부 파드(Pod)에는 암호가 풀린 평문(HTTP)으로 전달

2. Ingress의 두 가지 요소: Resource와 Controller ⚙️

기본적으로 쿠버네티스는 분산 시스템입니다. 매니페스트(YAML)를 등록하는 것만으로는 아무 일도 일어나지 않으며, 이를 실제로 수행할 '작업자'가 필요합니다.
(디플로이먼트 매니페스트를 만들면, 디플로이먼트 컨트롤러가 레플리카셋을 생성하고 개수를 조절하는 것과 같음)

Ingress 역시 이처럼 두 가지 요소로 나뉘어 동작합니다.


구성 요소

-Ingress Resource (설계도): K8s API에 등록되는 규칙의 모음입니다. "A 도메인으로 오면 A 서비스로, /test 경로로 오면 B 서비스로 보내라"고 적어둔 단순한 명세서입니다.

-Ingress Controller (작업자): Ingress Resource가 등록되었을 때, 그 규칙을 읽고 실제로 트래픽 라우팅 처리를 수행하는 주체입니다.

💡컨트롤러는 구체적으로 무슨 일을 할까?

인그레스 컨트롤러는 리소스(규칙)가 생성되면 이를 감지하고 실질적인 인프라 설정을 변경합니다.
예를 들어, 퍼블릭 클라우드 환경이라면 GCP의 GCLB(L7 로드밸런서)를 조작하여 설정을 맞추고, 자체 구축 환경이라면 Nginx 설정 파일(nginx.conf)을 자동으로 수정하고 리로드(Reload)하여 트래픽이 올바른 경로로 갈 수 있도록 만듭니다.


3. 구축 방식에 따른 Ingress의 종류

인그레스를 구성하여 트래픽을 처리하는 방식은 크게 두 가지로 나뉩니다.

1) 클러스터 외부 LoadBalancer를 활용하는 방식

클라우드 제공자 (AWS, GCP 등)가 제공하는 외부 L7 로드밸런서를 인그레스로 사용하는 방식입니다.

외부 로드밸런서가 규칙을 파악하여 K8s 내부의 NodePort 서비스로 트래픽을 넘겨주면, NodePort가 각 목적지 파드로 요청을 전달하는 구조입니다.


2) 클러스터 내부에 인그레스용 파드를 배포하는 방식 (내부 컨트롤러)

쿠버네티스 클러스터 안에 L7 리버스 프록시 역할을 직접 수행하는 파드(Pod) 를 띄워두는 방식입니다.

이름은 '컨트롤러'이지만, 직접 트래픽을 받고 쪼개주는 핵심 처리까지 모두 수행합니다. 가장 대중적으로 사용되는 Nginx Ingress Controller가 바로 이 방식에 속합니다.

💡왜 내부 파드로 배포할까요? (추가 설명)

클라우드 제공자(CSP)가 만들어주는 비싼 L7 로드밸런서를 매번 생성하지 않고, 상대적으로 저렴한 L4 로드밸런서나 NodePort만 앞단에 둔 채 K8s 내부에서 직접 세밀한 라우팅 통제를 하고 싶을 때 주로 사용합니다.

특히 클라우드가 아닌 온프레미스(자체 서버) 환경에서는 거의 필수적인 방식이며, 기존 Nginx가 가진 강력한 기능 (경로 재작성, 트래픽 제한 등)을 쿠버네티스에 그대로 녹여낼 수 있다는 엄청난 장점이 있습니다.


4. Nginx Ingress Controller의 동작 구조

앞서 설명한 플러그인 가능한 아키텍처 덕분에 다양한 인그레스 컨트롤러를 선택할 수 있지만, 현업에서 가장 많이 쓰이는 것은 단연 nginx-ingress-controller입니다. 전체적인 트래픽 흐름은 다음과 같습니다.


  1. 진입점 (LoadBalancer Service): 외부 사용자가 접속할 수 있도록 K8s 외부에 L4 로드밸런서(단일 공인 IP)를 하나 둡니다. 이것이 인그레스의 엔드포인트(Endpoint)가 됩니다.

  2. 분배자 (Nginx Ingress Controller Pod): 트래픽이 로드밸런서를 타고 들어오면, 클러스터 내부에 Deployment로 배포된 nginx-ingress-controller 파드가 이 요청을 받습니다.

  3. 목적지 (Target Services): Nginx 컨트롤러는 우리가 사전에 만들어둔 'Ingress Resource(라우팅 규칙)'를 확인하고, 요청된 도메인이나 경로(Path)에 맞는 각 백엔드 서비스(Service A, Service B...)로 트래픽을 정확히 분기시켜 줍니다.

결과적으로 외부에서는 단 하나의 IP로 접근하지만, 내부에서는 Nginx 프록시가 길잡이 역할을 해주어 수많은 마이크로서비스가 유기적으로 동작할 수 있게 됩니다.


5. Nginx Ingress Controller 배포 및 기본 실습

이론을 파악했으니 이제 직접 클러스터에 배포하고 트래픽을 라우팅해 보겠습니다. 실습은 로컬환경을 기준으로 진행하겠습니다.

1) Ingress Controller 설치

먼저 공식 매니페스트를 이용해 클러스터 내부에 Nginx 인그레스 컨트롤러를 배포합니다.

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.6.4/deploy/static/provider/baremetal/deploy.yaml

설치가 잘 되었는지 확인해 봅니다.

$ kubectl get all -n ingress-nginx

정상적으로 배포되었다면 ingress-nginx-controller 서비스에 NodePort(예: 80:31165/TCP) 가 할당되고, 컨트롤러 파드가 Running 상태인 것을 확인할 수 있습니다.


2) 테스트용 애플리케이션 및 서비스 배포

트래픽을 받아준 간단한 파이썬 웹 서버 파드와 이를 노출하는 Service를 생성합니다.

deploy-test.yaml

apiVersion: apps/v1
kind: Deployment
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/sh"]
        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
# 리소스 배포 실행
$ kubectl apply -f deploy-test.yaml
$ kubectl apply -f svc-test.yaml


3) Ingress 리소스 배포 및 접속 테스트

이제 ingress.test.com/test 라는 주소로 요청이 오면 방금 만든 service-test로 연결하라는 라우팅 규칙을 작성합니다.

ingress-test.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: "ingress.test.com"
    http:
      paths:
      - pathType: Prefix
        path: /test
        backend:
          service:
            name: service-test
            port:
              number: 80

📝참고: Nginx Ingress Controller를 설치하면 기본적으로 객체들의 라벨명이 ingress-nginx로 지정되므로, 매핑을 위해 Ingress 이름이나 어노테이션 클래스를 동일하게 맞춰주어야 합니다.


PC의 /etc/hosts 파일 수정 및 확인

실제 도메인이 아니므로 내 PC가 해당 도메인을 워커 노드의 IP로 인식하도록 로컬 DNS 설정을 수정합니다. (IP는 본인의 워커 노드 IP에 맞게 변경하세요.)

# /etc/hosts 파일에 추가
10.0.2.102 ingress.test.com


접속 테스트

앞서 확인한 인그레스 컨트롤러의 NodePort(예: 31165)를 통해 트래픽을 전송해 봅니다.

curl -s http://ingress.test.com:31165/test

응답으로 Hello from ... 문구가 출력된다면 라우팅이 완벽하게 성공한 것입니다! 🎉


6. 실전 라우팅 1: Host 기반 분기 🌐 (하나의 IP로 2개 서비스 접속)

클라우드 환경(AWS, GCP 등)이라면 인그레스(Ingress) 생성 시 자동으로 공인 IP를 가진 외부 로드밸런서가 붙습니다.

하지만 우리가 실습하는 로컬 가상머신(VM)이나 베어메탈 환경에는 외부 '외부 Ingress'가 없습니다.

그렇다면 외부 트래픽을 어떻게 클러스터 내부로 끌고 들어올까요?
바로 NodePort가 외부 로드밸런서의 역할을 대신하여 '게이트웨이'역할을 하고, 클러스터 내부에는 Ingress Controller 파드(Nginx) 를 직접 띄워서 Ingress Controller 역할을 맡기는 방식을 사용합니다.

🚦 트래픽이 흘러가는 전체 흐름

1.NodePort 진입: 외부에서 노드IP:31165(예시)로 접속하여 클러스터의 대문을 통과합니다.

2.Ingress Controller 파드: 대문을 통과한 트래픽은 Nginx 인그레스 컨트롤러 파드로 전달됩니다.

3.Host 기반 라우팅: 인그레스 컨트롤러가 사용자가 입력한 도메인(Host) 이름을 확인하고, example1.com이면 A 서비스로, example2.com이면 B 서비스로 길을 나눠줍니다.

이제 이 구조를 바탕으로 직접 2개의 백엔드 서비스를 구성하고 라우팅을 테스트해 보겠습니다.


Step 1. 백엔드 서비스 배포 (Deployment & Service)

먼저 트래픽을 받아줄 두 개의 목적지(backend1, backend2)를 생성합니다.

(각각 8080 포트를 사용하는 웹 서버 컨테이너를 띄우고, 클러스터 내부 통신용 80 포트 서비스를 연결합니다.)

# 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: 2
  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-resources.yaml 명령어로 배포합니다.


Step 2. Ingress 규칙 적용

이제 Ingress를 생성합니다. 여기서 중요한 것은 host 필드입니다.
하나의 Ingress 안에서 도메인 이름에 따라 트래픽이 갈라지도록 규칙을 작성합니다. (Host 기반 분기에 집중하기 위해 Path는 /로 단순화)

# ingress-test1.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx-host
  annotations:
 	# 파드에 도달할 때 /test 경로를 /로 변환하여 전달 (Rewrite)
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
  - host: "example1.com" # 1번 도메인으로 오면
    http:
      paths:
      - pathType: Prefix
        path: /
        backend:
          service:
            name: backend1-svc # backend1-svc로 전달
            port:
              number: 80

  - host: "example2.com" # 2번 도메인으로 오면
    http:
      paths:
      - pathType: Prefix
        path: /
        backend:
          service:
            name: backend2-svc # backend2-svc로 전달
            port:
              number: 80

kubectl apply -f ingress-test1.yaml 명령어로 적용합니다.


Step 3. 도메인 설정 및 라우팅 테스트

로컬 환경에서 도메인 이름을 인식할 수 있도록 PC(또는 마스터 노드)의 /etc/hosts파일에 도메인과 노드 IP를 매핑합니다.

(이때 IP는 인그레스 컨트롤러 파드가 떠 있는 노드의 IP를 지정해야 합니다.)

# /etc/hosts에 추가
10.0.2.102 example1.com
10.0.2.103 example2.com

마지막으로 kubectl get svc -n ingress-nginx를 통해 인그레스 컨트롤러의 NodePort 번호(예: 31165) 를 확인한 뒤 접속 테스트를 진행합니다.

결과 확인: 접속한 IP(10.0.2.102)와 외부 포트(31165)는 완전히 똑같지만, 호출할 때 사용한 도메인(Host) 이름에 따라 서로 다른 백엔드(backend1, backend2)가 응답하는 것을 완벽하게 확인할 수 있습니다.


7. 실전 라우팅 2: Path 기반 분기 🛤️

앞선 실습은 도메인 이름(Host)을 기준으로 트래픽을 나눴다면, 이번에는 도메인은 하나(example.com)로 통일하고, 뒤에 붙는 경로(Path) 에 따라 트래픽을 쪼개는 방식입니다. 현업 MSA 환경에서 게이트웨이를 구성할 때 가장 많이 쓰이는 패턴입니다.

사용자가 example.com/backend1로 접속하면 Service 1로, example.com/backend2로 접속하면 Service 2로 보내주는 규칙을 만들어 보겠습니다.

Step 1. Ingress 규칙 적용

기존 호스트 기반 인그레스를 삭제하거나, 아래 매니페스트를 이용해 ingress-nginx-path라는 이름으로 새로 생성합니다.

# ingress-test2.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx-path
  annotations:
    # 중요한 설정 (설명은 아래 인용구 참고)
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
  - host: "example.com" # 도메인은 하나로 통일
    http:
      paths:
      - pathType: Prefix
        path: /backend1   # /backend1 경로 요청 시
        backend:
          service:
            name: backend1-svc # backend1-svc로 전달
            port:
              number: 80

      - pathType: Prefix
        path: /backend2   # /backend2 경로 요청 시
        backend:
          service:
            name: backend2-svc # backend2-svc로 전달
            port:
              number: 80

💡이건 뭐죠 annotation: rewrite-target: /

우리가 만든 백엔드 파드는 보통 루트 경로(/)에서 서비스를 하도록 설계되어 있습니다. 하지만 외부 사용자는 /backend1 이라는 경로로 들어오죠.

Nginx 인그레스 컨트롤러는 이 트래픽을 받고 파드에게 넘겨주기 직전, 주소창의 /backend1 부분을 지우고 /로 바꿔서 전달합니다.
이 설정이 없다면 파드는 해당 주소의 폴더가 없어 404 에러를 뱉게 됩니다.


Step 2. 로컬 DNS 설정 및 라우팅 테스트

베어메탈 환경에서는 클라우드 전용 로드밸런서가 없으므로 접속 시 '포트 번호' 에 주의해야 합니다.

  1. /etc/hosts 수정: 도메인(example.com)을 인그레스 컨트롤러가 돌고 있는 노드 IP(예: 10.0.2.102)로 매핑합니다.
# /etc/hosts에 추가
10.0.2.102 example.com

  1. 접속 포트 확인: 인그레스 컨트롤러 서비스의 NodePort를 확인합니다. (예: 31165)
kubectl get svc -n ingress-nginx

  1. 테스트
curl http://example.com:31165/backend1


⚠️ 왜 꼭 포트번호(:31165)를 붙여야 할까?

실습 중 포트 번호 없이 curl http://example.com/backend1을 입력했을 때 Apache의 404 페이지가 뜬다면? 이는 아주 중요한 네트워크 개념을 시사합니다.

  • 포트 80번의 주인: 리눅스 서버 자체에 설치된 Apache가 이미 80번 포트를 점유하고 있는 경우가 많습니다. 포트 번호를 생략하면 자동으로 80번으로 연결되어 아파치를 만나게 됩니다.

  • 인그레스: 우리 클러스터의 인그레스 컨트롤러는 31165번이라는 별도의 문(NodePort)을 열고 기다리고 있습니다.

결론: 베어메탈 환경에서는 /etc/hosts건물(노드 IP) 을 찾아준다면, NodePort는 건물 안의 수많은 방 중 쿠버네티스를 찾아가는 열쇠 역할을 합니다. 포트 번호를 명시해야만 아파치를 지나쳐 인그레스 컨트롤러에 무사히 도달할 수 있습니다.


마치며 ✍️

이번 포스팅에서는 이렇게 쿠버네티스 클러스터의 'L7 게이트웨이' 역할을 수행하는 Ingress에 대해 알아보았습니다.

Ingress를 한 줄로 요약하자면, 외부의 트래픽을 단일 진입점(IP)로 받아서, L7 수준(도메인, 경로)의 규칙에 따라 클러스터 내부의 알맞은 서비스로 라우팅해주는 API 객체입니다.

이러한 Ingress를 활용하면 불필요한 클라우드 로드밸런서 비용을 획기적으로 절감할 수 있고, 단일 지점에서 라우팅 규칙을 관리하여 운영 효율성을 크게 높일 수 있습니다. 또한, SSL/TLS 터미네이션 같은 고급 기능까지 제공하므로, 본격적인 MSA 환경을 구축하려는 실무에서 Ingress 도입은 선택이 아닌 필수라 할 수 있습니다.

지금까지는 파드, 디플로이먼트 등 애플리케이션의 배포와 네트워크를 담당하는 리소스들을 주로 살펴보았습니다.

다음 시간에는 시야를 조금 더 넓혀서, 쿠버네티스 클러스터 자체를 구성하고 관리하는 Cluster API 영역으로 들어갑니다. 실제 물리적인 컴퓨터 역할을 하는 노드(Node) 를 상세히 조회하는 방법부터, 클러스터를 논리적으로 분할하여 효율적으로 사용하는 네임스페이스(Namespace) 개념까지, 시스템 운영의 근간이 되는 핵심 리소스들을 다뤄보겠습니다.

0개의 댓글