콘텐츠로 이동

AIStor Release Storage 설치 및 구성

0. 전제

다음 환경 기준으로 작성:

  • 규모: 프로젝트 5~20개 / 서버 소수
  • 인력: 1~2명 (소수 정예)
  • 멀티 스택 환경: Node.js, Express, Next.js, Nuxt.js, PHP, Laravel, C++ 등
  • 운영 서버: Linux / Windows 혼합 가능
  • Docker 사용 범위: Runner 빌드 환경 및 Release Storage 서버의 AIStor 실행 용도
  • 배포 방식: 운영 서버가 Release Storage에서 빌드 결과물을 직접 가져가는 pull 배포

인프라 전제:

  • GitLab 서버, Runner 서버, Release Storage 서버, 운영 서버는 논리적, 물리적으로 분리 가능한 구성으로 보며, 분리 운영을 전제로 작성함.
  • GitLab 서버는 외부 비공개로 유지하며, 운영 서버는 GitLab 서버에 직접 접근하지 않음.
  • Release Storage 서버는 운영 서버가 접근 가능한 별도 서버로 구성함.

운영 원칙:

  • build -> publish -> operation server pull deploy 구조 유지
  • {YYYYMMDD-HHMMSS}-{declared_version}-{CI_COMMIT_SHORT_SHA} 기반 immutable package version 운영
  • 인증은 목적별 계정 및 Access Key로 분리
  • 빌드: Runner 서버
  • CI 내부 산출물 보관: GitLab Generic Package Registry
  • 운영 배포 기준 저장소: Release Storage (S3-compatible Object Storage)
  • 운영: download, verify, extract, activate, report 수행

금지 사항:

  • 운영 서버에 write 권한 Access Key 배치 금지
  • .env 및 서버 고유 설정 파일을 패키지에 포함 금지
  • 운영 서버에서 배포 파일을 수동 수정한 뒤 재사용 금지
  • Generic Package Registry에 업로드된 package version/file overwrite 금지
  • Release Storage 의 releases 경로 Release Object overwrite 금지
  • Release Storage 의 current/{RELEASE_ENV}.json 외 object overwrite 금지
  • 운영 서버가 GitLab 서버에 직접 접근하도록 구성 금지

위 전제를 기반으로
AIStor 기반 Release Storage를 실제로 운영 가능한 상태까지 구성하는 단계를 정의함.


1. AIStor Release Storage 사용 목적

본 구조에서 GitLab 서버는 내부 개발 및 CI/CD 전용 시스템으로 사용함.

운영 기준:

  • gitlab.example.com 은 외부 비공개
  • 운영 서버는 GitLab 직접 접근 금지
  • 운영 서버는 Release Storage만 접근 가능

즉:

GitLab ≠ 운영 서버 Release Artifact Endpoint

2. 전체 구조

GitLab Runner
 ├── GitLab Generic Package Registry (CI 내부 산출물 보관)
 └── AIStor Release Storage (운영 Release Source)
    ↓
AIStor Release Storage
    ↓
운영 서버 Polling / Deploy

3. 도메인 구성

Release Storage API:

release.example.com

AIStor Console (WebUI):

release-console.example.com

운영 기준:

  • 운영 서버는 release.example.com 만 접근
  • AIStor Console (WebUI) 은 관리자 전용 접근
  • 가능하면 AIStor Console (WebUI) 은 공인 인터넷 직접 공개 대신 VPN 또는 내부망 기반 접근 사용
  • AIStor Console (WebUI) 은 운영 서버가 접근할 필요 없음
  • AIStor Console (WebUI) 과 API 접근 정책은 반드시 분리

4. Rocky Linux 서버 준비

  • 본 작업은 Release Storage 서버에서 수행함.
  • 기존에 podman 또는 다른 container runtime 을 사용 중인 서버에서는 제거 전 영향 범위를 확인함.
  • 신규 Release Storage 전용 서버라면 충돌 방지를 위해 기존 container runtime 제거 후 Docker CE 를 설치함.

패키지 설치:

sudo dnf update -y
sudo dnf remove -y docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine podman runc || true
sudo dnf install -y dnf-plugins-core nginx
sudo dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Docker 및 Nginx 활성화:

sudo systemctl enable docker
sudo systemctl start docker
sudo systemctl enable nginx
sudo systemctl start nginx

검증:

docker --version
docker compose version
docker info

sudo systemctl status docker
sudo systemctl status nginx

검증 기준:

Docker Engine - Community
docker-ce
containerd.io
docker compose plugin

등이 정상 출력되어야 함.


5. Release Storage 데이터 경로 및 Mount 구성

  • 본 작업은 Release Storage 서버에서 수행함.
  • Release Storage 데이터는 OS Root Volume 과 분리
  • AIStor Object Data 는 별도 Storage 에 저장
  • 서비스 표준 경로(/var/opt/aistor)와 실제 Storage 경로를 bind mount 로 연결
  • 실제 데이터 위치를 분리하여 Storage 이전 및 확장 용이성 확보
  • 향후 Storage 이전, NAS, Object Storage 확장 등을 고려하여 구조화
  • 다른 운영자가 보더라도 저장 데이터의 역할과 이전 대상을 즉시 이해 가능하도록 구성

구조:

/aistor
/var/opt/aistor
/etc/aistor
├── .env
└── docker-compose.yml

설명:

  • /aistor

    • 실제 Storage Mount 위치
    • 별도 Disk / Partition 사용 가능
  • /var/opt/aistor

    • 서비스 표준 경로
    • Docker Compose 에서 사용하는 경로
  • /etc/aistor

    • Docker Compose 및 환경 변수 관리 경로

5.1 설치 디스크 또는 파티션 결정 및 디렉터리 준비

AIStor 설치 전에 /aistor 를 어느 디스크 또는 파티션에 둘지 먼저 결정해야 함. 이 결정에 따라 이후 모든 경로와 마운트 구성이 달라짐.

디스크 또는 파티션 현황 확인:

sudo lsblk

선택 기준:

구성 방식 설명 권장 대상
OS 디스크 또는 파티션 내 LVM 별도 디스크 없이 OS 내부에 구성 소규모 / 테스트
별도 디스크 또는 파티션 분리 /aistor 전용 디스크 또는 파티션을 마운트 후 구성 운영 환경 권장

별도 디스크 또는 파티션 사용 시 (해당하는 경우만 수행)

⚠️ 해당 디스크 또는 파티션 마운트가 bind mount보다 반드시 먼저 적용되어야 함. 순서가 잘못되면 부팅 시 bind mount가 빈 경로에 걸려 AIStor 데이터가 유실 또는 데이터가 잘못된 위치에 기록되어 기존 데이터가 가려질 수 있음.

# ⚠️ 아래 DISK_OR_PARTITION 변수를 본인 환경에 맞게 먼저 수정 (lsblk 결과 참고)
# 예: /dev/sda, /dev/sdb, /dev/nvme1n1 등 환경마다 다름
DISK_OR_PARTITION=/dev/sda

# 필요시! 신규 디스크 또는 파티션 포맷 (최초 1회, 기존 데이터 삭제됨 주의)
# sudo mkfs.xfs ${DISK_OR_PARTITION}

# 마운트
sudo mkdir -p /aistor
sudo mount ${DISK_OR_PARTITION} /aistor

# UUID 확인 (fstab 등록용)
sudo blkid ${DISK_OR_PARTITION}

디렉터리 생성 (모든 환경 공통)

sudo mkdir -p /aistor
sudo chown -R root:root /aistor
sudo chmod -R 755 /aistor

sudo mkdir -p /var/opt/aistor
sudo chown -R root:root /var/opt/aistor
sudo chmod -R 755 /var/opt/aistor

sudo mkdir -p /etc/aistor
sudo chown -R root:root /etc/aistor
sudo chmod -R 755 /etc/aistor

5.2 bind mount 적용

  • bind mount는 상위 경로 → 하위 경로 순으로 적용함
sudo mount --bind /aistor /var/opt/aistor

5.3 영구 적용 (/etc/fstab)

  • nofail 옵션 사용 시 mount 실패 상태에서도 부팅이 진행되므로, 부팅 후 mount 상태를 반드시 확인해야 함
  • systemd는 fstab을 병렬로 처리할 수 있으므로 x-systemd.requires 로 의존성을 명시해야 함
sudo vi /etc/fstab

OS 디스크 또는 파티션 내 LVM 사용 시:

/aistor        /var/opt/aistor                            none   bind,nofail                                   0 0

별도 디스크 또는 파티션 사용 시 (UUID는 blkid 결과로 대체):

# 반드시 bind mount보다 위에 위치해야 함
UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx   /aistor   xfs   defaults,noatime   0 0

/aistor        /var/opt/aistor                            none   bind,nofail,x-systemd.requires=/aistor,x-systemd.after=/aistor              0 0
sudo systemctl daemon-reload
sudo mount -a

5.4 mount 확인

# mount 상태 확인
mount | grep aistor
findmnt /var/opt/aistor
findmnt | grep aistor

# 디렉터리 구조 확인
sudo ls -ld /aistor /var/opt/aistor /etc/aistor
cat /etc/fstab | grep aistor

5.5 동작 테스트

# data 영역 테스트
sudo touch /var/opt/aistor/testfile
sudo ls /aistor
sudo rm /aistor/testfile

5.6 SELinux 주의사항

현재 프로젝트 기준처럼 /aistor 를 각 AIStor 기본 경로에 bind mount 하여 사용하는 구조에서는, SELinux Enforcing 환경이라도 추가 context 설정 없이 동작하는 경우가 많아 설정이 항상 필요한 것은 아님. Docker Compose 기준으로 /var/opt/aistor 를 컨테이너 /mnt/data 에 mount 하여 사용하는 구조에서는, SELinux Enforcing 환경에서 접근 문제가 발생할 수 있음.

단, 다음 상황에서는 SELinux 문제 발생 가능:

  • 커스텀 경로 직접 사용 시
  • context가 변경된 경우
  • 서비스 접근 실패(AVC 발생)

이 경우에만 아래를 확인함.

sudo sestatus
sudo ausearch -m avc -ts recent

필요 시 참고:

sudo restorecon -Rv /aistor /var/opt/aistor
sudo ls -Zd /aistor /var/opt/aistor

5.7 주의사항 (중요)

  • /aistor 디렉터리는 반드시 충분한 디스크 또는 파티션 용량을 확보할 것
  • bind mount는 AIStor 설치 전에 적용해야 함
  • mount 누락 시 AIStor 데이터가 /var/opt/aistor에 직접 저장됨 (복구 어려움)
  • /etc/fstab 설정 오류 시 부팅 실패 가능 → 반드시 mount -a 검증 수행
  • 상위 경로 mount 후 하위 경로를 순서대로 mount 해야 함

5.8 서버 이전 및 데이터 이동 전략

본 구조는 AIStor 데이터 영역을 분리하여 다음과 같은 다양한 이전 시나리오를 지원함.

⚠️ 반드시 AIStor 서비스 중지 후 수행

cd /etc/aistor
sudo docker compose down

전체 AIStor 이전

rsync -avz /aistor/ new-server:/aistor/
rsync -avz /etc/aistor/ new-server:/etc/aistor/

5.9 운영 기준

  • 각 디렉터리는 독립적으로 이동 가능해야 함
  • mount 구조를 유지하면 부분 이전이 가능함
  • 이전 시 AIStor 서비스 중지 상태에서 수행
  • 데이터 이동 후 mount 및 경로 정합성 반드시 검증
  • --delete 옵션 사용 시 대상 서버의 기존 데이터가 삭제되므로 사용 전 반드시 확인
  • 서버 간 권한/소유권 불일치 주의: rsync 완료 후 /aistor, /var/opt/aistor, /etc/aistor 의 권한과 mount 상태를 재검증할 것

5.10 요약

  • bind mount를 통해 기본 경로와 연결함
  • 해당 작업은 반드시 설치 전에 수행해야 함
  • 이 구조로 두면 이후 서버 이전 및 저장소 분리가 쉬워짐.

6. AIStor Docker Compose 구성

  • 본 작업은 Release Storage 서버에서 수행함.

6.1 환경 변수 File

위치:

/etc/aistor/.env

생성:

sudo vi /etc/aistor/.env

내용:

MINIO_ROOT_USER=<aistor-root-admin>
MINIO_ROOT_PASSWORD=<change_this_password>

권한 설정:

sudo chown root:root /etc/aistor/.env
sudo chmod 600 /etc/aistor/.env

AIStor Free License 발급:

  • 아래 내용을 통해 신청 후 Mail 을 통해 minio.license 를 Download.
https://www.min.io/pricing
→ AIStor Free
→ Get Started

AIStor Free License File 배치:

  • Release Storage 서버의 /home/{user}/ 로 Upload 후 이동
sudo mv /home/{user}/minio.license /etc/aistor/minio.license

권한 설정:

sudo chown root:root /etc/aistor/minio.license
sudo chmod 600 /etc/aistor/minio.license
sudo ls -l /etc/aistor/minio.license

운영 기준:

  • MINIO_ROOT_PASSWORD 는 32자 이상 랜덤 문자열 사용
  • .envminio.license 파일은 Git 저장소에 커밋 금지
  • AIStor Free 는 License File 기반으로 실행함
  • License File 은 Release Storage 서버 내부에서만 관리함
  • Root 계정은 초기 구성 및 관리자 AIStor Console (WebUI) 접근 용도로만 사용
  • CI 및 운영 서버에서 Root 계정 사용 금지
  • AIStor Free 는 single-node / standalone 운영 기준으로 사용함
  • distributed clustering 또는 HA 구성이 필요하면 Free tier 범위를 벗어날 수 있음

6.2 Docker Compose File

6.2.1 AIStor Image 선정 기준

AIStor 공식 Container image:

quay.io/minio/aistor/minio

공식 설치 기준:

docker pull quay.io/minio/aistor/minio:latest
docker image inspect quay.io/minio/aistor/minio:latest --format='{{index .RepoDigests 0}}'

운영 기준:

  • AIStor Release Storage 는 애플리케이션처럼 매 pipeline 마다 재배포하는 대상이 아니고 장기간 실행되는 운영 인프라 서비스로 사용함
  • 서버 reboot 시 Docker 는 local image 로 container 를 재기동하므로 단순 reboot 만으로 image version 이 변경되지는 않음
  • latest tag 는 docker pull 또는 docker compose pull 수행 시에만 갱신됨
  • 본 문서에서는 운영 단순성을 위해 latest tag 를 사용함
  • AIStor update 는 필요 시 release note 확인 후 수동 수행함
  • update 전후 AIStor Console (WebUI), S3 API, upload/download 동작을 검증함

6.2.2 docker-compose.yml

  • AIStor Server 공식 container image는 quay.io/minio/aistor/minio:latest를 사용함.
  • AIStor Server의 실행 바이너리 이름은 minio이므로 container command도 minio server ... 형식을 사용함.
  • 따라서 본 문서에서 minio server, MINIO_* 환경 변수, mc CLI가 함께 등장하는 것은 MinIO Community Server와 혼용한 것이 아니라 AIStor 공식 실행 방식에 따른 것임.
  • AIStor 컨테이너는 Object Data를 직접 기록해야 하므로 /mnt/data volume은 read-write 상태여야 함.
  • SELinux Enforcing 환경 호환을 위해 /var/opt/aistor:/mnt/data:z 옵션을 사용함.

위치:

/etc/aistor/docker-compose.yml

생성:

sudo vi /etc/aistor/docker-compose.yml

내용:

services:
  aistor:
    image: quay.io/minio/aistor/minio:latest
    container_name: aistor-release-storage
    restart: unless-stopped

    command: minio server /mnt/data --console-address ":9001" --license /minio.license

    environment:
      MINIO_ROOT_USER: ${MINIO_ROOT_USER}
      MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
      MINIO_SERVER_URL: https://release.example.com
      MINIO_BROWSER_REDIRECT_URL: https://release-console.example.com

    volumes:
    - /var/opt/aistor:/mnt/data:z
    - /etc/aistor/minio.license:/minio.license:ro

    ports:
    - "127.0.0.1:9000:9000"
    - "127.0.0.1:9001:9001"

    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "10"

권한 설정:

sudo chown root:root /etc/aistor/docker-compose.yml
sudo chmod 644 /etc/aistor/docker-compose.yml

실행:

  • .env 자동 로딩을 위해 반드시 /etc/aistor 경로에서 docker compose 명령을 실행함.
cd /etc/aistor
sudo docker compose up -d

검증:

cd /etc/aistor
sudo docker compose ps
sudo docker logs --tail=100 aistor-release-storage
curl -I http://127.0.0.1:9000
curl -I http://127.0.0.1:9001
sudo ss -tulnp | grep -E '9000|9001'

검증 기준:

API: https://release.example.com
AIStor Console (WebUI): https://release-console.example.com

7. Nginx 구성 전 사전 확인

TLS 인증서 발급

release.example.comrelease-console.example.com 에 대한 TLS 인증서가 Nginx 설정 적용 전에 발급되어 있어야 함.

방화벽 포트 개방

  • AIStor 포트 9000, 9001127.0.0.1 바인딩이므로 방화벽 개방 불필요
  • HTTP/HTTPS 만 개방하여 Nginx Reverse Proxy 경유 접근만 허용
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --permanent --list-all

8. Nginx Reverse Proxy 구성

  • 본 작업은 Release Storage 서버에서 수행함.

운영 기준:

  • AIStor 포트 9000, 9001 직접 외부 공개 금지
  • Nginx Reverse Proxy 를 통해서만 접근
  • API 와 AIStor Console (WebUI) 도메인 분리

8.1 Upgrade 헤더용 map

WebSocket 지원을 위해 http 블록 또는 공통 include에 추가함.

ex) /etc/nginx/nginx.conf

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

8.2 Release API Proxy

위치:

/etc/nginx/conf.d/sites-available/infrastructure/release.example.com.conf

생성:

sudo vi /etc/nginx/conf.d/sites-available/infrastructure/release.example.com.conf
sudo ln -s /etc/nginx/conf.d/sites-available/infrastructure/release.example.com.conf /etc/nginx/conf.d/sites-enabled/infrastructure/release.example.com.conf

내용:

# 인증서 발급 전
# server {
#   listen 80;
#   listen [::]:80;
#   server_name release.example.com;

#   location ^~ /.well-known/acme-challenge/ {
#       root /var/www/letsencrypt;
#       default_type "text/plain";
#       try_files $uri =404;
#   }

#   location / {
#       return 200 "release.example.com nginx ready\n";
#   }
# }

# 인증서 발급 후
server {
    listen 80;
    listen [::]:80;
    server_name release.example.com;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
        default_type "text/plain";
        try_files $uri =404;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl;
    server_name release.example.com;

    # [보안 설정] Release Storage API 접근 제어
    # ---------------------------------------------------------
    # GitLab Runner 및 운영 서버만 S3 API 접근을 허용함
    # 관리자 PC는 초기 검증 및 mc 관리 작업이 필요한 경우에만 허용함
    # allow 192.168.0.0/24;  # 사내 네트워크 대역
    # allow 127.0.0.1;       # 로컬 호스트 허용
    # allow <gitlab-runner-ip>;
    # allow <operation-server-ip>;
    # allow <admin-pc-or-vpn-ip>;
    # deny all;
    # ---------------------------------------------------------

    ssl_certificate     /etc/letsencrypt/live/release.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/release.example.com/privkey.pem;
    include             /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam         /etc/letsencrypt/ssl-dhparams.pem;

    client_max_body_size 0;
    client_body_buffer_size 16m;

    proxy_connect_timeout 300s;
    proxy_send_timeout    3600s;
    proxy_read_timeout    3600s;
    send_timeout          3600s;

    location / {
        proxy_pass http://127.0.0.1:9000;  # 또는 http://192.168.0.100:9000;

        proxy_http_version 1.1;
        proxy_buffering off;
        proxy_request_buffering off;

        proxy_set_header Host              $http_host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Port  443;
        proxy_set_header X-Forwarded-Host  $host;

        proxy_set_header Connection "";
    }
}

확인:

sudo nginx -t
sudo systemctl reload nginx

8.3 AIStor Console (WebUI) Proxy

위치:

/etc/nginx/conf.d/sites-available/infrastructure/release-console.example.com.conf

생성:

sudo vi /etc/nginx/conf.d/sites-available/infrastructure/release-console.example.com.conf
sudo ln -s /etc/nginx/conf.d/sites-available/infrastructure/release-console.example.com.conf /etc/nginx/conf.d/sites-enabled/infrastructure/release-console.example.com.conf

내용:

# 인증서 발급 전
# server {
#   listen 80;
#   listen [::]:80;
#   server_name release-console.example.com;

#   location ^~ /.well-known/acme-challenge/ {
#       root /var/www/letsencrypt;
#       default_type "text/plain";
#       try_files $uri =404;
#   }

#   location / {
#       return 200 "release-console.example.com nginx ready\n";
#   }
# }

# 인증서 발급 후
server {
    listen 80;
    listen [::]:80;
    server_name release-console.example.com;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
        default_type "text/plain";
        try_files $uri =404;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl;
    server_name release-console.example.com;

    # [보안 설정] AIStor Console (WebUI) 접근 제어
    # ---------------------------------------------------------
    # AIStor Console (WebUI) 은 관리자 및 내부 운영 네트워크만 접근 허용 권장
    # allow 192.168.0.0/24;
    # allow 127.0.0.1;
    # allow <admin-pc-or-vpn-ip>;
    # deny all;
    # ---------------------------------------------------------

    ssl_certificate     /etc/letsencrypt/live/release-console.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/release-console.example.com/privkey.pem;
    include             /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam         /etc/letsencrypt/ssl-dhparams.pem;

    client_max_body_size 0;
    client_body_buffer_size 16m;

    proxy_connect_timeout 300s;
    proxy_send_timeout    3600s;
    proxy_read_timeout    3600s;
    send_timeout          3600s;

    location / {
        proxy_pass http://127.0.0.1:9001;  # 또는 http://192.168.0.100:9001;

        proxy_http_version 1.1;
        proxy_buffering off;
        proxy_request_buffering off;

        proxy_set_header Host              $http_host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Port  443;
        proxy_set_header X-Forwarded-Host  $host;

        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}

확인:

sudo nginx -t
sudo systemctl reload nginx

8.4 nginx 설정 검증 및 반영

sudo nginx -t
sudo systemctl reload nginx

접속 확인:

curl -I http://release.example.com
curl -I https://release.example.com

curl -I http://release-console.example.com
curl -I https://release-console.example.com