Kubernetes ConfigMap, Secret, Ingress 제대로 이해하기 — 앱이 뜨는 것과 운영 가능한 것은 다르다
Pod가 Running 상태라고 해서 운영 가능한 상태는 아닙니다. 진짜 운영 가능한 애플리케이션은 환경별 설정이 분리되어 있고, 민감 정보가 코드 밖에서 관리되며, 외부 트래픽 진입 경로가 설계되어 있어야 합니다. 이번 글에서는 그 세 가지를 담당하는 ConfigMap, Secret, Ingress를 정리합니다.
왜 하드코딩이 문제인가
코드에 설정값을 직접 박아 넣으면 두 가지 문제가 생깁니다.
보안 관점
- Git 저장소에 비밀번호가 노출됩니다
- Docker 이미지 레이어에 값이 남습니다
- 팀원 누구나 접근할 수 있는 정보가 됩니다
운영 관점
- 설정 하나 바꾸려면 전체 재빌드가 필요합니다
- 개발/운영 환경 분기가 코드 레벨에서 생깁니다
- 같은 이미지를 여러 환경에 재사용할 수 없습니다
12-Factor App 원칙: 설정은 환경에서 주입하고, 코드와 분리해야 합니다.
ConfigMap vs Secret — 무엇이 다른가
기준은 하나입니다. "이 값이 공개되면 보안 사고가 나는가?"
| 종류 | 용도 | 예시 |
|---|---|---|
| ConfigMap | 노출되어도 큰 문제 없는 비민감 설정 | APP_ENV, LOG_LEVEL, PUBLIC_API_URL |
| Secret | 절대 노출되면 안 되는 민감 설정 | DB_PASSWORD, API_KEY, JWT_SECRET |
ConfigMap 사용법
YAML 구조
apiVersion: v1
kind: ConfigMap
metadata:
name: team-board-config
namespace: default
data:
APP_ENV: "production"
LOG_LEVEL: "info"
핵심:
data필드에 키-값 쌍을 정의합니다. 모든 값은 문자열로 저장되므로 숫자도 따옴표로 감쌉니다.
주입 방식 — 환경변수 vs 파일 마운트
| 방식 | 언제 쓰나 | 특징 |
|---|---|---|
| envFrom / env | process.env, os.getenv()로 읽을 때 | Pod 재시작 시에만 새 값 반영 |
| volume 마운트 | nginx.conf처럼 파일 형태가 필요할 때 | ConfigMap 변경 시 파일 자동 갱신 |
적용 확인 방법
kubectl get configmap team-board-config
kubectl describe configmap team-board-config
kubectl exec -it [pod-name] -- env | grep APP_ENV
kubectl rollout restart deployment/team-board-app
자주 하는 실수
- 키 이름 오타: configMapKeyRef.key가 data의 키와 정확히 일치해야 합니다
- 이름 오타: Pod가 CreateContainerConfigError 상태가 되면 kubectl get configmap으로 확인합니다
- 민감 정보를 ConfigMap에 저장: 비밀번호는 반드시 Secret으로 분리합니다
Secret — base64는 암호화가 아니다
Secret의 data 필드 값이 알 수 없는 문자로 보여서 암호화된 것으로 오해하기 쉽습니다.
cGFzc3dvcmQ= 이건 password를 base64로 인코딩한 것입니다. 누구나 즉시 복호화할 수 있습니다.
echo "cGFzc3dvcmQ=" | base64 -d # → password
Secret이 안전한 이유는 base64가 아니라, etcd 암호화 설정, RBAC 접근 제어, 네트워크 격리 때문입니다.
YAML 구조 — stringData 방식 권장
apiVersion: v1
kind: Secret
metadata:
name: team-board-db-secret
namespace: default
type: Opaque
stringData:
DB_HOST: "postgres-service"
DB_USER: "boarduser"
DB_PASSWORD: "s3cur3P@ss!"
DB_NAME: "teamboard"
stringData는 평문으로 입력하면 Kubernetes가 자동으로 base64로 저장합니다. 단, 이 파일을 Git에 올리면 절대 안 됩니다.
Ingress — Service만으로 충분한가
Service만 쓰면 서비스마다 별도 포트(30001, 30002…)가 필요하고 도메인 기반 라우팅이 불가능합니다. Ingress를 추가하면 하나의 진입점(80/443)으로 통합할 수 있습니다.
host 기반 vs path 기반 라우팅
| 방식 | 사용 시점 | 예시 |
|---|---|---|
| host 기반 | 서브도메인으로 서비스를 나눌 때 | app.example.com → team-board-service |
| path 기반 | 하나의 도메인 아래 여러 서비스를 운영할 때 | example.com/api → api-service |
Ingress YAML 구조
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: team-board-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: team-board.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: app-service
port:
number: 8080
Ingress Controller를 별도로 설치해야 합니다
Ingress 리소스는 라우팅 규칙을 선언하는 설계도일 뿐입니다. 실제로 동작하려면 Controller가 필요합니다.
# minikube 환경
minikube addons enable ingress
운영 환경에서는 nginx-ingress-controller, Traefik, AWS ALB Ingress Controller 중 환경에 맞는 것을 선택합니다.
정리
| 리소스 | 역할 |
|---|---|
| ConfigMap | 비민감 설정을 이미지 밖으로 분리, 재빌드 없이 설정 변경 가능 |
| Secret | 민감 정보 보호, base64 인코딩(암호화 아님) + RBAC으로 접근 제어 |
| Ingress | 외부 트래픽 단일 진입점, host/path 기반 라우팅, TLS 한 곳에서 관리 |
설정 분리는 단순한 코드 정리가 아닙니다. 동일한 이미지를 여러 환경에 재사용하고, 민감 정보를 코드 바깥에서 관리하고, 외부 트래픽 흐름을 선언적으로 설계하는 것이 운영 가능한 애플리케이션의 기준입니다.