본문 바로가기

컨테이너/쿠버네티스 네트워크

[KANS] 쿠버네티스 네트워크 (10) Service : LoadBalancer

목표:

1. Service 에 대해 알아보자 ( Nodeport Hostport의 한계와 Loadbalancer ) 

2. MetalLB에 대해서 알아보자 

3. 실습을 통해 통신흐름을 분석해보자.

 서비스 - Nodeport와 Hostport 

서비스(Service)  - Nodeport와 Hostport? 

 

  • 서비스 중에서 다음과 같이 cloudfront에 접근해야 되는 상황이 있었다. worker 노드의 IP가 변경되어서 통신이 안된 상황이 있었다.
  • 서비스는 pod가 위치한 노드의 인터페이스를 통해 외부로 snat 되어 나가기 때문에 바뀐 ip를 cloudfront와 AWS LB 사이드에 방화벽 허용신청을 해야했다. 기본적으로 외부 간의 통신은 ingress를 통해 통신 할거라고 생각했지만 어느 쪽에서 request를 날리느냐에 따라 다르다는것을 알았다. 
  • 그리고 반대로 ingress를 통해 외부에서 들어오는 트래픽은 nginx ingress controller를 통해 들어오는데 hostport 로 이루어진게 특이했다. 
nginx-ingress-controller iptables duplicate 이부분 이슈에 대해서 규리님이 내용을 적어주셨는데, 이 부분에서 굉장히 
시사하는 점이 많다고 생각했습니다. 
 

(1) 현상Kubernetes 버전 1.19 -> 1.23으로 순차적으로 업그레이드 하는 상황에서 Kubernetes 버전 1.21 -> 1.22 업그레이드 시 ingress 통신이 실패하는 현상 발생

(2) 현상 분석Kubernetes 버전 1.21 -> 1.22로 업그레이드를 한 후 nginx-ingress-controller pod가 새롭게 뜨면 기존의 CNI-HOSTPORT-DNAT 체인이 사라지고 새로운 CNI-HOSTPORT-DNAT 체인이 생기는 것이 아니라 계속해서 체인이 쌓이면서 통신이 불가능해짐.

(3) 원인 kubelet에서 dockershim을 사용하는 경우에 해당하는 이슈였다. kubelet의 dockershim은 hostport로 뜨는 Pod에 대해서 kubelet 컨테이너의 /var/lib/dockershim/sandbox하위에 Portmapping 정보를 넣어놓고, pod가 바뀔 때마다 기존의 정보는 삭제하고, 최신 정보를 iptables에 등록하게 된다.

https://ranchermanager.docs.rancher.com/v2.0-v2.4/how-to-guides/new-user-guides/migrate-from-v1.6-v2.x/expose-services


Kubernetes의 1.22에서 Portmap 플러그인이 v0.8.6에서 v1.0.0으로 업그레이드 되었는데, 해당 버전의 Portmap 플러그인에는 Portmapping 정보가 주어지지 않으면 아무 작업을 수행하지 않도록 코드가 업데이트 되었다.

kubelet은 /var/lib/dockershim에 Pod 메타데이터를 저장하는데, 업그레이드 직후에는 kubelet이 재시작되면서 해당 디렉토리의 메타데이터가 사라진다.  hostPort pod의 경우, portMappings의 Pod 메타데이터를 기반으로 portmap 바이너리가 iptables 규칙을 변경하게 된다.

일반적으로 kubelet은 Pod 메타데이터가 없더라도 정상적으로 Pod를 종료하지만, hostPort와 새로운 버전의 Portmap 플러그인을 사용할 경우, pod 메타데이터가 사라지고 portMappings 리스트가 빈 값으로 반환되면서 iptables에서 사라진 Pod 관련 규칙 처리가 정상적으로 이루어지지 않고, 결과적으로 iptables duplicate 현상이 발생한다.

 

이러한 경우에  Ingress의 IP가 워커 노드의 IP 로 생성되는 이유는 Kubernetes 클러스터에서 Ingress controller pod가 HostPort 방식을 사용하기 때문입니다.

[HostPort ]

  • 데몬셋을 통해 배포됩니다. 호스트에서 사용 가능한 모든 포트가 노출될 수 있습니다.
  • 구성은 간단하고 HostPort는 Kubernetes pod 사양에 직접 설정됩니다. NodePort와 달리 앱을 노출하기 위해 다른 객체 (서비스) 를 만들 필요가 없습니다.
  • 선택한 포트에 공석이 있는 호스트만 사용할 수 있으므로 포드의 스케줄링이 제한됩니다. sessionAffinityrule  및externalTrafficpolicy:local 은 서비스 객체가 필요하다 보니 사용할수 없습니다. 
  • 동일한 HostPort를 지정하는 두 개의 워크로드는 동일한 노드에 배포될 수 없습니다.
  • 포드가 실행 중인 호스트를 사용할 수 없게 되면 쿠버네티스는 포드를 다른 노드로 재예약합니다. 따라서 워크로드의 IP 주소가 변경되면 애플리케이션의 외부 클라이언트는 포드에 액세스할 수 없게 됩니다. 포드를 재시작할 때도 같은 일이 발생합니다. 쿠버네티스는 포드를 다른 노드로 재예약합니다.

이 경우, Ingress Controller는 클러스터 내에서 트래픽을 처리하기 위해 워커 노드의 IP와 포트를 사용하여 외부에서 들어오는 요청을 받습니다.

 

HostPort는 사실상 단점이 많은 기능인데 이것은 컨테이너의 기능 중 하나입니다 서비스 없이 pod를 노출하는 포트매핑 기능을 활용한다.

이를 통해 ingress-controller을 배포하여 response를 하는 파드가 위치한 노드의 포트로 통신을 합니다.

구조 자체는 Metallb의 speaker pod가 호스트 port 매핑을 하는것과 유사하지 않은가 싶다.

 k describe pod -n metallb-system speaker-kcslj

...
  speaker:
    Container ID:  containerd://9790dee38b2201bf4284e14ca99258b5e1b78c790d43ece8fdc9d08253c14c31
    Image:         quay.io/metallb/speaker:main
    Image ID:      quay.io/metallb/speaker@sha256:a2ff936e429d37ac159593f93bf323db7e187e440ee7762dd5c81644ee65b112
    Ports:         7472/TCP, 7946/TCP, 7946/UDP
    Host Ports:    7472/TCP, 7946/TCP, 7946/UDP
    Args:
      --port=7472
      --log-level=info

 

 클라우드를 사용하지 않고 ( 소프트웨어 LB 또는 L4 장비 없이 ) 뭔가 노드에서 모든것을 처리하려고 하다 보니까 정말 기괴한 아키텍처들이 많이 있는것 같습니다. 커피고래님 블로그 및 k8s io 에는 극단적으로 알필요도 없다고 하지만, 아무튼 저희 운영환경에서는 사용하고 있습니다.ㅋ

 

 

https://coffeewhale.com/k8s/network/2019/05/30/k8s-network-03/

HostPort and HostNetwork

마지막 두개 소개하고 싶은 내용은 실질적으로 사용하는 방법이라기 보다는 실험용입니다. 사실 99.99%의 경우 anti-pattern으로 사용될 것이고 실제로 사용하는 곳이 있다면 전체적으로 아키텍처 디자인 리뷰를 다시 받아 봐야할 것입니다. 그렇기 때문에 아예 설명에서 빠트릴까도 생각했지만 이러한 방법 또한 네트워킹 방법 중 하나이기에 간단하게나마 설명 들릴까 합니다.

첫번째는 HostPort입니다. 이것은 컨테이너의 기능 중 하나입니다. 만약 특정 포트 번호를 노드에 열게 되면 해당 노드의 해당 포트로 들어오는 요청은 곧바로 컨테이너로 직접 전달이 됩니다. proxying 되지 않으며 해당 컨테이너가 위치한 노드의 포트만 열립니다. 쿠버네티스에 DaemonSetsStatefulSets과 같은 리소스가 존재하지 않았을 때 사용하던 기법으로 오직 한개의 컨테이너만 어느 한개의 노드 위에 실행되길 바랄 때 사용하였습니다. 예를 들어 elasticsearch 클러스터를 생성할 때 HostPort를 9200으로 설정하고 각 컨테이너들이 마치 elasticsearch의 노드처럼 인식되게 만들었습니다. 현재는 이러한 방법을 사용하지 않기 때문에 쿠버네티스 컴포넌트를 개발하는 사람이 아닌 이상 사용할 일이 거의 없습니다.

 

 

 

 

  • 이러한 그림에서 cloudfront의 로그를 봤을때는 pod1에서 curl을 날렸을때 pod가 위치한 노드의 IP가 SNAT 되어 패킷을 확인할수 있었다. 
  • 반면 외부의 서비스에서 ingress의 도메인을 통해 http 요청을 보냈을때는 hostport의 ingress-controller pod를 통해 해당 팟이 위치한 워커노드를 통해 직접적으로 외부 네트워크 트래픽을 받기 위해 해당 노드의 특정 포트에 바인딩 됩니다.이러한 동작방식은 로드밸런서가 없는 환경이나 내부적으로 노드 IP 주소를 통해 외부 트래픽을 처리하고자 할 때 사용합니다. 

실습 환경 준비

  • 클러스터 구성 
#
cat <<EOT> kind-svc-2w.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
featureGates:
  "InPlacePodVerticalScaling": true  #실행 중인 파드의 리소스 요청 및 제한을 변경할 수 있게 합니다.
  "MultiCIDRServiceAllocator": true  #서비스에 대해 여러 CIDR 블록을 사용할 수 있게 합니다.
nodes:
- role: control-plane
  labels:
    mynode: control-plane
    topology.kubernetes.io/zone: ap-northeast-2a
  extraPortMappings:  #컨테이너 포트를 호스트 포트에 매핑하여 클러스터 외부에서 서비스에 접근할 수 있도록 합니다.
  - containerPort: 30000
    hostPort: 30000
  - containerPort: 30001
    hostPort: 30001
  - containerPort: 30002
    hostPort: 30002
  - containerPort: 30003
    hostPort: 30003
  - containerPort: 30004
    hostPort: 30004
  kubeadmConfigPatches:
  - |
    kind: ClusterConfiguration
    apiServer:
      extraArgs:  #API 서버에 추가 인수를 제공
        runtime-config: api/all=true  #모든 API 버전을 활성화
    controllerManager:
      extraArgs:
        bind-address: 0.0.0.0
    etcd:
      local:
        extraArgs:
          listen-metrics-urls: http://0.0.0.0:2381
    scheduler:
      extraArgs:
        bind-address: 0.0.0.0
  - |
    kind: KubeProxyConfiguration
    metricsBindAddress: 0.0.0.0
- role: worker
  labels:
    mynode: worker1
    topology.kubernetes.io/zone: ap-northeast-2a
- role: worker
  labels:
    mynode: worker2
    topology.kubernetes.io/zone: ap-northeast-2b
- role: worker
  labels:
    mynode: worker3
    topology.kubernetes.io/zone: ap-northeast-2c
networking:
  podSubnet: 10.10.0.0/16  #파드 IP를 위한 CIDR 범위를 정의합니다. 파드는 이 범위에서 IP를 할당받습니다.
  serviceSubnet: 10.200.1.0/24  #서비스 IP를 위한 CIDR 범위를 정의합니다. 서비스는 이 범위에서 IP를 할당받습니다.
EOT

# k8s 클러스터 설치
kind create cluster --config kind-svc-2w.yaml --name myk8s --image kindest/node:v1.31.0
docker ps

# 노드에 기본 툴 설치
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bsdmainutils bridge-utils net-tools dnsutils ipset ipvsadm nfacct tcpdump ngrep iputils-ping arping git vim arp-scan -y'
for i in worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i sh -c 'apt update && apt install tree psmisc lsof wget bsdmainutils bridge-utils net-tools dnsutils ipset ipvsadm nfacct tcpdump ngrep iputils-ping arping -y'; echo; done


# k8s v1.31.0 버전 확인
kubectl get node

# 노드 labels 확인
kubectl get nodes -o jsonpath="{.items[*].metadata.labels}" | jq

# kind network 중 컨테이너(노드) IP(대역) 확인
docker ps -q | xargs docker inspect --format '{{.Name}} {{.NetworkSettings.Networks.kind.IPAddress}}'

# 파드CIDR 과 Service 대역 확인 : CNI는 kindnet 사용
kubectl get cm -n kube-system kubeadm-config -oyaml | grep -i subnet
kubectl cluster-info dump | grep -m 2 -E "cluster-cidr|service-cluster-ip-range"

# MultiCIDRServiceAllocator : https://kubernetes.io/docs/tasks/network/extend-service-ip-ranges/
kubectl get servicecidr
NAME         CIDRS           AGE
kubernetes   10.200.1.0/24   2m13s

# 노드마다 할당된 dedicated subnet (podCIDR) 확인
kubectl get nodes -o jsonpath="{.items[*].spec.podCIDR}"
10.10.0.0/24 10.10.4.0/24 10.10.3.0/24 10.10.1.0/24

# kube-proxy configmap 확인
kubectl describe cm -n kube-system kube-proxy
...
mode: iptables
iptables:
  localhostNodePorts: null
  masqueradeAll: false
  masqueradeBit: null
  minSyncPeriod: 1s
  syncPeriod: 0s
...


# 노드 별 네트워트 정보 확인 : CNI는 kindnet 사용
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i cat /etc/cni/net.d/10-kindnet.conflist; echo; done
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i ip -c route; echo; done
for i in control-plane worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i ip -c addr; echo; done

# iptables 정보 확인
for i in filter nat mangle raw ; do echo ">> IPTables Type : $i <<"; docker exec -it myk8s-control-plane  iptables -t $i -S ; echo; done
for i in filter nat mangle raw ; do echo ">> IPTables Type : $i <<"; docker exec -it myk8s-worker  iptables -t $i -S ; echo; done
for i in filter nat mangle raw ; do echo ">> IPTables Type : $i <<"; docker exec -it myk8s-worker2 iptables -t $i -S ; echo; done
for i in filter nat mangle raw ; do echo ">> IPTables Type : $i <<"; docker exec -it myk8s-worker3 iptables -t $i -S ; echo; done

# 각 노드 bash 접속
docker exec -it myk8s-control-plane bash
docker exec -it myk8s-worker bash
docker exec -it myk8s-worker2 bash
docker exec -it myk8s-worker3 bash
----------------------------------------
exit
----------------------------------------

# kind 설치 시 kind 이름의 도커 브리지가 생성된다 : 172.18.0.0/16 대역
docker network ls
docker inspect kind

# arp scan 해두기
docker exec -it myk8s-control-plane arp-scan --interfac=eth0 --localnet

# mypc 컨테이너 기동 : kind 도커 브리지를 사용하고, 컨테이너 IP를 지정 없이 혹은 지정 해서 사용
docker run -d --rm --name mypc --network kind --ip 172.18.0.100 nicolaka/netshoot sleep infinity # IP 지정 실행 시
IP 지정 실행 시 에러 발생 시 아래 처럼 IP 지정 없이 실행
docker run -d --rm --name mypc --network kind nicolaka/netshoot sleep infinity # IP 지정 없이 실행 시
docker ps

# mypc2 컨테이너 기동 : kind 도커 브리지를 사용하고, 컨테이너 IP를 지정 없이 혹은 지정 해서 사용
docker run -d --rm --name mypc2 --network kind --ip 172.18.0.200 nicolaka/netshoot sleep infinity # IP 지정 실행 시
IP 지정 실행 시 에러 발생 시 아래 처럼 IP 지정 없이 실행
docker run -d --rm --name mypc2 --network kind nicolaka/netshoot sleep infinity # IP 지정 없이 실행 시
docker ps

# kind network 중 컨테이너(노드) IP(대역) 확인
docker ps -q | xargs docker inspect --format '{{.Name}} {{.NetworkSettings.Networks.kind.IPAddress}}'


# kube-ops-view 설치
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30000 --set env.TZ="Asia/Seoul" --namespace kube-system

# myk8s-control-plane 배치
kubectl -n kube-system edit deploy kube-ops-view
---
spec:
  ...
  template:
    ...
    spec:
      nodeSelector:
        mynode: control-plane
      tolerations:
      - key: "node-role.kubernetes.io/control-plane"
        operator: "Equal"
        effect: "NoSchedule"
---

# 설치 확인
kubectl -n kube-system get pod -o wide -l app.kubernetes.io/instance=kube-ops-view

# kube-ops-view 접속 URL 확인 (1.5 , 2 배율) : macOS 사용자
echo -e "KUBE-OPS-VIEW URL = http://localhost:30000/#scale=1.5"
echo -e "KUBE-OPS-VIEW URL = http://localhost:30000/#scale=2"

# kube-ops-view 접속 URL 확인 (1.5 , 2 배율) : Windows 사용자
echo -e "KUBE-OPS-VIEW URL = http://192.168.50.10:30000/#scale=1.5"
echo -e "KUBE-OPS-VIEW URL = http://192.168.50.10:30000/#scale=2"

# kube-ops-view 접속 URL 확인 (1.5 , 2 배율) : AWS_EC2 사용자
echo -e "KUBE-OPS-VIEW URL = http://$(curl -s ipinfo.io/ip):30000/#scale=1.5"
echo -e "KUBE-OPS-VIEW URL = http://$(curl -s ipinfo.io/ip):30000/#scale=2"

  • webpod1, webpod2 배포
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: webpod1
  labels:
    app: webpod
spec:
  nodeName: myk8s-worker
  containers:
  - name: container
    image: traefik/whoami
  terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
  name: webpod2
  labels:
    app: webpod
spec:
  nodeName: myk8s-worker2
  containers:
  - name: container
    image: traefik/whoami
  terminationGracePeriodSeconds: 0
EOF

 

 

서비스 - LoadBalancer 

 

요약 : 외부 클라이언트가 '로드밸런서' 접속 시 부하분산 되어 노드 도달 후 iptables 룰목적지 파드와 통신됨

 

  • 노드는 외부에 공개되지 않고 로드밸런서만 외부에 공개되어, 외부 클라이언트는 로드밸랜서에 접속을 할 뿐 내부 노드의 정보를 알 수 없다
  • DNAT 2번 동작 : 첫번째(로드밸런서 접속 후 빠져 나갈때), 두번째(노드의 iptables 룰에서 파드IP 전달 시)
  • 로드밸런서부하분산하여 파드가 존재하는 노드들에게 전달한다, iptables 룰에서는 자신의 노드에 있는 파드만 연결한다 (externalTrafficPolicy: local)
  • 외부 클라이언트 IP 보존(유지) : AWS NLB 는 타켓인스턴스일 경우 클라이언트 IP를 유지, iptables 룰 경우도 externalTrafficPolicy 로 클라이언트 IP를 보존
  • 쿠버네티스는 Service(LB Type) API 만 정의하고 실제 구현은 add-on 에 맡김

MetalLB

 

  • MetalLB 설치 시 프로메테우스 설치 시 생성되는 CRD가 없다는 내용이다. 프로메테우스를 사용하지 않을것이면 에러 발생은 무시해도 된다.
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/refs/heads/main/config/manifests/metallb-native-prometheus.yaml


# 에러발생
resource mapping not found for name: "controller-monitor" namespace: "metallb-system" from "https://raw.githubusercontent.com/metallb/metallb/refs/heads/main/config/manifests/metallb-native-prometheus.yaml": no matches for kind "ServiceMonitor" in version "monitoring.coreos.com/v1"
ensure CRDs are installed first
resource mapping not found for name: "speaker-monitor" namespace: "metallb-system" from "https://raw.githubusercontent.com/metallb/metallb/refs/heads/main/config/manifests/metallb-native-prometheus.yaml": no matches for kind "ServiceMonitor" in version "monitoring.coreos.com/v1"
  • 재빠르게 프로메테우스 스택을 설치하고 왔다.
#
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts

# 파라미터 파일 생성
cat <<EOT > monitor-values.yaml
prometheus:
  service:
    type: NodePort
    nodePort: 30001

  prometheusSpec:
    podMonitorSelectorNilUsesHelmValues: false
    serviceMonitorSelectorNilUsesHelmValues: false
    nodeSelector:
      mynode: control-plane
    tolerations:
    - key: "node-role.kubernetes.io/control-plane"
      operator: "Equal"
      effect: "NoSchedule"


grafana:
  defaultDashboardsTimezone: Asia/Seoul
  adminPassword: kans1234

  service:
    type: NodePort
    nodePort: 30002
  nodeSelector:
    mynode: control-plane
  tolerations:
  - key: "node-role.kubernetes.io/control-plane"
    operator: "Equal"
    effect: "NoSchedule"

  sidecar:
    dashboards:
      enabled: true
  dashboards:
    default:
      custom-dashboard:
        gnetId: 20162  # MetalLB 대시보드 ID
        datasource: Prometheus  # 사용할 데이터소스 이름을 명시
        revision: 1    # 대시보드의 버전

defaultRules:
  create: false
alertmanager:
  enabled: false

EOT

# 배포
kubectl create ns monitoring
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 62.3.0 -f monitor-values.yaml --namespace monitoring

# 확인
helm list -n monitoring

# Grafana 접속 계정 : admin / kans1234 : macOS 사용자
echo -e "Prometheus URL = http://localhost:30001"
echo -e "Grafana URL = http://localhost:30002"

# Grafana 접속 계정 : admin / kans1234 : Windows 사용자
echo -e "Prometheus URL = http://192.168.50.10:30001"
echo -e "Grafana URL = http://192.168.50.10:30002"

# Grafana 접속 계정 : admin / kans1234 : AWS_EC2 사용자
echo -e "Prometheus URL = http://$(curl -s ipinfo.io/ip):30001"
echo -e "Grafana URL = http://$(curl -s ipinfo.io/ip):30002"


# (참고) helm 삭제
helm uninstall -n monitoring kube-prometheus-stack

  • 정상적으로 설치 됨

  • ippool을 설정해야 한다.( 이는 로드밸런서의 IP 대역을 정하는것이다.)
    • 클러스터 내 서비스에 할당할 수 있는 IP 주소 범위를 정의합니다
    • MetalLB는 서비스를 위한 외부 IP 주소를 관리하고, 서비스가 생성될 때 해당 IP 주소를 동적으로 할당할 수 있습니다
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: my-ippool
  namespace: metallb-system
spec:
  addresses:
  - 172.18.255.200-172.18.255.250
EOF

 

  • L2Advertisement 생성
    • 설정한 IPpool을 기반으로 Layer2 모드로 LoadBalancer IP 사용 허용 
    • Kubernetes 클러스터 내의 서비스가 외부 네트워크에 IP 주소를 광고하는 방식을 정의
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: my-l2-advertise
  namespace: metallb-system
spec:
  ipAddressPools:
  - my-ippool
EOF
  • 3개의 서비스 생성
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: svc1	#서비스의 이름
spec:
  ports:
    - name: svc1-webport
      port: 80	#서비스가 외부에 노출할 포트
      targetPort: 80	#클러스터 내 파드의 실제 포트입니다. 이 포트로 트래픽이 전달됩니다.
  selector:
    app: webpod	#서비스가 트래픽을 전달할 파드를 선택하는 기준입니다. app: webpod 레이블을 가진 파드가 이 서비스의 대상입니다.
  type: LoadBalancer	#서비스의 유형을 지정
---
apiVersion: v1
kind: Service
metadata:
  name: svc2
spec:
  ports:
    - name: svc2-webport
      port: 80
      targetPort: 80
  selector:
    app: webpod
  type: LoadBalancer
---
apiVersion: v1
kind: Service
metadata:
  name: svc3
spec:
  ports:
    - name: svc3-webport
      port: 80
      targetPort: 80
  selector:
    app: webpod
  type: LoadBalancer
EOF
  • 서비스가 생성되면 EXTERNAL-IP 가 생성됨
  • 4년전 에 처음 쿠버네티스를 공부할때 ip pool에 어떤 IP 대역을 줘야하는지 몰라서 MetalLB 설치를 못했었는데.. 이는 구성되지 않은 IP를 사용하지 못하는것 때문이었던거 같습니다. 

MetalLB는 OpenStack VMware에서 작동하나요?

기본적으론 사용할수 있습니다.  하지만  OpenStack 및 VMware 은 스푸핑 방지 보호 기능을 활성화하여 VM이 OpenStack 제어 평면에서 구성되지 않은 IP(예: MetalLB의 LoadBalancer IP)를 사용하지 못하도록 합니다. openstack port set –allowed-address 를 참조하세요 .

k get svc -o wide
NAME         TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)        AGE   SELECTOR
kubernetes   ClusterIP      10.200.1.1     <none>           443/TCP        72m   <none>
svc1         LoadBalancer   10.200.1.175   172.18.255.200   80:31012/TCP   54s   app=webpod
svc2         LoadBalancer   10.200.1.145   172.18.255.201   80:32685/TCP   54s   app=webpod
svc3         LoadBalancer   10.200.1.153   172.18.255.202   80:31140/TCP   54s   app=webpod
  • 통신테스트
$ SVC1EXIP=$(kubectl get svc svc1 -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
$ SVC2EXIP=$(kubectl get svc svc2 -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
$ SVC3EXIP=$(kubectl get svc svc3 -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
$ echo $SVC1EXIP $SVC2EXIP $SVC3EXIP
 172.18.0.200 172.18.0.201 172.18.0.202

$ docker exec -it mypc ip -c neigh

172.18.0.2 dev eth0 lladdr 02:42:ac:12:00:02 STALE
172.18.0.3 dev eth0 lladdr 02:42:ac:12:00:03 STALE
172.18.0.4 dev eth0 lladdr 02:42:ac:12:00:04 STALE
172.18.0.5 dev eth0 lladdr 02:42:ac:12:00:05 STALE
172.18.0.1 dev eth0 lladdr 02:42:ee:00:e3:e2 STALE


$ for i in 172.18.0.2 172.18.0.3 172.18.0.4 172.18.0.5; do docker exec -it mypc ping -c 1 -w 1 -W 1 $i; done

PING 172.18.0.2 (172.18.0.2) 56(84) bytes of data.
64 bytes from 172.18.0.2: icmp_seq=1 ttl=64 time=0.181 ms

--- 172.18.0.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.181/0.181/0.181/0.000 ms
PING 172.18.0.3 (172.18.0.3) 56(84) bytes of data.
64 bytes from 172.18.0.3: icmp_seq=1 ttl=64 time=0.136 ms

--- 172.18.0.3 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.136/0.136/0.136/0.000 ms
PING 172.18.0.4 (172.18.0.4) 56(84) bytes of data.
64 bytes from 172.18.0.4: icmp_seq=1 ttl=64 time=0.154 ms

--- 172.18.0.4 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.154/0.154/0.154/0.000 ms
PING 172.18.0.5 (172.18.0.5) 56(84) bytes of data.
64 bytes from 172.18.0.5: icmp_seq=1 ttl=64 time=0.170 ms

--- 172.18.0.5 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.170/0.170/0.170/0.000 ms
  • 최초 통신이 나간뒤 부터는 다음과 같이 arp 를 통해 목적지의 mac 어드레스를 브로드캐스팅으로 얻었기 때문에 정상적인 서비스 접속이 가능하다.

  • 서비스간 통신 확인 
$ docker exec -it mypc curl -s $SVC1EXIP
Hostname: webpod2
IP: 127.0.0.1
IP: ::1
IP: 10.10.1.2
IP: fe80::a483:c1ff:fe75:98d6
RemoteAddr: 10.10.1.1:31148
GET / HTTP/1.1
Host: 172.18.0.200
User-Agent: curl/8.7.1
Accept: */*

$ docker exec -it mypc curl -s $SVC1EXIP | grep Hostname
Hostname: webpod2


$ for i in $SVC1EXIP $SVC2EXIP $SVC3EXIP; do docker exec -it mypc curl -s $i | grep Hostname ; done
Hostname: webpod2
Hostname: webpod1
Hostname: webpod2

$ for i in $SVC1EXIP $SVC2EXIP $SVC3EXIP; do echo ">> Access Service External-IP : $i <<" ; docker exec -it mypc curl -s $i | grep Hostname ; echo ; done
>> Access Service External-IP : 172.18.0.200 <<
Hostname: webpod1

>> Access Service External-IP : 172.18.0.201 <<
Hostname: webpod1

>> Access Service External-IP : 172.18.0.202 <<
Hostname: webpod1