[K8s] MetalLB로 외부에서 클러스터 내부로 접근하기

Kaite.Kang·2025년 4월 20일

외부에서 클러스터 내부로 접속하기 적합한 서비스는?

클러스터에서 서비스에 접속하기 위해서는 service를 거쳐야 한다. 서비스 타입 별로 어떤 서비스가 외부로 서비스를 노출하기 적합한지 살펴보자.

서비스는 ClusterIP, NodePort, LoadBalancer 등이 있다. clusterIP는 내부 리소스 간 접속하기 위한 용도이기 때문에 제외해야 한다. NodePort, LoadBalancer 를 활용하여 외부에서 내부로 접속할 수 있도록 구현할 수 있다.

NodePort는 클러스터 안의 각 노드에 특정 포트를 열어서 외부에서 접근 가능하게 만드는 방식이다.

예를 들어,

  • Node IP: 192.168.1.10
  • NodePort: 30082

라면 접근 가능한 URL은 http://192.168.1.10:30982 가 된다. 즉, 사용자는 NodePort 방식의 서비스에 접근할때 노드의 IP와 포트를 알아야 한다. 만약 해당 노드나 네트워크가 죽으면 사용자는 http://192.168.1.10:30982 로 더 이상 접근할 수 없으며, 다른 노드가 살아있더라도 접속 경로가 끊긴 것 처럼 보이게 된다. 그래서 운영자가 사용자한테
"접속 IP 바뀌었어요! 이제 192.168.1.11:30982로 접속하세요"
이렇게 알려줘야 한다는 번거로움이 있다.

반면 LoadBalancer는 IP 하나를 고정으로 잡아주는 방식이다. 해당 IP는 한 노드가 죽더라도 다른 노드로 자동으로 옮겨가기 때문에 사용자는 항상 같은 IP로 접속할 수 있다.

온프라미스에서 LoadBalancer 사용하기

AWS, Azure, GCP와 같은 퍼블릭 클라우드 환경에서는 LoadBalancer 타입의 서비스를 생성하면, 클라우드에서 자동으로 외부 IP가 할당되어 바로 사용할 수 있다. 그러나 온프레미스 환경에서는 이러한 자동화된 LoadBalancer 기능이 없기 때문에, 별도의 외부 IP 할당 및 로드밸런싱을 수행할 수 있는 서비스가 필요하다.

온프라미스에서 LoadBalancer 를 제공하는 서비스는 MetalLB가 있다.

MetalLB 구성 작업 절차

구성은 https://metallb.universe.tf/installation/#installation-by-manifest 페이지를 참고하여 진행하였다.

본 테스트 환경에서는 ingress nginx로 pod에 example.com 도메인을 할당하였다. example.com 도메인을 외부로 노출시키기 위해 MetalLB를 사용하였다.

1. MetalLB 설치 하기

root@k8s-m1:~# kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml
namespace/metallb-system created
customresourcedefinition.apiextensions.k8s.io/bfdprofiles.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bgpadvertisements.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bgppeers.metallb.io created
customresourcedefinition.apiextensions.k8s.io/communities.metallb.io created
customresourcedefinition.apiextensions.k8s.io/ipaddresspools.metallb.io created
customresourcedefinition.apiextensions.k8s.io/l2advertisements.metallb.io created
customresourcedefinition.apiextensions.k8s.io/servicel2statuses.metallb.io created
serviceaccount/controller created
serviceaccount/speaker created
role.rbac.authorization.k8s.io/controller created
role.rbac.authorization.k8s.io/pod-lister created
clusterrole.rbac.authorization.k8s.io/metallb-system:controller created
clusterrole.rbac.authorization.k8s.io/metallb-system:speaker created
rolebinding.rbac.authorization.k8s.io/controller created
rolebinding.rbac.authorization.k8s.io/pod-lister created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:controller created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:speaker created
configmap/metallb-excludel2 created
secret/metallb-webhook-cert created
service/metallb-webhook-service created
deployment.apps/controller created
daemonset.apps/speaker created
validatingwebhookconfiguration.admissionregistration.k8s.io/metallb-webhook-configuration created

root@k8s-m1:~# kubectl get pod -o wide -n=metallb-system
NAME                         READY   STATUS    RESTARTS   AGE     IP             NODE         NOMINATED NODE   READINESS GATES
controller-75b7b4c4b-l9rzj   1/1     Running   0          60s     10.233.74.71   k8s-w1   <none>           <none>
speaker-9snc2                1/1     Running   0          60s     172.16.10.93   k8s-w2   <none>           <none>
speaker-gq547                1/1     Running   0          60s     172.16.10.91   k8s-m1   <none>           <none>
speaker-vrbsc                1/1     Running   0          60s     172.16.10.92   k8s-w1   <none>           <none>

root@k8s-m1:~# kubectl get all -n=metallb-system
NAME                             READY   STATUS    RESTARTS   AGE
pod/controller-75b7b4c4b-l9rzj   1/1     Running   0          61s
pod/speaker-9snc2                1/1     Running   0          61s
pod/speaker-gq547                1/1     Running   0          61s
pod/speaker-vrbsc                1/1     Running   0          61s

NAME                              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/metallb-webhook-service   ClusterIP   10.233.58.74   <none>        443/TCP   61s

NAME                     DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
daemonset.apps/speaker   3         3         3       3            3           kubernetes.io/os=linux   61s

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/controller   1/1     1            1           61s

NAME                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/controller-75b7b4c4b   1         1         1       61s

2. MetalLB에 ip pool 할당하기

이제 서비스를 LoadBalencer 타입으로 생성하면 MetalLB가 해당 서비스에 ip를 할당한다. MetalLB가 할당한 IP Pool 을 설정한다. 여기서 IP Pool 대역은 클러스터 노드들이 접근할 수 있고, DHCP가 관리하지 않는 IP 대역을 설정해야 한다.

## 노드에서 통신 가능한 IP 대역은 172.16.10.91/24이다.
root@k8s-m1:~# ip -4 -o a |grep ens2
2: ens2    inet 172.16.10.91/24 brd 172.16.10.255 scope global ens2\       valid_lft forever preferred_lft forever

root@k8s-m1:~# kubectl get nodes -o wide
NAME         STATUS   ROLES           AGE    VERSION    INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
k8s-m1   Ready    control-plane   2d5h   v1.27.10   172.16.10.91   <none>        Ubuntu 22.04.3 LTS   5.15.0-86-generic   containerd://1.7.13
k8s-w1   Ready    <none>          2d5h   v1.27.10   172.16.10.92   <none>        Ubuntu 22.04.3 LTS   5.15.0-86-generic   containerd://1.7.13
k8s-w2   Ready    <none>          2d5h   v1.27.10   172.16.10.93   <none>        Ubuntu 22.04.3 LTS   5.15.0-86-generic   containerd://1.7.13

root@k8s-m1:~# vi ip-pool.metallb.yml
---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: my-ip-pool
  namespace: metallb-system
spec:
  addresses:
  - 172.16.10.97-172.16.10.99
  
  
root@k8s-m1:~# kubectl apply -f ip-pool.metallb.yml
ipaddresspool.metallb.io/my-ip-pool created

root@k8s-m1:~# kubectl get ipaddresspools.metallb.io -n metallb-system
NAME         AUTO ASSIGN   AVOID BUGGY IPS   ADDRESSES
my-ip-pool   true          false             ["172.16.10.97-172.16.10.99"]

3. ingress 서비스 타입을 LoadBalancer 로 변경하여 IP 할당 받기

## example.com 도메인을 외부로 노출시키기 위해 해당 ingress controller의 서비스를 LoadBalancer 타입으로 변경해주자.
root@k8s-m1:~# kubectl get ingress
NAME            CLASS   HOSTS         ADDRESS        PORTS     AGE
nginx-ingress   nginx   example.com   10.233.53.77   80, 443   2d1h

## 현재 NodePort 타입로 설정되어 있다.
root@k8s-m1:~# kubectl get svc -n=ingress-nginx
NAME                                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             NodePort    10.233.53.77   <none>        80:30982/TCP,443:30859/TCP   2d1h
ingress-nginx-controller-admission   ClusterIP   10.233.16.31   <none>        443/TCP                      2d1h

root@k8s-m1:~# kubectl edit svc ingress-nginx-controller -n ingress-nginx
---
...
spec:
  type: LoadBalancer #NodePort -> LoadBalancer 로 변경
---

## MetalLB 에서 설정한 ip pool 대역으로 EXTERNAL-IP를 할당 받았다.
root@k8s-m1:~# kubectl get svc -n=ingress-nginx
NAME                                 TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)                      AGE
ingress-nginx-controller             LoadBalancer   10.233.53.77   172.16.10.97   80:30982/TCP,443:30859/TCP   2d1h
ingress-nginx-controller-admission   ClusterIP      10.233.16.31   <none>         443/TCP                      2d1h

5. 외부에서 접근 테스트

root@client:~# cat /etc/hosts |grep example.com
172.16.10.97 example.com

root@client:~# curl -k https://example.com
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

결론

MetalLB는 온프라미스에서 로스밸런서를 간편하게 구현할 수 있다.

MetalLB는 내부적으로 L2 스위치의 동작을 소프트웨어적으로 구현한 것과 유사한 방식으로 동작한다. 서비스 규모가 작은 환경에서는 MetalLB가 좋은 선택지가 될 수 있겠다.
규모가 큰 엔터프라이즈 환경에서는 MetalLB 대신 외부의 L3 BGP 라우터를 사용한 구성 방식을 권장한다고 한다.

참고

[1] https://andrewpage.tistory.com/23
[2] https://metallb.universe.tf/installation/
[3] https://www.youtube.com/watch?v=hJO1nxsB5uY

0개의 댓글