Kubernetes Secret 관리
Secret은 민감한 정보 (데이터베이스 암호, API 키 등)를 코드와 분리하여 Kubernetes 자원으로 안전하게 저장하는 메커니즘이다
저장 → 주입 흐름
Secret 생성 (중요 정보 저장)
↓
Deployment에서 참조
↓
Pod 실행 시 환경 변수로 주입
↓
Spring 애플리케이션에서 사용
Secret 생성
kubectl create secret generic <시크릿명> \ --from-literal=<KEY>=<VALUE> \ --from-literal=<KEY>=<VALUE> \ -n <네임스페이스>
실제 예시
kubectl create secret generic my-app-secrets \ --from-literal=DB_HOST=database-1.xxxxx.ap-northeast-2.rds.amazonaws.com \ --from-literal=DB_PW=your-password \ -n hyeok
- my-app-secrets: Secret의 이름으로 임의 지정이 가능하다
- DB_HOST: Key로 RDS의 엔드포인트이다
- DB_PW: Key로 RDS의 마스터 암호이다
Secret 조회
kubectl get secret my-app-secrets -n hyeok -o yaml
출력 예시
apiVersion: v1 data: DB_HOST: ZGF0YWJhc2UtMS54eHh4eC5hcC1ub3J0aGVhc3QtMi5yZHMuYW1hem9uYXdzLmNvbQ== DB_PW: eW91ci1wYXNzd29yZA== kind: Secret metadata: name: my-app-secrets namespace: hyeok type: Opaque
참고: Secret의 데이터는 Base64로 인코딩 되어 있다(암호화가 아니다). Base64 디코더로 확인할 수 있다
AWS Console에서 Secret 확인
- EKS Console → 클러스터 선택
- 리소스 탭 → 구성 및 보안 → 보안 암호 (Secrets)
- 생성한 Secret 클릭 → 디코딩 옵션 사용 가능


Secret 삭제
kubectl delete secret my-app-secrets -n hyeok
Secret 수정하기
Secret에 Key-Value를 추가하는 직접적인 명령어는 없다. 삭제 후 재생성을 권장한다
# 1. 기존 Secret 삭제 kubectl delete secret my-app-secrets -n hyeok # 2. 새로운 내용으로 재생성 kubectl create secret generic my-app-secrets \ --from-literal=DB_HOST=... \ --from-literal=DB_PW=... \ --from-literal=JWT_SECRET=... # 새로 추가 -n hyeok
Deployment와 Service 구성
depl_svc.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ordersystem-backend
namespace: hyeok
spec:
revisionHistoryLimit: 2
replicas: 2
selector:
matchLabels:
app: ordersystem-backend
template:
metadata:
labels:
app: ordersystem-backend
spec:
containers:
- name: ordersystem-backend
image: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/order-backend:latest
ports:
- containerPort: 8080
resources:
limits:
cpu: "1"
memory: "500Mi"
requests:
cpu: "0.2"
memory: "250Mi"
env:
- name: DB_HOST
valueFrom:
secretKeyRef:
name: my-app-secrets
key: DB_HOST
- name: DB_PW
valueFrom:
secretKeyRef:
name: my-app-secrets
key: DB_PW
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: ordersystem-backend-service
namespace: hyeok
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8080
selector:
app: ordersystem-backend
Deployment 구성 요소 상세 설명
revisionHistoryLimit
spec: revisionHistoryLimit: 2
역할: 이전 ReplicaSet을 몇 개까지 보관할지 지정
ReplicaSet이 새로 생성되는 경우
- Deployment YAML 수정 시
- kubectl rollout restart 실행 시
| 제한 없음(기본값: 10) | 제한 설정(2) |
| ReplicaSet이 계속 쌓임 | 최근 2개만 유지 |
| ArgoCD UI에서 정신없음 | 깔끔한 관리 |
| 리소스 낭비 가능성 | 효율적인 자원 관리 |
이미지 설정
containers: - name: ordersystem-backend image: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/order-backend:latest
- ECR URL는 본인의 ECR 주소로 변경이 필수이다. 그 이유는 각자의 이미지 저장소가 다르기 때문이다
- 태그는 항상 ‘latest’를 사용한다. 그 이유는 새 이미지 push 시 자동으로 latest로 변경되기 때문이다
이미지 버전 관리 흐름
- 기존 이미지 latest
- 새 이미지가 push → latest는 <none>으로 변경
- 새 이미지가 latest가 됨
포트 설정
ports: - containerPort: 8080
- Spring Boot 기본 포트는 8080
- application(-prod).yml에 별도로 포트를 지정하지 않으면 Spring Boot는 기본적으로 8080 포트를 사용한다
리소스 제한 설정
리소스 제한이 필요한 이유
[리소스 제한 없음] EC2 (t3.small: 2 vCPU, 2GB RAM) ├── Pod A: CPU 80% 사용 (부하 몰림) └── Pod B: CPU 20% 사용 (자원 부족) [리소스 제한 설정] EC2 (t3.small: 2 vCPU, 2GB RAM) ├── Pod A: CPU 최대 50% (제한됨) └── Pod B: CPU 최대 50% (균등 분배)
리소스 설정
resources:
limits: # 최대 사용 가능 리소스
cpu: "1"
memory: "500Mi"
requests: # 최소 보장 리소스
cpu: "0.2"
memory: "250Mi"
- limits: Pod가 사용할 수 있는 최대치로 CPU 1코어, 메모리 500Mi까지
- requests: Pod 시작 시 보장받는 최소 자원으로 CPU 0.2코어, 메모리 250Mi 확보
환경 변수와 Secret 연동
환경 변수 설정
env:
- name: DB_HOST
valueFrom:
secretKeyRef:
name: my-app-secrets
key: DB_HOST
- name: DB_PW
valueFrom:
secretKeyRef:
name: my-app-secrets
key: DB_PW
Spring 설정과의 연동
spring:
datasource:
url: jdbc:mysql://${DB_HOST}:3306/ordersystem?useSSL=false&allowPublicKeyRetrieval=true
username: admin
password: ${DB_PW}
연동 메커니즘
1. Secret 생성
DB_HOST: database-1.xxxxx.rds.amazonaws.com
DB_PW: your-password
2. Deployment env 설정
name: DB_HOST (← Spring의 ${DB_HOST}와 일치)
name: DB_PW (← Spring의 ${DB_PW}와 일치)
3. Pod 실행 시
환경 변수로 주입
DB_HOST=database-1.xxxxx.rds.amazonaws.com
DB_PW=your-password
4. Spring 실행 시
${DB_HOST} → 환경 변수에서 값을 가져옴
${DB_PW} → 환경 변수에서 값을 가져옴
핵심: Deployment의 env.name과 Spring의 ${} 변수명이 정확히 일치해야 한다
추가 변수 설정 방법
JWT Secret을 추가하고 싶다면 아래와 같은 방법을 사용한다
Secret 재생성
kubectl delete secret my-app-secrets -n hyeok kubectl create secret generic my-app-secrets \ --from-literal=DB_HOST=... \ --from-literal=DB_PW=... \ --from-literal=JWT_SECRET=your-jwt-secret \ -n hyeok
Deployment env 추가
env:
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: my-app-secrets
key: JWT_SECRET
application.yml 수정
jwt:
secretKey: ${JWT_SECRET}
ReadinessProbe와 롤링 업데이트
ReadinessProbe란 Pod가 실제로 준비되었는지 확인하는 헬스체크 메커니즘이다
설정
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
- path: 헬스체크 엔드포인트
- port: 확인할 포트
- initialDelaySeconds: 컨테이너 시작 후 첫 체크까지 대기 시간
- periodSeconds: 체크 반복 주기
롤링 업데이트 동작 원리
[기존 상태] Pod A (v1.0) - Running Pod B (v1.0) - Running [새 버전 배포 시작] Pod A (v1.0) - Running ← 아직 유지 Pod B (v1.0) - Running ← 아직 유지 Pod C (v2.0) - Starting (0/1 Ready) [Pod C 헬스체크] 10초 후: GET /health → 실패 (Spring 아직 시작 중) 20초 후: GET /health → 실패 30초 후: GET /health → 성공 (200 OK) [Pod C Ready] Pod A (v1.0) - Running Pod B (v1.0) - Running Pod C (v2.0) - Running (1/1 Ready) ← 준비 완료 [Pod A 교체] Pod A (v1.0) - Terminating ← 이제 종료 Pod B (v1.0) - Running Pod C (v2.0) - Running Pod D (v2.0) - Starting [최종 상태] Pod C (v2.0) - Running Pod D (v2.0) - Running
핵심: ReadinessProbe가 성공할 때까지 기존 Pod를 유지하므로 무중단 배포가 가능하다
Spring Controller 구현
HealthController
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HealthController {
@GetMapping("/health")
public String healthCheck() {
return "ok";
}
}
Service 구성
apiVersion: v1
kind: Service
metadata:
name: ordersystem-backend-service
namespace: hyeok
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8080
selector:
app: ordersystem-backend
- type: 클러스터 내부에서만 접근 가능 (Ingress 필요)
- port: Service가 노출하는 포트
- targetPort: Pod의 containerPort와 일치
- selector: Deployment의 label과 일치
Deployment 배포 및 트러블 슈팅
배포
kubectl apply -f depl_svc.yml 예상 출력 deployment.apps/ordersystem-backend created service/ordersystem-backend-service created
Pod 상태 확인
kubectl get pods -n hyeok
정상 상태
NAME READY STATUS RESTARTS AGE ordersystem-backend-6bbb6bb789-xxxxx 1/1 Running 0 2m ordersystem-backend-6bbb6bb789-yyyyy 1/1 Running 0 2m redis-84bbb78b75-zzzzz 1/1 Running 0 9h
READY 컬럼의 의미
- 1/1: Pod가 준비됨 (ReadinessProbe 성공)
- 0/1: Pod가 준비 안 됨 (Spring 시작 중 또는 에러)
일반적인 문제와 해결
kubectl describe pod <pod-name> -n hyeok
일반적인 원인
| 에러 메시지 | 원인 | 해결 방법 |
| no match for platform in manifest | CPU 아키텍처 불일치 (M1 Mac에서 빌드) | --platform linux/amd64 옵션으로 재빌드 |
not found | ECR URI 오타 | Deployment YAML의 이미지 URI 확인 |
| unauthorized | ECR 로그인 안 됨 | aws ecr get-login-password 재실행 |
문제 1: M1/M2/M3 Mac 사용자 해결
# 재빌드 (AMD64 아키텍처 지정) docker build --platform linux/amd64 \ -t 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/order-backend:latest . # 재업로드 docker push 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/order-backend:latest # Pod 재배포 kubectl delete -f depl_svc.yml kubectl apply -f depl_svc.yml
컴퓨터(Mac)는 ARM64 아키텍처이고, 배포하려는 AWS 서버(EC2)는 AMD64(x86_64) 아키텍처이기 때문에, Dockerfile의 내용은 문제가 없으나 빌드 명령어를 수정해야 한다
문제 2: Error / CrashLoopBackOff
증상으로는 Status가 Error이 일어나면서 Restart가 n번 실행한다
로그 확인
kubectl logs -f <pod-name> -n hyeok
- Unknown database: RDS 스키마 미생성 및 오타
- Access denied for user: DB 비밀번호 오류로 Secret의 DB_PW 확인
- Connection refused: 프로파일 설정 오류
문제 3: Pending 상태
증상으로는 STATUS가 Pending 상태로 계속 머무른다
원인 확인
kubectl describe pod <pod-name> -n hyeok
- Insufficient cpu: CPU 리소스 부족으로 requests.cpu를 낮추거나 (예: 0.5 → 0.2), 서버 스펙을 올리면 해결
- Insufficient memory: 메모리 부족으로 EC2 인스턴스 타입 업그레이드 또는 requests 조정
문제 4: 프로파일 설정 오류
증상으로는 Connection refused: localhost:6379이나 localhost:3306이 있다
원인: application.yml 프로파일에 설정 오류이다
해결 방법 1: application.yml 수정
spring:
profiles:
default: prod # local에서 prod로 변경
application-local.yml
spring :
config:
activate:
on-profile: local
application-prod.yml
spring :
config:
activate:
on-profile: prod
prod.yml 프로파일을 사용해야 하는데 local로 사용하도록 설정한 후 빌드 하여 생긴 문제로 수정 이후 반드시 재빌드 및 재배포를 해주어야 한다
docker build --platform linux/amd64 -t <ECR-URI>:latest . docker push <ECR-URI>:latest
해결 방법 2: 환경 변수로 강제 지정
env: - name: SPRING_PROFILES_ACTIVE value: "prod"
Ingress 설정 및 외부 접근
ingress.yml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: order-backend-ingress
namespace: hyeok
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: my-issuer # HTTPS 설정 (다음 단계)
spec:
tls: # HTTPS 설정 (다음 단계)
- hosts:
- "server.hyeok4444.shop"
secretName: server-hyeok4444-com-tls
rules:
- host: server.hyeok4444.shop
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ordersystem-backend-service # Service 이름과 일치
port:
number: 80
Ingress 배포
kubectl apply -f ingress.yml
HTTP 테스트 (HTTPS 설정 전)
포스트맨을 사용하여서 HTTP 통신이 잘 되는지 테스트를 진행하였다
헬스체크
GET http://server.hyeok4444.shop/health → 응답: "ok"
로그인
POST http://server.hyeok4444.shop/member/doLogin
{
"email": "hong@example.com",
"password": "1234"
}
→ 응답: { "token": "eyJhbGciOiJIUzI1..." }
상품 생성 (인증 필요)
POST http://server.hyeok4444.shop/product/create
Headers:
Authorization: Bearer eyJhbGciOiJIUzI1...
Body:
{
"name": "상품명",
"price": 10000
}
주문 생성 (인증 필요)
POST http://server.hyeok4444.shop/ordering/create
Headers:
Authorization: Bearer eyJhbGciOiJIUzI1...
Body:
{
"productId": 1,
"quantity": 2
}
롤링 업데이트 실습
수동으로 롤링 업데이트 트리거
kubectl rollout restart deployment ordersystem-backend -n hyeok
- Deployment YAML 변경 없이도 실행
- 새로운 ReplicaSet 생성
- 이미지를 다시 Pull(latest 태그)
- Pod를 순차적으로 교체
롤링 업데이트 과정 관찰
kubectl get pods -n hyeok NAME READY STATUS RESTARTS AGE ordersystem-backend-5b487bb675-xxxxx 1/1 Running 0 42m ← 기존 ordersystem-backend-5b487bb675-yyyyy 1/1 Running 0 42m ← 기존 ordersystem-backend-9649ccc9c-zzzzz 0/1 Running 0 3s ← 새로운 (아직 Ready 아님) 2단계: 첫 번째 새 Pod가 Ready ordersystem-backend-5b487bb675-xxxxx 1/1 Running 0 43m ← 기존 (곧 종료) ordersystem-backend-5b487bb675-yyyyy 1/1 Running 0 43m ← 기존 ordersystem-backend-9649ccc9c-zzzzz 1/1 Running 0 34s ← 새로운 (Ready!) ordersystem-backend-9649ccc9c-wwwww 0/1 Running 0 12s ← 새로운 (시작 중) 3단계: 모든 Pod 교체 완료 ordersystem-backend-9649ccc9c-zzzzz 1/1 Running 0 55s ← 새로운 ordersystem-backend-9649ccc9c-wwwww 1/1 Running 0 33s ← 새로운
ReadinessProbe 동작 확인
- 0초: Pod 생성, 컨테이너 시작
- 10초: 첫 번째 /health 요청 → 실패 (Spring 시작 중)
- 20초: 두 번째 /health 요청 → 실패
- 25초: Spring 완전히 시작됨
- 30초: 세 번째 /health 요청 → 성공 (200 OK)
- Pod READY 상태로 전환 (1/1)
- 기존 Pod 종료 시작
데이터베이스 확인
데이터베이스 테이블 생성을 확인한다
USE ordersystem; SHOW TABLES; 예상 결과 +----------------------+ | Tables_in_ordersystem| +----------------------+ | member | | ordering | | product | +----------------------+