사내 환경에서 Baremetal 용 Ingress Controller 를 설치해서 Spring Boot 애플리케이션을 이전하는 작업을 진행하였다. Ingress Controller 는 환경/목적 별로 정의서가 구분되어 있는 오픈 소스를 많이 활용한다. 사내는 외부와 연동이 어렵고 VM 을 할당받아 사용하기 때문에 Baremetal 용으로 사용하였다.
(https://kubernetes.github.io/ingress-nginx/deploy/baremetal/)
Baremetal 용은 Ingress Controller 서비스가 Node Port 서비스이기 때문에 워커 노드들의 Node 를 활용해서 Ingress Controller로의 접근을 제어해야 한다. 따라서 모든 워커노드들에 대한 접근을 제어하기 위해 앞단에 Load Balance 용 Nginx 를 두었고, 다음과 같이 설정하였다 (그냥 단순 컨테이너로 하나 띄움)
upstream k8s-cluster{ # Node Port 로 각 Node 에 오픈된 포트로 LB 를 진행한다
server {WORKER_NODE_1}:{INGRESS_OPEN_PORT};
server {WORKER_NODE_2}:{INGRESS_OPEN_PORT};
}
server {
listen 443 ssl;
server_name foo.samsungds.net;
ssl_certificate ~/~.crt; # SSL 해제는 LB 에서 진행한다
ssl_certificate_key ~/~.key;
location / {
proxy_pass http://k8s-cluster; # SSL Termination 진행
proxy_set_header HOST $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
X-Real-IP 는 이전에 Spring Application 에서 ServletRequest.getRemoteAddr() 을 통해 기존 요청지를 받기 위해 추가되었다. 이 때는 K8s 없이 앞단에 Nginx LB 만 두었었기 때문에 LB에서 직접 X-Real-IP 로 주소를 전달해 주었었다. 일단 Spring Boot 기준 X-Real-IP 로 들어오는건 tomcat 에 remoteip 설정을 해줘야 수신을 해주는건 맞는 것 같다. 그래서 별도의 tomcat 설정 없이 진행하기 위해서, X-Real-IP 는 이번 설정에서 제외하겠다. (어차피 X-Forwarded-For 를 해주면 IP들이 다 누적되어서 들어오게 된다)
참고로 위 설정 중 proxy_set_header host 가 들어가지 않는다면, foo.something.net 이런 Host 값이 후속으로 전달되지 않는다. 따라서 Ingress 가 host 를 가지고 판단할 수가 없다! 따라서 HOST는 필수! (그리고 아래 더 설명되어 있는데, 이 때는 $host 로 넣어야 한다)
또한, Ingress 에서 Nginx Pod 에게 (Ingress 리소스 기동시 Ingress Controller 는 자신을 위한 Nginx Pod 을 만듬, Ingress 관련해서 어느정도 알고 있다는 전제하의 게시물) 전달할 Nginx 설정으로는 다음과 같이 정의하였다. (정의서 전체)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ing-ssomec
namespace: production
annotations:
kubernetes.io/ingress.class: "nginx"
# kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/configuration-snippet: |
proxy_set_header HOST $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
spec: # SSL 해제는 LB 에서 진행
# tls:
# - hosts:
# - foo.net
# secretName: secret-ing-ssomec
rules:
- host: foo.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: svc-foo-be
port:
number: 80
헤더에 필요한 정보가 다 전달되어야 한다고 생각해서, Forward 를 많이 해주었고.. 사실 디버깅하다가 이것저것 넣어주다 더 많아졌다.. ㅋㅋㅋ 어쨌든 문제는 다음과 같은 Tomcat 에서 에러가 발생하는 점이였다.
그리고 이는 Spring Boot 이기 때문에, 내장된 Tomcat 에서 View 해주는 에러, 즉 Spring Layer 에 진입하기 전에 발생하는 에러로, Filter 단에서 발생하는 에러로 보였다. 다음과 같이 애플리케이션 yml 설정을 통해 Tomcat 의 로깅을 명확하게 추가해줬다.
logging:
level:
org:
springframework: DEBUG
apache:
catalina: DEBUG
coyote: DEBUG
tomcat:
util: DEBUG
spring:
web: DEBUG
DEBUG 로그로 톰캣이 수신한 요청에 본문들을 모두 확인할 수 있었다. 이 때, 정말 다양한 헤더들이 있고, Host 는 HOST 와 중복되어 있고 등등을 확인할 수 있었다. 아마 Ingress Nginx Pod 에서 default 로 전달해주도록 설정된 Header들과 와 중복이 되어서 발생하는 문제로 보였다 (이 한문장을 알아내는데 거진 이틀이 걸렸다). 그렇다면 Nginx 가 기본적으로 생성해주는 값들은 어떤 값들인지, 실제 Pod 에 들어가서 확인을 해봤다. (ingress nginx pod 으로 들어감) 복잡한 설정들 투성이나, 다음과 같은 설정 부분을 확인해줄 수 있다.
// ... nginx.conf 중
proxy_set_header Host $best_http_host;
# Pass the extracted client certificate to the backend
# Allow websocket connections
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-Request-ID $req_id;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Host $best_http_host;
proxy_set_header X-Forwarded-Port $pass_port;
proxy_set_header X-Forwarded-Proto $pass_access_scheme;
proxy_set_header X-Forwarded-Scheme $pass_access_scheme;
proxy_set_header X-Scheme $pass_access_scheme;
# Pass the original X-Forwarded-For
proxy_set_header X-Original-Forwarded-For $http_x_forwarded_for;
# 버퍼 사이즈, timeout 시간, 쿠키 등 설정에 대한 부분 ###
... ~~~~~~~~
# Ingress Rule 에 직접 annotation 으로 추가한 부분은 맨 마지막에 추가된다
proxy_set_header HOST $host;
# proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Forwarded-Proto $scheme;
// ...
즉, X-Forwarded-For, Host, X-Real-IP, Proto 모두 추가를 기본적으로 해주기 때문에, 부가적으로 추가해줄 필요가 없던 것이다. 내가 해준 설정 중 Host 와 HOST 두개의 차이로 인해, Tomcat 이 Host IP 가 두 개 존재하므로, 비정상 요청이라고 판단한 부분이였던 것이다. 따라서 Ingress 에선 별도로 추가를 해주지 않아도, 잘 동작할 것이라고 예상하고 시도해봤으나, 작동하지 않았다(참고로 헤더 설정을 해주면 대체가 아니라 누적이 되더라... 즉 X-Forwarded-For 도 두번 해주면 두 번 들어간다). 어쨌든 작동하지 않은 이유를 자세히 살펴봤더니 다음과 같이 다른 부분이 있었다.
1. proxy_set_header X-Forwarded-For $remote_addr VS $proxy_add_x_forwarded_for
전자는 요청을 전달받은 Client IP 주소를 X-Forwarded-For 헤더의 값으로 설정하여, 기존 값을 덮어씌우게 된다 (X-Forwarded-For 은 하나의 Value 값만 존재). 하지만 후자는 기존 값에 요청을 전달받은 Client IP 주소를 추가한다. 즉, 기존 값이 있다면 그 값 뒤에 추가를 하게 된다
2. proxy_set_hader Host $best_http_host VS $host
전자는 같은 설정 안에서 proxy_pass 로 설정한 URL 의 호스트 부분이 헤더로 설정되고, 후자는 Client 의 요청 URL 에서 호스트 부분을 헤더로 설정한다
아무튼 1번에서 X-Forwarded-For 에 proxy_add_x_forwarded_for (내가 했던 설정) 을 지워주게 되었으므로, 기본 설정인 $remote_addr 로 들어가서, Ingress Controller 가 인지한 Remote Address 만 헤더 값으로 들어가게 되고 App 단에서는 에러가 발생하였다 (Client 의 IP 를 사용하는 서비스였기 때문). 따라서 해당 설정만 추가해주도록 하였다. (best_http_host 로 설정된 것은 ingress 설정대로 Client 요청 URL 로 잘 설정 되는 것 같았다 (Ingress 자체가 도메인으로 라우팅을 해줬기 때문, 만약 서브 도메인이나 다른 IP 등으로 하는 통신이면 Host 설정 제어도 필요해보임)
또한 이 때, 기본 설정이 $remote_addr 인 점이 의아하여, 더 살펴보니 앞단에서 받는 Forwarded 값을 믿고 그대로 사용하거나, Nginx 스스로 판단하여 새로운 값으로 넣어주거나 둘 중의 하나를 선택할 수 있는 옵션이 있었다. [use-forwarded-for] 라는 옵션인데, Ingress Nginx Controller 정의서 중 ConfigMap 에 data 부에 추가해주면 된다. 너무 길어지니 해당 블로그를 참고하길 바란다 (https://findstar.pe.kr/2021/08/22/nginx-ingress-controller-use-forwarded-for-option/). 참고로 나는 true 로 설정하여, 앞단에 load balancer 가 전달하는 Client IP 를 그대로 사용하였다.
하지만 의문점은 더 있었다.
1. 현재 Base 요청에 대해 뜨는 내용 중 X-Forwarded-For 에 계속 묶여있는 10.244.1.0 은 무슨 값일까?
우선 내가 셋업한 클러스터였으므로 CNI 에 대해선 어느 정도 생각하고 있었으니, 조금 더 이해해보기 위해 위 그림을 참고해보자.
10.244.1.0과 2.0 은 flannel.1 의 가상 IP이다. 정확히 말하면 해당 노드의 Flannel 대역을 할당하는 가상 라우터의 IP라고 볼 수 있을 것 같다 (자신이 해당 노드 컨테이너 네트워크에서 10.244.1.0/24 범위를 담당하며, 자신이 Center 라우터 IP 에 배치되고, 그게 0으로 끝나는 이유이다)
궁금했던 점은, 내가 ingress-nginx-pod (내 Ingress 리소스를 위해 Controller 가 띄운 팟) 을 보면, A 노드에 배치되었고, 내 서비스 팟 역시 A 노드에 배치되었는데, 노드 내부 통신을 위해서는 Flannel 망 사용이 불필요한 것 아닌가 싶었고, 그 생각이 틀리진 않았던 것 같다. 사진을 첨부하진 않았지만 애플리케이션 로그를 보면, X-Forwarded-For 의 두번째 IP 가 10.244.1.0, 10.244.2.1 을 번갈아가면서 전달되는 모습을 확인했다. 이는 Load Balancer 가 Worker Node 중 하나를 선택해서 Ingress Controller 로 보내는 모습같다.
$kubectl get po {INGRESS_NGINX_POD_NAME} -n ingress-nginx -o wide
위 명령어를 통해 NGINX POD 의 노드를 확인해보면 2번 노드라서, 10.244.2.0 의 대역에서 IP 를 할당 받았을 것이다. 따라서, 이 Pod는 같은 노드에서 보낸 요청을 수신하거나, 아니면 1번 노드에서 들어오는 요청을 수신하게 된다. 이 때, 같은 노드에서 들어올 때는 바로 컨테이너 망 (cni0) 을 사용하게 되고, 다른 노드와의 통신은 Flannel 망 (VXLAN망) 을 사용하게 되는 것으로 보인다!
2. 왜 Load Balancer 의 IP 는 X-Forwarded-For 헤더에 누적되어 묶이지 않는 것일까?
이건 처음에 들었던 의문인데, 1번 의문을 해결하면서 자연스럽게 해결되었다. 첫번째 Load Balancer 는 당연히 Client의 IP 를 Forward 해주게 된다. 두번재는 위에서 언급했듯이, Ingress Nginx Controller 의 Pod 이 수신하게 되는데, 이 때 자신은 Client 를 Flannel 혹은 CNI 와 같은 라우터들로 생각하게 된다. 그래서 Load Balancer 의 IP 가 아닌, 이들의 IP 를 X-Forwarded-For 에 적게 되는 것이다.
2. Spring Boot Application 에서 따로 설정은 안해줬는데 getRemoteAddr 에 어떻게 Client IP가 들어가는지 건가?
Application 단에 원래 목적이였듯이 Servlet.getRemoteAddr() 을 출력해보니 Client IP 가 잘 들어가 있었다. 다만, 이건 아까 위에서 살짝 언급했던 X-Real-IP 설정도 빼주었기 때문에, 그 어떠한 설정도 해주지 않았었다. ForwardedHeaderFilter 와 연관이 된 부분인가 싶었는데, 이 필터가 동작하는 기본 설정은 없다고 한다 (옵션이 있는데 좀 다양함). 어떻게 자동으로 Servlet 에 X-Forwarded-For 의 가장 앞 헤더가 들어가게 된건지 의문이다. 그리고 이건 아직 해결하지 못했다 ^^..
....
조금 복잡한 Ingress Controller 를 통해 배포를 해보니, Ingress Controller 를 어떻게 사용하는건지 조금.. 아주 조금은 감이 잡힌 것 같다. 어쨌든 결과적으론 Nginx 설정 문제였다. 해도해도 정말 어려운 것 같다. 혹시 비슷한 오류를 겪으시는 분들.. 도움이 되었으면 좋겠다 (진짜 아무리 찾아도 안나왔음.. ㅠㅠ). 이틀 넘게 같이 싸우면서 도와준 GPT 형님 감사합니다.

'Logging > 인프라' 카테고리의 다른 글
[문제기록] K3s 에서 Airflow 사용 중 Iptables 규칙 과부하로 인한 통신 불가 문제 (2) | 2025.03.08 |
---|