[CKS] 26. Ingress
쿠버네티스 Ingress: 단일 URL로 외부 요청을 받아 경로/호스트 기반 지능적 라우팅을 제공하며, 서비스 관리 복잡성과 비용을 절감합니다.
개요
Ingress는 kubernetes 기반 서비스의 외부 엑세스를 관리하는 데 중요한 구성 요소입니다.
웹 스토어 애플리케이션 배포를 가정할 때 이미지를 빌드하고 Deployments로 구성한 뒤 ClusterIP, NodePort 등으로 외부의 포트를 노출하고 애플리케이션을확장합니다.
하지만 프로덕션 급 애플리케이션의 경우 더욱 디테일한 구성을 요구합니다. 예를들면 다음과 같습니다.
- 사용자에게 친숙한 도메인 호스트를 통해 애플리케이션에 엑세스할 수 있도록 Node IP를 가리키는 DNS 서버 구성
- HTTPS(443)로 수신한 트래픽을 뒷 단의 NodePort로 전달하는 프록시 서버
…
프로덕션 상에서는 이러한 구조의 고민이 필요하며 AWS, GCP 등의 서비스는 LoadBalancer를 통해 이를 지원하며 외부 IP 주소를 로드밸런서에 할당할 수 있도록 지원하며 해당 로드밸런서는 다시 NodePort로 포트를 숨기는 중요한 역할을 할 수 있습니다.
이러한 로드밸런서를 각각의 Deployment들을 구성하게되면 월 많은 비용이 발생할 수 있으며 관리에 매우 복잡해질 수 있습니다. 이러한 목표를 위해 해당 구성을 중앙화하는데 중요한 역할을 수행하는 것이 바로 Ingress
입니다.
Service와 Ingress의 기본적인 차이점
서비스는 여러 개의 파드를 하나의 논리적인 그룹으로 묶고, 고유한 IP 주소(Cluster IP)와 DNS 이름을 부여합니다. 이를 통해 다른 애플리케이션이나 사용자는 파드의 IP가 변경되더라도 서비스의 고정된 주소를 통해 항상 접근할 수 있습니다.
서비스는 TCP/IP와 같은 4계층 프로토콜 수준에서 작동합니다. 들어오는 트래픽을 해당 서비스에 연결된 여러 파드에 분산시켜 부하를 분산하는 기본적인 로드밸런싱 기능을 수행할 수 있습니다.
서비스가 단순히 '어떤 파드 그룹으로 갈 것인가'를 결정한다면, 인그레스는 '어떤 HTTP 요청을 어떤 서비스로 전달할 것인가'를 결정하는 더 지능적인 역할을 수행합니다. 인그레스는 그 자체로 트래픽을 처리하는 것이 아니라, 인그레스 컨트롤러(Ingress Controller)라는 실제 프록시 서버(예: Nginx, Traefik)를 통해 작동하는 규칙의 집합입니다.
7계층 라우팅: 인그레스는 HTTP/HTTPS 프로토콜, 즉 7계층에서 작동합니다. 따라서 URL의 호스트 이름(e.g., store.example.com
, api.example.com
)이나 경로(e.g., /products
, /users
)를 기반으로 트래픽을 서로 다른 서비스로 전달할 수 있습니다.
Ingress
Ingress는 외부에서 접근 가능한 단일 URL을 제공하며 URL 경로 또는 호스트 이름을 기반으로 클러스터 내 다양한 서비스로 들어오는 요청을 라우팅하는 동시에 SSL 종료도 처리합니다.
기본 역할로는 Kubernetes 상의 7계층 역할을 수행하며 Kubernetes의 Native한 객체로 자리하고 있습니다.
Ingress Controller를 노드포트(NodePort)나 클라우드 네이티브 로드 밸런서를 통해 노출시켜야 하긴 하지만, 모든 고급 로드 밸런싱, 인증, SSL 및 URL 라우팅 설정은 Ingress Controller에서 관리됩니다.
Ingress가 없다면, NGINX, HAProxy 또는 Traefik과 같은 리버스 프록시를 수동으로 배포하고 관리하며, 각각에 대해 URL 경로와 SSL 인증서를 설정해야 할 것입니다. Ingress가 있다면, 단순히 Ingress Controller(프록시 자체)를 배포하고 Ingress Resource(라우팅 규칙)를 쿠버네티스 오브젝트로 정의하기만 하면 됩니다.
인그레스 컨트롤러의 역할을 조금 더 명확히 작성하면 다음과 같습니다.
1. 인그레스 컨트롤러 (Ingress Controller)
- "두뇌" (Controller):
Ingress Controller
Pod (예: NGINX Ingress Controller)가 실행됩니다. 이 컨트롤러는Ingress
규칙을 계속 감시합니다.
- "몸" (Proxy):
Ingress Controller
는 자신이 관리하는 프록시(Proxy) Pod (예: NGINX)의 설정을 동적으로 변경합니다.
- 트래픽 흐름:
- 외부 트래픽은 프록시 Pod으로 들어옵니다.
Ingress Controller
자체로 들어오는 것이 아닙니다.
- 외부 트래픽은 프록시 Pod으로 들어옵니다.
비슷한 역할로 AWS의 LoadBalancer Controller가 존재합니다. Ingress는 내부의 리소스를 통해 조절하는 것이라면, LoadBalancer Controller는 클러스터 외부의 리소스를 조정하게 됩니다.
2. 로드밸런서 컨트롤러 (LoadBalancer Controller - ex: AWS Load Balancer Controller)
- "두뇌" (Controller):
AWS Load Balancer Controller
Pod가 클러스터 내에서 실행됩니다. 이 컨트롤러는Ingress
규칙이나Service
객체를 계속 감시합니다.
- "몸" (Cloud Load Balancer):
- 컨트롤러는 감시한 내용에 따라 AWS API를 호출하여 클러스터 외부의 AWS 로드밸런서(ALB, NLB)를 생성하고 설정합니다.
- 트래픽 흐름:
- 외부 인터넷 트래픽은 쿠버네티스 클러스터가 아닌 AWS의 로드밸런서(ALB/NLB)가 직접 받습니다. 그리고 이 로드밸런서가 트래픽을 클러스터 내부의 적절한 노드(Node)로 전달합니다.
기본적으로 쿠버네티스에는 Ingress Controller가 포함되지 않았습니다. 또한 Ingress Controller 없이 Ingress를 작성하더라도 리소스가 실제 생성되진 않습니다. 따라서 NGINX, HAProxy, Istio 등 지원되는 Ingress Controller를 배포해야하며 해당 문서에서는 NGINX Controller를 통해 정리하였습니다.
NGINX Controller 배포
NGINX Ingress Controller를 배포하는 예시입니다.
Ingress Controller가 배포되면 리소스 정의를 모니터링하며 그에 맞는 구성으로 재조정합니다.
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-ingress-controller
spec:
replicas: 1
selector:
matchLabels:
name: nginx-ingress
template:
metadata:
labels:
name: nginx-ingress
spec:
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.21.0
args:
- /nginx-ingress-controller
ConfigMap을 설정 데이터(Configuration Data)를 코드나 이미지와 분리하여 관리하기 위해 사용할 수 있습니다. ConfigMap이 없다면 어떻게 될까요? NGINX의 동작을 조금이라도 변경하고 싶을 때마다 다음과 같은 불편한 작업을 해야 합니다.
- NGINX 설정 파일(
nginx.conf
)을 수정합니다. - 수정된 설정 파일을 포함하는 새로운 컨테이너 이미지를 다시 빌드합니다.
- 새 이미지를 이미지 레지스트리(Docker Hub, Quay.io 등)에 푸시합니다.
- 쿠버네티스의 Deployment YAML 파일에서 이미지 버전을 수정하여 Pod를 재배포합니다.
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-ingress-controller
spec:
replicas: 1
selector:
matchLabels:
name: nginx-ingress
template:
metadata:
labels:
name: nginx-ingress
spec:
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.21.0
args:
- /nginx-ingress-controller
- --configmap=$(POD_NAMESPACE)/nginx-configuration. ConfigMap은 설정 데이터(Configuration Data)를 코드나 이미지와 분리하여 관리하기 위해 사용
ConfigMap을 사용한다면 애플리케이션 코드(컨테이너 이미지)는 그대로 둔 채, 설정값만 독립적으로 변경할 수 있습니다. 다음과 같이 ConfigMap을 수행할 수 있으며 ConfigMap 내의 옵션을 변경했을 때 Ingress Controller는 이를 동적으로 감지하고 새로운 옵션값으로 배포를 수행합니다.
# 실제 사용 예시
kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-configuration
data:
# 클라이언트 요청 Body의 최대 크기를 20MB로 설정
proxy-body-size: "20m"
# 프록시의 타임아웃 시간을 600초로 설정
proxy-read-timeout: "600"
proxy-send-timeout: "600"
# NGINX 로그 형식 지정
log-format-upstream: '{"time": "$time_iso8601", "remote_addr": "$proxy_protocol_addr", ...}'
또 중요한 점은 Ingress Controller는 특정 환경 변수가 필요하며 외부에서 오는 트래픽을 수신하기 위해 포트 80과 443을 노출해야 합니다. Controller를 노출하고 Ingress Controller 권한을 제공하기 위해 Service와 ServiceAccount를 생성한다는 것을 알 수 있었습니다.
# ------------------- #
# Ingress Controller 배포 #
# ------------------- #
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-ingress-controller
spec:
replicas: 1
selector:
matchLabels:
name: nginx-ingress # 이 라벨을 가진 Pod를 관리 대상으로 선택
template:
metadata:
labels:
name: nginx-ingress # Pod에 'name: nginx-ingress' 라벨을 부여
spec:
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.21.0
args:
- /nginx-ingress-controller
# 'nginx-configuration' ConfigMap을 설정 소스로 사용하도록 지정
- --configmap=$(POD_NAMESPACE)/nginx-configuration
env:
# 환경 변수로 Pod의 이름과 네임스페이스를 주입
# 컨트롤러가 자기 자신을 인식하는 데 사용될 수 있음
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
# 외부 트래픽을 받을 포트 (HTTP/HTTPS)
- name: http
containerPort: 80
- name: https
containerPort: 443
---
# ---------------------------- #
# Ingress Controller 외부 노출 서비스 #
# ---------------------------- #
apiVersion: v1
kind: Service
metadata:
name: nginx-ingress
spec:
# NodePort 타입: 클러스터의 모든 노드(서버)에 특정 포트를 열어 외부 트래픽을 받음
type: NodePort
ports:
- port: 80
targetPort: 80 # 컨테이너의 80번 포트로 트래픽 전달
protocol: TCP
name: http
- port: 443
targetPort: 443 # 컨테이너의 443번 포트로 트래픽 전달
protocol: TCP
name: https
selector:
# 'name: nginx-ingress' 라벨을 가진 Pod로 트래픽을 보냄
# 위 Deployment에서 생성한 Pod를 가리킴
name: nginx-ingress
---
# ----------------- #
# 설정값을 위한 ConfigMap #
# ----------------- #
# NGINX의 세부 설정을 저장하는 공간. 현재는 비어있음
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-configuration
---
# ---------------------- #
# 권한 관리를 위한 서비스 계정 #
# ---------------------- #
# Ingress Controller Pod가 쿠버네티스 API에 접근할 수 있는 권한을 부여하기 위함
apiVersion: v1
kind: ServiceAccount
metadata:
name: nginx-ingress-serviceaccount
- Deployment:
nginx-ingress-controller
Pod를 생성하고 실행을 보장합니다. 이 Pod가 실질적인 트래픽 라우팅을 담당합니다. - Service (NodePort 타입): 클러스터 외부에서 들어온 요청을 위에서 생성한
nginx-ingress-controller
Pod로 전달하는 통로 역할을 합니다. 클러스터 내 모든 서버(노드)의 특정 포트(예: 30080)가 열리고, 이 포트로 들어온 요청이 컨트롤러의 80번 포트로 전달됩니다. - ConfigMap: 인그레스 컨트롤러의 세부 설정을 저장하기 위한 공간을 미리 만들어 둡니다. (현재 내용은 비어 있습니다.)
- ServiceAccount: 인그레스 컨트롤러가 클러스터 내의 다른 리소스(예: Service, Endpoint 등)를 감시하고 제어하는 데 필요한 권한을 부여하기 위한 서비스 계정을 생성합니다.
Ingress Resource
기본 Ingress 리소스
모든 트래픽이 단일 백엔드 서비스로 전달되는 간단한 설정의 경우 다음과 같이 Ingress 리소스를 정의합니다.
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-wear
spec:
backend:
serviceName: wear-service
servicePort: 80
여러 URL 경로가 있는 Ingress
다양한 URL 경로를 기반으로 트래픽을 라우팅해야 하는 경우(예: /wear
한 서비스로 트래픽을 보내고 /watch
다른 서비스로 트래픽을 보내는 경우) 명시적 규칙을 사용하여 Ingress 리소스를 정의합니다.
apiVersion: extensions/v1beta1 # 참고: 현재는 networking.k8s.io/v1이 권장됩니다.
kind: Ingress
metadata:
name: ingress-wear-watch
spec:
rules:
- host: "wear.example.com"
http:
paths:
- path: /wear
backend:
serviceName: wear-service
servicePort: 80
- path: /watch
backend:
serviceName: watch-service # 다른 서비스로 연결하는 것이 일반적입니다.
servicePort: 80
path 값에 따라 도메인의 접속하는 트래픽을 라우팅하여 처리합니다.
도메인 이름 기반 Ingress
URL 경로가 아닌 호스트 이름을 기준으로 트래픽을 분할하려면 호스트 필드를 사용하여 별도의 규칙을 정의해야합니다.
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-wear-watch
spec:
rules:
- host: wear.my-online-store.com
http:
paths:
- backend:
serviceName: wear-service
servicePort: 80
- host: watch.my-online-store.com
http:
paths:
- backend:
serviceName: watch-service
servicePort: 80
사용자가 접속하는 각 호스트에 따라 어떤 backend service로 전달될지가 나뉘어집니다.
이것 외에도 default 규칙을 정의하여 일치하는 규칙이 없는 경우 라우팅 처리를 할 수 있으며, 이에 대한 404 Error 처리 또한 가능합니다.
Ingress는 다양한 규칙 뿐만 아니라 SSL까지 처리하며 여러 로드밸런서를 관리하는 복잡성 문제에서 자유롭습니다. 기본적인 기능은 동일하게 LoadBalancer 등에서 사용되지만 비용 절감, 효율적인 관리 등을 고려해본다면 NGINX Controller 구성을 통해 이를 달성할 수 있습니다.
Hands-on
kubectl create ingress --rule="host/path=service:port"
kubectl create ingress ingress-test --rule="wear.my-online-store.com/wear*=wear-service:80"
k describe ingress -n app-space ingress-wear-watch
---------------
Name: ingress-wear-watch
Labels: <none>
Namespace: app-space
Address: 172.20.178.103
Ingress Class: <none>
Default backend: <default>
Rules:
Host Path Backends
---- ---- --------
*
/wear wear-service:8080 (172.17.0.4:8080)
/watch video-service:8080 (172.17.0.5:8080)
Annotations: nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: false
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 116s (x2 over 116s) nginx-ingress-controller Scheduled for sync
nginx.ingress.kubernetes annotation 주의할 것
kubectl create ingress critical-ingress --rule="/pay=pay-service:8282" \
--annotation "nginx.ingress.kubernetes.io/rewrite-target=/" \
-n critical-space