3대의 노드에 하나의 ip로 서비슬르 만들기 위해서 처음에 구상할때는 keepalived + haproxy 를 이용하려고 했다.
그런더 검색을 하다보니 이방법이 예전방식이고 Metallb 가 사실상 표준이 되었다고 한다.
MetalLB 설치
마스터 노드에서 설치 명령 실행
$ sudo k3s kubectl get nodes
NAME STATUS ROLES AGE VERSION
k3s-node1 Ready control-plane,master 6h40m v1.33.5+k3s1
k3s-node2 Ready <none> 6h34m v1.33.5+k3s1
k3s-node3 Ready <none> 6h31m v1.33.5+k3s1
먼저 노드는 모두 잘 살아 있는지 확인 하고
$ sudo kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.5/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
뭔가 엄청 빠른 속도로 끝이 났다.
각 노드에서 잘 설치 되었는지 확인해 보자.
$ sudo k3s kubectl get pods -n metallb-system
NAME READY STATUS RESTARTS AGE
controller-654858564f-tkxcc 1/1 Running 0 3h41m
speaker-4hm7x 1/1 Running 0 3h41m
speaker-r5pvd 1/1 Running 0 3h41m
speaker-w5k89 1/1 Running 0 3h41m
컨트롤 1개에 노드 3개니까 스피커 3개면 정상
restarts 가 0에 가까워야 이슈없음
$ sudo k3s kubectl get ds -n metallb-system
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
speaker 3 3 3 3 3 kubernetes.io/os=linux 3h42m
Desired : 3, 즉 3대의 노드에 실행되기를 희망하는데 현재 3개가 돌고 있으므로 잘 설정됨
이제부터는 k3s 설치후 처음으로 설정파일을 만들어 보려고 한다.
설정파일은 어디에 두어도 좋다고 하는데
나는 루트 밑에 k8s 디렉토리를 만들어서 두려고 한다.
master 에만 두어도 되는건지 아직은 잘 모르겠으나 일단 가이드대로 해본다.

AI가 이런식으로 설정 구조를 가져가라고 추천해준다.
일단 이렇게 해보는것이 시행착오를 줄일것 같으니 해보자.
1단계: IPAddressPool 생성 (IP 주소 범위 정의)
@k3s-node1:/k8s/system/metallb
위치에 생성한다.
$ sudo vi ipaddresspool.yaml
# ipaddresspool.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: my-proxmox-vip-pool
namespace: metallb-system
spec:
# 10.34.1.150 단일 IP만 할당하기 위해 /32 서브넷 마스크를 사용합니다.
addresses:
- 10.34.1.150/32
~
~
$ sudo kubectl apply -f ipaddresspool.yaml
ipaddresspool.metallb.io/my-proxmox-vip-pool created
적용까지 하니까 뭔가 빠르게 끝난다.
2단계: L2Advertisement 생성 (L2 모드 광고 활성화)
정의된 IP 주소 풀을 L2(ARP/NDP) 모드로 클러스터 노드가 네트워크에 광고하도록 설정하는 설정이라고 한다.
내가 이런 아이피를 vip를 쓸거야 니들 노드들 잘 알고 있어..~ 뭐 이런건가 보다.
# l2advertisement.yaml
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: l2-advertisement-for-vip
namespace: metallb-system
spec:
# 위에서 정의한 IPAddressPool 이름을 지정합니다.
ipAddressPools:
- my-proxmox-vip-pool
$ sudo kubectl apply -f l2advertisement.yaml
l2advertisement.metallb.io/l2-advertisement-for-vip created
3단계: 서비스 생성 및 VIP 할당 확인
2단계까지 했으면 실제 VIP 셋팅은 끝난것으로 보인다.
그럼 실제로 서비스 + pod 를 만들어서 잘 되는지를 확인해 보면 될것 같다.
일단 nginx 를 하나 올리고 기본 페이지가 뜨는 것을 해보도록 한다.
nginx pod 배포
파일 구조는 아래처럼 하려고 한다.

베이스를 두고 설정이 달라질수 있는 부분을 별도로 빼서 관리하는게 정석이라고 안내를 해준다.
# k8s/apps/test-nginx/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1 # 기본값은 1개
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
# k8s/apps/test-nginx/base/service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-vip-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
type: LoadBalancer # MetalLB가 인식하는 타입
# k8s/apps/test-nginx/base/kustomization.yaml
resources:
- deployment.yaml
- service.yaml
순서대로 이렇게 만들어 주고 production 설정을 만든다.
내용을 쭉보니 base 에서 만든설정들을 베이스로 overwrite 를 해서 최종 설정을 하는듯 하다.
production 설정
# k8s/apps/test-nginx/production/kustomization.yaml 최종 수정본
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../base
- ingress.yaml
patches:
# --- 패치 1: Deployment의 Pod 개수(replicas) 수정 (유지) ---
- target:
kind: Deployment
name: nginx-deployment
patch: |
- op: replace
path: /spec/replicas
value: 3
# --------------------------------------------------
# --- 패치 2: Service 타입을 ClusterIP로 변경 (새로 추가) ---
- target:
kind: Service
name: nginx-vip-service
patch: |
- op: replace
path: /spec/type
value: ClusterIP # LoadBalancer 대신 ClusterIP로 변경
# -----------------------------------------------------------
ingress 도 설정한다.
(Ingress는 클러스터 외부에 Pod의 IP 주소나 NodePort를 직접 노출하지 않으면서, 사용자 친화적인 방식으로 서비스에 접근할 수 있게 해줍니다.)
$ vi ingress.yaml
# nginx-ingress.yaml 파일 수정 (또는 새로운 ingress 추가)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-default-ingress
annotations:
kubernetes.io/ingress.class: traefik
spec:
rules:
# --- 기존 Host 규칙 ---
- host: nginx.prod.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-vip-service
port:
number: 80
~
~
~
여기까지 하고 적용하면
$ sudo kubectl apply -k /k8s/system/apps/test-nginx/production
service/nginx-vip-service created
deployment.apps/nginx-deployment configured
요렇게 잘 끝났다고 나온다.
그럼 잘 돌아가는지 한 번 볼까?
$ sudo k3s kubectl get svc nginx-vip-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-vip-service LoadBalancer 10.43.229.103 <pending> 80:32558/TCP 2m36s
이제 호출해보자
$ curl -H "Host: nginx.prod.local" http://10.34.1.150
<!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>
taehyongi@k3s-node1:/k8s/system/apps/test-nginx/production$
성공했다.
그런데 왜 curl 로 했느냐..
traefik 이라는 솔루션은 Host 기반으로 처리한다고 한다.
가상 컨테이너 구조상 같은 vip 로 여러 서비스가 올라갈텐데 서비스를 구분하기 위해서
포트를 구분할수도 있을텐데 도메인으로 구분한다는 것이다.
설정을 하면 아이피로도 할수 있는것 같은데 host 가 기본이고 이것이 맞으므로 일단 그대로 둔다.
브라우저에서 10.34.1.150 으로 접속하면 not found 에러가 나온다.
Git 으로 설정 관리
참고로 여기까지 하고 이 설정들이 master 노드에 있다면 이 노드인 1번 서버가 죽어버리면 어떻게 되는거지?
하는 의문이 생겼다. 검색해보니.. etcd 라는 내부 저장소에 이것들이 모두 올라간다고 한다.
그럼 실제 노드들끼리는 그 etcd 를 바라본다는 얘기이다.
위에서 만든 yaml 파일을을 kubectl 로 apply 하는 과정은 설치 과정 뿐만 아니라 etcd 에 올리는 과정이 포함되어 있을것이라고 본다.
그럼 이 설정파일들을 잊어버리면 안되므로 git 을 통해 최신화 관리를 하는것이 바람직한 방법으로 보인다.
다음으로는 도메인으로 어느 서비스로 갈지 분기하는것을 해보자.
ssl 인증서도 자동 셋팅하고...