본문 바로가기

DevOps/CI\CD

[CI/CD] 권한이 제한된 환경에서 GitLab CI/CD 구축하기: Kaniko, SSH

3줄 요약

  • 문제: Runner 설정과 서버 root 권한 변경 불가,배포 대상은 Docker Compose로 운영되는 단일.
  • 해결: Docker daemon 없이 이미지를 빌드하는 Kaniko와 SSH를 통한 Docker Compose 배포.
  • 결과: 수동 배포 절차를 main 브랜치 push 를 트리거로 자동 배포.

1. 왜 이 구조를 선택했는가? (제약 조건)

일반적인 Docker-in-Docker(dind)나 Kubernetes 배포 방식을 사용할 수 없는 환경이었습니다.

  • 서버 Root 및 Runner 권한 없음: privileged 옵션 설정 불가 ➔ Kaniko 사용
  • 단일 서버 운영: Kubernetes가 아닌 Docker Compose 환경 ➔ SSH 접속 배포
  • 서버 환경 파편화: 서버마다 다른 Compose 명령/경로 ➔ compose.sh 래퍼 스크립트 활용
[개발자 main Push]
       │
       ▼
[GitLab Runner] ──(Kaniko)──► [Container Registry (이미지 Push)]
       │
     (SSH)
       ▼
[배포 서버] ──► [compose.sh 실행] ──► [프론트엔드 컨테이너 재시작]

2. 사전 준비: GitLab CI/CD Variables 설정

프로젝트의 Settings > CI/CD > Variables에 아래 환경 변수를 등록합니다. (민감 정보는 Masked 체크)

 

REGISTRY_USER REGISTRY_PASSWORD는 Docker Hub 계정 정보(또는 Access Token)를 사용합니다. 발급 방법은 [Docker] Docker Hub란? Access Token 발급 및 사용법 글을 참고하세요.

SSH_PRIVATE_KEYSSH_KNOWN_HOSTS는 SSH 공개키 인증에 사용됩니다. 키 생성 및 known_hosts 등록 방법은 [DevOps] CI/CD를 위한 SSH Private Key와 known_hosts 설정 글을 참고하세요. 

 

변수명 설명 예시
REGISTRY_USER Docker Hub 사용자명 my-user
REGISTRY_PASSWORD Docker Hub 비밀번호 또는 Access Token glpat-xxx...
DEPLOY_HOST 배포 서버 IP / Domain 192.168.1.100
DEPLOY_USER SSH 접속 계정 deploy-user
DEPLOY_PORT SSH 포트 22
DEPLOY_PATH 서버 내 배포 스크립트 경로 /var/www/app
DEPLOY_ENV_FILE Compose 환경변수 파일 경로 .env.prod
SSH_PRIVATE_KEY 배포 서버 접속용 Private Key -----BEGIN OPENSSH...
SSH_KNOWN_HOSTS 서버 신원 검증용 Known Hosts 192.168.1.100 ssh-ed25519 ...
 

3. Dockerfile 작성 (멀티 스테이지)

Node.js 빌드 환경과 Nginx 실행 환경을 분리하여 최종 이미지 용량을 최소화합니다.

 
# ===== 1. Build Stage =====

# Node.js 환경에서 프로젝트 빌드
FROM node:lts AS builder

# pnpm 활성화
RUN corepack enable && corepack prepare pnpm@latest --activate

# 작업 디렉터리 설정
WORKDIR /app

# 의존성 정보 복사 및 다운로드
COPY pnpm-lock.yaml ./
RUN pnpm fetch

# 프로젝트 복사 및 의존성 설치
COPY . .
RUN pnpm install --offline

# 프로젝트 빌드
RUN pnpm --prefix packages/admin build
RUN pnpm --prefix packages/user build

# ===== 2. Runtime Stage =====

# Nginx만 포함된 경량 이미지 사용
FROM nginx:stable-alpine

# Nginx 설정 복사
COPY --from=builder /app/nginx.conf /etc/nginx/nginx.conf

# 빌드 결과물 복사
COPY --from=builder /app/packages/admin/dist /usr/share/nginx/html/admin
COPY --from=builder /app/packages/user/dist /usr/share/nginx/html/user

4. .gitlab-ci.yml 작성

① Stage 1: Kaniko를 이용한 이미지 빌드 & Push

Docker Daemon이나 privileged 권한 없이 이미지 빌드가 가능합니다.

# 실행할 Stage 순서
stages:
  - build_push
  - deploy

# 이미지 빌드 및 Registry Push
build_push:
  stage: build_push

  # Docker Daemon 없이 이미지를 빌드하는 Kaniko 사용
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]

  # main 브랜치 Push 시에만 실행
  rules:
    - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "main"'

  script:
    # Docker Registry 인증 정보 생성
    - mkdir -p /kaniko/.docker
    - AUTH="$(printf '%s:%s' "$REGISTRY_USER" "$REGISTRY_PASSWORD" | base64 | tr -d '\n')"
    - printf '{"auths":{"https://index.docker.io/v1/":{"auth":"%s"}}}' "$AUTH" > /kaniko/.docker/config.json

    # Docker 이미지 빌드 및 Push
    - >
      /kaniko/executor
      --context "$CI_PROJECT_DIR"
      --dockerfile "$CI_PROJECT_DIR/Dockerfile"
      --destination "$IMAGE_NAME"

② Stage 2: SSH 접속을 통한 Docker Compose 배포

서버의 compose.sh 래퍼 스크립트를 호출하여 컨테이너를 안전하게 교체합니다.

# 배포 Stage
deploy:
  stage: deploy

  # SSH 접속을 위한 Alpine 이미지 사용
  image: alpine:3.20

  # main 브랜치 Push 시에만 실행
  rules:
    - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "main"'

  before_script:
    # SSH 클라이언트 설치
    - apk add --no-cache openssh-client

    # SSH 디렉터리 생성
    - mkdir -p ~/.ssh && chmod 700 ~/.ssh

    # Private Key 등록 (파일/문자열 모두 지원)
    - |
      if [ -f "$SSH_PRIVATE_KEY" ]; then
        cp "$SSH_PRIVATE_KEY" ~/.ssh/id_ed25519
      else
        printf '%s\n' "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
      fi

    # Private Key 권한 설정
    - chmod 600 ~/.ssh/id_ed25519

    # Known Hosts 등록
    - printf '%s\n' "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts

  script:
    # 배포 서버에서 Docker Compose 실행
    - >
      ssh -p "$DEPLOY_PORT" "$DEPLOY_USER@$DEPLOY_HOST"
      "cd \"$DEPLOY_PATH\" &&
      ./compose.sh --env-file \"$DEPLOY_ENV_FILE\" up -d --pull always --force-recreate --no-deps frontend"

💡 --pull always 옵션이 중요한 이유
latest 태그를 사용할 때 배포 서버가 캐시된 예전 로컬 이미지를 바라보는 문제를 방지합니다.

썸네일용 생성형 이미지

 

 

728x90