EKS – Kubernetes Secret과 Spring Deployment

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 manifestCPU 아키텍처 불일치 (M1 Mac에서 빌드)--platform linux/amd64 옵션으로 재빌드
not foundECR URI 오타Deployment YAML의 이미지 URI 확인
unauthorizedECR 로그인 안 됨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              |
+----------------------+

출처 – eks를 활용한 spring 운영서버 배포(feat. devops의 모든것)