쿠버네티스로 WEB-WAS 연동은 Nginx(WEB)와 Tomcat(WAS)을 각각 파드로 띄우고, 서비스(Service)의 클러스터 IP와 DNS를 이용해 두 파드를 연결하는 작업이다. 이 글에서는 매니페스트(정의 파일)로 디플로이먼트·서비스·ConfigMap을 작성하고, Nginx를 리버스 프록시로 설정해 WAS로 요청을 넘기는 전 과정을 구성 개요 → WAS(Tomcat) → WEB(Nginx) → 배포·연동 확인 순서로 정리한다. 원문 실습 환경은 Docker Desktop의 쿠버네티스 + 윈도우 PowerShell이다.
구성 개요: 쿠버네티스 WEB-WAS 연동 구조
쿠버네티스는 매니페스트에 기재된 ‘바람직한 상태(desired state)’를 데이터베이스(etcd)에 등록하고, 그 상태가 유지되도록 파드를 생성·관리한다. 이번 연동에 필요한 오브젝트는 다음 6개다.
- Nginx Deployment — WEB 파드 생성(리버스 프록시)
- Nginx ConfigMap — nginx.conf 주입(WAS로 proxy_pass)
- Nginx Service — NodePort로 외부 노출
- Tomcat Deployment — WAS 파드 생성
- Tomcat ConfigMap — index.jsp 주입(연동 확인용)
- Tomcat Service — ClusterIP로 내부 노출(8080)
요청 흐름은 브라우저 → Nginx Service(NodePort 30080) → Nginx 파드 → Tomcat Service(ClusterIP 8080) → Tomcat 파드다. Nginx 파드는 Tomcat 파드의 IP를 직접 알 필요가 없다. 파드는 생성·삭제될 때마다 IP가 바뀌지만, 서비스는 생명주기 동안 고정된 클러스터 IP와 DNS 이름을 제공하기 때문이다. Nginx는 이 안정적인 서비스 주소로만 요청을 넘기면 된다.
매니페스트 파일의 기본 구조
모든 쿠버네티스 매니페스트는 네 개의 최상위 항목으로 이뤄진다.
apiVersion: # API 그룹 및 버전 (예: v1, apps/v1)
kind: # 리소스 유형 (Pod, Service, Deployment ...)
metadata: # 메타데이터 (이름, 네임스페이스, 레이블 등)
spec: # 리소스 내용 (어떤 리소스를 만들 것인가)
- apiVersion — 사용할 쿠버네티스 API 버전. 리소스가 발전하며 버전도 바뀐다(예:
v1,apps/v1). - kind — 만들려는 리소스 종류. Pod, Service, Deployment 등.
- metadata — 리소스의 이름·네임스페이스·레이블·주석. 리소스 식별·관리에 쓰인다.
- spec — 리소스의 상세 사양. 파드라면 컨테이너 이미지·포트 등을 여기에 적는다.
디플로이먼트(Deployment) 개념
디플로이먼트는 파드와 레플리카셋(ReplicaSet)에 대한 선언적 업데이트를 제공한다. 디플로이먼트에서 바람직한 상태를 정의하면, 디플로이먼트 컨트롤러가 현재 상태를 그 상태로 맞춰 나간다. 디플로이먼트 spec의 핵심 항목은 다음과 같다.
apiVersion:
kind:
metadata:
name: # 디플로이먼트 이름
spec:
selector:
matchLabels: # 셀렉터가 관리할 파드의 레이블
replicas: # 유지할 파드(레플리카) 개수
template: # 생성할 파드의 정보(템플릿)
metadata: # 파드의 메타데이터(레이블 등)
spec: # 파드의 스펙(컨테이너 등)
- 셀렉터(selector) —
matchLabels로 지정한 레이블을 가진 파드를 이 디플로이먼트가 관리한다. 이 레이블은template.metadata.labels의 값과 일치해야 한다. - 레플리카(replicas) — 유지할 파드 개수를 관리한다.
- 템플릿(template) — 생성할 파드의 메타데이터·스펙을 기재한다. spec이 두 번 등장하므로(디플로이먼트 spec / 파드 spec) 들여쓰기에 주의한다.
파드 템플릿의 컨테이너 설정에는 컨테이너 이름·이미지·접속 포트, 그리고 필요하면 볼륨을 지정한다. 이번에는 Nginx의 설정 파일(nginx.conf)과 Tomcat의 index.jsp를 바꿔야 하므로 볼륨 마운트를 사용한다.
- volumeMounts — 컨테이너 내부에서 볼륨을 마운트할 위치.
name은 볼륨 이름,mountPath는 컨테이너 내 경로,subPath는 마운트할 특정 파일을 지정한다. - volumes — 실제 볼륨 소스.
name으로 볼륨 이름을 정하고configMap.name에 설정 데이터 이름을 적는다.
ConfigMap 개념
ConfigMap은 쿠버네티스에서 설정 정보를 저장·관리하는 API 오브젝트다. 애플리케이션 코드와 설정 데이터를 분리해, nginx.conf나 index.jsp 같은 설정 파일을 이미지 재빌드 없이 주입할 수 있다. 구조는 다음과 같다.
apiVersion:
kind: ConfigMap
metadata:
name: # ConfigMap 이름
data: # 키-값 형태의 설정 데이터
Tomcat(WAS) Deployment와 Service 작성
WEB이 바라볼 대상인 WAS부터 만든다. 먼저 연동 성공 여부를 눈으로 확인할 수 있도록 Tomcat의 /usr/local/tomcat/webapps/ROOT/index.jsp를 ConfigMap으로 주입한다.
apiVersion: v1
kind: ConfigMap
metadata:
name: tomcat-index
data:
index.jsp: |
<%@ page contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>WEB-WAS 연동 성공</title>
</head>
<body>
<h1>WEB-WAS 연동 성공</h1>
</body>
</html>
다음은 Tomcat 디플로이먼트다. 위 ConfigMap을 index.jsp 경로에 마운트해 컨테이너의 기본 페이지를 교체한다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: tomcat-deployment
spec:
selector:
matchLabels:
app: tomcat000ex99
replicas: 1
template:
metadata:
labels:
app: tomcat000ex99
spec:
containers:
- name: tomcat000con01
image: tomcat:latest
ports:
- containerPort: 8080
volumeMounts:
- name: tomcat-index-volume
mountPath: /usr/local/tomcat/webapps/ROOT/index.jsp
subPath: index.jsp
volumes:
- name: tomcat-index-volume
configMap:
name: tomcat-index
이어서 Tomcat 서비스를 만든다. WAS는 클러스터 내부에서만 Nginx가 접근하면 되므로 ClusterIP(기본값)로 노출한다. type을 생략하면 자동으로 ClusterIP가 적용된다.
apiVersion: v1
kind: Service
metadata:
name: tomcat-service
spec:
selector:
app: tomcat000ex99
ports:
- port: 8080
targetPort: 8080
port와 targetPort를 모두 8080으로 지정했다. 이 서비스는 app: tomcat000ex99 레이블을 가진 파드로 8080 트래픽을 전달한다. 서비스 이름이 tomcat-service이므로, 같은 네임스페이스의 Nginx 파드는 tomcat-service라는 이름만으로 이 WAS에 접근할 수 있다.
Nginx(WEB) ConfigMap·Deployment·Service 작성
Nginx ConfigMap — 리버스 프록시 설정
Nginx를 WAS 앞단의 리버스 프록시로 쓰려면 proxy_pass로 Tomcat 서비스를 가리키는 nginx.conf가 필요하다. 이 내용을 ConfigMap으로 작성한다.
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
data:
nginx.conf: |
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
proxy_pass http://tomcat-service:8080;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
data의 nginx.conf: |에서 |는 이어지는 블록 전체를 nginx.conf의 내용으로 그대로 보존한다는 YAML 표기다. 핵심은 proxy_pass 한 줄로, 사용자의 요청을 Tomcat 서비스의 8080 포트로 넘긴다.
여기서 한 가지 중요한 선택이 있다. proxy_pass의 대상을 서비스 이름(tomcat-service)으로 적었다는 점이다. 쿠버네티스는 모든 서비스에 안정적인 클러스터 IP와 함께 <서비스이름>.<네임스페이스>.svc.cluster.local 형식의 DNS 이름을 자동 부여한다. 같은 네임스페이스 안에서는 tomcat-service처럼 짧은 이름만 써도 클러스터 DNS가 알아서 해당 서비스의 클러스터 IP로 해석한다.
클러스터 IP를 직접 박아 넣는 방식(예: proxy_pass http://10.111.83.30:8080;)도 동작은 하지만, 클러스터 IP는 서비스를 지우고 다시 만들면 바뀔 수 있어 권장되지 않는다. 서비스 이름으로 지정하면 IP가 바뀌어도 설정을 고칠 필요가 없다. (원문 실습은 클러스터 IP를 직접 지정했으나, 여기서는 DNS 기반 방식으로 정리했다. 동작 원리는 동일하다.)
Nginx Deployment
위 ConfigMap을 컨테이너의 /etc/nginx/nginx.conf에 마운트하는 디플로이먼트를 작성한다.
apiVersion: apps/v1 # API 버전
kind: Deployment # 리소스 종류: 디플로이먼트
metadata:
name: nginx-deployment # 디플로이먼트 이름
spec:
selector:
matchLabels: # 셀렉터 레이블
app: nginx000ex99
replicas: 1 # 레플리카 1개
template:
metadata:
labels:
app: nginx000ex99 # 셀렉터와 동일한 레이블
spec:
containers:
- name: nginx000con01 # 컨테이너 이름
image: nginx:latest # 이미지 및 버전
ports:
- containerPort: 80 # 접속 포트
volumeMounts:
- name: nginx-config-volume
mountPath: /etc/nginx/nginx.conf # 마운트 경로
subPath: nginx.conf
volumes:
- name: nginx-config-volume
configMap:
name: nginx-config # 마운트할 ConfigMap 이름
volumeMounts.subPath: nginx.conf 덕분에 디렉터리 전체가 아니라 nginx.conf 파일 하나만 교체된다. 이렇게 하면 Nginx 컨테이너가 든 파드가 레플리카 수(1개)만큼 생성되고, 컨테이너 내 /etc/nginx/nginx.conf에 ConfigMap의 내용이 적용된다. 디플로이먼트는 이 파드 수를 항상 유지한다.
Nginx Service — NodePort로 외부 노출
WEB은 외부 브라우저가 접근해야 하므로 외부 노출 타입이 필요하다. 서비스 타입은 외부에서 어떤 방식으로 접근할지를 결정한다.
| 타입 | 접근 방식 |
|---|---|
| ClusterIP | 클러스터 내부 IP로만 접근(기본값). 내부 통신용. |
| NodePort | 각 워커 노드의 IP:포트로 접근. 기본 포트 범위 30000~32767. |
| LoadBalancer | 클라우드 로드밸런서가 발급한 외부 IP로 접근. 운영 환경 표준. |
| ExternalName | 외부 DNS 이름(CNAME)으로 매핑. 셀렉터·포트 없음. |
실무에서는 보통 LoadBalancer를 쓰지만, 이 실습은 로드밸런서가 없는 로컬 환경이므로 NodePort를 사용한다. NodePort로 지정한 포트로 브라우저가 워커 노드에 직접 접근하게 된다.
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort
selector:
app: nginx000ex99
ports:
- port: 80
targetPort: 80
nodePort: 30080
- type: NodePort — 노드 IP의 정적 포트로 외부 노출.
- selector: app: nginx000ex99 — 이 레이블을 가진 Nginx 파드로 트래픽 전달.
- port / targetPort: 80 — 서비스 포트와 컨테이너 포트.
- nodePort: 30080 — 외부에서
NodeIP:30080으로 접근할 포트(범위 30000~32767 내에서 지정).
서비스를 쓰면 파드의 IP·포트가 바뀌어도 영향을 받지 않고 안정적으로 트래픽을 관리할 수 있다. 외부에서 이 서비스에 접근하려면 노드 IP와 30080 포트를 사용한다.
매니페스트 배포와 연동 확인
6개 오브젝트(Nginx·Tomcat 각각의 Deployment·Service·ConfigMap)를 하나의 파일 nginx-tomcat-deployment.yaml에 작성하고, 각 문서를 ---로 구분하면 한 번에 적용할 수 있다.
# 하나의 파일에 여러 오브젝트를 --- 로 구분
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
# ... (생략)
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
# ... (생략)
---
# tomcat-index ConfigMap / tomcat-deployment / 두 Service ...
윈도우 PowerShell에서 kubectl apply -f로 매니페스트를 적용한다. 적용하면 매니페스트에 정의한 대로 파드가 생성되고, 파드 안에 컨테이너가 만들어진다.
kubectl apply -f C:DockerANDk8skube_foldernginx-tomcat_v2_foldernginx-tomcat-deployment.yaml
생성된 파드와 서비스 상태를 확인한다. 파드가 모두 Running이고, 서비스가 정상 등록됐는지 본다.
kubectl get pods
kubectl get services
# 출력 예시
NAME READY STATUS RESTARTS AGE
nginx-deployment-xxxxxxxxxx-xxxxx 1/1 Running 0 30s
tomcat-deployment-xxxxxxxxxx-xxxxx 1/1 Running 0 30s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 10d
nginx-service NodePort 10.110.45.12 <none> 80:30080/TCP 30s
tomcat-service ClusterIP 10.111.83.30 <none> 8080/TCP 30s
kubectl get services의 CLUSTER-IP 열에 각 서비스의 클러스터 IP가 표시된다. 예시에서 tomcat-service의 클러스터 IP는 10.111.83.30이다. 앞서 nginx.conf에서 proxy_pass를 서비스 이름으로 지정했으므로, 클러스터 DNS가 이 이름을 바로 이 클러스터 IP로 해석해 요청을 전달한다.
Nginx의 NodePort를 30080으로 지정했으니 브라우저에서 localhost:30080으로 접속해 연동을 확인한다. Tomcat ConfigMap에 넣은 index.jsp의 “WEB-WAS 연동 성공” 화면이 뜨면 성공이다.

이 화면은 브라우저 → Nginx(NodePort) → Tomcat(ClusterIP)으로 요청이 정상적으로 프록시됐다는 뜻이다. Nginx 파드가 Tomcat 파드의 실제 IP를 몰라도, 서비스라는 안정적인 중간 계층 덕분에 연동이 성립한다.
자주 묻는 질문과 트러블슈팅
왜 Tomcat 서비스는 ClusterIP, Nginx 서비스는 NodePort인가?
Tomcat(WAS)은 외부에 직접 노출할 필요가 없고 Nginx만 접근하면 되므로 클러스터 내부 전용인 ClusterIP로 충분하다. Nginx(WEB)는 외부 브라우저가 들어와야 하므로 노드 포트로 외부에 노출한다. WAS를 외부에 직접 열지 않는 것이 보안상 더 안전하다.
proxy_pass에 클러스터 IP를 직접 적어도 되나?
동작은 한다. 다만 클러스터 IP는 서비스를 재생성하면 달라질 수 있으므로, tomcat-service:8080처럼 서비스 이름(DNS)으로 지정하는 편이 안정적이다. 그래야 IP가 바뀌어도 ConfigMap을 고칠 필요가 없다.
연동이 안 될 때 점검 순서
- 파드 상태 —
kubectl get pods로 두 파드가Running인지 확인.CrashLoopBackOff면kubectl logs <파드명>으로 원인을 본다. - 서비스 셀렉터 — 서비스의
selector레이블과 디플로이먼트template.metadata.labels가 정확히 일치하는지 확인. 어긋나면 트래픽이 파드에 닿지 않는다. - 엔드포인트 —
kubectl get endpoints tomcat-service로 서비스가 실제 파드와 연결됐는지 확인. 비어 있으면 셀렉터 불일치다. - nginx.conf 마운트 —
kubectl exec로 파드에 들어가/etc/nginx/nginx.conf에proxy_pass가 들어갔는지 확인.subPath누락 시 파일이 아니라 디렉터리로 마운트돼 Nginx가 뜨지 않을 수 있다. - 포트 — NodePort는 30000~32767 범위여야 한다. 범위를 벗어난 값은 적용되지 않는다.
정리
쿠버네티스로 WEB-WAS를 연동하는 핵심은 파드는 디플로이먼트로 만들고, 파드 간 통신은 서비스로 연결한다는 원칙이다. 설정 파일(nginx.conf·index.jsp)은 ConfigMap으로 주입하고, Nginx는 Tomcat 서비스 이름으로 proxy_pass를 걸어 안정적으로 WAS에 요청을 넘긴다. 전체 순서는 매니페스트 작성(Deployment·Service·ConfigMap) → kubectl apply -f → kubectl get pods/services 확인 → NodePort로 접속 테스트다. 파드 IP가 아니라 서비스를 통해 연동하기 때문에 파드가 재생성돼도 연결이 유지된다.
다음 글에서는 여기에 이어 DB(MariaDB)까지 연동해 WEB-WAS-DB 3계층 구성을 완성한다.



💬 댓글 0