일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
- springboot
- JPA
- 중간 장소 추천
- 30기
- GitHub Actions
- redis
- 객체지향 쿼리 언어
- java
- 한국대학생it경영학회
- 쿠버네티스
- Domain Driven Design
- 약속 장소 추천
- Spring
- Spring Batch
- 영속성
- 중간 지점 추천
- ddd
- 도메인 주도 개발 시작하기
- Container Registry
- 자바 ORM 표준 JPA 프로그래밍
- RESTClient
- kusitms
- cicd
- K3S
- 이펙티브자바
- 백엔드
- 최범균
- 모임 장소 추천
- 큐시즘
- JPQL
- Today
- Total
코딩은 마라톤
[Kubernetes] K3s로 운영·스테이징 환경을 단일 서버에서 분리하기 (2) 본문
https://developer-anxi.tistory.com/79
[Kubernetes] K3s로 운영·스테이징 환경을 단일 서버에서 분리하기 (1)
나는 현재 중간 지점 추천 서비스인 "스팟(SPOT)"을 디벨롭하고 있으며 8월 중에 릴리즈 예정이다.지금은 UI 개선과 API 로직 개선 등 다방면에서 개선하고 있다. SPOT은 릴리즈 전인 지금도 사용할
developer-anxi.tistory.com
1부에서는 K3s로 단일서버에서 운영·스테이징 환경으로 분리한 이유를 적어보았다.
이번에는 단일서버에서 분리한 방식을 적어보려 한다.
쿠버네티스를 적용한 아키텍처
기존 아키텍처와 바뀐 부분은 쿠버네티스를 적용해서 Ingress 및 운영, 스테이징 네임서버 분리 그리고 스프링 애플리케이션 파드와 레디스 파드를 추가하였다.
요청 흐름은 아래와 같다.
- 인그레스 컨트롤러인 Traefik에서 요청 도메인에 따라 Prod, Staging으로 연결한다.
- 네임스페이스에서 Service를 통해 스프링 애플리케이션 파드로 접속한다. 이때 레디스 파드도 Service를 통해 스프링 애플리케이션 파드와 내부적으로 연결된다.
이제 K3s 설치부터 네임스페이스 생성, 리소스 매니페스트 작성, 인그레스 설정 및 Github Actions 배포 파이프라인을 알아보자.
1. K3s 설치
도커는 설치되었다고 가정한다.
# k3s 설치
curl -sfL https://get.k3s.io | sh -
# k3s.yaml 권한 설정
sudo chmod 644 /etc/rancher/k3s/k3s.yaml
K3s를 설치하고 kubeconfig 파일인 k3s.yaml 권한을 설정해서 kubectl 명령어를 사용할 수 있도록 했다.
2. Namespace 생성
운영, 스테이징으로 격리하기 위해선 네임스페이스를 생성해야 한다.
kubectl create namespace prod # 운영
kubectl create namespace stg # 스테이징
3. NCP Container Registry 시크릿 생성 (선택)
3번은 선택사항이다.
스팟은 Naver Cloud Platform의 Container Registry를 사용해서 스프링 애플리케이션 이미지를 관리하고 있다.
아래 쿠버네티스 파드 생성 시 보겠지만, 프라이빗 저장소(AWS ECR이나 Docker Hub Private)에서는 Docker Image를 가져올 수 없기 때문에 시크릿을 생성하여 접근 권한을 부여해야 한다.
kubectl create secret docker-registry ncp-registry-secret \
--docker-server={} \ # NCP Registry 이름
--docker-username={} \ # Access Key ID
--docker-password={} \ # Secret Key
--namespace=prod
kubectl create secret docker-registry ncp-registry-secret \
--docker-server={} \ # NCP Registry 이름
--docker-username={} \ # Access Key ID
--docker-password={} \ # Secret Key
--namespace=stg
참고
시크릿 생성에서 --namespace를 지정하는 것과 같이 파드나 서비스 등의 리소스 생성 시 네임스페이스 설정은 필수!
4. 스프링 애플리케이션 파드, 서비스 생성
현재 운영(prod)과 스테이징(stg)의 차이는 네임스페이스(namespace)뿐이므로, 아래는 운영 환경을 기준으로 리소스를 생성하는 과정을 설명한다.
Deployment 리소스
apiVersion: apps/v1
kind: Deployment
metadata:
name: spot-backend
namespace: prod
spec:
replicas: 1
selector:
matchLabels:
app: spot-backend
template:
metadata:
labels:
app: spot-backend
spec:
containers:
- name: spot-backend
image: __IMAGE__
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: prod
- name: TZ
value: Asia/Seoul
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 40
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
imagePullSecrets:
- name: ncp-registry-secret
설명
- Deployment 사용
파드를 직접 생성하는 대신, Deployment 리소스를 사용해 파드 관리 및 배포를 세밀하게 하도록 했다. - 네임스페이스 분리
metadata.namespace에 prod 또는 stg를 명시하여, 운영/스테이징 환경을 논리적으로 격리했다. - Spring Profile 설정
SPRING_PROFILES_ACTIVE=prod 환경변수를 통해 해당 환경에 맞는 설정 파일(application-prod.yml)을 사용하도록 구성했다. - livenessProbe
/actuator/health/liveness 엔드포인트를 통해 컨테이너가 정상 작동 중인지 주기적으로 확인한다.
스프링 애플리케이션의 기동 시간(start-up time)을 고려해 initialDelaySeconds를 40초로 설정하였다. - readinessProbe
/actuator/health/readiness를 통해 애플리케이션이 실제 요청을 처리할 준비가 되었는지를 확인한다.
준비되지 않은 상태에서는 서비스에 트래픽이 전달되지 않도록 제어한다. - imagePullSecrets
프라이빗 레지스트리(NCP Container Registry)에서 이미지를 가져오기 위해, 3번에서 생성한 ncp-registry-secret을 설정했다.
Service 리소스
apiVersion: v1
kind: Service
metadata:
name: spot-backend
namespace: prod
spec:
selector:
app: spot-backend
ports:
- protocol: TCP
port: 80
targetPort: 8080
설명
- Service는 내부적으로 spot-backend라는 이름의 Deployment에 연결된다. selector를 통해 app: spot-backend 라벨을 가진 파드와 연결된다.
- port는 외부에서 접근할 포트를 의미하고, targetPort는 파드 내부 컨테이너가 실제로 리스닝하는 포트를 의미한다. 즉, 클라이언트는 80번 포트로 요청을 보내지만, 실제로는 8080번 포트로 전달된다.
- Ingress Controller에서 서비스의 80 포트로 전달하고, 이는 Spring Boot 애플리케이션의 8080 포트로 연결된다.
5. 레디스 파드, 서비스 생성
Deployment 리소스
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: prod
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7.4
ports:
- containerPort: 6379
volumeMounts:
- mountPath: /data
name: redis-storage
volumes:
- name: redis-storage
emptyDir: { }
설명
- 스프링 애플리케이션 파드 생성과 유사하지만 다른 점은 volume 설정이다.
- emptyDir 볼륨 사용
/data 디렉터리에 데이터를 저장하도록 volumeMounts를 설정했으며, 여기에 연결된 볼륨은 emptyDir이다.
이는 파드가 살아 있는 동안만 유지되는 임시 저장소로, 파드가 재시작되거나 삭제되면 데이터도 함께 사라진다. - 스팟은 레디스에 경로 데이터를 임시로 캐싱하는데, 데이터가 유실돼도 외부 API를 통해 다시 가져오기 때문에 PersistentVolume 대신 emptyDir를 사용했다.
- emptyDir 볼륨 사용
Service 리소스
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: prod
spec:
type: NodePort
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
nodePort: 30079
설명
- 스프링 애플리케이션 서비스 생성과 유사하지만 다른 점은 ServiceType이 ClusterIP가 아닌 NodePort를 사용했다.
- 외부 API Rate Limiter를 만들어서 외부 API 호출 수를 확인해야 하는데, ClusterIP는 클러스터 내부에서만 연결되기 때문에 외부에서 연결하기 위해 NodePort를 사용했다.
- 서버 IP:30079를 사용해서 레디스에 접속 가능하며, 특정 IP에서만 접속할 수 있도록 서버 인바운드에 스팟 개발자만 추가했다.
- 참고) https://developer-anxi.tistory.com/76
[Redis] 외부 API Rate Limiter를 만들어보기 (with. AOP)
SPOT은... 외부 API 호출이 많다. SPOT의 메인 기능은 애플리케이션 레벨에서 최적의 중간 지점(역)을 계산하고, 이후 여러 출발지부터 중간 지점까지의 경로를 보여주는 것이다.이때 출발지 ~ 중간
developer-anxi.tistory.com
6. 인그레스 생성 + SSL 인증 추가
스팟에서는 Traefik을 인그레스 컨트롤러로 사용한다.
Traefik은 K3s 번들로 기본 제공되며, Let's Encrypt(ACME) 인증서를 자동으로 발급 및 갱신해 주고 아래 사진과 같이 여러 기능을 사용할 수 있기 때문에 추가적으로 인그레스 컨트롤러를 설치하지 않았다.
SSL 인증서는 아래 링크를 통해 쉽게 적용할 수 있다.
k3s 클러스터에 SSL 인증 추가하기: 상세 가이드
k3s 클러스터에 SSL 인증 추가하기: 상세 가이드
medium.com
인그레스 리소스
SSL 인증서 발급이 완료되었다면, 이제 인그레스 리소스를 통해 외부 트래픽을 스프링 애플리케이션 서비스로 연결한다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: spot-backend-ingress
namespace: prod
annotations:
cert-manager.io/cluster-issuer: "prod-letsencrypt" # 인증서 발급용 클러스터 발급자
kubernetes.io/ingress.class: "traefik" # Traefik Ingress Controller 사용
spec:
tls:
- hosts:
- {도메인}
secretName: prod-tls # 인증서가 저장될 시크릿 이름
rules:
- host: {도메인}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: spot-backend
port:
number: 80
설명
- cert-manager.io/cluster-issuer
SSL 인증서 발급을 위해 미리 생성한 ClusterIssuer (prod-letsencrypt)를 지정하여, Let's Encrypt를 통한 인증서 발급이 가능하도록 했다. - secretName
발급된 TLS 인증서는 Kubernetes Secret(prod-tls)에 저장되며, Traefik은 이를 사용해 HTTPS 통신을 제공한다. - rules
특정 host(도메인)로 들어온 요청을 spot-backend 서비스의 80번 포트로 라우팅 한다.
kubectl apply -f ingress.yaml # 인그레스 리소스 이름
이후 서버에서 인그레스 리소스를 적용한다.
7. GitHub Actions Workflow 변경
https://developer-anxi.tistory.com/78
[KUSITMS] SPOT x 네이버 클라우드 : Container Registry를 이용하여 서버 배포 자동화하기
🍀 네이버 클라우드를 이용해 SPOT 서버 개발큐시즘 30기에 이어 31기에서도 네이버 클라우드에서 다양한 서비스를 프로덕트에 적용해 볼 수 있도록 크레딧을 제공해 주셨다 🥳🥳🥳이번 SPOT을
developer-anxi.tistory.com
위 글에서는 Docker Compose 기반으로 구성된 기존의 GitHub Actions 배포 Workflow를 확인할 수 있다.
이번에는 K3s + Traefik으로 환경이 바뀜에 따라, Deploy Job이 변경되었다.
deploy:
needs: build
name: deploy
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: true
token: ${{ secrets.GH_TOKEN }}
- name: Set IMAGE variable
run: echo "IMAGE=${{ secrets.NCP_CONTAINER_REGISTRY }}/spot-backend:${{ github.sha }}" >> $GITHUB_ENV
- name: Substitute image tag in deployment.yaml
run: |
sed -i "s|__IMAGE__|${IMAGE}|g" deploy/prod/spot-backend-deployment.yaml
- name: Copy deployment.yaml to server
uses: appleboy/scp-action@master
with:
host: ${{ secrets.NCP_HOST }}
username: ${{ secrets.NCP_USERNAME }}
password: ${{ secrets.NCP_PASSWORD }}
source: deploy/prod/
target: /home/ubuntu/deploy/prod
strip_components: 2
- name: Deploy to K3s via kubectl
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.NCP_HOST }}
username: ${{ secrets.NCP_USERNAME }}
password: ${{ secrets.NCP_PASSWORD }}
port: ${{ secrets.NCP_PORT }}
script: |
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
kubectl apply -f /home/ubuntu/deploy/prod/
변경 사항
- Set IMAGE variable & Substitute image tag in deployment.yaml
스프링 애플리케이션 Deployment의 Image는 __IMAGE__ 로 설정되어 있다. 도커 이미지의 태그가 동적으로 변경되기 때문에 이미지 이름을 환경변수로 지정한다. 그리고 환경변수를 Deployment Image에 수정한다. - Deploy to K3s via kubectl
kubectl apply -f 명령어를 사용하기 위해 KUBECONFIG를 명시한다. kubectl apply -f 명령으로 deploy/prod/ 디렉터리 내 모든 매니페스트 파일을 한 번에 적용한다.
배포 이후, 위 명령어를 통해 스프링 애플리케이션과 레디스 파드가 띄어졌음을 확인할 수 있다.
🎬 마무리
이로써 1부에서는 Docker Compose 대신 K3s를 선택한 이유를, 2부에서는 K3s를 이용해 단일 서버에서 운영·스테이징 환경을 분리한 과정을 다뤄보았다.
단일 노드로 구축해본 경험이라 쿠버네티스의 기능을 완전히 사용해보지 못해 아쉽다. 하지만 스팟 출시 이후 트래픽이 증가해서 서버 증설을 해야할 경우, 시스템 확장이나 장애 대응에도 유리할 것이라 기대하고 있다.
앞으로는 모니터링을 공부해서 서버 리소스 사용량을 파악하고, Resource Quotas를 설정해 서버 리소스를 효율적으로 관리할 예정이다. 또한 레플리카 수를 늘려 운영 환경에서 롤링 업데이트 기반의 무중단 배포도 적용해볼 계획이다.
아직 쿠버네티스의 일부 기능만 학습하고 적용해본 수준이지만, 앞으로도 꾸준히 학습을 이어가며 HPA(Horizontal Pod Autoscaler)를 활용한 오토 스케일링도 적용해보고 싶다.
'Backend > CI CD' 카테고리의 다른 글
[Kubernetes] K3s로 운영·스테이징 환경을 단일 서버에서 분리하기 (1) (0) | 2025.07.22 |
---|---|
[FAIL] Github Actions을 이용해 Docker Cache 관리하고 싶었으나,, (2) | 2024.04.21 |
Github Actions, Docker, EC2를 이용한 CI/CD 구현하기 (3) | 2024.04.07 |