k8s 직접 해보기 (4) - 선언적 방식으로 서버 이미지 배포하기

Endermaru·2025년 6월 24일

k8s 직접 해보기

목록 보기
5/11

목표

  • local 프로필을 포함한 각종 환경변수 설정
  • 서버에 필요한 Redis 컨테이너 연결(Spring Actuator로 확인)

cf. 만약 앱이 Spring Actuator 설정이 되어 있지 않다면, 다음 자료를 참고하여 미리 적용, 이미지로 배포되어야 함.

1. Manifest 파일로 deployment, service 선언

spring-deployment.yaml v1

  • --- 구분자를 기준으로 여러 파일로 나눠서 등록 가능(spring-deployment.yaml, spring-service.yaml)
# deployment
apiVersion: apps/v1 # Kubernetes 리소스 안정화(stable) 버전
kind: Deployment
metadata:
  name: spring # deployment 이름
spec:
  replicas: 1  # 동시에 실행가능한 pod 개수
  selector:
    matchLabels:
      app: spring # Deployment가 관리할 Pod를 고르는 기준(label)
  # pod 템플릿
  template:
    metadata:
      labels:
        app: spring # app: spring 라벨(selector.matchLabels와 반드시 일치해야 deployment가 pod 관리 가능)
    spec:
      # Pod 안에서 돌 컨테이너 목록
      containers:
        - name: spring
          image: endermaru/22-5-team1-server
          ports:
            - containerPort: 8080
---
# service
apiVersion: v1
kind: Service
metadata:
  name: spring  # 서비스 이름
spec:
  type: LoadBalancer 
  selector:
    app: spring    # 어떤 pod에 트래픽을 전달할지 -> app: spring 라벨
  ports:
    - port: 8080        # service port: 다른 Pod나 외부에서 이 서비스에 접근할 때 사용
      targetPort: 8080  # target port: 서비스가 선택된 Pod의 컨테이너로 트래픽을 전달할 포트

v1 적용

  • 만약 여러 파일로 나눴다면 개별적으로 적용 필요
$ kubectl apply -f spring-deployment.yaml
deployment.apps/spring created
service/spring created

$ minikube service spring

$ kubectl logs spring-64dc785cdf-d57vf
...
No active profile set, falling back to 1 default profile: "default"
... 
  • /actuator/health는 Down(redis 연결 불가)
  • readiness, liveness는 Up
  • 프로필이 설정되지 않아 default 프로필 설정

2. env 속성으로 프로필 설정하기

spring-deployment.yaml v2

  • Pod template에 env를 추가(spec.template.spec.containers[].env)
# deployment
apiVersion: apps/v1 # Kubernetes 리소스 안정화(stable) 버전
kind: Deployment
metadata:
  name: spring # deployment 이름
spec:
  replicas: 1  # 동시에 실행가능한 pod 개수
  selector:
    matchLabels:
      app: spring # Deployment가 관리할 Pod를 고르는 기준(label)
  # pod 템플릿
  template:
    metadata:
      labels:
        app: spring # app: spring 라벨(selector.matchLabels와 반드시 일치해야 deployment가 pod 관리 가능)
    spec:
      # Pod 안에서 돌 컨테이너 목록
      containers:
        - name: spring
          image: endermaru/22-5-team1-server
          ports:
            - containerPort: 8080
          # env 속성으로 환경변수 설정하기
          env:
            - name: SPRING_PROFILES_ACTIVE
              value: local
---
# service
apiVersion: v1
kind: Service
metadata:
  name: spring  # 서비스 이름
spec:
  type: LoadBalancer 
  selector:
    app: spring    # 어떤 pod에 트래픽을 전달할지 -> app: spring 라벨
  ports:
    - port: 8080        # service port: 다른 Pod나 외부에서 이 서비스에 접근할 때 사용
      targetPort: 8080  # target port: 서비스가 선택된 Pod의 컨테이너로 트래픽을 전달할 포트

v2 적용

  • yaml 파일의 변경점을 감지, Deployment는 자동으로 롤링 업데이트를 수행
    → 기존 Pod는 점진적으로 종료되고, 새 설정(env 포함)을 반영한 새 Pod가 순차적으로 올라옴
  • replicas: 1이라 pod가 종료되고 다시 새로운 pod가 생김
$ kubectl apply -f spring-deployment.yaml
deployment.apps/spring configured
service/spring unchanged

$ kubectl get pods
NAME                      READY   STATUS    RESTARTS   AGE
spring-54fcc7ddbf-qv78c   1/1     Running   0          80s

$ kubectl logs spring-54fcc7ddbf-qv78c
...
The following 1 profile is active: "local"
...

3. configmap으로 환경변수 설정하기

spring-deployment.yaml v3

  • ConfigMap 리소스를 정의: 모든 key-value 쌍이 컨테이너의 환경변수로 자동 설정
  • Deployment에서도 개별 환경변수를 설정하는 env 대신 ConfigMap 전체를 가져와 환경변수로 등록하는 envFrom 사용
  • redis의 health check만을 확인하기 위해 Mail Health Check는 비활성화
# deployment
apiVersion: apps/v1 # Kubernetes 리소스 안정화(stable) 버전
kind: Deployment
metadata:
  name: spring # deployment 이름
spec:
  replicas: 1  # 동시에 실행가능한 pod 개수
  selector:
    matchLabels:
      app: spring # Deployment가 관리할 Pod를 고르는 기준(label)
  # pod 템플릿
  template:
    metadata:
      labels:
        app: spring # app: spring 라벨(selector.matchLabels와 반드시 일치해야 deployment가 pod 관리 가능)
    spec:
      # Pod 안에서 돌 컨테이너 목록
      containers:
        - name: spring
          image: endermaru/22-5-team1-server
          ports:
            - containerPort: 8080
          # ConfigMapRef를 정의해서 환경변수를 가져옴
          envFrom:
            - configMapRef:
                name: spring-config # 사용할 configmap 이름
---
# service
apiVersion: v1
kind: Service
metadata:
  name: spring  # 서비스 이름
spec:
  type: LoadBalancer 
  selector:
    app: spring    # 어떤 pod에 트래픽을 전달할지 -> app: spring 라벨
  ports:
    - port: 8080        # service port: 다른 Pod나 외부에서 이 서비스에 접근할 때 사용
      targetPort: 8080  # target port: 서비스가 선택된 Pod의 컨테이너로 트래픽을 전달할 포트
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: spring-config
data:
  SPRING_PROFILES_ACTIVE: local
  SPRING_MAIL_USERNAME: internhasha.dev@gmail.com
  # Health Check 상세 보기 & Mail Health Check 비활성화
  MANAGEMENT_ENDPOINT_HEALTH_SHOW_DETAILS: "always"
  MANAGEMENT_HEALTH_MAIL_ENABLED: "false"
  # 다른 환경변수도 설정 가능

v3 적용

  • local 프로필 적용됨
# ConfigMap을 새로 추가 & Spring Deployment 업데이트
$ kubectl apply -f spring-deployment.yaml

$ kubectl logs spring-5ddb6b7d4d-ts5dm
...
The following 1 profile is active: "local"
...


# cf. ConfigMap을 변경할 경우 재배포가 자동으로 되지 않으므로 재시작 필요
$ kubectl apply -f spring-deployment.yaml
$ kubectl rollout restart deployment spring
  • Health Check
    • minikube service spring & /actuator/health 경로로 접속
      → Redis가 연결되지 않아 Down 상태
{
  "status": "DOWN",
  "groups": [
    "liveness",
    "readiness",
    "startup"
  ],
  "components": {
    "db": {
      "status": "UP",
      "details": {
        "database": "H2",
        "validationQuery": "isValid()"
      }
    },
    "diskSpace": {
      "status": "UP",
      "details": {
        "total": 1081101176832,
        "free": 1019471405056,
        "threshold": 10485760,
        "path": "/app/.",
        "exists": true
      }
    },
    "livenessState": {
      "status": "UP"
    },
    "ping": {
      "status": "UP"
    },
    "readinessState": {
      "status": "UP"
    },
    "redis": {
      "status": "DOWN",
      "details": {
        "error": "org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis"
      }
    },
    "ssl": {
      "status": "UP",
      "details": {
        "validChains": [],
        "invalidChains": []
      }
    }
  }
}

4. Redis도 같이 띄우기

  • 서버와 Redis를 각자 Pod으로 분리해서 Deployment + Service로 연결하는 방식
  • redis 서비스는 ClusterIP 타입 : 클러스터 내부에서만 접근 가능한 가상의 IP 주소를 부여하는 서비스 타입
    • type을 따로 지정 안 하면 ClusterIP 타입
    • 외부 접속이 가능한 NodePort, LoadBalancer와 달리 외부 접근 불가능

redis-deployment.yaml

# Redis Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
        - name: redis
          image: redis:7.0-alpine
          ports:
            - containerPort: 6379 # 외부에서 Redis에 접근할 포트
---
apiVersion: v1
kind: Service
metadata:
  name: redis  # application.yml의 redis host 이름과 일치해야함!
               # 서비스가 다른 네임스페이스(redisNameSpace)에 있다면
               # application.yml의 redis host는 serviceName.redisNameSpace가 되어야 함
spec:
  # type을 따로 지정하지 않으면 ClusterIP 타입
  selector:
    app: redis
  ports:
    - port: 6379    # application.yml의 redis port와 일치해야 함!
      targetPort: 6379

v4 적용

# redis deployment, pod, service를 생성
$ kubectl apply -f redis-deployment.yaml
deployment.apps/redis created
service/redis created

# spring deployment 재시작
$ kubectl rollout restart deployment spring
deployment.apps/spring restarted

# 확인
$ kubectl get pods,svc
NAME                          READY   STATUS    RESTARTS   AGE
pod/redis-5dd6f67ff-kvt82     1/1     Running   0          23m
pod/spring-67955bb566-b2lc2   1/1     Running   0          79s

NAME                 TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
service/kubernetes   ClusterIP      10.96.0.1      <none>        443/TCP          4d20h
service/redis        ClusterIP      10.98.209.17   <none>        6379/TCP         23m
service/spring       LoadBalancer   10.98.44.112   <pending>     8080:31628/TCP   58m
  • Health Check
    • minikube service spring & /actuator/health 경로로 접속
      → Redis가 연결되어 전체 상태도 Up 상태
{
  "status": "UP",
  "groups": [
    "liveness",
    "readiness",
    "startup"
  ],
  "components": {
    "db": {
      "status": "UP",
      "details": {
        "database": "H2",
        "validationQuery": "isValid()"
      }
    },
    "diskSpace": {
      "status": "UP",
      "details": {
        "total": 1081101176832,
        "free": 1019471253504,
        "threshold": 10485760,
        "path": "/app/.",
        "exists": true
      }
    },
    "livenessState": {
      "status": "UP"
    },
    "ping": {
      "status": "UP"
    },
    "readinessState": {
      "status": "UP"
    },
    "redis": {
      "status": "UP",
      "details": {
        "version": "7.0.15"
      }
    },
    "ssl": {
      "status": "UP",
      "details": {
        "validChains": [],
        "invalidChains": []
      }
    }
  }
}
  • 도식화
                       ┌────────────────────────────────┐
                       │      External Client        │
                       │   (e.g., Browser, Postman)  │
                       └────────────┬───────────────────┘
                                   │
                                   ▼
                       ┌───────────────────────────────┐
                       │  Service: spring (LoadBalancer)
                       │  EXTERNAL-IP: 127.0.0.1:12277
                       └────────────┬──────────────────┘
                                   │
                                   ▼
                      ┌──────────────────────────────────┐
                      │     Pod: spring               │
                      │  - Container: spring          │
                      │  - Env: SPRING_REDIS_HOST=redis
                      └────────────┬─────────────────────┘
                                  │
                                  ▼
                   ┌────────────────────────────────────────────┐
                   │      Service: redis (ClusterIP)        │
                   │      - Internal name: redis:6379       │
                   └────────────┬───────────────────────────────┘
                               │
                               ▼
                      ┌──────────────────────────────────┐
                      │     Pod: redis                │
                      │  - Container: redis:7.0-alpine│
                      └──────────────────────────────────┘

정리

  • 선언적 방식은 리소스(Deployment, Service, ConfigMap 등)를 yaml 형태로 정의, kubectl apply를 이용한 생성과 업데이트를 통해 유지보수, 버전관리 등에 있어 유용함
  • 데이터베이스(mysql, redis 등) 또한 새로운 Deployment로 Pod을 생성하고, ClusterIP 서비스를 정의하여 Spring Pod에서 접근 가능(서비스의 metadata.name이 Spring 앱의 host 이름으로 사용됨)
  • 그러나 민감한 환경변수가 ConfigMap에 그대로 노출되는 문제가 발생함
    → Secret 리소스, Vault 등으로 값 노출을 방지하는 방법 적용 필요

0개의 댓글