k8s로 리액트를 배포해보자.

devicii·2025년 5월 18일

클라우드

목록 보기
3/3

1. React 앱 구성 및 빌드

처음엔 역시 리액트 프로젝트부터 시작했다.
npx create-react-app my-app 한 줄이면 기본 뼈대가 뚝딱 만들어진다. Vite나 Next.js로도 충분히 대체 가능하다.
프로젝트 폴더가 생성되면, npm install![](https://velog.velcdn.com/images/asroq1/post/f9b97d4f-74e6-47f5-b2e0-f12a1b680818/image.png) 로 필요한 라이브러리들을 쭉 깔아준다.

개발 서버는 npm run dev로 바로 확인 가능.
(개발 중엔 이 명령어를 거의 손에 달고 살게 된다.)

프로덕션 빌드는 npm run build로 진행.
이 과정에서 소스코드가 최적화된 정적 파일(HTML, JS, CSS 등)로 변환된다.
build 혹은 dist 폴더에 결과물이 생기는데, 이게 바로 실제 배포에 올라가는 파일들이다.


2. Docker 컨테이너화

2-1. .dockerignore 파일 만들기

Docker 빌드할 때 불필요한 파일들(node_modules, .git 등)이 이미지에 포함되면 용량도 커지고 빌드도 느려진다.
.gitignore랑 비슷하게 .dockerignore 파일을 만들어서 이런 애들은 아예 빌드에서 제외시킨다.

2-2. Dockerfile 작성

컨테이너 이미지를 어떻게 만들지 설계도 그리듯 작성한다.

# 1단계: 빌드
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# 2단계: Nginx로 서비스
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

1단계에서 Node.js로 빌드하고, 2단계에서 Nginx로 정적 파일을 서비스한다.
빌드 도구는 최종 이미지에 남지 않으니, 이미지는 가볍고 빠르다.

2-3. 도커 이미지 빌드 & 실행

docker build -t my-react-app .
이렇게 하면 Dockerfile을 읽어서 이미지를 만든다.

실행은
docker run -d -p 8080:80 my-react-app
로컬 8080 포트로 접속하면 Nginx에서 리액트 앱이 서비스되는 걸 볼 수 있다.


3. Kubernetes 배포

3-1. 배포 정의 (Deployment YAML)

쿠버네티스에서는 “이 앱을 어떻게, 몇 개나, 어떤 식으로 돌릴지”를 YAML 파일로 정의한다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: react-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: react-app
  template:
    metadata:
      labels:
        app: react-app
    spec:
      containers:
      - name: react-container
        image: my-react-app:latest
        ports:
        - containerPort: 80

replicas로 파드 개수를 지정하고, 이미지와 포트를 설정한다.

3-2. 서비스 정의 (Service YAML)

외부에서 내 앱에 접속하려면 Service 리소스를 만들어야 한다.

apiVersion: v1
kind: Service
metadata:
  name: react-service
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: react-app

type이 LoadBalancer면 클라우드 환경에서 외부 IP가 할당된다.
(로컬 쿠버네티스에서는 NodePort나 Ingress로 대체 가능)

3-3. 쿠버네티스에 배포

kubectl apply -f deploy.yaml
kubectl apply -f service.yaml
명령어 두 방이면 클러스터에 리소스가 생성된다.

3-4. 배포 상태 확인

kubectl get deployments
배포가 잘 됐는지 READY, AVAILABLE 등 상태를 꼭 확인하자.


4. CI/CD & GitOps

Jenkins

코드가 바뀌면 자동으로 빌드/테스트/도커 이미지 생성/레지스트리 푸시/쿠버네티스 적용까지 쭉 이어주는 자동화 파이프라인을 만든다.

이 Jenkinsfile은 React 앱을 자동으로 빌드하고, Docker 이미지로 패키징해서 레지스트리에 푸시한 뒤, 쿠버네티스 배포 파일까지 자동으로 갱신해주는 풀-오토메이션 파이프라인이다.

단계(Stage)주요 역할 및 설명
Clone Repository지정한 GitHub 저장소와 브랜치에서 소스 코드를 클론합니다.
Build React AppNode.js 컨테이너 환경에서 npm install 및 npm run build로 리액트 앱을 빌드합니다.
Docker Build & Push빌드된 소스코드로 Docker 이미지를 생성하고, 고유 태그로 이미지 레지스트리에 푸시합니다.
Update deploy.yaml and Pushdeploy.yaml의 이미지 태그를 최신으로 갱신하고, GitHub 저장소에 자동 커밋/푸시합니다.

실제 Jenkinsfile 예시는 아래와 같다.

pipeline {
    agent any  // 어떤 agent(노드)에서든 실행

    environment {
        // 파이프라인 전체에서 사용할 환경 변수 정의
        GIT_URL = 'https://github.com/asroq1/my-42-app'           // 소스 코드 저장소
        GIT_BRANCH = 'main'                                       // 기본 브랜치
        GIT_ID = 'skala-github-id'                                // GitHub PAT credential ID
        GIT_USER_NAME = 'asroq1'                                  // GitHub 사용자 이름
        GIT_USER_EMAIL = 'asroq7434@gmail.com'
        IMAGE_REGISTRY = 'amdp-registry.skala-ai.com/skala25a'    // 도커 이미지 레지스트리 주소
        IMAGE_NAME = 'sk088-my-42-app'                            // 이미지 이름
        IMAGE_TAG = '2.0.0'                                       // 기본 태그
        DOCKER_CREDENTIAL_ID = 'skala-image-registry-id'          // 도커 레지스트리 인증 정보 ID
    }

    stages {
        stage('Clone Repository') {
            steps {
                // GitHub에서 소스코드 체크아웃
                git branch: "${GIT_BRANCH}",
                    url: "${GIT_URL}",
                    credentialsId: "${GIT_ID}"
            }
        }

        stage('Build React App') {
            agent {
                docker {
                    image 'node:16-alpine'  // Node.js가 설치된 컨테이너에서 빌드
                    reuseNode true          // 워크스페이스 재사용
                }
            }
            steps {
                sh '''
                    npm install
                    chmod +x node_modules/.bin/react-scripts
                    npm run build
                '''
                // 의존성 설치 후, 리액트 앱 빌드
            }
        }

        stage('Docker Build & Push') {
            steps {
                script {
                    // 빌드마다 유니크한 이미지 태그 생성 (버전-BUILD번호-해시)
                    def hashcode = sh(
                        script: "date +%s%N | sha256sum | cut -c1-12",
                        returnStdout: true
                    ).trim()
                    def FINAL_IMAGE_TAG = "${IMAGE_TAG}-${BUILD_NUMBER}-${hashcode}"
                    echo "Final Image Tag: ${FINAL_IMAGE_TAG}"

                    // 도커 빌드 & 레지스트리 푸시 (amd64 아키텍처 지정)
                    docker.withRegistry("https://${IMAGE_REGISTRY}", "${DOCKER_CREDENTIAL_ID}") {
                        def appImage = docker.build("${IMAGE_REGISTRY}/${IMAGE_NAME}:${FINAL_IMAGE_TAG}", "--platform linux/amd64 .")
                        appImage.push()
                    }
                    // 이후 deploy.yaml에서 사용할 태그를 env에 저장
                    env.FINAL_IMAGE_TAG = FINAL_IMAGE_TAG
                }
            }
        }

        stage('Update deploy.yaml and Git Push') {
            steps {
                script {
                    // deploy.yaml의 image 라인만 새 태그로 치환
                    def newImageLine = "        image: ${env.IMAGE_REGISTRY}/${env.IMAGE_NAME}:${env.FINAL_IMAGE_TAG}"
                    def gitRepoPath = env.GIT_URL.replaceFirst(/^https?:\/\//, '')

                    sh """
                        sed -i 's|^[[:space:]]*image:.*\$|${newImageLine}|g' ./argocd-k8s/deploy.yaml
                        cat ./argocd-k8s/deploy.yaml
                    """

                    // 변경된 deploy.yaml을 커밋 준비
                    sh """
                        git config user.name "$GIT_USER_NAME"
                        git config user.email "$GIT_USER_EMAIL"
                        git add ./argocd-k8s/deploy.yaml || true
                    """

                    // 깃허브 PAT로 푸시 (변경사항 있을 때만)
                    withCredentials([usernamePassword(credentialsId: "${env.GIT_ID}", usernameVariable: 'GIT_PUSH_USER', passwordVariable: 'GIT_PUSH_PASSWORD')]) {
                        sh """
                            if ! git diff --cached --quiet; then
                                git commit -m "[AUTO] Update deploy.yaml with image ${env.FINAL_IMAGE_TAG}"
                                git remote set-url origin https://${GIT_PUSH_USER}:${GIT_PUSH_PASSWORD}@${gitRepoPath}
                                git push origin ${env.GIT_BRANCH}
                            else
                                echo "No changes to commit."
                            fi
                        """
                    }
                }
            }
        }
    }
}

ArgoCD

ArgoCD는 내가 처음 써봤을 때 자동화의 끝판왕이구나 싶었던 도구다.

어떻게 동작하냐면?

  • Git 저장소만 계속 바라본다.

    • ex) Jenkins가 빌드 끝나고 deploy.yaml의 이미지 태그를 최신으로 바꿔서 Git에 푸시하면 ArgoCD는 이 변화를 바로 감지한다.
  • 실제 쿠버네티스 클러스터와 Git의 상태를 항상 비교한다.

    • 지금 클러스터에 올라간 리소스 상태와 Git에 정의된 매니페스트가 다르면,
    • ArgoCD가 자동으로 클러스터를 Git 상태로 맞춰준다.
    • 이걸 동기화(Sync)라고 부른다.
  • 수동 배포? 이제는 안녕

    • 예전에는 kubectl apply로 직접 배포했지만 이제는 Git에만 올리면 자동으로 배포된다.
    • 실수로 잘못 배포해도, Git의 이전 커밋으로 롤백하면 ArgoCD가 알아서 복구해준다.

실전에서 느낀 ArgoCD의 장점

  • 배포 이력과 상태가 Git에 남으니, 언제든 추적/롤백이 가능하다.
  • 배포 실수, 누락 걱정이 줄고, 운영이 훨씬 안정적이다.
  • ArgoCD 대시보드에서 각 앱의 상태, 동기화 여부, 헬스 상태까지 한눈에 볼 수 있다.
  • Helm, Kustomize, 여러 클러스터까지도 한 번에 관리 가능하다.

5. 실행 플로우 한눈에 보기

  1. 개발자가 코드 작성 → Git에 Push
  2. CI 파이프라인이 빌드/테스트/도커 이미지 생성 → 레지스트리 Push
  3. 템플릿 처리로 YAML 파일에 최신 이미지 태그 반영
  4. ArgoCD가 Git 저장소 감시 → 변경 감지 시 쿠버네티스에 자동 배포
  5. 쿠버네티스 클러스터에서 Pod/Service/Ingress 생성 및 동작
  6. 사용자는 도메인으로 HTTPS 접속 → Nginx 컨테이너에서 React 앱 서비스

이 구조 덕분에, 코드만 고치고 푸시하면 배포까지 자동으로 쭉쭉 진행된다.
(실제로 써보면 이 맛에 GitOps 한다는 말이 절로 나온다.)


6. 문제 해결 & 최적화 팁

  • 도커 권한 문제: 도커 소켓 권한 맞춰주면 해결
  • CI에서 Node.js 환경 문제: Jenkins 에이전트에 Node.js 설치 필요
  • 컨테이너 생성 오류: ConfigMap/Secret 누락 시 오류 발생 → 리소스 생성 잊지 말기
  • 헬스체크: livenessProbe/readinessProbe로 파드 상태 자동 감시

7. 요약 표

단계주요 행위상세 설명
React 앱 구성개발/빌드소스코드 작성 및 정적 파일 생성
Docker 컨테이너화.dockerignore, Dockerfile, 빌드불필요 파일 제외, 이미지 설계, 빌드/실행
Kubernetes 배포deploy/service.yaml, 배포원하는 상태 정의, 네트워크 연결, 실제 클러스터에 배포
CI/CD & GitOpsJenkins/ArgoCD 자동화코드 변경 → 자동 빌드/배포, Git 기반 배포 일관성 유지
문제 해결/최적화권한, 환경, 헬스체크 등각종 오류 해결 및 서비스 안정성, 효율성 강화

마무리하며

이번 프로젝트를 하면서 모던 앱 배포의 핵심 원리를 체험했다.
단순히 기술을 나열하는 게 아니라 실제로 써보고 겪은 시행착오와 개선점을 기록해두니 나중에 다시 봐도 큰 도움이 된다.
이 구조 덕분에 코드만 고치고 Git에 올리면 자동으로 빌드/배포까지 쭉 이어지는 환경이 완성됐다.
코드가 배포 되는 순간 개발자로서의 만족감은 언제나 짜릿하다.

profile
흘러가는대로 사는

0개의 댓글