Kubernetes에서 애플리케이션을 안정적으로 운영하려면 컨테이너를 실행하는 것만으로는 부족하다. 애플리케이션의 배포 상태를 관리하고, 외부 트래픽을 내부 Pod로 전달하며, 서비스 간 통신과 설정을 체계적으로 관리해야 한다.
Kubernetes는 이러한 작업을 Deployment, ReplicaSet, Service, Ingress, ConfigMap, Secret 등의 리소스를 통해 제공한다.
각 리소스는 서로 다른 책임을 가진다. Deployment는 애플리케이션의 배포 상태를 관리하고, ReplicaSet은 지정된 Pod 수를 유지한다. Service는 Pod에 안정적인 네트워크 접근점을 제공하며, Ingress는 외부 HTTP 및 HTTPS 요청을 클러스터 내부 서비스로 라우팅한다.
Deployment와 ReplicaSet
Deployment
Deployment는 Kubernetes에서 애플리케이션의 배포와 업데이트를 관리하는 리소스다.
컨테이너 이미지, 실행할 Pod 수, 업데이트 전략 등의 원하는 상태를 선언하면 Kubernetes는 실제 상태가 원하는 상태와 일치하도록 조정한다.
Deployment가 관리하는 주요 항목은 다음과 같다.
- ReplicaSet 관리: 애플리케이션의 원하는 Pod 수를 유지하도록 ReplicaSet을 생성하고 관리한다.
- 롤링 업데이트: 기존 Pod를 한꺼번에 종료하지 않고 새 버전의 Pod로 점진적으로 교체한다.
- 롤백: 배포 이력에 따라 이전 버전으로 되돌릴 수 있다.
- 상태 관리: 선언한 배포 상태와 실제 실행 상태의 차이를 지속적으로 조정한다.
Deployment는 일반적으로 웹 서버, API 서버, 게임 플랫폼의 백엔드 서버처럼 지속적으로 실행되어야 하는 애플리케이션에 사용한다.
ReplicaSet
ReplicaSet은 지정된 수의 Pod가 실행되도록 보장하는 리소스다.
예를 들어 API 서버 Pod를 여러 개 실행하도록 설정하면, 일부 Pod가 종료되더라도 ReplicaSet은 부족한 수만큼 새로운 Pod를 생성한다.
ReplicaSet의 핵심 책임은 Pod의 개수를 유지하는 것이다.
Deployment와 ReplicaSet의 관계는 다음과 같다.
- Deployment는 배포 버전과 업데이트 전략을 관리한다.
- ReplicaSet은 특정 Pod 템플릿을 기반으로 원하는 Pod 수를 유지한다.
- Deployment는 새 버전을 배포할 때 새로운 ReplicaSet을 생성하고 기존 ReplicaSet의 규모를 조정한다.
따라서 일반적인 애플리케이션 배포에서는 ReplicaSet을 직접 관리하기보다 Deployment를 사용하는 것이 적절하다.
Deployment 선언 예시
다음은 Nginx 컨테이너를 실행하는 Deployment 설정이다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
replicas: 3
selector:
matchLabels:
app: api-server
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: api-server
spec:
containers:
- name: api-server
image: nginx:stable
ports:
- containerPort: 80
replicas는 원하는 Pod 수를 지정한다. selector는 Deployment가 관리할 Pod를 식별하며, Pod 템플릿의 labels와 일치해야 한다.
maxSurge는 업데이트 중 원하는 Pod 수를 초과해 추가로 생성할 수 있는 Pod 수를 의미한다. maxUnavailable은 업데이트 과정에서 사용 불가능한 상태가 될 수 있는 Pod 수를 제한한다.
위 설정에서는 기존 Pod를 사용할 수 없는 상태로 만들지 않으면서 새 Pod를 추가해 점진적으로 교체하도록 지정했다. 다만 실제 무중단 배포 여부는 애플리케이션의 준비 상태, 프로세스 종료 처리, 트래픽 전환 방식 등에도 영향을 받는다.
롤링 업데이트와 롤백
롤링 업데이트는 애플리케이션의 새 버전을 단계적으로 배포하는 방식이다.
기존 Pod를 모두 종료한 뒤 새 Pod를 생성하는 방식과 달리, 새 버전의 Pod가 준비되는 과정에서 기존 버전의 Pod를 유지할 수 있다.
배포 과정에서 고려해야 할 요소는 다음과 같다.
- Readiness Probe: Pod가 실제로 요청을 처리할 준비가 되었는지 확인한다.
- Liveness Probe: 컨테이너가 정상적으로 동작하고 있는지 확인한다.
- 리소스 제한: 새 버전의 Pod를 추가로 실행할 수 있는 CPU와 메모리를 확보한다.
- 호환성: 이전 버전과 새 버전이 동일한 데이터베이스 스키마 및 API 계약을 사용할 수 있도록 설계한다.
배포 후 문제가 발생하면 다음 명령어로 이전 리비전으로 롤백할 수 있다.
kubectl rollout undo deployment/api-server
배포 상태는 다음 명령어로 확인한다.
kubectl rollout status deployment/api-server
kubectl rollout history deployment/api-server
롤백은 애플리케이션 이미지와 Deployment 설정을 이전 상태로 복원하는 데 유용하다. 하지만 이미 실행된 데이터베이스 마이그레이션이나 외부 시스템에 발생한 부작용까지 자동으로 되돌리지는 않는다.
Service
Service의 역할
Kubernetes의 Pod는 생성과 삭제, 재시작, 스케일링에 따라 IP 주소가 변경될 수 있다.
클라이언트가 개별 Pod의 IP 주소를 직접 사용한다면 Pod가 교체될 때마다 연결 정보를 갱신해야 한다. 이러한 방식은 서비스 운영에 적합하지 않다.
Service는 여러 Pod를 논리적인 단위로 묶고, 안정적인 접근점을 제공하는 리소스다.
Service는 라벨 셀렉터를 통해 대상 Pod를 식별하고, 해당 Pod로 트래픽을 전달한다.
주요 목적은 다음과 같다.
- 안정적인 접근점 제공: Pod의 IP가 변경되어도 Service의 접근 주소를 유지한다.
- 트래픽 분산: 선택된 여러 Pod에 요청을 전달한다.
- 서비스 간 통신: 다른 애플리케이션이 개별 Pod의 주소를 알지 않고도 서비스를 호출할 수 있도록 한다.
Service는 일반적으로 L4 수준의 TCP 또는 UDP 연결을 전달한다. HTTP 경로나 호스트 이름을 기준으로 세밀하게 라우팅하는 기능은 보통 Ingress 또는 Gateway API와 같은 별도 계층에서 처리한다.
Service 유형
Kubernetes는 사용 목적에 따라 여러 Service 유형을 제공한다.
ClusterIP
클러스터 내부에서만 접근할 수 있는 가상 IP를 제공한다. 별도 유형을 지정하지 않았을 때 기본적으로 사용되는 유형이다.
내부 API 서버, 인증 서버, 매칭 서버처럼 클러스터 내부에서 호출되는 서비스에 적합하다.
NodePort
각 노드의 지정된 포트를 통해 Service에 접근할 수 있도록 한다. 일반적으로 외부 로드 밸런서가 없는 환경에서 외부 접근을 구성하거나 테스트할 때 사용할 수 있다.
운영 환경에서는 방화벽, 접근 제어, 포트 관리 등을 함께 고려해야 한다.
LoadBalancer
클라우드 제공자의 로드 밸런서와 연동해 외부 접근점을 생성할 수 있다. 실제 동작은 클라우드 환경과 설치된 컨트롤러에 따라 달라진다.
ExternalName
외부 DNS 이름을 Service 이름에 연결한다. 일반적인 Pod 트래픽 프록시 방식과는 다르며, DNS 별칭이 필요한 경우에 활용할 수 있다.
Service 선언 예시
apiVersion: v1
kind: Service
metadata:
name: api-service
spec:
type: ClusterIP
selector:
app: api-server
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
port는 Service가 제공하는 포트이고, targetPort는 대상 Pod에서 요청을 전달받는 포트다.
selector는 app: api-server 라벨을 가진 Pod를 대상으로 지정한다. 따라서 Deployment에서 생성한 Pod에 동일한 라벨이 있어야 정상적으로 연결된다.
다음과 같이 클러스터 내부에서 Service를 호출할 수 있다.
http://api-service
다른 네임스페이스에 있는 서비스라면 네임스페이스를 포함한 DNS 이름을 사용할 수 있다.
http://api-service.backend
Service와 EndpointSlice
Service는 트래픽을 전달할 대상을 식별하기 위해 EndpointSlice 등의 리소스를 활용한다.
EndpointSlice에는 Service와 연결된 Pod의 네트워크 엔드포인트 정보가 기록된다. 일반적으로 Pod의 준비 상태와 셀렉터 일치 여부 등에 따라 대상 엔드포인트가 관리된다.
Service는 존재하지만 요청이 전달되지 않는 경우에는 다음을 확인해야 한다.
- Service의
selector와 Pod의labels가 일치하는가? - 대상 Pod가 정상적으로 준비되었는가?
port와targetPort가 올바른가?- NetworkPolicy 또는 방화벽이 통신을 차단하지 않는가?
- 애플리케이션이 지정된 인터페이스와 포트에서 요청을 수신하고 있는가?
Service 객체가 생성되었다는 사실만으로 애플리케이션의 정상 응답까지 보장되는 것은 아니다.
Ingress
Ingress의 역할
Ingress는 클러스터 외부에서 들어오는 HTTP 및 HTTPS 요청을 내부 Service로 전달하기 위한 라우팅 규칙을 정의하는 리소스다.
Service가 내부 애플리케이션의 안정적인 접근점을 제공한다면, Ingress는 외부 요청을 어떤 Service로 전달할지 결정하는 역할을 담당한다.
예를 들어 다음과 같은 서비스를 운영한다고 가정할 수 있다.
api.example.com은 API 서버로 전달한다.admin.example.com은 관리자 웹 서버로 전달한다.example.com은 프런트엔드 서버로 전달한다.
이러한 호스트 기반 라우팅을 Ingress 규칙으로 구성할 수 있다.
Ingress는 리소스 선언만으로 동작하지 않는다. 실제 요청을 처리하려면 NGINX Ingress Controller와 같은 Ingress Controller가 클러스터에 설치되어 있어야 한다.
Ingress는 Kubernetes의 전통적인 HTTP 진입점 모델이며, 신규 환경에서는 Gateway API도 대안으로 검토할 수 있다.
Ingress 선언 예시
다음 예시는 api.example.com으로 들어오는 HTTP 요청을 api-service로 전달하는 설정이다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
ingressClassName은 요청을 처리할 Ingress Controller의 클래스를 지정한다. 예시의 nginx 클래스가 실제 클러스터에 등록되어 있어야 한다.
host는 요청의 호스트 이름을 기준으로 라우팅하며, path는 URL 경로를 기준으로 라우팅한다. backend에는 요청을 전달할 Service와 포트를 지정한다.
HTTPS와 TLS 인증서
외부에서 API를 호출할 때는 HTTPS를 적용해 통신 구간을 암호화하는 것이 일반적이다.
Ingress는 TLS 설정을 통해 인증서를 지정할 수 있다.
spec:
tls:
- hosts:
- api.example.com
secretName: api-tls
이 설정을 기존 Ingress의 spec에 추가하면 된다. api-tls Secret에는 해당 호스트에 사용할 인증서와 개인 키가 올바른 형식으로 저장되어 있어야 한다.
인증서 발급과 갱신은 cert-manager 같은 도구로 자동화할 수 있다. 다만 인증서 발급 방식, DNS 설정, Ingress Controller 구성에 따라 추가 설정이 필요하다.
Ingress를 구성할 때는 다음 사항을 함께 검토해야 한다.
- DNS 레코드가 Ingress의 외부 진입점으로 연결되는가?
- TLS 인증서의 호스트 이름이 요청 도메인과 일치하는가?
- Ingress Controller가 정상적으로 실행 중인가?
- 요청을 전달할 Service와 엔드포인트가 정상인가?
- 인증, 요청 크기 제한, 타임아웃, 접근 제어 정책이 적절한가?
Ingress는 일반적인 HTTP 및 HTTPS 트래픽을 처리하기에 적합하지만, 모든 네트워크 프로토콜을 포괄하는 범용 라우터는 아니다.
ConfigMap과 Secret
ConfigMap
ConfigMap은 애플리케이션의 설정값을 컨테이너 이미지와 분리해 관리하는 Kubernetes 리소스다.
애플리케이션의 실행 환경에 따라 달라지는 설정을 이미지에 직접 포함하면 환경마다 이미지를 별도로 빌드해야 하거나 설정 변경을 위해 이미지를 다시 배포해야 할 수 있다.
ConfigMap을 사용하면 동일한 이미지를 개발, 스테이징, 운영 환경에서 재사용하면서 환경별 설정을 분리할 수 있다.
주요 활용 대상은 다음과 같다.
- 애플리케이션의 실행 모드
- 로그 레벨
- 외부 API의 주소
- 기능 활성화 여부
- 비밀 정보가 아닌 일반적인 설정 파일
ConfigMap은 환경 변수를 통해 주입하거나 파일 형태로 컨테이너에 마운트할 수 있다.
ConfigMap 선언 예시
apiVersion: v1
kind: ConfigMap
metadata:
name: api-config
data:
NODE_ENV: production
LOG_LEVEL: info
API_TIMEOUT: "5000"
Deployment에서 ConfigMap의 값을 환경 변수로 사용할 수 있다.
envFrom:
- configMapRef:
name: api-config
이 설정은 컨테이너의 환경 변수로 ConfigMap의 값을 전달한다.
환경 변수로 주입된 설정은 ConfigMap을 수정하더라도 이미 실행 중인 컨테이너에 자동으로 반영되지 않는다. 새 값을 적용하려면 일반적으로 Pod를 재생성해야 한다.
파일 형태로 마운트하는 방식은 갱신된 내용이 일정 시간 후 반영될 수 있지만, 애플리케이션이 파일을 다시 읽도록 구현되어 있어야 한다. subPath로 마운트한 파일은 일반적인 볼륨 갱신 방식으로 업데이트되지 않는다.
Secret
Secret은 비밀번호, API 키, 토큰, 인증서 등 민감한 정보를 관리하기 위한 Kubernetes 리소스다.
일반적인 설정값을 ConfigMap으로 관리한다면, 외부에 노출되면 안 되는 인증 정보는 Secret으로 분리하는 것이 기본 원칙이다.
Secret은 환경 변수 또는 파일로 컨테이너에 전달할 수 있다.
예를 들어 데이터베이스 접속 비밀번호나 외부 서비스의 API 키를 이미지 내부에 하드코딩하지 않고 Secret을 통해 주입할 수 있다.
Secret의 주요 활용 대상은 다음과 같다.
- 데이터베이스 인증 정보
- 외부 API 인증 키
- 서비스 간 통신에 사용하는 비밀 정보
- TLS 인증서와 개인 키
Secret의 데이터는 YAML에 직접 작성하거나 별도 관리 시스템을 통해 생성할 수 있다. 일반적인 Secret YAML에서는 data 값이 Base64로 인코딩되어 있을 수 있지만, Base64 인코딩은 암호화가 아니다.
Secret을 사용한다는 이유만으로 민감 정보가 자동으로 안전해지는 것은 아니다. 다음과 같은 보호 조치가 필요하다.
- Kubernetes RBAC으로 Secret 조회 권한을 최소화한다.
- 저장 데이터에 대한 암호화를 구성한다.
- Git 저장소에 실제 비밀 정보를 커밋하지 않는다.
- 로그와 오류 메시지에 비밀 정보가 출력되지 않도록 한다.
- 외부 Secret 관리 시스템과 연동하는 방식을 검토한다.
- 인증 정보의 교체와 폐기 절차를 마련한다.
ConfigMap과 Secret은 모두 설정을 외부화하는 역할을 하지만, Secret은 민감 정보에 대한 접근 통제와 보호를 전제로 사용해야 한다.
DNS와 서비스 디스커버리
서비스 디스커버리란?
서비스 디스커버리는 애플리케이션이 통신할 대상 서비스의 위치를 동적으로 찾는 기능이다.
분산 시스템에서는 서비스가 여러 Pod로 구성되고, Pod의 IP 주소가 변경되거나 인스턴스 수가 늘어날 수 있다. 이러한 환경에서 모든 클라이언트가 각 Pod의 IP 주소를 직접 관리하는 방식은 확장성과 운영 편의성이 떨어진다.
Kubernetes는 Service와 DNS를 활용해 서비스의 위치를 이름으로 찾을 수 있도록 지원한다.
예를 들어 API 서버가 인증 서버에 요청을 보내야 한다면 인증 서버 Pod의 IP를 직접 지정하는 대신 다음과 같은 Service 이름을 사용할 수 있다.
http://auth-service
같은 네임스페이스에서는 일반적으로 Service 이름만으로 접근할 수 있다. 다른 네임스페이스에 있는 서비스는 네임스페이스를 포함한 이름을 사용한다.
http://auth-service.backend
클러스터의 기본 도메인이 cluster.local인 환경에서는 다음과 같은 정규화된 DNS 이름을 사용할 수도 있다.
auth-service.backend.svc.cluster.local
기본 클러스터 도메인은 환경에 따라 다를 수 있다.
Kubernetes DNS의 동작
Kubernetes 클러스터에서는 일반적으로 CoreDNS가 클러스터 내부 DNS 서비스를 담당한다.
서비스 디스커버리는 다음과 같은 흐름으로 동작한다.
- 애플리케이션이 Service의 DNS 이름을 사용해 요청한다.
- 컨테이너의 DNS 설정에 따라 DNS 질의가 전달된다.
- CoreDNS가 Kubernetes 리소스 정보를 기반으로 Service 이름을 해석한다.
- 클라이언트는 해석된 주소를 이용해 Service에 연결한다.
- Service는 구성된 엔드포인트 중 적절한 Pod로 트래픽을 전달한다.
일반적인 ClusterIP Service의 DNS 이름은 해당 Service의 가상 IP 주소로 해석된다. 이후 Service의 네트워크 처리 계층이 실제 Pod 엔드포인트로 트래픽을 전달한다.
반면 Headless Service는 clusterIP: None으로 설정하며, 일반적인 ClusterIP를 제공하지 않는다. 이 경우 DNS 응답에 개별 Pod의 주소가 포함될 수 있어 StatefulSet이나 직접적인 엔드포인트 탐색이 필요한 애플리케이션에서 유용하다.
Headless Service 예시
apiVersion: v1
kind: Service
metadata:
name: game-server
spec:
clusterIP: None
selector:
app: game-server
ports:
- name: tcp
port: 7000
targetPort: 7000
이 방식은 클라이언트가 개별 Pod의 주소를 알아야 하거나, 각 Pod를 구분해 통신해야 하는 경우에 활용할 수 있다.
다만 Headless Service는 자체적으로 트래픽을 분산하는 가상 IP를 제공하지 않는다. 애플리케이션이 DNS 응답에 포함된 주소를 어떻게 선택하고 연결할지 고려해야 한다.
DNS와 네트워크 문제 해결
서비스 이름으로 통신이 되지 않는다면 DNS 문제와 실제 네트워크 연결 문제를 구분해야 한다.
먼저 Pod 내부에서 DNS 이름이 해석되는지 확인한다.
kubectl exec -it deploy/api-server -- nslookup auth-service
실제 컨테이너 이미지에 nslookup이 없을 수 있으므로, 필요한 경우 진단용 컨테이너를 사용한다.
그다음 Service와 엔드포인트를 확인한다.
kubectl get svc
kubectl get endpointslice
kubectl get pods --show-labels
DNS가 정상적으로 해석되더라도 연결이 실패할 수 있다. 이 경우 Service의 셀렉터, Pod의 준비 상태, 포트 설정, NetworkPolicy, 애플리케이션의 수신 주소 등을 확인해야 한다.
DNS 응답은 캐시될 수 있으므로, Pod 교체나 주소 변경이 발생한 직후에는 클라이언트의 DNS 캐시 정책도 영향을 줄 수 있다.
배포 및 네트워크 구성의 전체 흐름
Kubernetes에서 외부 요청이 애플리케이션에 도달하는 일반적인 흐름은 다음과 같다.
Client
|
v
DNS
|
v
Ingress Controller
|
v
Ingress Rule
|
v
Service
|
v
Pod
|
v
Application
각 구성 요소의 역할을 분리하면 장애 원인을 분석하기 쉬워진다.
- DNS는 도메인 이름을 네트워크 주소로 해석한다.
- Ingress Controller는 외부 HTTP 및 HTTPS 요청을 처리한다.
- Ingress는 요청을 어떤 Service로 전달할지 규칙을 정의한다.
- Service는 안정적인 접근점을 제공하고 대상 Pod로 트래픽을 전달한다.
- Pod는 컨테이너를 실행하며 실제 애플리케이션을 구동한다.
- Deployment와 ReplicaSet은 애플리케이션의 배포 상태와 Pod 수를 관리한다.
- ConfigMap과 Secret은 애플리케이션의 설정과 민감 정보를 분리해 관리한다.
내부 서비스 간 통신에서는 Ingress를 거치지 않고 Service의 DNS 이름으로 직접 통신하는 것이 일반적이다. 모든 내부 요청을 외부 진입점으로 우회시키면 불필요한 네트워크 경로와 운영 복잡성이 증가할 수 있다.
게임 서버 운영에서의 활용
게임 플랫폼에서는 서비스의 특성에 따라 Kubernetes 리소스를 구분해 설계해야 한다.
HTTP API 서버
Deployment로 실행 개수를 관리하고, ClusterIP Service로 내부 접근점을 제공한다. 외부에서 접근해야 하는 API는 Ingress 또는 Gateway를 통해 노출할 수 있다.
매칭 서버
매칭 요청을 처리하는 API와 실제 매칭 작업을 수행하는 서버를 분리할 수 있다. 서비스 간 통신은 DNS 이름을 사용하고, 처리량 증가에 따라 Deployment의 Pod 수를 조정할 수 있다.
실시간 게임 서버
TCP 또는 UDP 기반 게임 서버는 일반적인 HTTP Ingress만으로 충분하지 않을 수 있다. 사용하는 프로토콜과 연결 방식에 따라 LoadBalancer, NodePort, Gateway 또는 별도 네트워크 구성이 필요하다.
WebSocket은 HTTP 기반 연결에서 시작하므로 Ingress Controller의 지원 여부, 연결 유지 시간, 타임아웃, 연결 종료 처리 등을 검토해야 한다.
인증 및 설정 관리
인증 서버의 내부 주소는 Service DNS 이름으로 관리하고, 데이터베이스 비밀번호와 외부 API 키는 Secret으로 분리한다. ConfigMap에는 로그 레벨이나 일반적인 실행 환경 설정을 저장한다.
무중단 배포
Readiness Probe를 통해 새 Pod가 요청을 처리할 준비가 되었는지 확인하고, 롤링 업데이트 정책을 설정한다. WebSocket처럼 장시간 유지되는 연결이 있다면 기존 Pod 종료 시 연결을 정리하는 방식과 클라이언트 재접속 정책도 설계해야 한다.