로컬에서 쿠버네티스 + GitLab CI/CD 구축하기 (2)

나근민·2025년 4월 9일

멀티 스테이지 Dockerfile과 Kubernetes로 프론트엔드 애플리케이션 배포하기

안녕하세요! 오늘은 Vue.js 프론트엔드 애플리케이션을 Docker와 Kubernetes를 이용해 배포하는 방법에 대해 알아볼 예정이다! 처음부터 차근차근 진행하면서 각 단계별로 필요한 파일들을 상세히 설명할거니 끝까지 함께 따라와보자!

📚 목차

  1. 프론트엔드 Dockerfile 작성하기
  2. Nginx 설정 파일 구성
  3. Kubernetes 배포 구성하기
  4. GitLab 저장소 연동하기
  5. CI/CD 오류 해결하기
  6. 미니큐브에서 실행하기

프론트엔드 Dockerfile 작성하기

먼저 Vue.js 프론트엔드 애플리케이션을 위한 Dockerfile을 작성해보자!

# 빌드 단계 Node.js 이미지를 사용하여 Vue.js 애플리케이션 빌드
FROM node:16 as build
WORKDIR /app                   # 컨테이너 내부의 작업 디렉토리 설정
COPY package*.json ./          # package.json과 package-lock.json 먼저 복사
RUN npm install                # 의존성 설치
COPY . .                       # 소스 코드 전체 복사
RUN npm run build              # Vue.js 애플리케이션 빌드 실행

# 실행 단계 빌드된 파일을 Nginx로 서빙
FROM nginx:alpine              # 경량화된 Nginx 이미지 사용
# 빌드 단계에서 생성된 dist 폴더를 Nginx의 웹 루트 디렉토리로 복사
COPY --from=build /app/dist /usr/share/nginx/html
# Nginx 설정 파일 복사
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80                      # 80번 포트 노출
CMD ["nginx", "-g", "daemon off;"]  # Nginx 서버 실행 명령

이 Dockerfile은 멀티 스테이지 빌드라는 기법을 사용한다!

  1. 첫 번째 스테이지 Node.js 환경에서 Vue.js 애플리케이션을 빌드한다
  2. 두 번째 스테이지 빌드된 정적 파일만 Nginx 이미지로 복사해서 경량화된 최종 이미지를 만든다

이렇게 하면 빌드 도구나 소스 코드가 최종 이미지에 포함되지 않아서 이미지 크기가 작아지고 보안성도 좋아진다!

Dockerfile 명령어 이해하기

  • FROM node:16 as build Node.js 16 버전 이미지를 사용하고, 이 단계에 "build"라는 이름을 붙인다
  • WORKDIR /app 컨테이너 안에서 작업할 기본 디렉토리를 지정한다 (일반 컴퓨터에서 cd 명령어로 디렉토리 이동하는 것과 비슷함)
  • COPY package*.json ./ 의존성 파일을 먼저 복사한다
  • COPY . . 첫 번째 점은 내 컴퓨터의 현재 폴더, 두 번째 점은 컨테이너의 현재 작업 폴더(/app)를 의미한다

이렇게 package*.json을 먼저 복사하고 npm install을 실행한 다음 소스 코드를 복사하는 이유는 Docker의 레이어 캐싱 기능을 활용하기 위해서다! 의존성 설치는 시간이 오래 걸리지만 자주 변경되지 않으니까, 소스 코드가 변경되어도 이 단계는 캐시를 사용할 수 있어서 빌드 시간이 단축된다!

Nginx 설정 파일 구성

이제 Nginx 설정 파일을 작성해보자!

server {
    listen 80;                # 80번 포트에서 HTTP 요청 대기
    server_name localhost;    # 서버 이름 설정

    root /usr/share/nginx/html;  # 웹 루트 디렉토리 설정
    index index.html;           # 기본 인덱스 파일 설정

    location / {
        try_files $uri $uri/ /index.html;  # SPA를 위한 URL 리다이렉션 설정
    }

    # 백엔드 API 프록시 설정
    location /api {
        proxy_pass http://backend-app:8080;  # /api 요청을 백엔드로 전달
        proxy_set_header Host $host;         # 원본 호스트 헤더 유지
        proxy_set_header X-Real-IP $remote_addr;  # 클라이언트 IP 정보 전달
    }
}

이 설정 파일은 Nginx가 두 가지 중요한 역할을 수행하도록 해준다!

  1. 정적 파일 서빙 Vue.js 애플리케이션의 빌드된 파일들을 제공한다
  2. API 요청 프록시 /api로 시작하는 요청을 백엔드 서비스(backend-app)로 전달한다

특히 try_files $uri $uri/ /index.html; 설정은 SPA(Single Page Application)에서 매우 중요하다! Vue Router 같은 클라이언트 측 라우팅을 사용할 때, 모든 URL이 서버에서 처리되지 않고 index.html로 돌아가서 클라이언트 측에서 라우팅이 처리되도록 해준다.

Kubernetes 배포 구성하기

이제 Kubernetes에 애플리케이션을 배포하기 위한 설정 파일을 작성해보자!

Deployment 파일 (frontend-deployment.yaml)

apiVersion: apps/v1          # Kubernetes API 버전
kind: Deployment             # 리소스 유형 Deployment
metadata:
  name: vue-login            # 배포 이름
  labels:
    app: vue-login           # 리소스 식별 라벨
spec:
  replicas: 1                # 실행할 Pod 복제본 수
  selector:
    matchLabels:
      app: vue-login         # 이 Deployment가 관리할 Pod 선택기
  template:
    metadata:
      labels:
        app: vue-login       # Pod에 적용할 라벨
    spec:
      containers:
      - name: vue-login                        # 컨테이너 이름
        image: ${DOCKER_REGISTRY}/vue-login:latest  # 사용할 이미지
        ports:
        - containerPort: 80                    # 컨테이너가 노출하는 포트
        resources:
          limits:                              # 리소스 사용 상한선
            cpu: "0.5"                         # 최대 0.5 CPU 코어
            memory: "512Mi"                    # 최대 512MB 메모리
          requests:                            # 리소스 요청 (최소 보장)
            cpu: "0.2"                         # 최소 0.2 CPU 코어
            memory: "256Mi"                    # 최소 256MB 메모리
        livenessProbe:                         # 컨테이너 생존 확인 설정
          httpGet:
            path: /                            # 확인할 HTTP 경로
            port: 80                           # 확인할 포트
          initialDelaySeconds: 30              # 시작 후 검사 시작까지 대기 시간
          periodSeconds: 10                    # 검사 주기
        readinessProbe:                        # 컨테이너 준비 상태 확인 설정
          httpGet:
            path: /                            # 확인할 HTTP 경로
            port: 80                           # 확인할 포트
          initialDelaySeconds: 5               # 시작 후 검사 시작까지 대기 시간
          periodSeconds: 5                     # 검사 주기

이 파일은 쿠버네티스에 Vue.js 프론트엔드 애플리케이션을 어떻게 배포할지 정의한다!

  1. 복제본 수(replicas) 1개의 Pod를 유지한다
  2. 컨테이너 설정 도커 이미지, 포트, 리소스 제한 등을 지정한다
  3. 상태 검사
    • livenessProbe 컨테이너가 살아있는지 확인하고, 응답이 없으면 재시작한다
    • readinessProbe 컨테이너가 요청을 처리할 준비가 됐는지 확인하고, 준비되지 않았으면 트래픽을 보내지 않는다

Service 파일 (frontend-service.yaml)

apiVersion: v1        # Kubernetes API 버전
kind: Service         # 리소스 유형 Service
metadata:
  name: vue-login     # 서비스 이름
spec:
  selector:
    app: vue-login    # 트래픽을 전달할 Pod 선택기
  ports:
  - port: 80          # 서비스 포트
    targetPort: 80    # 컨테이너 포트
  type: NodePort      # 서비스 유형 NodePort

이 파일은 배포된 Pod에 접근하기 위한 서비스를 정의한다!

  1. selector app: vue-login 라벨을 가진 Pod들을 서비스 대상으로 선택한다
  2. 포트 매핑 서비스의 80번 포트를 Pod의 80번 포트로 연결한다
  3. 서비스 유형 NodePort는 각 노드의 IP와 특정 포트를 통해 서비스에 접근할 수 있게 해준다

GitLab 저장소 연동하기

이제 작성한 파일들을 GitLab 저장소에 푸시해보자!

1. 현재 remote 확인하기

git remote -v

이 명령어를 실행하면 origin이라는 이름으로 GitHub 주소만 나올 거다.

2. GitLab remote 추가하기

git remote add gitlab https://gitlab.com/rmsals2020/vue-login.git

여기서 gitlab은 remote 이름이고, 원하는 이름으로 바꿔도 되지만 나중에 기억하기 쉽게 이렇게 두는 게 좋다!

3. GitLab 저장소로 푸시하기

git add .
git commit -m "CI/CD 파이프라인 설정 추가"
git push gitlab main

main 대신 master를 쓰는 경우도 있으니, 브랜치 이름 확인은 아래 명령어로 확인해보자!

git branch

이제 GitLab으로 돌아가보면 "CI/CD 파이프라인 설정 추가"라고 커밋 메시지로 쓴 내용이 push된 걸 확인할 수 있다!

CI/CD 오류 해결하기

GitLab CI/CD 파이프라인을 설정하면서 여러 오류가 발생할 수 있다. 몇 가지 일반적인 오류와 해결 방법을 살펴보자!

1. Docker 레지스트리 연결 오류

$ docker push $DOCKER_REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHORT_SHA
The push refers to repository [localhost:5000/vue-login]
Get "http://localhost:5000/v2/": dial tcp [::1]:5000: connect: connection refused

원인 CI/CD 파이프라인에서 Docker 이미지를 로컬 레지스트리(localhost:5000)로 푸시하려고 시도했으나, 이 레지스트리가 존재하지 않거나 접근할 수 없다!

해결 방법 DOCKER_REGISTRY 변수를 로컬 레지스트리 주소에서 GitLab 레지스트리 주소(registry.gitlab.com/사용자명/프로젝트명)로 변경하자!

2. GitLab 레지스트리 접근 권한 오류

$ docker push $DOCKER_REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHORT_SHA
denied: requested access to the resource is denied

원인 GitLab 레지스트리에 이미지를 푸시하려고 했으나, 레지스트리에 접근 권한이 없다!

해결 방법 CI/CD 스크립트에 Docker 로그인 단계를 추가하자!

- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY

이렇게 GitLab이 자동으로 제공하는 인증 변수를 사용하면 레지스트리에 로그인할 수 있다!

3. Kubernetes 명령어 관련 오류

error: unknown command "sh" for "kubectl"

원인 kubectl 명령어가 제대로 실행되지 않거나 Kubernetes 클러스터 접속을 위한 인증 정보가 없다!

해결 방법
1. 다른 베이스 이미지(alpine:latest) 사용하기
2. kubectl 직접 설치하기
3. 쉘 스크립트 블록으로 명령어 실행하기

deploy:
  stage: deploy
  image: alpine:latest
  script:
    - apk add --no-cache bash curl
    - |
      # 배포 스크립트
      set -e
      
      # kubectl 설치
      curl -LO "https://dl.k8s.io/release/v1.27.0/bin/linux/amd64/kubectl"
      chmod +x kubectl
      mv kubectl /usr/local/bin/
      
      # 나머지 배포 명령...

4. 미니큐브 환경에서 CI/CD 연동 문제

CI/CD 파이프라인에서 로컬 미니큐브 클러스터에 접근하려고 할 때 경로 불일치나 네트워크 접근 제한 등의 문제가 발생할 수 있다!

해결 방법 CI/CD 파이프라인에서는 이미지 빌드와 레지스트리 푸시까지만 수행하고, 실제 배포는 로컬에서 수동으로 처리하자!

stages:
  - build
  - package

# 여기에 build, package 단계 설정...

미니큐브에서 실행하기

로컬 환경에서 미니큐브를 이용해 배포해보자!

  1. 배포 파일에서 이미지 이름 수정하기

    image: registry.gitlab.com/rmsals2020/vue-login/vue-login:latest
  2. 쿠버네티스에 배포하기

    kubectl apply -f kubernetes/frontend-deployment.yaml
    kubectl apply -f kubernetes/frontend-service.yaml
  3. 서비스 확인하기

    kubectl get pods
    minikube service vue-login

이렇게 하면 브라우저가 열리면서 배포된 Vue.js 애플리케이션에 접속할 수 있다!

정리

오늘은 Vue.js 프론트엔드 애플리케이션을 Docker와 Kubernetes를 사용해 배포하는 방법을 알아봤다! 멀티 스테이지 빌드로 효율적인 Docker 이미지를 만들고, Kubernetes 배포 구성으로 안정적인 운영 환경을 구성했다! GitLab CI/CD 연동 과정에서 발생할 수 있는 다양한 오류들과 해결 방법도 살펴봤다!

CI/CD 파이프라인을 구축하면 코드 변경사항을 자동으로 테스트하고 배포할 수 있어서 개발 생산성이 크게 높아진다! 다음에는 백엔드 애플리케이션을 배포하고 프론트엔드와 연동하는 방법에 대해 알아보자!

궁금한 점이나 의견이 있으면 댓글로 남겨주세요! 다음 글에서 만나요! 👋

profile
개발 공부중인 학생입니다~

0개의 댓글