[IT-방법] 서비스 YAML 설정 파일 작성법 – 실무 예제와 필수 필드 해설

서비스 관련 쿠버네티스 구조를 설명하는 대표 이미지

서비스 매니페스트가 해결해야 할 네트워크 문제

새로운 버전의 애플리케이션을 배포했는데, 갑자기 API 호출이 실패한다는 알람이 울려요. 로그를 확인해보면 Pod는 아주 건강하게 살아있어요. 하지만 클라이언트가 접속하려고 하면 연결할 수 없다는 에러만 반복돼요. SRE라면 이 순간 가장 먼저 무엇을 의심할까요? 바로 서비스 YAML 설정 파일의 불일치예요.

쿠버네티스 환경에서 Pod는 생성되고 삭제될 때마다 IP가 계속 변해요. 이 유동적인 환경에서 고정된 진입점을 제공하지 못한다면 서비스 운영은 불가능해요. 서비스 매니페스트는 바로 이 불안정한 Pod들의 집합을 하나의 고정된 논리적 주소로 묶어주는 핵심 장치예요. 단순히 연결만 해주는 것이 아니라, 트래픽을 어떤 방식으로 분산할지, 외부에서 어떻게 접근하게 할지를 결정하는 설계도라고 봐도 무방해요.

대규모 트래픽을 다루는 환경일수록 서비스 설정 하나가 인프라 비용과 성능에 직결돼요. 잘못 설정된 LoadBalancer는 불필요한 클라우드 비용을 발생시키고, 잘못된 Session Affinity 설정은 특정 Pod에 트래픽을 몰아넣어 시스템 전체를 마비시키기도 해요. 그래서 서비스 설정 파일을 작성할 때는 단순한 문법 확인을 넘어, 네트워크 아키텍처를 설계한다는 마음가짐이 필요해요.

이 글을 모두 읽고 나면 다음과 같은 능력을 갖추게 돼요.

  • 서비스 매니페스트의 필수 필드가 실제 트래픽 흐름에서 어떤 역할을 하는지 이해해요.
  • 상황에 맞는 최적의 서비스 타입을 선택하고 YAML로 구현할 수 있어요.
  • 실무에서 자주 발생하는 포트 매칭 오류와 엔드포인트 미연결 문제를 스스로 해결해요.
  • 배포 전 YAML 파일의 무결성을 검증하는 절차를 익혀요.

서비스 설정을 시작하기 전 필수 체크리스트

서비스 매니페스트를 작성하기 전에 반드시 확인해야 할 전제 조건들이 있어요. 무턱대고 YAML을 작성했다가는 No endpoints available라는 절망적인 메시지만 마주하게 될 거예요. 가장 먼저 확인해야 할 것은 타깃이 되는 Pod들에 정확한 레이블(Label)이 붙어있는지 여부예요. 서비스는 이름이 아니라 레이블을 보고 대상을 찾아가기 때문이에요.

또한, 사용하는 네트워크 플러그인(CNI)이 어떤 서비스 타입을 지원하는지도 파악해야 해요. 예를 들어, 온프레미스 환경의 특정 CNI에서는 클라우드 전용 기능인 LoadBalancer 타입이 제대로 작동하지 않을 수 있어요. 이럴 때는 NodePort를 활용하거나 별도의 Ingress 컨트롤러를 고려해야 해요.

💡 알아두기
서비스는 Pod에 직접 연결되는 것이 아니라, 내부적으로 생성되는 Endpoints 객체를 통해 Pod의 IP 목록을 관리해요. 따라서 서비스 설정이 끝났다면 반드시 Endpoints가 정상적으로 생성되었는지 확인하는 습관을 들여야 해요.

자신의 인프라 환경과 요구사항에 맞는 서비스 타입을 선택할 수 있도록 아래 비교 표를 참고해 보세요.

서비스 타입 접근 범위 주요 사용 사례 특징
ClusterIP 클러스터 내부 DB, 내부 API 서버 기본값, 외부 노출 없음
NodePort 노드 IP를 통한 외부 접속 테스트, 간단한 외부 노출 고정 포트 범위(30000-32767) 사용
LoadBalancer 클라우드 제공자 LB를 통한 접속 실제 운영 서비스 가장 편리하지만 비용 발생
ExternalName 클러스터 내부에서 외부로 외부 DB 연결용 별칭 포트 설정 없이 CNAME 활용

선택 기준은 단순해요. 내부 통신만 필요하면 ClusterIP, 클라우드 환경에서 가장 안정적인 외부 노출이 필요하면 LoadBalancer를 선택하면 돼요. 만약 비용을 아끼면서 여러 서비스를 하나의 IP로 운영하고 싶다면 Ingress를 함께 사용하는 것이 현명한 선택이에요.

서비스 YAML 설정 단계별 실행 가이드

이제 본격적으로 서비스 설정 파일 작성을 시작해 볼게요. 복잡해 보이는 YAML 구조도 핵심 원리만 파악하면 아주 단순해요. 단계별로 차근차근 따라와 주세요.

STEP 1. YAML의 기본 뼈대 구성하기

모든 쿠버네티스 매니페스트는 apiVersion, kind, metadata, spec이라는 네 가지 핵심 축으로 이루어져요. 서비스의 경우 kind: Service로 명시해야 하며, apiVersion은 보통 v1을 사용해요. metadata 영역에서는 서비스의 이름을 정의하는데, 이 이름이 곧 클러스터 내부에서 사용할 DNS 이름이 된다는 점을 기억해야 해요. 예를 들어 서비스 이름이 my-api라면, 다른 Pod에서는 http://my-api라는 주소로 접근할 수 있어요.

STEP 2. Selector와 Label을 통한 타깃 매칭

서비스 설정에서 가장 빈번하게 실수가 발생하는 지점이 바로 spec.selector예요. 서비스는 자신이 트래픽을 전달할 Pod를 찾기 위해 이 셀렉터를 사용해요. 만약 Pod에 app: web-server라는 레이블이 붙어 있다면, 서비스의 셀렉터도 반드시 동일하게 작성해야 해요.

⚠️ 주의
셀렉터에 오타가 있거나 레이블이 하나라도 다르면 서비스는 트래픽을 보낼 곳을 찾지 못해요. 이 경우 서비스는 정상적으로 생성되지만, 연결된 엔드포인트가 없는 상태가 되어 접속 장애가 발생해요.

STEP 3. 포트(Port) 설정의 삼각 관계 파악하기

서비스의 포트 설정은 세 가지 개념을 명확히 구분해야 해요. 이 개념이 섞이면 네트워크 연결이 절대 이루어지지 않아요.

  • port: 서비스 자체의 포트예요. 클러스터 내부의 다른 Pod들이 이 서비스에 접속할 때 사용하는 포트예요.
  • targetPort: 트래픽이 최종적으로 도달할 Pod 내부의 컨테이너 포트예요.
  • nodePort: (NodePort 타입 사용 시) 클러스터의 모든 노드 IP를 통해 외부에서 접속할 수 있게 개방되는 포트예요.

예를 들어, 외부에서는 80번 포트로 접속하고(port), 실제 컨테이너는 8080번에서 돌고 있다면(targetPort), 이 관계를 정확히 명시해줘야 해요.

STEP 4. 최소 구성 YAML 예제 (ClusterIP)

가장 기본이 되는 내부 통신용 ClusterIP 예제를 살펴볼게요. 이 설정은 내부 마이크로서비스 간의 통신에 가장 많이 쓰여요.

apiVersion: v1
kind: Service
metadata:
  name: backend-service
spec:
  type: ClusterIP
  selector:
    app: backend-api
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

위 예제에서 backend-service라는 이름의 서비스는 app: backend-api 레이블을 가진 Pod들에게 80번 포트로 요청을 전달하며, 실제 Pod의 8080번 포트로 데이터를 넘겨줘요.

STEP 5. 실무형 확장 예제 (LoadBalancer 및 세션 유지)

대규모 트래픽 환경에서는 사용자 세션을 유지해야 하는 경우가 많아요. 특정 사용자가 항상 동일한 Pod로 연결되도록 Session Affinity를 설정한 LoadBalancer 예제예요.

apiVersion: v1
kind: Service
metadata:
  name: web-lb-service
spec:
  type: LoadBalancer
  selector:
    app: frontend
  sessionAffinity: ClientIP
  externalTrafficPolicy: Local
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

여기서 sessionAffinity: ClientIP는 클라이언트의 IP 주소를 기준으로 동일한 Pod에 계속 연결되도록 보장해요. 또한 externalTrafficPolicy: Local 옵션은 트래픽이 노드를 거치며 발생하는 추가적인 네트워크 홉(Hop)을 줄이고, 클라이언트의 실제 소스 IP를 보존하는 데 매우 유용해요. 단, 이 옵션을 쓰면 트래픽이 도착한 노드에 해당 Pod가 반드시 실행 중이어야 한다는 제약이 생겨요.

STEP 6. 서비스 검증과 상태 확인

설정을 마친 후에는 반드시 아래 명령어를 통해 정상 동작 여부를 확인해야 해요. 단순히 kubectl apply가 성공했다고 해서 끝이 아니에요.

  1. 서비스 생성 확인: kubectl get svc 명령어로 서비스가 생성되었고, 필요한 경우 IP가 할당되었는지 확인해요.
  2. 엔드포인트 연결 확인: kubectl get endpoints [서비스명]을 입력했을 때, 연결된 Pod의 IP 목록이 나타나야 해요. 만약 이라고 나온다면 셀렉터 오류예요.
  3. 네트워크 통신 테스트: 임시 Pod를 띄워 curl [서비스명]:[포트] 명령어로 직접 응답을 받아보는 것이 가장 확실해요.

자주 하는 실수와 해결법

실무에서 서비스 설정 시 가장 흔하게 마주치는 문제들을 정리했어요. 이 패턴만 익혀둬도 장애 대응 시간이 절반으로 줄어들어요.

  • 실수: Selector의 레이블이 Pod와 일치하지 않음
    왜 발생하는가: YAML 작성 시 오타가 나거나, Pod의 레이블 명세가 변경되었는데 서비스는 업데이트하지 않았을 때 발생해요.
    해결법: kubectl get pods --show-labels로 Pod의 정확한 레이블을 확인하고, kubectl get endpoints를 통해 서비스에 엔드포인트가 잡히는지 반드시 확인하세요.
  • 실수: targetPort를 컨테이너 포트와 다르게 설정함
    왜 발생하는가: 컨테이너 내부 애플리케이션이 사용하는 포트와 서비스 매니페스트의 targetPort가 불일치할 때 발생해요.
    해결법: Pod의 정의(Deployment)를 확인하여 containerPort가 무엇인지 정확히 파악하고 이를 targetPort에 적어주세요.
  • 실수: LoadBalancer 타입에서 외부 IP가 할당되지 않음
    왜 발생하는가: 클라우드 환경이 아니거나, 사용 중인 CNI가 LoadBalancer 타입을 지원하지 않기 때문이에요.
    해결법: 클라우드 환경인지 확인하고, 로컬 환경이라면 NodePort나 Ingress로 전환하여 구성하세요.
  • 실수: 서비스 포트 범위(NodePort)를 벗어남
    왜 발생하는가: nodePort를 수동으로 지정할 때 쿠버네티스 기본 허용 범위(30000-32767)를 벗어난 값을 입력했을 때 발생해요.
    해결법: 포트 번호를 기본 범위 안으로 조정하거나, API 서버 설정에서 범위를 변경해야 해요.
  • 실수: 외부 통신 시 Source IP 유실
    externalTrafficPolicy: Cluster(기본값)를 사용하면 트래픽이 노드 간에 전달되면서 클라이언트 IP가 노드의 IP로 바껴요.
    해결법: 클라이언트의 실제 IP가 로그에 남아야 한다면 externalTrafficPolicy: Local로 변경하세요.

자주 묻는 질문

Q. ClusterIP와 NodePort의 근본적인 차이는 무엇인가요?

ClusterIP는 클러스터 내부에서만 접근 가능한 가상 IP를 생성해요. 반면 NodePort는 클러스터의 모든 노드에 특정 포트를 열어서 외부에서 노드 IP를 통해 접근할 수 있게 해줘요. 보안상 내부 서비스는 ClusterIP를 쓰는 것이 원칙이에요.

Q. Headless Service는 언제 사용하나요?

서비스의 clusterIPNone으로 설정하면 Headless Service가 돼요. 이는 단일 IP로 트래픽을 묶는 대신, DNS 쿼리 시 연결된 모든 Pod의 IP를 직접 반환해요. 상태 저장 애플리케이션(StatefulSet)에서 개별 Pod의 주소를 직접 알아야 할 때 주로 사용해요.

Q. 서비스 이름으로 통신하는 것이 가능한 이유는 무엇인가요?

쿠버네티스 내부의 CoreDNS가 서비스 이름을 IP 주소로 변환해주는 역할을 하기 때문이에요. 덕분에 Pod의 IP가 바뀌어도 서비스 이름만 알면 안정적인 통신이 가능해요.

Q. 포트 번호를 변경하면 어떻게 되나요?
서비스의 port를 변경하면 클러스터 내부의 다른 Pod들이 호출하는 주소(URL)도 함께 변경해야 해요. 그렇지 않으면 기존 주소로 접속하는 통신이 모두 끊기게 돼요.

Q. 외부 IP가 할당되지 않을 때는 어떻게 하나요?
클라우드 환경이라면 프로비저닝 속도 때문에 시간이 걸릴 수 있어요. kubectl describe svc [서비스명]을 통해 이벤트 로그를 확인하여 클라우드 공급자의 에러 메시지가 있는지 체크해 보세요.

댓글 남기기