Skip to content

Pod 안에 컨테이너가 여러 개면 어떻게 Service로 노출하나요? #29

Description

@sysnet4admin

4회차에 이런 질문이 나왔습니다.

파드 내부에서도 여러 컨테이너가 있을 수 있는데 이들끼리는 어떻게 구별하나요?

Service로 노출할 때 어느 컨테이너로 가는지가 궁금하신 것이라 그 각도로 정리합니다.

답부터

Service는 컨테이너를 모릅니다. 포트만 봅니다.

Service의 targetPort에 적는 것은 컨테이너 이름이 아니라 포트입니다. 그 포트를 어느 컨테이너가 열었는지는 Service가 신경 쓰지 않습니다. 열어 둔 쪽으로 갈 뿐입니다.

그래서 컨테이너를 구별하는 것이 아니라 포트를 구별하는 것입니다.

왜 그런가

같은 Pod 안 컨테이너는 IP를 공유하기 때문입니다. 쿠버네티스 문서의 서술입니다.

Within a Pod, containers share an IP address and port space, and can find each other via localhost.
(Pod 안에서 컨테이너들은 IP 주소와 포트 공간을 공유하며 localhost로 서로를 찾을 수 있습니다.)

IP가 같으니 IP로는 구별할 수가 없습니다. 남는 것이 포트입니다.

flowchart LR
  SVC["Service<br/>port 80 → targetPort 80<br/>port 9090 → targetPort 9090"]
  subgraph POD["Pod 하나 (IP 10.1.2.3 하나)"]
    direction TB
    C1["컨테이너 A<br/>80에서 대기"]
    C2["컨테이너 B<br/>9090에서 대기"]
  end
  SVC -->|"10.1.2.3 : 80"| C1
  SVC -->|"10.1.2.3 : 9090"| C2
  C1 <-->|"서로는 localhost로 부름"| C2

  class POD podbox
  classDef podbox fill:#1e293b,stroke:#94a3b8,color:#ffffff
Loading

Pod 상자 하나에 IP가 하나입니다. Service가 보내는 주소는 둘 다 10.1.2.3이고 포트만 다릅니다.

같은 Pod 안이라 서로는 localhost로 부릅니다. 컨테이너 A에서 컨테이너 B를 부를 때 Service를 거치지 않고 localhost:9090이면 됩니다.

노출하는 방법

Service의 ports는 배열입니다. 여러 개를 적을 수 있습니다.

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: my-app
  ports:
    - name: web
      port: 80
      targetPort: 80
    - name: metrics
      port: 9090
      targetPort: 9090

이러면 하나의 Service로 두 포트가 열립니다. 클러스터 안에서 my-service:80으로 부르면 컨테이너 A로 가고 my-service:9090으로 부르면 컨테이너 B로 갑니다.

포트가 두 개 이상이면 name을 붙여야 합니다. 하나뿐일 때는 생략할 수 있습니다.

포트는 겹치면 안 됩니다

같은 Pod 안에서 두 컨테이너가 같은 포트를 열 수는 없습니다. 포트 공간이 하나이기 때문입니다. 문서에도 그렇게 적혀 있습니다.

When containers in a Pod communicate with entities outside the Pod, they must coordinate how they use the shared network resources (such as ports).
(Pod 안의 컨테이너들이 Pod 바깥과 통신할 때는 공유하는 네트워크 자원(포트 같은)을 어떻게 쓸지 서로 맞춰야 합니다.)

컨테이너 하나만 있을 때는 신경 쓸 일이 없다가, 둘이 되는 순간 생기는 제약입니다. 같은 이미지를 두 개 넣으면 둘 다 같은 포트를 열려고 해서 하나가 뜨지 못합니다.

번호 대신 이름을 붙이면 편합니다

Pod 쪽에서 포트에 이름을 붙이고 Service가 그 이름을 가리킬 수 있습니다. 제 실습 클러스터에 실제로 그렇게 쓰는 것이 있습니다.

Pod 쪽입니다.

[
  { "containerPort": 9978, "name": "grpc-xds-agw" },
  { "containerPort": 9093, "name": "health" },
  { "containerPort": 9092, "name": "metrics" }
]

그것을 가리키는 Service입니다.

[
  { "name": "grpc-xds-agw", "port": 9978, "targetPort": "grpc-xds-agw" },
  { "name": "health",       "port": 9093, "targetPort": "health" },
  { "name": "metrics",      "port": 9092, "targetPort": "metrics" }
]

targetPort가 숫자가 아니라 이름입니다. 이렇게 두면 나중에 앱이 포트 번호를 바꿔도 Service는 안 고쳐도 됩니다. 이름만 그대로면 됩니다.

이 예는 컨테이너 하나가 포트를 세 개 연 경우인데 동작 원리는 컨테이너가 여러 개일 때와 같습니다. Service는 어차피 컨테이너를 보지 않고 포트만 보기 때문입니다.

Service를 하나로 할지 여러 개로 할지

둘 다 됩니다. 기준은 누가 부르느냐입니다.

상황 이렇게 합니다
부르는 쪽이 같은 무리이고 접근 범위도 같다 Service 하나에 포트를 여러 개
하나는 밖에 열고 하나는 안에서만 쓴다 Service를 둘로 나누고 타입을 다르게

예를 들어 웹은 밖에 열고 메트릭은 클러스터 안에서만 보게 하려면, 웹용 Service는 LoadBalancer로 메트릭용 Service는 ClusterIP로 각각 둡니다. 같은 Pod를 가리키는 Service가 둘이어도 됩니다. Service는 라벨로 Pod를 고를 뿐이라 여러 Service가 같은 Pod를 가리켜도 문제가 없습니다.

이 과정 앱은 어떤가

k8s/frontend-deployment.yamlk8s/backend-deployment.yaml을 보시면 각 Pod에 컨테이너가 하나씩입니다. 그래서 이 과정에서는 위 상황을 만나지 않습니다.

frontend Pod 안의 nginx가 /api 요청을 backend로 넘기는데, 그건 같은 Pod 안 컨테이너끼리가 아니라 다른 Pod로 가는 것입니다. 그래서 localhost가 아니라 backend-service라는 Service 이름으로 부릅니다. 4회차에 두 Service를 만든 이유가 이것입니다.

컨테이너를 여러 개 넣는 것은 주로 본체 옆에 도우미를 붙일 때입니다. 로그를 모아 보내는 것, 앞에서 트래픽을 받아 넘기는 것 같은 역할입니다. 본체와 생명주기를 같이해야 하고 같은 파일이나 네트워크를 봐야 할 때 한 Pod에 넣습니다. 그럴 이유가 없으면 Pod를 나누는 편이 낫습니다.

직접 확인해 보시기 바랍니다

# Pod마다 컨테이너가 몇 개인지
kubectl get pods -o custom-columns='NAME:.metadata.name,CONTAINERS:.spec.containers[*].name'

# 컨테이너가 여는 포트
kubectl get pods -o jsonpath='{.items[*].spec.containers[*].ports}'

# Service가 어느 포트로 보내는지
kubectl get svc -o jsonpath='{.items[*].spec.ports}'

컨테이너가 여러 개인 Pod에서는 명령에 -c로 어느 컨테이너인지 알려 줘야 합니다. 하나뿐일 때는 생략해도 됩니다.

kubectl logs <pod-이름> -c <컨테이너-이름>
kubectl exec -it <pod-이름> -c <컨테이너-이름> -- sh

포트 세 개가 각각 어느 문인지는 #28에 있습니다.

이 글은 참고용입니다. 읽지 않아도 수업 진행에는 지장이 없습니다.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions