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
nodePort가 적혀 있지 않은데도 브라우저에서 그 IP로 그냥 접속됩니다. 지금까지 그렇게 화면을 보셨습니다. 30000번대를 친 적이 없습니다.
AI에게 물어보신 답은 type: NodePort인 경우를 말한 것입니다. 그 경우에는 맞는 말입니다. 다만 타입이 세 가지이고 답이 타입마다 다릅니다.
포트가 세 개인 이유
각각 다른 문입니다. 어디에 있는 문인지가 다릅니다.
필드
어디에 있는 문인가
누가 두드리나
targetPort
Pod의 컨테이너가 열어 둔 문
Service
port
Service 자신의 문
같은 클러스터 안의 다른 Pod, 그리고 로드밸런서
nodePort
노드(서버) 자체의 문
클러스터 밖에서 노드 IP로 직접
flowchart LR
B["브라우저<br/>(클러스터 밖)"]
OP["같은 클러스터의<br/>다른 Pod"]
LB["클라우드 로드밸런서<br/>외부 IP : port"]
NODE["노드 IP : nodePort<br/>30000-32767"]
SVC["Service<br/>ClusterIP : port"]
POD["Pod<br/>: targetPort"]
B -->|"LoadBalancer일 때"| LB
B -->|"NodePort일 때"| NODE
OP -->|"항상 가능"| SVC
LB --> SVC
NODE --> SVC
SVC --> POD
class POD target
classDef target fill:#166534,stroke:#4ade80,color:#ffffff
Loading
port와 targetPort가 왜 따로인가
부르는 쪽이 Pod 사정을 몰라도 되게 하려는 것입니다.
앱이 3001번에서 돌고 있어도 부르는 쪽은 80번으로 부릅니다. 나중에 앱이 포트를 8000번으로 바꿔도 부르는 쪽 코드는 그대로입니다. Service의 targetPort만 고치면 됩니다.
문서의 서술입니다.
A Service can map any incoming port to a targetPort. By default and for convenience, the targetPort is set to the same value as the port field.
(Service는 들어오는 port를 아무 targetPort로나 연결할 수 있습니다. 기본값은 편의상 port와 같은 값입니다.)
우리 frontend-service는 둘 다 80이라 차이가 안 보입니다. 기본값이 같은 값이기 때문입니다. 그래서 왜 두 개인지 이해가 안 되는 것이 자연스럽습니다.
실제로 다른 예를 보시면 바로 잡힙니다. 이건 제 실습용 클러스터의 Service 목록입니다.
NAME TYPE CLUSTER-IP PORT TARGET NODEPORT
kubernetes ClusterIP 10.96.0.1 443 6443 <none>
mcp-a LoadBalancer 10.105.3.14 80 3001 30128
mcp-b LoadBalancer 10.106.17.130 80 8000 30840
mcp-a를 보시면 숫자 세 개가 전부 다릅니다. 밖에서는 80으로 부르고, 노드 IP로 오면 30128이고, 실제 앱은 3001에서 돌고 있습니다. 부르는 쪽은 3001을 몰라도 됩니다.
kubernetes Service도 443에서 6443으로 바뀝니다. 이건 여러분 클러스터에도 있습니다.
한 걸음 더.targetPort에는 숫자 대신 이름을 쓸 수도 있습니다.
ports:
- port: 80targetPort: http-web-svc
Pod가 자기 포트에 http-web-svc라고 이름을 붙여 두면 Service는 그 이름만 알면 됩니다. 번호가 바뀌어도 Service는 안 고쳐도 됩니다. 두 필드를 나눈 목적이 여기서 분명해집니다.
docker -p와 무엇이 다른가
가장 헷갈리시는 부분이라 표로 놓겠습니다.
Docker
쿠버네티스
쓰는 법
docker run -p 8080:3001
Service의 port와 targetPort
왼쪽 숫자가 여는 문
내 컴퓨터(호스트)의 문
Service의 문 (클러스터 안의 가상 주소)
오른쪽 숫자
컨테이너가 여는 문
Pod가 여는 문
대상이 몇 개인가
컨테이너 하나
라벨에 맞는 Pod 전부
밖에서 바로 되나
됩니다. 내 컴퓨터 IP로
타입에 따라 다릅니다
그림으로 놓으면 차이가 분명해집니다. 먼저 Docker입니다.
flowchart LR
DB["브라우저"] -->|"내 컴퓨터 IP : 8080"| DH["호스트의 8080<br/>(-p 왼쪽)"]
DH -->|"연결"| DC["컨테이너의 3001<br/>(-p 오른쪽)"]
class DH host
classDef host fill:#334155,stroke:#94a3b8,color:#ffffff
Loading
기계 한 대 안에서 층이 둘입니다. 호스트의 문과 컨테이너의 문이고, 끝은 컨테이너 하나입니다.
이미지 하나를 빌드해 놓고, 그것을 로컬에서 부를 때와 클라우드에서 부를 때를 나란히 놓으면 이렇습니다. 이 과정에서 실제로 하는 일이라 값을 그대로 썼습니다.
flowchart TB
B["app/frontend 코드를 docker build<br/>이미지 ssf15-frontend:v1 (컨테이너 안은 80)"]
subgraph L["이 이미지를 내 컴퓨터에서 부를 때"]
direction LR
L1["브라우저"] -->|"localhost : 8080"| L2["호스트 8080<br/>docker run -p 8080:80"] --> L3["컨테이너 : 80"]
end
subgraph C["같은 이미지를 클라우드에서 부를 때"]
direction LR
C1["브라우저"] -->|"외부 IP : 80"| C2["로드밸런서"] --> C3["Service<br/>ClusterIP : 80"] --> C4["Pod : 80"]
end
B --- L
L --- C
class B same
class L3 inside
class C4 inside
classDef same fill:#166534,stroke:#4ade80,color:#ffffff
classDef inside fill:#166534,stroke:#4ade80,color:#ffffff
Loading
초록 칸을 보시기 바랍니다. 맨 위의 이미지 하나가 아래 두 줄의 끝에 그대로 놓여 있습니다. 컨테이너 안은 양쪽이 똑같이 80입니다. 내 컴퓨터에서 돌리든 클라우드에 올리든 이미지도 그 안의 포트도 바뀌지 않습니다.
두 줄의 차이는 가운데뿐입니다. 로컬은 -p가 문 하나를 뚫어서 바로 컨테이너로 갑니다. 클라우드는 로드밸런서와 Service가 차례로 놓입니다. 그래서 적을 포트가 늘어난 것이지 컨테이너가 복잡해진 것이 아닙니다.
기계 여러 대에 걸쳐 층이 셋입니다. 노란 칸이 Docker에는 없는 층입니다. 그리고 끝이 Pod 여러 개로 갈라집니다.
두 그림의 차이는 이 두 가지가 전부입니다. 가운데 층이 하나 더 있다는 것, 그리고 끝이 하나가 아니라 여럿이라는 것입니다.
층이 하나 더 있습니다. Docker는 기계 한 대 안에서 호스트와 컨테이너 두 층입니다. 쿠버네티스는 여러 대를 묶어 놓아서 그 사이에 "클러스터 안의 가상 주소"라는 층이 하나 더 있습니다. Pod는 죽고 살아나며 IP가 바뀌는데 그 층 덕분에 부르는 쪽 주소가 안 바뀝니다.
그리고 docker -p의 왼쪽 숫자는 그 기계의 진짜 포트입니다. Service의 port는 진짜 기계의 포트가 아니라 클러스터 안에서만 통하는 가상 주소의 포트입니다. 그래서 그것만으로는 밖에서 안 보이는 것이 맞습니다. 밖에서 들어오게 하는 것은 타입이 정합니다.
타입이 정하는 것
타입
밖에서 접근
무엇으로
ClusterIP
안 됩니다
클러스터 안에서만 ClusterIP:port
NodePort
됩니다
노드IP:nodePort. 기본 범위는 30000에서 32767
LoadBalancer
됩니다
외부IP:port. 클라우드가 만들어 준 주소
k8s/backend-service.yaml이 ClusterIP이고 k8s/frontend-service.yaml이 LoadBalancer입니다. backend는 밖에서 못 부르고 frontend는 부를 수 있습니다. 4회차에 이 둘을 나눈 이유가 이것입니다.
LoadBalancer는 NodePort도 같이 만듭니다. 위 목록에서 mcp-a가 LoadBalancer인데 NODEPORT 열에 30128이 찍혀 있는 것이 그 증거입니다. 적은 적이 없는데 생겼습니다. 문서의 서술입니다.
By default, spec.allocateLoadBalancerNodePorts is true and type LoadBalancer Services will continue to allocate node ports.
(기본값이 true이며 LoadBalancer 타입 Service는 노드 포트를 계속 할당합니다.)
즉 LoadBalancer는 NodePort 위에 외부 로드밸런서를 하나 더 얹은 것입니다. 그래서 외부 IP로도 되고 노드 IP의 30128로도 됩니다. 다만 밖에서 쓰기 편한 쪽은 외부 IP이고 여러분은 그쪽을 쓰고 계십니다.
8080으로 열고 싶으면
질문하신 그 지점입니다. Ingress 없이 IP:8080으로 열고 싶을 때입니다.
LoadBalancer라면 됩니다.port를 8080으로 적으면 외부 IP의 8080으로 접속됩니다.
NodePort라면 안 됩니다. 8080은 기본 범위 30000에서 32767 밖입니다. 범위를 바꾸려면 API 서버의 --service-node-port-range 설정을 고쳐야 하는데, GKE 같은 관리형 서비스에서는 컨트롤 플레인을 클라우드가 관리하므로 학생이 바꿀 수 없습니다.
그래서 정리하면 이렇습니다. Docker처럼 원하는 번호로 열고 싶으면 LoadBalancer를 쓰고 port를 그 번호로 적으면 됩니다. NodePort는 번호를 고를 자유가 좁습니다.
AI 답을 검증하는 사례이기도 합니다
받으신 답이 틀린 것은 아닙니다. NodePort 타입에 대해서는 정확합니다. 다만 질문에 타입이 안 적혀 있었고, AI가 한 가지 경우만 놓고 답했습니다.
#20에 적었듯이 AI에게 물을 때 조건을 같이 주면 답이 달라집니다. 이 경우라면 이렇게 물으면 됩니다.
type: LoadBalancer 인 Service에서 port: 8080, targetPort: 80으로 두면
외부에서 IP:8080으로 접근할 수 있어? NodePort를 따로 지정해야 해?
그리고 받은 답이 맞는지 확인할 방법이 이미 손에 있습니다. 지금 브라우저로 접속되는 그 주소가 반례입니다.
직접 확인해 보시기 바랍니다
# 세 필드를 한 번에 봅니다
kubectl get svc -o custom-columns='NAME:.metadata.name,TYPE:.spec.type,CLUSTER-IP:.spec.clusterIP,PORT:.spec.ports[*].port,TARGET:.spec.ports[*].targetPort,NODEPORT:.spec.ports[*].nodePort'# frontend-service에 nodePort가 생겼는지
kubectl get svc frontend-service -o jsonpath='{.spec.ports[0]}'# backend-service는 왜 EXTERNAL-IP가 없는지
kubectl get svc backend-service
frontend-service의 출력에 nodePort가 들어 있을 것입니다. 적은 적이 없는데 있습니다. #26에서 말한 "내가 쓴 적 없는 값"의 또 다른 예입니다.
이 글은 참고용입니다. 다만 세 필드가 각각 어느 문인지는 4회차 내용이라 한 번 보시기를 권합니다.
4회차에 이런 질문이 나왔습니다. 세 번에 걸쳐 물어봐 주셨는데 마지막에 스스로 정리해 주신 문장이 핵심입니다.
답부터
Service 타입에 따라 다릅니다. 그리고 지금 여러분이 쓰고 계신 타입에서는
port와targetPort만으로 외부에서 열립니다.k8s/frontend-service.yaml을 보시면 이렇습니다.nodePort가 적혀 있지 않은데도 브라우저에서 그 IP로 그냥 접속됩니다. 지금까지 그렇게 화면을 보셨습니다. 30000번대를 친 적이 없습니다.AI에게 물어보신 답은
type: NodePort인 경우를 말한 것입니다. 그 경우에는 맞는 말입니다. 다만 타입이 세 가지이고 답이 타입마다 다릅니다.포트가 세 개인 이유
각각 다른 문입니다. 어디에 있는 문인지가 다릅니다.
targetPortportnodePortport와 targetPort가 왜 따로인가
부르는 쪽이 Pod 사정을 몰라도 되게 하려는 것입니다.
앱이 3001번에서 돌고 있어도 부르는 쪽은 80번으로 부릅니다. 나중에 앱이 포트를 8000번으로 바꿔도 부르는 쪽 코드는 그대로입니다. Service의
targetPort만 고치면 됩니다.문서의 서술입니다.
우리
frontend-service는 둘 다 80이라 차이가 안 보입니다. 기본값이 같은 값이기 때문입니다. 그래서 왜 두 개인지 이해가 안 되는 것이 자연스럽습니다.실제로 다른 예를 보시면 바로 잡힙니다. 이건 제 실습용 클러스터의 Service 목록입니다.
mcp-a를 보시면 숫자 세 개가 전부 다릅니다. 밖에서는 80으로 부르고, 노드 IP로 오면 30128이고, 실제 앱은 3001에서 돌고 있습니다. 부르는 쪽은 3001을 몰라도 됩니다.kubernetesService도 443에서 6443으로 바뀝니다. 이건 여러분 클러스터에도 있습니다.한 걸음 더.
targetPort에는 숫자 대신 이름을 쓸 수도 있습니다.Pod가 자기 포트에
http-web-svc라고 이름을 붙여 두면 Service는 그 이름만 알면 됩니다. 번호가 바뀌어도 Service는 안 고쳐도 됩니다. 두 필드를 나눈 목적이 여기서 분명해집니다.docker -p와 무엇이 다른가
가장 헷갈리시는 부분이라 표로 놓겠습니다.
docker run -p 8080:3001port와targetPort그림으로 놓으면 차이가 분명해집니다. 먼저 Docker입니다.
기계 한 대 안에서 층이 둘입니다. 호스트의 문과 컨테이너의 문이고, 끝은 컨테이너 하나입니다.
이미지 하나를 빌드해 놓고, 그것을 로컬에서 부를 때와 클라우드에서 부를 때를 나란히 놓으면 이렇습니다. 이 과정에서 실제로 하는 일이라 값을 그대로 썼습니다.
flowchart TB B["app/frontend 코드를 docker build<br/>이미지 ssf15-frontend:v1 (컨테이너 안은 80)"] subgraph L["이 이미지를 내 컴퓨터에서 부를 때"] direction LR L1["브라우저"] -->|"localhost : 8080"| L2["호스트 8080<br/>docker run -p 8080:80"] --> L3["컨테이너 : 80"] end subgraph C["같은 이미지를 클라우드에서 부를 때"] direction LR C1["브라우저"] -->|"외부 IP : 80"| C2["로드밸런서"] --> C3["Service<br/>ClusterIP : 80"] --> C4["Pod : 80"] end B --- L L --- C class B same class L3 inside class C4 inside classDef same fill:#166534,stroke:#4ade80,color:#ffffff classDef inside fill:#166534,stroke:#4ade80,color:#ffffff초록 칸을 보시기 바랍니다. 맨 위의 이미지 하나가 아래 두 줄의 끝에 그대로 놓여 있습니다. 컨테이너 안은 양쪽이 똑같이 80입니다. 내 컴퓨터에서 돌리든 클라우드에 올리든 이미지도 그 안의 포트도 바뀌지 않습니다.
두 줄의 차이는 가운데뿐입니다. 로컬은
-p가 문 하나를 뚫어서 바로 컨테이너로 갑니다. 클라우드는 로드밸런서와 Service가 차례로 놓입니다. 그래서 적을 포트가 늘어난 것이지 컨테이너가 복잡해진 것이 아닙니다.6회차에 본인 이미지를 빌드해서 올리면 아래 줄을 직접 하시게 됩니다.
여기까지가 답입니다. 아래는 그 클라우드 쪽 줄을 좀 더 자세히 본 것입니다.
기계 여러 대에 걸쳐 층이 셋입니다. 노란 칸이 Docker에는 없는 층입니다. 그리고 끝이 Pod 여러 개로 갈라집니다.
두 그림의 차이는 이 두 가지가 전부입니다. 가운데 층이 하나 더 있다는 것, 그리고 끝이 하나가 아니라 여럿이라는 것입니다.
층이 하나 더 있습니다. Docker는 기계 한 대 안에서 호스트와 컨테이너 두 층입니다. 쿠버네티스는 여러 대를 묶어 놓아서 그 사이에 "클러스터 안의 가상 주소"라는 층이 하나 더 있습니다. Pod는 죽고 살아나며 IP가 바뀌는데 그 층 덕분에 부르는 쪽 주소가 안 바뀝니다.
그리고
docker -p의 왼쪽 숫자는 그 기계의 진짜 포트입니다. Service의port는 진짜 기계의 포트가 아니라 클러스터 안에서만 통하는 가상 주소의 포트입니다. 그래서 그것만으로는 밖에서 안 보이는 것이 맞습니다. 밖에서 들어오게 하는 것은 타입이 정합니다.타입이 정하는 것
ClusterIPClusterIP:portNodePort노드IP:nodePort. 기본 범위는 30000에서 32767LoadBalancer외부IP:port. 클라우드가 만들어 준 주소k8s/backend-service.yaml이ClusterIP이고k8s/frontend-service.yaml이LoadBalancer입니다. backend는 밖에서 못 부르고 frontend는 부를 수 있습니다. 4회차에 이 둘을 나눈 이유가 이것입니다.LoadBalancer는 NodePort도 같이 만듭니다. 위 목록에서
mcp-a가LoadBalancer인데NODEPORT열에 30128이 찍혀 있는 것이 그 증거입니다. 적은 적이 없는데 생겼습니다. 문서의 서술입니다.즉 LoadBalancer는 NodePort 위에 외부 로드밸런서를 하나 더 얹은 것입니다. 그래서 외부 IP로도 되고 노드 IP의 30128로도 됩니다. 다만 밖에서 쓰기 편한 쪽은 외부 IP이고 여러분은 그쪽을 쓰고 계십니다.
8080으로 열고 싶으면
질문하신 그 지점입니다. Ingress 없이
IP:8080으로 열고 싶을 때입니다.LoadBalancer라면 됩니다.
port를 8080으로 적으면 외부 IP의 8080으로 접속됩니다.NodePort라면 안 됩니다. 8080은 기본 범위 30000에서 32767 밖입니다. 범위를 바꾸려면 API 서버의
--service-node-port-range설정을 고쳐야 하는데, GKE 같은 관리형 서비스에서는 컨트롤 플레인을 클라우드가 관리하므로 학생이 바꿀 수 없습니다.그래서 정리하면 이렇습니다. Docker처럼 원하는 번호로 열고 싶으면
LoadBalancer를 쓰고port를 그 번호로 적으면 됩니다.NodePort는 번호를 고를 자유가 좁습니다.AI 답을 검증하는 사례이기도 합니다
받으신 답이 틀린 것은 아닙니다.
NodePort타입에 대해서는 정확합니다. 다만 질문에 타입이 안 적혀 있었고, AI가 한 가지 경우만 놓고 답했습니다.#20에 적었듯이 AI에게 물을 때 조건을 같이 주면 답이 달라집니다. 이 경우라면 이렇게 물으면 됩니다.
그리고 받은 답이 맞는지 확인할 방법이 이미 손에 있습니다. 지금 브라우저로 접속되는 그 주소가 반례입니다.
직접 확인해 보시기 바랍니다
frontend-service의 출력에nodePort가 들어 있을 것입니다. 적은 적이 없는데 있습니다. #26에서 말한 "내가 쓴 적 없는 값"의 또 다른 예입니다.이 글은 참고용입니다. 다만 세 필드가 각각 어느 문인지는 4회차 내용이라 한 번 보시기를 권합니다.