몇 개월 전, 내부 프로젝트에 필요한 클러스터를 구축하고 어플리케이션 및 Prometheus, Grafana 등과 같은 도구들을 Pod로 배포했을 때의 일이다.
회사 IP에서만 노출되어야 하는 Pod들(Prometheus, Grafana, Jaeger, 어드민 서버 등)을 배포할 때 당시에는 시간이 촉박했었고 빠르게 배포를 해야 하는 상황이었던지라 Ingress를 사용하지 않고 LoadBalancer를 사용해서 노출했었다.
Ingress 리소스의 존재도 알고 있었고, 이전에 잠깐이나마 실습을 한 적도 있었지만 그 때 당시에는 Helm을 사용해서 배포하는 것조차 익숙하지가 않아서 많이 헤메고 시간이 걸렸던지라 Ingress까지 적용하는 것은 무리가 있었다고 판단했다.(물론 지금에 와서 돌이켜보면 그렇게 까지 급했나? 싶지만... 반성한다...)
아무튼 그렇게 LoadBalancer로 배포해둔 Pod들이 몇 개 있었는데 어쨌든 외부로 노출되는 IP가 생성되고 이에 대해 과금이 들어가는 구조이기 때문에 찜찜할 수 밖에 없었다.
거기다가 노드에 NotReady가 되는 등의 문제가 생겨 기존에 배포해두었던 Pod와 Service등 많은 리소스가 다른 노드에 다시 배포될 경우, LoadBalancer가 다시 생성되면서 External-IP가 변경이 되는데 이 External-IP와 DNS를 연결해둔 것이 매칭이 되지 않는 문제도 간혹 발생했다.
그렇게 이런저런 문제로 찜찜함을 가지고 있었지만 좀처럼 고치질 못하고 있었는데 최근 또 다른 프로젝트를 진행하면서 기존 프로젝트에 대한 개선 작업을 할 시간이 생기게 되어 벼르고 있던 LoadBalancer -> Ingress 교체 작업을 진행하게 되었다.
Ingress는 Kubernetes Service 리소스에 대한 외부에서의 접근을 관리하는 리소스로, 다음과 같은 역할과 기능을 가진다
Ingress를 사용하게 되면 Ingress가 외부 접근에 대한 단일 진입점 역할을 하고 Ingress에서 정한 PathType, 호스트 룰에 따라 다른 Service 리소스로 로드 밸런싱을 해줄 수 있다.
Ingress를 적용하기 위해선 Ingress Controller가 필요하다.
Ingress Controller는 외부에서 들어온 요청을 Ingress에서 지정한 룰에 따라 라우팅을 수행하기 위해 존재하는 리소스이며, Nginx Ingress Controller는 이런 Ingress Controller의 대표적인 구현체이다.
이전까지는 일일이 외부 노출이 필요한 어플리케이션마다 Service의 타입을 LoadBalancer로 지정하여 배포했다면, 이제는 Ingress만 LoadBalancer로 외부로 노출시키고 나머지 어플리케이션의 Service는 ClusterIP로 변경할 수 있다.
이렇게 되면 다음과 같은 장점이 있다.
물론 장점만 있지는 않고 단점도 있다.
하지만 애초에 Ingress를 적용할 Pod들은 HTTP 통신만 사용하기 때문에 큰 문제는 없었고 Ingress Controller의 장애 전파 부분도 모든 사람들에게 공개되는 서비스의 어플리케이션이었다면 문제가 될 수 있었겠지만 어차피 사내에서만 공개될 어플리케이션에 적용하는 것이었기 때문에 큰 문제는 없었다.
그래서 이러한 연유로 LoadBalancer가 적용된 주요 어플리케이션들에 Ingress를 적용하기로 했다.
Nginx Ingress Controller 설치 방법은 공식 문서에 잘 나와있다.
https://kubernetes.github.io/ingress-nginx/deploy/
나같은 경우 회사에서 이미 Helm을 사용하고 있기 때문에 공식 문서에 나와 있는 방법대로 Helm을 사용하여 설치하였다.
helm upgrade --install ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
--namespace ingress-nginx --create-namespace
Helm을 통해 설치할 경우 Nginx Ingress Controller가 배포되며, 위의 명령어에서는 ingress-nginx 네임스페이스에 배포가 되었기 때문에 External-IP를 포함해 배포된 Pod의 상태를 확인하기 위해선 kubectl get all -n ingress-nginx
명령어를 사용한다.
참고로 클라우드 환경에서 배포할 경우 클라우드에서 제공하는 LoadBalancer와 연계하여 External-IP가 생성이 되는데, 그렇지 않은 경우엔 pending 상태가 된다. LoadBalancer를 사용할 수 없는 환경이라면 NodePort로 변경하고 노드의 특정 포트와 포트 포워딩을 해야 한다.
External-IP에 DNS 레코드를 설정한다. 이 부분은 환경마다 다르기 때문에 별도로 기술하지는 않는다. 참고로 본인은 현재 NHN Cloud를 사용하고 있어 NHN Cloud의 DNS 서비스를 사용하였다. 여기서는 예제로 '<이름>.mrcocoball.io' 라고 가정한다. (argocd.mrcocoball.io, prometheus.mrcocoball.io 등...)
Nginx Ingress Controller의 경우 HTTPS 적용도 가능한데 이를 위해선 TLS Secret 리소스가 필요하다. 인증서는 사설 인증서 또는 Let's Encrypt로 생성한 인증서가 있다고 가정한다. 인증서 파일은 각각 .crt 파일과 .key 파일이 있어야 한다.
kubectl create secret tls <시크릿 이름> --key <key 파일 이름>.key --cert <crt 파일 이름>.crt -n <네임스페이스>
예제에서는 시크릿 이름을 'ingress-ssl-key'라고 가정한다.
Ingress 적용 대상은 GitOps를 위해 배포해둔 ArgoCD 서버의 웹 콘솔, 모니터링을 위해 배포해둔 Prometheus, Grafana의 웹 콘솔, Jaeger Query, 그리고 사내에서 사용할 통합 어드민 서비스로 정했다. 그리고 통합 어드민 서비스는 TLS를 적용해본다.
이들은 모두 Helm으로 배포하였으며 통합 어드민 서비스는 자사에서 직접 차트를 만들어 관리하고 있고 나머지는 공식 차트의 values.yaml을 수정하여 배포해둔 상태다.
values.yaml에 다음과 같이 추가하고 업그레이드한다.
# server 부분
server:
ingress:
enabled: true
ingressClassName: nginx
hostname: argocd.mrcocoball.io
path: /
pathType: Prefix
Helm Chart는 kube-prometheus-stack를 사용하였다.
values.yaml에 다음과 같이 추가하고 업그레이드한다.
# prometheus 부분
prometheus:
enabled: true
ingress:
enabled: true
ingressClassName: nginx
paths:
- /
hosts:
- prometheus.mrcocoball.io
pathType: Prefix
# grafana 부분
grafana:
ingress:
enabled: true
ingressClassName: nginx
path: /
hosts:
- grafana.mrcocoball.io
참고로 Grafana의 경우 호스트의 특정 엔드포인트 (예시 : /grafana) 로 호스트 룰을 적용하려면 grafana.ini에서 별도의 설정을 추가해줘야 했었다. 이를 알기 전까지는 infra.mrcocoball.io 의 엔드포인트별로 라우팅을 해줄 생각이었지만 이런 식으로 애매한 부분이 있어서 그냥 명확하게 도메인 이름을 다르게 설정해줬다.
Helm Chart는 jaegertracing을 사용하였다.
values.yaml에 다음과 같이 추가하고 업그레이드한다.
# query 부분
query:
ingress:
enabled: true
ingressClassName: nginx
hosts:
- jaeger.mrcocoball.io
pathType: Prefix
values.yaml에 다음과 같이 추가하고 업그레이드한다.
ingress:
enabled: true
className: "nginx"
annotations:
nginx.ingress.kubernetes.io/rewrite-target: "/"
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
kubernetes.io/tls-acme: "true"
hosts:
- host: admin.mrcocoball.io
paths:
- path: /
pathType: Prefix
tls:
- secretName: nginx-ssl-key
hosts:
- admin.mrcocoball.io
이전에 실습을 했었을 때는 클라우드 환경이 아닌 온프레미스 (그것도 VirtualBox로 VM을 4개 띄운) 에서 작업을 했었던지라 Ingress 적용에 고생을 많이 했었던 기억이 있었다.
그래서 시간이 촉박하다는 이유로 클라우드 환경에서의 Ingress를 자세히 알아보지 않고 LoadBalancer를 사용했었는데 막상 각잡고 해보니 별게 아니었다.
(물론 차트별 values.yaml의 양식이 서로 달라 ingress를 적용하는 포인트를 찾는 시간이 좀 걸리긴 했지만)
그나마 운영 환경이 아닌 서비스 런칭 이전의 개발 환경에서 이렇게 고칠 수 있었지만 만약 섣불리 운영 환경에서 조금 더 쉬운 길을 택하겠다고 임시방편으로 비용 효율적으로 좋지 않은 선택을 했다가 더 효율적인 방법으로 개선을 해야하는 상황이 온다면 어떻게 대처해야할지 막막했을 것이다.
이런 일이 앞으로 또 다신 일어나지 않으리라는 보장을 할 수 없으니 항상 기술적인 선택을 할 때 더 나은 방법이나 대안이 없는지 충분히 찾아보고 적용을 하고, 거기서 안주하지 말고 지속적으로 더 좋은 방법이 있는지 찾아보는 습관을 들여야겠다는 생각이 들었다.