쿠버네티스와 서비스
1. 기본 관계: 도커와 쿠버네티스
도커 컴포즈: “내 컴퓨터에서” 웹서버, 데이터베이스, 캐시 컨테이너를 한 번에 쉽게 띄우고 싶을 때 사용하는 ‘편리한 실행 도구’이다 마치 여러 가전제품의 전원 플러그를 멀티탭에 꽂아 한 번에 켜는 것과 같다
쿠버네티스: 여러 건물에 흩어져 있는 수백, 수천 개의 가전제품(컨테이너)이 항상 최적의 상태로 작동하도록 중앙에서 전력을 분배하고, 고장 나면 자동으로 교체해주는 ‘지능형 중앙 관제 시스템’
| 구분 | 도커 컴포즈 (Docker Compose) | 쿠버네티스 (Kubernetes) |
|---|---|---|
| 핵심 목적 | 단일 호스트(컴퓨터 한 대)에서 여러 컨테이너를 쉽게 정의하고 실행 | 여러 호스트(컴퓨터 여러 대)에 걸쳐 컨테이너들을 자동 관리, 확장, 복구 |
| 관리 규모 | 개발 환경, 소규모 프로젝트 | 프로덕션(실서비스), 대규모 분산 시스템 |
| 파일 | docker-compose.yml 파일 하나로 관리 | Deployment, Service 등 기능별로 여러 YAML 파일을 조합하여 관리 |
| 자동 복구 | 기능 없음. 컨테이너가 멈추면 수동으로 다시 실행해야 함 | 자동 복구 (Self-healing) 노드나 파드에 문제 발생 시 자동으로 재시작하거나 다른 노드에 새로 생성 |
| 확장성 | 수동으로만 가능 | 자동 확장 (Auto-scaling) CPU 사용량 같은 지표에 따라 자동으로 파드 개수를 늘리거나 줄임 |
| 네트워킹 | 같은 파일에 정의된 컨테이너끼리 간단한 네트워크로 연결 | 클러스터 전체에 걸친 복잡하고 강력한 네트워킹 및 서비스 검색 기능 제공 |
쿠버네티스에 대한 질문
1. 자동확장은 어떻게 설정하고 어떻게 작동하는거야?
쿠버네티스의 자동 복구는 별도로 ‘켜는’ 기능이 아니라, 쿠버네티스의 핵심 동작 원리 그 자체로 이 기능은 주로 디플로이먼트(Deployment)를 통해서 관리한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3 # <-- 바로 여기! "항상 3개의 파드를 실행시켜줘" 라는 원하는 상태를 정의
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app-container
image: nginx
디플로이 컨트롤러가 항상 감시 pod가 3개가 아니라 2개라면 즉시 1개 추가로 생성
또 파드에 대한 자동 확장은 HPA (Horizontal Pod Autoscaler) 라는 리소스가 담당한다 HPA를 사용하면 CPU나 메모리 사용량 같은 지표에 따라 파드의 개수를 자동으로 늘리거나 줄일 수 있다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app # <-- 특정 Deployment에 연결
minReplicas: 3
maxReplicas: 10 # <-- 확장 한계 정의
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50 # <-- 목표 기준
my-app이름의 디플로이먼트에 속한 모든 파드를 감시하고 cpu 사용량이 50%늘어나면 Replicas수(즉 pod의 개수)를 늘린다
2. 파드의 개수를 늘리거나할 수있다고 했는데 노드수는 그대로 잖아 그 상태에서 자동 확장을 한다면 노드가 애초에 파드를 여러개 실행할 수 있을만큼 여유로워야 하겠네? 그러면 평소에 서버 낭비 아니야?
실제로 서버 낭비가 맞음 하지만 안정성을 위해서는 감수해야한다
3. 쿠버네티스에서 노드안에 있는 파드의 부하가 커져서 파드를 증가 시킬때 같은노드에 파드를 추가하는 경우도 있어? 이런경우 같은 하드웨어를 사용하므로 속도차이가 없는거 아니야?
파드에 부하가 생겨서 파드가 추가로 생성된다면 보통 여유 노드를 찾아서 다른 노드에 파드를 생성한다 하지만 같은 노드에 파드를 추가하는 것이 의미 있는 이유는, ‘속도’의 개념을 ‘부하 분산’의 관점에서 봐야 하기 때문이다
목표는 부하 분산: 파드를 늘리는 첫 번째 목표는 하나의 파드에 집중되던 요청(Load)을 여러 파드로 나누는 것이다 예를 들어, 하나의 파드가 1000개의 동시 요청을 처리하느라 버거워했다면, 파드를 2개로 늘려 각각 500개의 요청을 처리하게 만드는 것
병렬 처리의 이점: 애플리케이션은 내부적으로 한 번에 하나의 요청만 처리하거나, 동시에 처리할 수 있는 작업의 수가 제한될 수 있다 여러 개의 파드(프로세스)를 띄우면, 비록 같은 CPU 코어를 나눠 쓰더라도 요청들을 병렬로 처리할 수 있게 되어 전체적인 응답 속도와 처리량이 향상될 수 있다
2. 클러스터의 기본 구조
클러스터와 노드
- 클러스터 (Cluster): 여러 대의 컴퓨터(노드)를 하나로 묶어, 마치 하나의 거대하고 강력한 컴퓨터처럼 보이게 만드는 관리 시스템의 전체 단위 비유: 아파트 단지
- 노드 (Node): 클러스터를 구성하는 개별 컴퓨터 한 대 한 대를 의미 이 노드는 물리 서버, 가상 머신(VM) 등 서로 다른 컴퓨터가 될 수 있다 비유: 아파트 동
파드 (Pod) 🏠
- 역할: 쿠버네티스에서 생성하고 관리할 수 있는 가장 작은 배포 단위
- 비유: 아파트 단지(클러스터) 안에 있는 각각의 ‘집’ 또는 ‘호실’
- 특징: 하나 이상의 컨테이너를 포함할 수 있으며, 같은 파드 내의 컨테이너들은 네트워크와 저장소를 공유 쿠버네티스는 컨테이너를 직접 다루지 않고, 이 파드 단위로 관리
3. 애플리케이션 배포 및 관리
레이블과 셀렉터 (Label & Selector)
- 레이블 (Label): 모든 쿠버네티스 리소스에 붙일 수 있는
key:value형태의 ‘꼬리표’ (예:app: my-web) - 셀렉터 (Selector): 특정 레이블이 붙은 리소스 그룹을 찾아내고 선택하기 위한 조건
- 관계: 이름(Name)이 각 리소스의 고유한 ‘주민등록번호’라면, 레이블은 여러 리소스를 묶는 ‘소속팀’이나 ‘직책’과 같다 컨트롤러는 이 셀렉터를 사용하여 자신이 관리할 리소스 그룹을 식별
디플로이먼트와 레플리카셋 (Deployment & ReplicaSet)
- 레플리카셋 (ReplicaSet): “지정된 수의 동일한 파드가 항상 실행되도록 보장”하는 단순한 역할을 하는 ‘작업 반장’
- 디플로이먼트 (Deployment): 레플리카셋을 사용하여 파드의 개수를 보장하면서, 무중단 업데이트와 롤백이라는 더 높은 수준의 기능을 제공하는 ‘프로젝트 총괄 매니저’
- 결론: 우리는 항상 상위 관리자인 디플로이먼트를 사용하여 애플리케이션을 배포
kind: ReplicaSet을 YAML에 직접 작성하는 경우는 거의 없다
4. 클러스터의 논리적 분리와 접근
네임스페이스 (Namespace) 🗺️
- 역할: 하나의 물리적인 클러스터를 여러 개의 논리적인 ‘가상 클러스터’로 나누는 방법
- 비유: 거대한 아파트 단지를 ‘1단지’, ‘2단지’, ‘관리동 구역’으로 나누는 것과 같다
- 차이점: 네임스페이스를 사용하지 않는 것은 컴퓨터의 C 드라이브에 모든 파일을 쏟아붓는 것과 같음 네임스페이스를 사용하면 이름 충돌 방지, 보안 및 접근 제어(RBAC), 자원 할당(ResourceQuota), 관리의 용이성 등 여러 이점을 얻을 수 있어, 실제 운영 환경에서는 사용이 필수적이다
kubectl: 클러스터 제어
- 역할: 쿠버네티스 클러스터와 통신하기 위한 ‘원격 제어기(리모컨)’
- 사용 위치: 클러스터 외부(개발자 PC)나 마스터 노드에서 사용워커 노드에서는 사용하지 않는다 워커 노드는
kubelet이라는 에이전트를 통해 마스터와 통신할 뿐, 관리 명령을 내리지 않음 - 네임스페이스와 관계:
kubectl get pods: 기본(default) 네임스페이스의 리소스만 출력kubectl get pods --all-namespaces(또는-A): 모든 네임스페이스의 리소스를 출력kubectl describe pod <pod-name> -n <namespace>: 특정 네임스페이스에 있는 리소스의 상세 정보를 출력
5. 아키텍처 전략: 단일 vs 멀티 클러스터
- 단일 클러스터: 하나의 거대한 클러스터를 네임스페이스로 나눠 쓰는 방식 리소스 효율성과 관리 용이성이 높지만, 장애 격리 수준이 낮고 단일 장애 지점이 될 수 있다
- 멀티 클러스터: 환경, 팀, 지역별로 여러 개의 독립적인 클러스터를 운영하는 방식 강력한 격리와 높은 가용성을 보장하지만, 비용이 많이 들고 운영이 복잡해짐
6. 쿠버네티스 네트워크
CNI (Container Network Interface) = ‘통신사’ (KT, SKT,U+ 등)
파드들에게 IP 주소를 할당하고, 서로 다른 노드에 있는 파드들 간의 통신을 가능하게 하는 네트워크 플러그인
- 비유: ‘통신사’가 아파트 단지 전체에 전화선과 인터넷 랜선을 깔고, 각 세대(Pod)에 고유한 전화번호(IP 주소)를 부여하는 것과 같음
- 핵심 역할:
- Pod에 IP 주소 할당: 새로운 Pod가 생성될 때마다, 클러스터 내에서 유일한 IP 주소를 부여
- Pod 간 통신 경로 확보: 가장 중요한 역할 101동에 있는 Pod A가 102동에 있는 Pod B의 IP 주소로 패킷을 보냈을 때, 그 패킷이 실제로 102동까지 도달할 수 있도록 보이지 않는 라우팅(경로 설정)을 책임진다
- 결론: CNI는 “어떻게 Pod A가 Pod B의 IP 주소로 도달할 수 있는가?” 라는 저수준(Low-level)의 물리적인 연결 문제를 해결합니다. CNI가 없으면 Pod들은 서로 절대 통신할 수 없다
서비스 (Service) = ‘114 전화번호 안내’ 또는 ‘가게 대표 번호’
서비스는 CNI가 깔아놓은 통신망 위에서, 애플리케이션들이 서로를 쉽고 안정적으로 찾아갈 수 있도록 돕는 역할
- 비유: 우리 동네 ‘피자 가게’의 배달원(Pod)은 계속 바뀐다 배달원의 개인 휴대폰 번호(Pod IP)도 계속 바뀜 이때 우리가 배달원의 개인 번호로 직접 전화하지 않고, 항상 같은 ‘피자 가게 대표 번호’(서비스)로 전화하는 것과 같음
- 해결하는 문제:
- 유동적인 Pod IP: Pod는 언제든지 죽고 새로 생기며, 그때마다 IP 주소가 바뀐다 이 변화무쌍한 IP를 다른 애플리케이션이 일일이 추적하는 것은 불가능
- 핵심 역할:
- 고정된 접속점 제공: 서비스는 생성될 때 절대 바뀌지 않는 고유한 IP(ClusterIP)와 DNS 이름(예:
my-pizza-service)을 가진다 - 자동 탐색 및 로드 밸런싱: 서비스는 자신의
selector와 일치하는 레이블을 가진 Pod들을 실시간으로 찾아내어, 그들의 실제 IP 목록을 유지 그리고 “피자 가게 대표 번호”로 걸려온 전화를 현재 일하고 있는 건강한 배달원(Pod) 중 한 명에게 알아서 연결하고 분배(로드밸런싱)해 준다
- 고정된 접속점 제공: 서비스는 생성될 때 절대 바뀌지 않는 고유한 IP(ClusterIP)와 DNS 이름(예:
| 구분 | CNI (예: Calico) | 서비스 (Service) |
|---|---|---|
| 핵심 역할 | Pod에 IP 할당, Pod 간 통신 경로 확보 | Pod 그룹에 안정적인 접속점(IP, DNS) 제공 |
| 작동 계층 | 저수준(Low-level) 네트워크 인프라 (L2/L3) | 고수준(High-level) 서비스 추상화 (L4) |
| 해결하는 문제 | “Pod A가 Pod B의 IP 주소로 어떻게 도달하는가?” | “Pod B의 IP가 계속 바뀌는데, 어떻게 항상 같은 주소로 B의 기능에 접속하는가?” |
| 비유 | 통신사 (전화선/랜선 설치, 전화번호 부여) | 114 전화번호 안내 / 가게 대표 번호 |
서비스가 제 기능을 하려면 반드시 그 아래에 CNI가 구축한 네트워크 인프라가 존재해야 한다
서비스 타입
쿠버네티스 서비스는 파드 그룹에 안정적인 단일 접속점을 제공하는 방법 어떤 서비스 타입을 선택하느냐에 따라 클러스터 외부에서 애플리케이션에 접근하는 방식이 완전히 달라진다
파드(Pod): 상가에서 실제 일하는 직원들 (계속 바뀔 수 있음)
서비스(Service): 상점의 대표 연락처 또는 주소
1. ClusterIP (기본값)
“단지 내 주민들만 사용할 수 있는 내부 전화번호(인터콤)”
ClusterIP는 가장 기본이 되는 서비스 타입 YAML 파일에서 type을 지정하지 않으면 자동으로 ClusterIP가 된다
- 역할: 클러스터 내부에서만 접근할 수 있는 고유한 가상 IP 주소를 생성
- 동작: 클러스터 안에 있는 다른 파드들은 이
ClusterIP주소나 서비스 이름을 통해 해당 서비스의 파드들에게 접근할 수 있다 하지만 클러스터 외부에서는 절대 이 주소로 접근할 수 없다 - 비유: 101동 주민이 102동 상가에 인터콤으로 연락할 수는 있지만, 단지 밖에 있는 외부 사람은 이 인터콤 번호를 알 수도, 사용할 수도 없음
- 주요 사용 사례:
- 프론트엔드 파드가 백엔드 파드에게 API를 요청할 때
- 백엔드 파드가 데이터베이스 파드에 접속할 때
- 클러스터 내부의 컴포넌트끼리 통신할 때
YAML 예시:
1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Service
metadata:
name: my-internal-service
spec:
type: ClusterIP # 생략 가능
selector:
app: my-app
ports:
- port: 80
targetPort: 8080

2. NodePort
“각 동(Node)의 경비실을 통해 외부에서 상점으로 연결하는 방법”
NodePort는 ClusterIP의 기능을 그대로 가지면서, 추가적으로 클러스터의 모든 워커 노드(Worker Node)에 외부에서 접근 가능한 특정 포트를 열어준다
- 역할: 모든 노드의 특정 포트(
30000~32767사이의 임의의 포트)를 열고, 그 포트로 들어오는 요청을 해당 서비스로 전달 - 동작: 외부 사용자는
<아무 노드의 IP 주소>:<열린 포트 번호>로 접속하여 서비스에 도달할 수 있음 예를 들어,http://192.168.56.11:31234로 접속하면, 요청은worker1을 거쳐 서비스로 전달 - 비유: 아파트 단지의 모든 동(Node) 경비실에 외부 직통 전화(
31234번)를 하나씩 설치하는 것과 같다 외부 고객이 아무 동 경비실에나31234번으로 전화해도, 경비 아저씨가 알아서 해당 상점으로 연결해 준다 - 주요 사용 사례:
- 개발 및 테스트 환경에서 간단하게 외부 접속을 테스트하고 싶을 때
- 클라우드 환경이 아니어서
LoadBalancer타입을 사용할 수 없을 때
- 단점: 노드가 다운되면 해당 IP로는 접속할 수 없고, 포트 번호가 임의로 정해져 관리하기 어렵기 때문에 운영 환경(Production)에서는 잘 사용하지 않는다
YAML 예시:
1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: my-nodeport-service
spec:
type: NodePort
selector:
app: my-app
ports:
- port: 80 # 서비스가 클러스터 내부에서 사용하는 포트
targetPort: 8080 # 파드(Pod)가 사용하는 실제 포트 컨테이너 안에서 실행되는 애플리케이션이 리스닝하는 포트 번호
# nodePort: 31000 # 직접 지정할 수도 있지만, 보통은 생략하여 자동 할당받음
# 외부에서 노드로 접속할때 사용하는 포트
🚦트래픽 흐름
- 외부 사용자가
노드 IP:nodePort로 접속- ➡️ 예:
http://192.168.10.11:31000
- ➡️ 예:
- 요청을 받은 노드는 해당
서비스의port로 트래픽을 전달- ➡️ 예:
31000번 포트로 온 요청을 서비스의80번 포트로 보낸다
- ➡️ 예:
- 서비스는 최종 목적지인 파드의
targetPort로 트래픽을 전달- ➡️ 예:
80번 포트로 온 요청을 파드의8080번 포트로 보낸다
- ➡️ 예:
3. LoadBalancer (주로 클라우드 환경에서 사용)
“아파트 단지 정문에 있는 대표 안내 데스크 및 로드밸런서”
LoadBalancer는 NodePort의 기능을 포함하며, 추가적으로 클라우드 제공업체(AWS, GCP, Azure 등)의 외부 로드밸런서를 자동으로 생성하고 서비스에 연결
- 역할: 외부에서 접근 가능한 고유한 공인 IP 주소를 가진 로드밸런서를 생성하고, 모든 요청을 클러스터 내부의 노드들로 분산
- 동작: 클라우드 플랫폼이 로드밸런서를 생성하고 외부 IP를 할당 사용자는 이 고정된 외부 IP로 접속하면, 로드밸런서가 알아서 트래픽을 여러 노드의
NodePort로 전달하고, 최종적으로 서비스에 도달 - 비유: 아파트 단지 정문에 있는 ‘대표 안내 데스크’와 비슷 외부 방문객은 복잡한 동, 호수를 알 필요 없이 무조건 정문 안내 데스크로만 찾아오면 된다 그러면 안내 데스크에서 알아서 가장 한가한 경로로 해당 상점까지 안내
- 주요 사용 사례:
- 클라우드 환경에서 애플리케이션을 외부에 안정적으로 노출할 때 사용하는 가장 표준적인 방법
YAML 예시:
1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Service
metadata:
name: my-loadbalancer-service
spec:
type: LoadBalancer
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
☁️ 외부 IP는 어디서 할당?
결론부터 말하면, 클라우드 제공업체(Cloud Provider)가 자동으로 할당하고 관리해 준다
YAML 파일이 간단한 이유는 복잡한 작업을 쿠버네티스가 클라우드 플랫폼에 위임하기 때문
- 사용자가
type: LoadBalancer서비스를 생성하면, 쿠버네티스 클러스터 내의 ‘클라우드 컨트롤러 매니저(Cloud Controller Manager)’가 이를 감지 - 이 컨트롤러는 AWS, GCP, Azure 등 현재 쿠버네티스가 실행되고 있는 클라우드 플랫폼의 API를 호출
- API를 통해 “네트워크 로드밸런서를 하나 만들고, 외부에서 접속 가능한 공인 IP를 할당해줘” 라고 요청
- 클라우드 플랫폼은 요청에 따라 로드밸런서를 생성하고, 자신들이 보유한 IP 주소 풀에서 하나를 할당한 뒤, 그 IP 주소를 쿠버네티스 클러스터에 알려준다
- 쿠버네티스는 이 IP를 서비스의
EXTERNAL-IP필드에 기록하고, 이제 이 IP를 통해 외부에서 서비스에 접근할 수 있게 된다
중요: 이 기능은 클라우드 환경에서만 자동으로 동작 직접 구축한 서버(On-premise) 환경에서는 MetalLB와 같은 별도의 솔루션을 설치해야
LoadBalancer타입을 사용할 수 있음
🚦 트래픽 흐름
외부 사용자 ➡️ 클라우드 로드밸런서 ➡️ 노드(NodePort) ➡️ 서비스(ClusterIP) ➡️ 파드(targetPort)
- 🌐 외부 사용자 → 클라우드 로드밸런서 사용자는 클라우드 제공업체가 할당해 준 고유한 외부 IP 주소와 서비스 YAML에 정의된
port(예: 80)로 접속 - ☁️ 클라우드 로드밸런서 → 노드(NodePort) 로드밸런서는 이 요청을 받아서 등록된 여러 워커 노드 중 하나로 트래픽을 전달 이때
LoadBalancer서비스가 생성될 때 자동으로 할당된NodePort로 요청을 보낸다 - 🖥️ 노드(kube-proxy) → 서비스(ClusterIP) 요청을 받은 노드의
kube-proxy는NodePort로 들어온 트래픽을 서비스의 내부 IP인ClusterIP와port(예: 80)로 다시 전달 - 📦 서비스 → 파드(targetPort) 서비스는
selector(예:app: my-app)를 보고 연결된 파드들 중 하나를 선택하여, 최종적으로 파드가 내부에서 사용하고 있는targetPort(예: 8080)로 트래픽을 전달
💡 참고: 이 방식은 서비스마다 외부 IP가 할당되어 비용이 증가할 수 있다 이런 문제를 해결하고 하나의 IP로 여러 서비스를 관리하고 싶을 때 사용하는 것이 바로 인그레스(Ingress)

4. ExternalName
“상점 대표 번호로 전화했더니, 외부 프랜차이즈 본사로 연결해주는 착신 전환”
ExternalName은 다른 서비스 타입들과는 완전히 다르게 동작 파드를 선택(selector)하거나 포트를 열지 않고, 단순히 서비스 이름을 외부 도메인 이름으로 연결(alias)시켜주는 역할
- 역할: 클러스터 내부에서 특정 서비스 이름으로 요청하면, 그 요청을 외부의 다른 도메인으로 전달해주는 DNS CNAME 레코드를 생성
- 동작: 클러스터 내의 파드가
my-external-service라는 이름으로 DNS 조회를 하면, 쿠버네티스 DNS는my.database.example.com이라는 외부 주소를 반환 - 비유: 가게 대표 번호로 전화했더니, “자세한 문의는 본사 고객센터
1588-xxxx로 연결해 드리겠습니다”라며 전화를 돌려주는 것과 같음 - 주요 사용 사례:
- 클러스터 내부의 애플리케이션이 클러스터 외부의 데이터베이스(예: AWS RDS)나 다른 API 서비스에 접속해야 할 때, 외부 주소를 코드에 하드코딩하는 대신 서비스 이름으로 추상화하여 사용하고 싶을 때
YAML 예시:
1
2
3
4
5
6
7
apiVersion: v1
kind: Service
metadata:
name: my-external-service
spec:
type: ExternalName
externalName: my.database.example.com # 연결할 외부 도메인 주소
| 구분 (Type) | 핵심 역할 | 접근 방식 | 주요 사용 사례 |
|---|---|---|---|
| ClusterIP | 클러스터 내부 통신 | 클러스터 내부 IP (외부 접근 불가) | 마이크로서비스 간 통신 (프론트엔드 ↔ 백엔드) |
| NodePort | 외부 테스트 및 개발용 노출 | <노드 IP>:<노드 포트> | 개발/테스트 환경, 외부 로드밸런서가 없을 때 |
| LoadBalancer | 안정적인 외부 서비스 노출 | 클라우드 로드밸런서의 공인 IP | 클라우드 환경의 프로덕션(운영) 서비스 |
| ExternalName | 외부 서비스에 대한 별칭(Alias) 제공 | DNS CNAME 리디렉션 | 외부 DB(RDS 등)나 API를 클러스터 내부 서비스처럼 사용 |