아키텍처 개요
클라우드 기반 AI 개발 플랫폼에서 Dify 온프레미스 환경으로의 원활한 전환을 목표로 하는 마이그레이션 파이프라인을 설계한다. 이 도구는 프로젝트 메타데이터 추출, 환경별 설정 변환, 컨테이너 이지 빌드까지 전 과정을 자동화한다.
핵심 모듈 구성
1. 프로젝트 파싱 엔진
원본 플랫폼의 프로젝트 아카이브를 분석하여 다음 요소를 식별한다:
- LLM 프로바이더 설정 및 API 엔드포인트
- 프롬프트 템플릿과 변수 스키마
- 외부 도구(툴) 호출 정의
- 지식베이스 임베딩 모델 및 벡터 저장소 연결
# parser/structure_analyzer.py
import json
from dataclasses import dataclass
from pathlib import Path
from typing import Optional
@dataclass
class ModelConfig:
provider: str
endpoint: str
credential_key: str
temperature: float = 0.7
max_tokens: int = 2048
class ProjectAnalyzer:
def __init__(self, archive_path: str):
self.root = Path(archive_path)
self.manifest: Optional[dict] = None
def extract_workflow(self) -> dict:
workflow_file = self.root / "workflow.json"
with open(workflow_file, encoding="utf-8") as f:
data = json.load(f)
return self._normalize_nodes(data)
def _normalize_nodes(self, raw: dict) -> dict:
# 노드 타입을 Dify 호환 형태로 변환
node_map = {
"llm_call": "llm",
"knowledge_retrieve": "knowledge-retrieval",
"code_execute": "code"
}
nodes = raw.get("nodes", [])
for node in nodes:
node["type"] = node_map.get(node["type"], node["type"])
return {"nodes": nodes, "edges": raw.get("edges", [])}2. Dify 설정 생성기
추출된 메타데이터를 Dify의 docker-compose.yml 및 환경변수 체계에 맞춰 재구성한다.
# generator/dify_builder.py
import os
from jinja2 import Environment, FileSystemLoader
class DifyConfigBuilder:
def __init__(self, template_dir: str = "templates"):
self.env = Environment(loader=FileSystemLoader(template_dir))
def generate_compose(self, services: list) -> str:
tpl = self.env.get_template("docker-compose.yml.j2")
return tpl.render(
api_image="langgenius/dify-api:0.6.9",
web_image="langgenius/dify-web:0.6.9",
sandbox_image="langgenius/dify-sandbox:0.2.1",
enabled_services=services
)
def build_env_file(self, model_cfg: ModelConfig, db_url: str) -> str:
lines = [
f"OPENAI_API_KEY={model_cfg.credential_key}",
f"OPENAI_API_BASE={model_cfg.endpoint}",
f"DB_DATABASE_URL={db_url}",
"REDIS_HOST=redis",
"REDIS_PORT=6379",
"SANDGBox_WORKERS=4",
# 보안 강화: 기본 시크릿 무작위화
f"SECRET_KEY={os.urandom(32).hex()}",
"FORCE_HTTPS=true"
]
return "\n".join(lines)3. 컨테이너화 파이프라인
마이그레이션 결과물을 검증된 단일 이미지로 빌드하는 과정을 자동화한다.
# packager/image_assembler.py
import subprocess
import tempfile
from pathlib import Path
class ImageAssembler:
def __init__(self, output_tag: str):
self.tag = output_tag
self.build_ctx = tempfile.TemporaryDirectory()
def stage_assets(self, config_dir: Path, app_bundle: Path):
"""빌드 컨텍스트에 필요 파일 배치"""
dest = Path(self.build_ctx.name)
self._copytree(config_dir, dest / "config")
self._copytree(app_bundle, dest / "app")
# 멀티스테이지 Dockerfile 생성
(dest / "Dockerfile").write_text(self._render_dockerfile())
def _render_dockerfile(self) -> str:
return '''
FROM langgenius/dify-api:0.6.9 as base
WORKDIR /app/api
COPY config /app/api/configs
COPY app /app/api/custom_extensions
ENV MODE=production
RUN pip install --no-cache-dir -r custom_extensions/requirements.txt
EXPOSE 5001
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5001", "app:app"]
'''
def build(self) -> str:
cmd = ["docker", "build", "-t", self.tag, self.build_ctx.name]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(f"빌드 실패: {result.stderr}")
return f"{self.tag} 이미지 빌드 완료"React 기반 설정 조정 인터페이스
마이그레이션 전 파라미터를 시각적으로 검토하고 수정할 수 있는 대시보드를 구현한다.
// src/components/ConfigTuner.tsx
import { useState } from 'react';
interface TuningParams {
temperature: number;
topP: number;
presencePenalty: number;
maxContextLength: number;
}
export default function ConfigTuner({
initialConfig,
onValidate
}: {
initialConfig: TuningParams;
onValidate: (cfg: TuningParams) => boolean;
}) {
const [params, setParams] = useState<TuningParams>(initialConfig);
const [errors, setErrors] = useState>({});
const handleSliderChange = (field: keyof TuningParams, value: number) => {
const updated = { ...params, [field]: value };
setParams(updated);
// 실시간 유효성 검사
const isValid = onValidate(updated);
if (!isValid) {
setErrors(prev => ({ ...prev, [field]: '권장 범위 초과' }));
} else {
setErrors(prev => { const n = { ...prev }; delete n[field]; return n; });
}
};
return (
<div className="tuner-panel">
<div className="control-group">
<label>Temperature: {params.temperature}</label>
<input
type="range"
min="0"
max="2"
step="0.05"
value={params.temperature}
onChange={e => handleSliderChange('temperature', parseFloat(e.target.value))}
/>
{errors.temperature && {errors.temperature}}
</div>
<div className="control-group">
<label>Max Context: {params.maxContextLength}</label>
<input
type="range"
min="1024"
max="128000"
step="1024"
value={params.maxContextLength}
onChange={e => handleSliderChange('maxContextLength', parseInt(e.target.value))}
/>
</div>
</div>
);
} 보안 설계 원칙
| 계층 | 적용 방안 |
|---|---|
| 전송 | TLS 1.3 강제, mTLS 상호 인증 |
| 저장 | PostgreSQL TDE, Redis AUTH + TLS |
| 비밀값 | HashiCorp Vault 연동, 런타임 주입 |
| 이미지 | Distroless 베이스, 비루트 실행, CVE 스캔 |
| 네트워크 | 내부 서비스 간 정책 기반 격리 |
배포 검증 체크리스트
- 컨테이너 헬스체크 엔드포인트(
/health) 응답 확인 - LLM 프로바이더 연결 테스트 및 토큰 계산 검증
- 지식베이스 문서 임베딩 및 유사도 검색 정확도 측정
- 동시 사용자 100명 부하 테스트
- 장애 복구 시나리오: DB 장애 시 서킷 브레이커 동작 확인
운영 자동화
# .github/workflows/deploy.yml
name: On-Premise Deploy
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 마이그레이션 실행
run: |
python -m migrate_tool \
--source "${{ secrets.CLOUD_PROJECT_URL }}" \
--target-format dify \
--output ./dify-package
- name: 이미지 빌드 및 서명
run: |
cosign generate-key-pair
docker build -t dify-local:${{ github.sha }} ./dify-package
cosign sign --key cosign.key dify-local:${{ github.sha }}
- name: 에어갭 환경으로 전달
run: |
docker save dify-local:${{ github.sha }} | \
gpg --symmetric --cipher-algo AES256 > release.tar.gz.gpg