You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
a2a-echo라고만 물었는데 전체 이름으로 바뀌어 답이 왔습니다. 그리고 그 주소가 Service의 ClusterIP와 같습니다.
$ kubectl get svc a2a-echo -o jsonpath='{.spec.clusterIP}'
10.103.103.158
정리하면 이런 순서입니다.
flowchart LR
P["Pod 안의 앱<br/>backend-service 로 부름"]
D["DNS Service<br/>10.96.0.10"]
N["backend-service<br/>.default.svc.cluster.local"]
C["ClusterIP<br/>10.x.x.x"]
S["backend Pod<br/>둘 중 하나"]
P -->|"1. 이 이름 어디야"| D
D -->|"2. search 목록을 붙여 전체 이름으로"| N
N -->|"3. 이 주소야"| C
P -->|"4. 그 주소로 연결"| C
C -->|"5. 뒤의 Pod 하나로"| S
class D dns
classDef dns fill:#854d0e,stroke:#fbbf24,color:#ffffff
Loading
이름의 전체 형태는 서비스이름.네임스페이스.svc.cluster.local입니다. 같은 네임스페이스 안이면 앞부분만 써도 되고, 다른 네임스페이스의 것을 부르려면 backend-service.other-namespace처럼 더 적으면 됩니다.
왜 이렇게 해 두었나. Pod IP는 바뀌지만 이 이름은 안 바뀝니다. 그래서 nginx 설정에 IP를 적지 않고 이름을 적어 둘 수 있습니다. 수업에서 "이름으로 부른다"고 한 것의 실제 내용이 이것입니다.
타입은 네 개입니다
수업에서는 둘만 봤습니다. 나머지 둘도 있습니다.
타입
하는 일
밖에서 접근
ClusterIP
클러스터 안에서만 쓰는 주소를 준다. 타입을 안 적으면 이것
안 됩니다
NodePort
위에 더해 모든 노드의 같은 포트를 연다
노드 IP로 됩니다
LoadBalancer
위에 더해 클라우드 로드밸런서를 붙인다
외부 IP로 됩니다
ExternalName
주소를 만들지 않고 다른 이름으로 넘긴다
해당 없음
앞의 세 개는 층층이 쌓입니다. 문서의 서술입니다.
The type field in the Service API is designed as nested functionality - each level adds to the previous.
(Service의 type 필드는 층층이 쌓이도록 설계되어 있습니다. 각 단계가 앞 단계에 더해집니다.)
쌓인다는 것은 바깥이 안을 품는다는 뜻입니다.
flowchart TB
subgraph LB["LoadBalancer = 안의 것 전부 + 외부 로드밸런서"]
subgraph NP["NodePort = 안의 것 + 노드마다 열리는 포트"]
CI["ClusterIP<br/>클러스터 안에서 쓰는 주소"]
end
end
class CI base
classDef base fill:#166534,stroke:#4ade80,color:#ffffff
Loading
가장 안쪽이 ClusterIP입니다. NodePort는 그것을 그대로 두고 노드의 포트를 하나 더 얹은 것이고, LoadBalancer는 거기에 외부 로드밸런서를 또 얹은 것입니다.
주의할 것이 하나 있습니다. 이 그림은 기능이 쌓이는 순서이지 요청이 흐르는 순서가 아닙니다. 요청은 반대 방향으로 흐릅니다. 밖에서 로드밸런서로 들어와 노드 포트를 지나 ClusterIP를 거쳐 Pod에 닿습니다. 흐르는 그림은 #28에 있습니다.
그래서 LoadBalancer를 만들면 ClusterIP도 NodePort도 같이 생깁니다. kubectl get svc frontend-service에 CLUSTER-IP 값이 찍혀 있는 것이 그 증거입니다. 외부 공개용인데 안쪽 주소도 가지고 있습니다. 자세한 것은 #28에 있습니다.
그럼 NodePort와 LoadBalancer는 무엇이 다른가
쌓인다고만 하면 둘이 같은 것처럼 보입니다. 실제로는 앞에 무엇이 서느냐가 다릅니다.
기본 동작은 문서에 이렇게 적혀 있습니다.
To implement a Service of type: LoadBalancer, Kubernetes typically starts off by making the changes that are equivalent to you requesting a Service of type: NodePort. The cloud-controller-manager component then configures the external load balancer to forward traffic to that assigned node port.
(type: LoadBalancer를 구현할 때 쿠버네티스는 보통 type: NodePort를 요청한 것과 같은 변경을 먼저 합니다. 그다음 cloud-controller-manager가 외부 로드밸런서를 그 노드 포트로 트래픽을 넘기도록 설정합니다.)
즉 기본 동작에서는 로드밸런서가 노드 포트로 보냅니다. 앞의 포함 그림이 트래픽 관점으로도 맞는 경우입니다. 차이는 그 앞에 무엇이 하나 더 있느냐입니다.
NodePort
LoadBalancer
밖에서 쓸 주소
노드 IP를 직접 알아야 합니다
클라우드가 IP 하나를 줍니다
포트 번호
30000에서 32767 사이
원하는 번호를 port에 적습니다
노드가 죽으면
그 노드 IP로 오던 요청은 실패합니다
로드밸런서가 살아 있는 노드로 보냅니다
노드를 늘리면
새 IP를 사용자에게 다시 알려야 합니다
로드밸런서가 알아서 포함시킵니다
비용
없습니다
클라우드 로드밸런서 요금이 붙습니다
정리하면 NodePort는 문을 열어 두기만 하고 그 뒤는 쓰는 쪽이 알아서 해야 합니다. 노드가 셋이면 IP도 셋이고 어느 것으로 갈지 사용자가 정해야 합니다. LoadBalancer는 그 앞에 창구를 하나 세워서 그 문제를 대신 처리합니다.
예외도 있습니다. 로드밸런서가 노드 포트를 거치지 않고 Pod로 바로 보내는 구현이 있습니다. 그럴 때는 노드 포트 할당을 끌 수 있습니다.
You can optionally disable node port allocation for a Service of type: LoadBalancer ... This should only be used for load balancer implementations that route traffic directly to pods as opposed to using node ports.
(type: LoadBalancer에서 노드 포트 할당을 끌 수 있습니다. 노드 포트를 쓰지 않고 Pod로 바로 보내는 로드밸런서 구현에서만 써야 합니다.)
그래서 정확히 말하면 이렇습니다. 가지고 있는 것은 항상 쌓여 있고, 그중 실제로 지나가는 경로는 로드밸런서 구현에 따라 다릅니다. 기본값에서는 노드 포트를 지나갑니다.
온프레미스에서는 또 다릅니다
위 설명은 클라우드를 전제로 한 것입니다. 클러스터 밖에 별도의 로드밸런서가 있고 그것이 노드로 보낸다는 그림입니다. 회사 서버실에 직접 올린 클러스터에는 그런 장비가 없습니다.
그래서 그 역할을 대신하는 것을 따로 설치합니다. 많이 쓰는 것이 MetalLB입니다. 제 실습 클러스터에 그것이 올라가 있어서 실제로 어떻게 되어 있는지 꺼내 봤습니다.
노드 IP LoadBalancer Service가 받은 IP
cp-k8s 192.168.2.60 192.168.2.100
w1-k8s 192.168.2.61 192.168.2.101
w2-k8s 192.168.2.62 192.168.2.102
외부 IP가 노드와 같은 대역입니다. 클라우드에서 받는 공인 IP와 성격이 다릅니다. 밖에 있는 장비가 준 주소가 아니라 같은 네트워크 안에서 비어 있는 주소를 가져다 쓴 것입니다.
동작 방식도 다릅니다. MetalLB 문서의 서술입니다.
In layer 2 mode, one node assumes the responsibility of advertising a service to the local network. ... MetalLB responds to ARP requests for IPv4 services.
(레이어 2 모드에서는 노드 하나가 그 서비스를 로컬 네트워크에 알리는 역할을 맡습니다. MetalLB가 그 IP에 대한 ARP 요청에 응답합니다.)
In that sense, layer 2 does not implement a load balancer. Rather, it implements a failover mechanism.
(그런 점에서 레이어 2 모드는 로드밸런서를 구현하는 것이 아닙니다. 장애 대비 전환을 구현하는 것입니다.)
All traffic for a service IP goes to one node. From there, kube-proxy spreads the traffic to all the service's pods.
(그 서비스 IP로 오는 모든 트래픽은 한 노드로 갑니다. 거기서 kube-proxy가 서비스의 모든 Pod로 나눠 보냅니다.)
정리하면 이렇습니다. 노드 하나가 그 IP를 자기 것처럼 응답해서 트래픽을 받고, 나누는 일은 그 노드 안에서 일어납니다. 로드밸런서가 밖에 서 있는 것이 아니라 노드 안에 있는 셈입니다. 그 노드가 죽으면 다른 노드가 그 IP를 넘겨받습니다.
그림으로 놓으면 이렇습니다. 먼저 클라우드입니다.
flowchart LR
U["브라우저"] -->|"공인 IP : 80"| LB["클라우드 로드밸런서<br/>클러스터 밖에 있음"]
LB --> N1["노드 1"]
LB --> N2["노드 2"]
LB --> N3["노드 3"]
N1 --> P["Pod들"]
N2 --> P
N3 --> P
class LB out
classDef out fill:#854d0e,stroke:#fbbf24,color:#ffffff
Loading
노란 칸이 클러스터 밖에 있습니다. 거기서 이미 여러 노드로 나뉩니다.
다음은 온프레미스에서 MetalLB 레이어 2 모드입니다.
flowchart LR
U["브라우저"] -->|"192.168.2.100 : 80"| N1["노드 1<br/>이 IP에 ARP로 응답<br/>모든 트래픽을 여기서 받음"]
N1 -->|"kube-proxy가 나눔"| P["Pod들"]
N2["노드 2<br/>대기"] -.->|"노드 1이 죽으면<br/>이 IP를 넘겨받음"| N1
N3["노드 3<br/>대기"] -.-> N1
class N1 out
classDef out fill:#854d0e,stroke:#fbbf24,color:#ffffff
Loading
노란 칸이 노드 안으로 들어왔습니다. 밖에 따로 서 있는 것이 없습니다. 그리고 나뉘는 지점이 그 노드 안입니다. 점선은 장애가 났을 때 IP가 옮겨 가는 것을 뜻하고 평소에는 트래픽이 흐르지 않습니다.
두 그림의 차이는 두 가지입니다. 나뉘는 곳이 클러스터 밖이냐 노드 안이냐, 그리고 평소에 일하는 노드가 여럿이냐 하나냐입니다.
클라우드
온프레미스 (MetalLB 레이어 2)
로드밸런서가 있는 곳
클러스터 밖
노드 안
외부 IP
클라우드가 주는 공인 IP
노드와 같은 대역의 남는 IP
트래픽을 받는 노드
여러 노드에 나눠 보냄
한 노드가 다 받음
나누는 일은 어디서
로드밸런서가
받은 노드의 kube-proxy가
노드가 죽으면
로드밸런서가 다른 노드로
다른 노드가 그 IP를 넘겨받음
MetalLB에는 라우터에 경로를 알리는 BGP 모드도 있고 그때는 여러 노드로 나뉩니다. 다만 그 경우에도 로드밸런서 장비가 따로 서는 것은 아닙니다.
그래서 type: LoadBalancer는 "무엇을 원하는가"를 적는 것이지 "어떻게 할 것인가"를 적는 것이 아닙니다. 밖에서 접근할 주소 하나를 달라는 선언이고, 그것을 어떻게 만들지는 클러스터가 있는 환경이 정합니다. GKE에서는 구글이 만들고 회사 서버실에서는 MetalLB가 만듭니다. yaml은 같습니다.
ExternalName만 성격이 다릅니다. 이건 주소를 만들지 않습니다. 문서의 서술입니다.
Maps the Service to the contents of the externalName field ... The mapping configures your cluster's DNS server to return a CNAME record with that external hostname value. No proxying of any kind is set up.
(externalName 필드의 값으로 연결합니다. 클러스터 DNS가 그 외부 호스트 이름을 담은 CNAME 레코드를 돌려주도록 설정합니다. 어떤 종류의 중계도 하지 않습니다.)
쓰임새는 이렇습니다. 클러스터 밖에 있는 것을 클러스터 안 이름으로 부르고 싶을 때입니다. 예를 들어 데이터베이스가 클라우드의 관리형 서비스로 밖에 있는데, 앱 코드에는 db라고만 적고 싶은 경우입니다.
앱은 db라고 부르고 DNS가 실제 주소로 바꿔 줍니다. 나중에 데이터베이스를 클러스터 안으로 옮기면 이 Service만 ClusterIP로 바꾸면 되고 앱 코드는 안 고쳐도 됩니다.
우리 앱은 어디에 해당하나
frontend-service LoadBalancer 외부 IP 있음 브라우저가 부름
backend-service ClusterIP 외부 IP 없음 frontend의 nginx가 부름
kubernetes ClusterIP 외부 IP 없음 Pod가 API 서버를 부를 때
kube-dns ClusterIP 외부 IP 없음 모든 Pod가 이름을 물어볼 때
앞의 둘은 수업에서 만든 것이고 뒤의 둘은 클러스터에 원래 있는 것입니다. kubernetes는 #27에서 본 그 주소이고, kube-dns가 방금 본 이름 풀이를 맡습니다.
kubectl get svc -A를 치면 이 넷이 다 보입니다. 넷 중 셋이 ClusterIP입니다. 밖에 열 이유가 없으면 열지 않는 것이 기본이라는 뜻이기도 합니다.
직접 확인해 보시기 바랍니다
# 클러스터의 모든 Service와 타입
kubectl get svc -A
# LoadBalancer인데 ClusterIP도 있는지
kubectl get svc frontend-service -o jsonpath='{.spec.type}{" "}{.spec.clusterIP}'# Pod 안에서 이름이 어떻게 풀리는지
kubectl exec -it deploy/frontend -- cat /etc/resolv.conf
kubectl exec -it deploy/frontend -- getent hosts backend-service
# 그 결과가 Service의 ClusterIP와 같은지
kubectl get svc backend-service -o jsonpath='{.spec.clusterIP}'
마지막 두 줄의 값이 같으면 이름이 주소로 풀린 것입니다.
타입 선택 기준은 decision-guides/service-type.md에도 있습니다. 그쪽은 수업에서 쓰는 둘을 다루고 이 글은 나머지 둘을 더한 것입니다.
4회차에 이런 질문이 나왔습니다.
수업에서는 두 가지를 다뤘습니다. Pod에 변하지 않는 이름을 준다는 것, 그리고 뒤에 있는 Pod들에 요청을 나눠 보낸다는 것입니다. 여기서는 그 위에 두 가지를 더합니다. 이름이 실제로 어떻게 주소가 되는지와 타입이 사실 네 개라는 것입니다.
Service가 하는 일은 셋입니다
backend-service라는 이름. Pod가 바뀌어도 그대로입니다세 번째는 수업에서
kubectl describe svc backend-service로 보신Endpoints입니다. Pod가 죽고 새로 생기면 그 목록이 알아서 바뀝니다.앞의 두 가지가 이 글에서 더 볼 것입니다.
이름은 이렇게 주소가 됩니다
frontend의 nginx 설정에backend-service라고만 적혀 있습니다. 그 짧은 이름이 어떻게 실제 주소가 되는지 보겠습니다. 아래는 제 실습 클러스터에서 Pod 안을 직접 들여다본 것입니다.Pod 안의
/etc/resolv.conf입니다. 이름을 어디에 물어보라고 적힌 파일입니다.nameserver가10.96.0.10입니다. 이게 무엇인지 보면 이렇습니다.클러스터 안의 DNS도 Service입니다. Pod가 이름을 물어볼 곳이 또 하나의 Service인 셈입니다.
search줄이 나머지를 설명합니다. 짧은 이름으로 물어보면 이 목록을 차례로 붙여서 다시 시도합니다. 실제로 조회해 보면 이렇게 나옵니다.a2a-echo라고만 물었는데 전체 이름으로 바뀌어 답이 왔습니다. 그리고 그 주소가 Service의 ClusterIP와 같습니다.정리하면 이런 순서입니다.
이름의 전체 형태는
서비스이름.네임스페이스.svc.cluster.local입니다. 같은 네임스페이스 안이면 앞부분만 써도 되고, 다른 네임스페이스의 것을 부르려면backend-service.other-namespace처럼 더 적으면 됩니다.왜 이렇게 해 두었나. Pod IP는 바뀌지만 이 이름은 안 바뀝니다. 그래서 nginx 설정에 IP를 적지 않고 이름을 적어 둘 수 있습니다. 수업에서 "이름으로 부른다"고 한 것의 실제 내용이 이것입니다.
타입은 네 개입니다
수업에서는 둘만 봤습니다. 나머지 둘도 있습니다.
ClusterIPNodePortLoadBalancerExternalName앞의 세 개는 층층이 쌓입니다. 문서의 서술입니다.
쌓인다는 것은 바깥이 안을 품는다는 뜻입니다.
flowchart TB subgraph LB["LoadBalancer = 안의 것 전부 + 외부 로드밸런서"] subgraph NP["NodePort = 안의 것 + 노드마다 열리는 포트"] CI["ClusterIP<br/>클러스터 안에서 쓰는 주소"] end end class CI base classDef base fill:#166534,stroke:#4ade80,color:#ffffff가장 안쪽이
ClusterIP입니다.NodePort는 그것을 그대로 두고 노드의 포트를 하나 더 얹은 것이고,LoadBalancer는 거기에 외부 로드밸런서를 또 얹은 것입니다.주의할 것이 하나 있습니다. 이 그림은 기능이 쌓이는 순서이지 요청이 흐르는 순서가 아닙니다. 요청은 반대 방향으로 흐릅니다. 밖에서 로드밸런서로 들어와 노드 포트를 지나 ClusterIP를 거쳐 Pod에 닿습니다. 흐르는 그림은 #28에 있습니다.
그래서
LoadBalancer를 만들면 ClusterIP도 NodePort도 같이 생깁니다.kubectl get svc frontend-service에CLUSTER-IP값이 찍혀 있는 것이 그 증거입니다. 외부 공개용인데 안쪽 주소도 가지고 있습니다. 자세한 것은 #28에 있습니다.그럼 NodePort와 LoadBalancer는 무엇이 다른가
쌓인다고만 하면 둘이 같은 것처럼 보입니다. 실제로는 앞에 무엇이 서느냐가 다릅니다.
기본 동작은 문서에 이렇게 적혀 있습니다.
즉 기본 동작에서는 로드밸런서가 노드 포트로 보냅니다. 앞의 포함 그림이 트래픽 관점으로도 맞는 경우입니다. 차이는 그 앞에 무엇이 하나 더 있느냐입니다.
port에 적습니다정리하면
NodePort는 문을 열어 두기만 하고 그 뒤는 쓰는 쪽이 알아서 해야 합니다. 노드가 셋이면 IP도 셋이고 어느 것으로 갈지 사용자가 정해야 합니다.LoadBalancer는 그 앞에 창구를 하나 세워서 그 문제를 대신 처리합니다.예외도 있습니다. 로드밸런서가 노드 포트를 거치지 않고 Pod로 바로 보내는 구현이 있습니다. 그럴 때는 노드 포트 할당을 끌 수 있습니다.
그래서 정확히 말하면 이렇습니다. 가지고 있는 것은 항상 쌓여 있고, 그중 실제로 지나가는 경로는 로드밸런서 구현에 따라 다릅니다. 기본값에서는 노드 포트를 지나갑니다.
온프레미스에서는 또 다릅니다
위 설명은 클라우드를 전제로 한 것입니다. 클러스터 밖에 별도의 로드밸런서가 있고 그것이 노드로 보낸다는 그림입니다. 회사 서버실에 직접 올린 클러스터에는 그런 장비가 없습니다.
그래서 그 역할을 대신하는 것을 따로 설치합니다. 많이 쓰는 것이 MetalLB입니다. 제 실습 클러스터에 그것이 올라가 있어서 실제로 어떻게 되어 있는지 꺼내 봤습니다.
외부 IP가 노드와 같은 대역입니다. 클라우드에서 받는 공인 IP와 성격이 다릅니다. 밖에 있는 장비가 준 주소가 아니라 같은 네트워크 안에서 비어 있는 주소를 가져다 쓴 것입니다.
동작 방식도 다릅니다. MetalLB 문서의 서술입니다.
정리하면 이렇습니다. 노드 하나가 그 IP를 자기 것처럼 응답해서 트래픽을 받고, 나누는 일은 그 노드 안에서 일어납니다. 로드밸런서가 밖에 서 있는 것이 아니라 노드 안에 있는 셈입니다. 그 노드가 죽으면 다른 노드가 그 IP를 넘겨받습니다.
그림으로 놓으면 이렇습니다. 먼저 클라우드입니다.
노란 칸이 클러스터 밖에 있습니다. 거기서 이미 여러 노드로 나뉩니다.
다음은 온프레미스에서 MetalLB 레이어 2 모드입니다.
노란 칸이 노드 안으로 들어왔습니다. 밖에 따로 서 있는 것이 없습니다. 그리고 나뉘는 지점이 그 노드 안입니다. 점선은 장애가 났을 때 IP가 옮겨 가는 것을 뜻하고 평소에는 트래픽이 흐르지 않습니다.
두 그림의 차이는 두 가지입니다. 나뉘는 곳이 클러스터 밖이냐 노드 안이냐, 그리고 평소에 일하는 노드가 여럿이냐 하나냐입니다.
MetalLB에는 라우터에 경로를 알리는 BGP 모드도 있고 그때는 여러 노드로 나뉩니다. 다만 그 경우에도 로드밸런서 장비가 따로 서는 것은 아닙니다.
그래서
type: LoadBalancer는 "무엇을 원하는가"를 적는 것이지 "어떻게 할 것인가"를 적는 것이 아닙니다. 밖에서 접근할 주소 하나를 달라는 선언이고, 그것을 어떻게 만들지는 클러스터가 있는 환경이 정합니다. GKE에서는 구글이 만들고 회사 서버실에서는 MetalLB가 만듭니다. yaml은 같습니다.ExternalName만 성격이 다릅니다. 이건 주소를 만들지 않습니다. 문서의 서술입니다.쓰임새는 이렇습니다. 클러스터 밖에 있는 것을 클러스터 안 이름으로 부르고 싶을 때입니다. 예를 들어 데이터베이스가 클라우드의 관리형 서비스로 밖에 있는데, 앱 코드에는
db라고만 적고 싶은 경우입니다.앱은
db라고 부르고 DNS가 실제 주소로 바꿔 줍니다. 나중에 데이터베이스를 클러스터 안으로 옮기면 이 Service만ClusterIP로 바꾸면 되고 앱 코드는 안 고쳐도 됩니다.우리 앱은 어디에 해당하나
앞의 둘은 수업에서 만든 것이고 뒤의 둘은 클러스터에 원래 있는 것입니다.
kubernetes는 #27에서 본 그 주소이고,kube-dns가 방금 본 이름 풀이를 맡습니다.kubectl get svc -A를 치면 이 넷이 다 보입니다. 넷 중 셋이ClusterIP입니다. 밖에 열 이유가 없으면 열지 않는 것이 기본이라는 뜻이기도 합니다.직접 확인해 보시기 바랍니다
마지막 두 줄의 값이 같으면 이름이 주소로 풀린 것입니다.
타입 선택 기준은
decision-guides/service-type.md에도 있습니다. 그쪽은 수업에서 쓰는 둘을 다루고 이 글은 나머지 둘을 더한 것입니다.이 글은 참고용입니다. 읽지 않아도 수업 진행에는 지장이 없습니다.