안녕하세요! Devloo입니다. 🙂 현대 애플리케이션 개발에서는 GitHub Actions와 같은 CI/CD를 통해 마이크로서비스와 컨테이너 오케스트레이션을 연동하는 것이 매우 중요합니다. Spring Boot의 간결함과 유연성을 활용하면 Kubernetes와 함께 분산 애플리케이션의 배포, 확장 및 관리를 원활하게 할 수 있습니다.
이 글에서는 Spring Boot 마이크로서비스를 생성하고 이를 Kubernetes 클러스터에 배포하는 방법을 설명합니다. 이러한 기술들의 결합된 잠재력을 활용하는 간단한 가이드를 제공합니다. 클라우드 네이티브 솔루션을 도입하는 조직이 늘어나면서 Spring Boot와 Kubernetes의 원활한 연동을 이해하는 것이 민첩하고 유지보수가 쉬우며 확장 가능한 애플리케이션을 구축하는 데 필수적입니다.
이 튜토리얼에서는 Gradle을 빌드 시스템으로 사용하여 Spring Boot 마이크로서비스를 만들고 이를 Kubernetes 클러스터에 배포하는 과정을 단계별로 안내해 드리겠습니다.
사전에 필요한 것들
튜토리얼을 시작하기 전에 다음 도구들이 설치되어 있는지 확인하세요:
- 자바 개발 키트 (JDK)
- Docker
- 실행 중인 Kubernetes 클러스터 (로컬 개발의 경우 Minikube와 같은 도구를 사용할 수 있습니다)
- Gradle (Gradle wrapper를 사용할 경우 Gradle을 수동으로 설치할 필요는 없습니다)
1단계: Spring Boot 프로젝트 설정
Spring Initializer 또는 선호하는 방법을 사용하여 새로운 Spring Boot 프로젝트를 생성합니다. 프로젝트 요구 사항에 따라 Spring Data, Security, JPA 등의 필요한 종속성을 build.gradle
파일에 포함할 수 있습니다. 이 튜토리얼에서는 프로젝트를 단순하게 유지하기 위해 Spring Web 종속성만 포함했습니다.
settings.gradle
파일에서 앱의 이름을 정의합니다:
rootProject.name = 'kube-app'
필요한 종속성을 build.gradle
파일에 추가합니다:
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.1'
id 'io.spring.dependency-management' version '1.1.4'
}
group = 'com.yourcompany'
version = '1.0'
java {
sourceCompatibility = '21'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
프로젝트가 올바르게 구성되었는지 확인하려면 다음 명령어로 빌드할 수 있습니다:
./gradlew build
그런 다음 프로젝트 디렉터리에서 다음 명령어로 프로젝트를 실행합니다:
./gradlew bootRun
웹 브라우저를 열고 http://localhost:8080
으로 이동하여 마이크로서비스가 실행 중인지 확인합니다.
2단계: Post 레코드와 PostController 생성
Post 엔티티 클래스를 정의하여 게시물의 데이터 구조를 나타냅니다. id, title, content와 같은 필드를 추가합니다.
package com.yourcompany.kubeapp.post;
public record Post(Integer id, String title, String content) {
}
Lombok 같은 프레임워크를 사용하지 않고도 간단하게 불변성을 제공하기 위해 클래스 대신 Record를 사용합니다.
이제 게시물과 관련된 HTTP 요청을 처리하기 위해 REST 컨트롤러 클래스(PostController
)를 생성합니다. @RestController
어너테이션을 사용하여 API의 기본 경로를 정의합니다.
package com.yourcompany.kubeapp.post;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.stream.IntStream;
@RestController
public class PostController {
private final TextPopulator populator;
public PostController(TextPopulator populator) {
this.populator = populator;
}
@GetMapping("/")
public String helloWorld() {
return "Hello World";
}
@GetMapping("/posts")
public List<Post> listPosts() {
return IntStream.rangeClosed(1, 20)
.mapToObj(this::createPost)
.toList();
}
private Post createPost(int id) {
String title = populator.randomTitle();
String content = String.format("%s %s", populator.randomContent(), populator.randomContent());
return new Post(id, title, content);
}
}
PostController
는 /
와 /posts
엔드포인트에서 데이터를 반환하도록 설정되어 있습니다.
Spring Boot 애플리케이션을 실행하고 웹 브라우저나 API 테스트 도구에서 http://localhost:8080/posts로 이동하세요. RapidAPI, Postman 또는 Swagger와 같은 도구를 사용하여 노출된 엔드포인트와 상호작용할 수 있습니다.
프로젝트가 성공적으로 실행되면 Dockerize / Containerize 단계로 넘어갈 수 있습니다.
3단계: Docker 이미지 구성
Spring Boot 마이크로서비스의 Docker 이미지를 정의하기 위해 프로젝트 루트에 Dockerfile을 생성하세요. 다음은 기본 예제입니다:
FROM openjdk:21-slim
WORKDIR /app
COPY build/libs/kube-app-1.0.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
여기서 kube-app
은 settings.gradle
파일의 rootProject.name
속성에서 가져온 프로젝트 이름이고, 버전 정보는 build.gradle
파일의 version
속성에서 가져옵니다. Dockerfile의 아래 지시문을 프로젝트 이름과 버전에 맞게 변경해야 합니다:
COPY build/libs/<your-app-name>-<version>.jar app.jar
Dockerfile을 필요에 맞게 구성한 후, 다음 명령어로 Docker 이미지를 빌드할 수 있습니다:
docker build -t kube-app .
Docker 이미지를 빌드한 후, 다음 명령어를 실행하여 이미지를 실행합니다:
docker run -p 8080:8080 kube-app
마이크로서비스를 설정한 후, 선호하는 웹 브라우저나 Postman 또는 RapidAPI와 같은 REST 도구를 사용하여 http://localhost:8080으로 이동하세요. /
와 /posts
엔드포인트에서 예상되는 응답을 확인하세요. 모든 것이 올바르게 구성되고 마이크로서비스가 적절한 값을 반환하면, 설정이 완료된 것입니다.
4단계: GitHub 액세스 토큰과 Docker 레지스트리 시크릿 생성
GitHub Actions 워크플로를 준비하기 전에, 이 링크에서 GitHub 개인 액세스 토큰을 생성해야 합니다. 토큰의 이름을 지정하고 레지스트리 이미지를 읽기 위해 read:packages
범위만 선택하세요. 토큰을 생성한 후에는 이를 안전한 곳에 저장해야 합니다. GitHub 액세스 토큰 페이지에서는 나중에 다시 볼 수 없습니다.
GitHub 개인 액세스 토큰을 생성한 후, 기존 Kubernetes 클러스터에서 다음 명령어를 실행해 GitHub 액세스 토큰을 사용하여 Docker 레지스트리 시크릿을 생성하세요:
kubectl create secret docker-registry my-docker-registry \
--docker-server=ghcr.io \
--docker-username=DEPLOYMENT_TOKEN \
--docker-password=TOKEN_VALUE
시크릿의 이름(my-docker-registry-secret
)에 주의하세요. 이 시크릿은 나중에 Kubernetes deployment.yaml
파일에서 다음과 같이 GitHub 프라이빗 패키지/Docker 레지스트리에서 애플리케이션의 Docker 이미지를 가져오는 데 사용됩니다:
spec:
containers:
- name: kube-app
image: ghcr.io/mustafaguc/kube-app:latest
imagePullSecrets:
- name: my-docker-registry
이 과정을 통해 Kubernetes 클러스터가 GitHub 레지스트리에서 Docker 이미지를 가져올 수 있게 됩니다.
5단계: Kubernetes 구성 파일 가져오기 및 GitHub Actions 시크릿 변수 생성
GitHub Actions가 Kubernetes 클러스터에 접근할 수 있도록 하려면 Kubernetes 클러스터 구성 파일의 내용을 복사하여 GitHub 저장소에 GitHub Actions 시크릿 변수로 저장해야 합니다.
Kubernetes 클러스터 구성 파일을 복사하려면 클러스터에서 다음 명령어를 실행하세요:
kubectl config view --minify --raw > kubeconfig.yaml
kubeconfig 파일에는 Kubernetes 클러스터에 접근하기 위한 민감한 정보가 포함되어 있으므로 이를 안전하게 보관해야 합니다.
kubeconfig.yaml
의 내용을 클립보드에 복사합니다. GitHub 저장소 설정으로 이동하여 이 링크에서 GitHub Actions 시크릿을 생성하세요. 시크릿 변수의 이름을 KUBECONFIG
로 지정하고 클립보드에서 복사한 kubeconfig.yaml
의 내용을 붙여넣습니다. 시크릿 변수의 이름은 나중에 GitHub Actions 워크플로 파일에서 사용되므로 중요합니다.
6단계: Kubernetes 배포, 서비스 및 인그레스 리소스 생성
Spring Boot 마이크로서비스 애플리케이션을 Kubernetes 클러스터에 배포하고 실행하려면 배포, 서비스 및 인그레스 리소스를 생성해야 합니다. Kubernetes 클러스터는 애플리케이션 실행을 위해 deployment.yaml
파일만 필요하지만, 클러스터 내 마이크로서비스 간 통신과 애플리케이션을 외부에 노출하기 위해 서비스 및 인그레스 리소스도 정의해야 합니다. 이를 더 쉽게 관리하기 위해 세 가지 리소스 파일을 하나의 project.yaml
리소스 파일로 통합했습니다.
# 배포 설정
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: kube-app
name: kube-app
namespace: default
spec:
selector:
matchLabels:
app: kube-app
template:
metadata:
labels:
app: kube-app
name: kube-app
namespace: default
spec:
containers:
- image: ghcr.io/mustafaguc/kube-app:latest
imagePullPolicy: Always
name: container-0
ports:
- containerPort: 8080
name: http
protocol: TCP
resources: {}
dnsPolicy: ClusterFirst
imagePullSecrets:
- name: my-docker-registry
restartPolicy: Always
# 서비스 설정
---
apiVersion: v1
kind: Service
metadata:
name: kube-app
namespace: default
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 8080
selector:
app: kube-app
sessionAffinity: None
type: ClusterIP
# 인그레스 설정
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: kube-app
namespace: default
spec:
ingressClassName: nginx
rules:
- host: kube-app.example.com
http:
paths:
- backend:
service:
name: kube-app
port:
name: http
path: /
pathType: Prefix
중요한 설정 값
배포, 서비스, 인그레스 이름 및 매치 레이블(matchLabels):
제공된 구성에서 kube-app
은 배포, 서비스 및 인그레스의 이름으로 사용됩니다. 이러한 이름을 필요에 따라 변경할 수 있으며, 선택자 레이블과 일치하도록 조정하면 됩니다.
Docker 이미지:ghcr.io/<github-username>/<repository-name>:latest
Docker 이미지를 사용합니다. 이 값을 프로젝트에 맞게 변경해야 합니다.
Docker 이미지 풀 시크릿:my-docker-registry
시크릿 값은 GitHub 프라이빗 Docker 레지스트리에서 Docker 이미지를 가져오기 위해 사용됩니다. 이 시크릿은 기존 Kubernetes 클러스터에서 생성되어야 합니다. 이 시크릿의 구성은 4단계에서 설명되었습니다.
Kubernetes에는 다양한 구성 방법이 있지만, 이 글에서는 단순하고 간결하게 설명하기 위해 상세한 구성은 생략했습니다.
7단계: GitHub Actions 워크플로우 생성
GitHub Actions를 이용한 Kubernetes 배포 워크플로우는 GitHub 저장소에서 Kubernetes 클러스터로 애플리케이션을 직접 배포하는 중요한 자동화 과정입니다. 이 워크플로우는 Kubernetes 환경에서 배포 과정을 자동화하고 간소화하며, 신뢰성을 높여 더 효율적이고 협업적인 개발 생태계를 조성합니다.
Gradle 기반의 Java Spring Boot 마이크로서비스 애플리케이션을 Kubernetes 클러스터에 빌드, 푸시 및 배포하려면 다음과 같은 GitHub Actions 워크플로우를 사용할 수 있습니다:
# 이 워크플로우는 Gradle을 사용하여 Java 프로젝트를 빌드하고, 종속성을 캐시/복원하여 워크플로우 실행 시간을 개선합니다.
# 자세한 내용은 https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle 를 참조하세요.
name: Java Gradle Build & Docker Push
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
# 비어 있을 경우 Docker Hub용 docker.io 사용
REGISTRY: ghcr.io
# github.repository를 <account>/<repo>로 사용
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
steps:
- uses: actions/checkout@v3
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'
- name: Build with Gradle
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
with:
arguments: build
- name: Install cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 #v3.1.1
with:
cosign-release: 'v2.1.1'
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
name: Deploy
needs: [ build ]
runs-on: ubuntu-latest
steps:
- name: Set the Kubernetes context
uses: azure/k8s-set-context@v3
with:
method: kubeconfig
kubeconfig: ${{ secrets.KUBECONFIG }}
- name: Checkout source code
uses: actions/checkout@v3
- name: Deploy to the Kubernetes cluster
uses: azure/k8s-deploy@v4
with:
skip-tls-verify: true
manifests: |
kubernetes/project.yaml
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
이 간소화된 워크플로우는 다음의 기본 단계로 구성됩니다:
빌드 단계:
- 코드 체크아웃: 빌드 프로세스를 시작하기 위해 저장소에서 소스 코드를 가져옵니다.
- Gradle 빌드: Gradle 빌드 프로세스를 실행하여 Spring Boot 마이크로서비스 애플리케이션을 컴파일하고 준비합니다.
- Docker 빌드: 애플리케이션을 캡슐화하고 이식성을 보장하는 Docker 이미지를 생성합니다.
- Docker 푸시: 새로 빌드한 Docker 이미지를 안전한 프라이빗 GitHub 레지스트리에 업로드하여 버전 관리된 이미지를 중앙 저장소에 저장합니다.
배포 단계:
- 코드 체크아웃: 이후 배포 단계를 위해 소스 코드를 다시 가져옵니다.
- Kubernetes 컨텍스트 설정: 지정된 클러스터에서 kubectl 명령을 실행할 수 있도록 Kubernetes 컨텍스트를 구성합니다.
- Kubernetes 클러스터에 배포: kubectl을 사용하여 프로젝트를 Kubernetes 클러스터에 배포하고, 업데이트된 애플리케이션을 원활하게 통합합니다.
이 워크플로우는 사용과 적용이 쉽도록 설계되었으며, 최소한의 수정만으로도 활용할 수 있습니다. 필수적인 커스터마이징은 5단계에서 설명한 대로 KUBECONFIG 값을 GitHub Actions 시크릿으로 정의하는 것입니다. 이를 통해 Kubernetes 클러스터에 안전한 접근을 보장할 수 있습니다.
위의 7가지 단계를 잘 따라해보시면, Spring Boot 마이크로서비스를 Kubernetes 클러스터에서 원활하게 자동으로 빌드하고 배포할 수 있어 소프트웨어 개발 생애 주기에서 효율성과 일관성을 높일 수 있습니다.
이번 시간에는 Kubernetes와 Github Actions를 활용한 Spring Boot 마이크로서비스 배포하는 방법을 알아 보았습니다. 궁금하신 사항은 편하게 댓글로 남겨주세요 ! 끝까지 읽어주셔서 감사합니다 🙂