시작하기
기본 개념
Git으로 관리하지 않는 파일과 결과물을 별도로 전송하기
Git에서는 소스 코드와 변경 이력을 관리하고, 빌드 결과물이나 배포 패키지, 대용량 데이터와 같은 파일은 별도의 전송 흐름으로 관리할 수 있습니다.
Git 작업 이후 생성되거나 준비된 파일을 필요한 장비로 전송하도록 연결하면, 파일의 특성과 활용 환경에 맞춰 관리 흐름을 구성할 수 있습니다.
Git Repository
│
│ Code Change
▼
Build / Processing
│
│ Output Files
▼
File Transfer
│
▼
Target Device

예를 들어 소스 코드를 변경한 후 빌드 작업이 완료되면 생성된 패키지나 결과 파일을 테스트 서버, 배포 서버, 데이터 처리 장비로 자동 전송할 수 있습니다.
연계 흐름
Git 작업부터 파일 전송까지 자동으로 이어가기
Git 작업을 기준으로 빌드나 파일 처리 작업을 진행하고, 필요한 파일이 준비되면 지정된 조건에 따라 파일 전송을 시작합니다.
전송이 완료되면 대상 장비에서 테스트, 배포, 데이터 처리와 같은 다음 업무를 이어갈 수 있습니다.
Git 작업
│
▼
빌드 · 파일 처리
│
▼
결과 파일 생성
│
▼
전송 작업 실행
│
▼
대상 장비 반영
│
▼
다음 작업 진행

분리 효과
소스 코드와 업무 파일을 각각의 방식으로 관리하기
Git과 파일 전송을 함께 활용하면 소스 코드의 변경 관리와 별도 파일의 전송 및 활용을 하나의 업무 흐름 안에서 연결할 수 있습니다.
| 구분 | Git | 파일 전송 |
|---|---|---|
| 관리 대상 | 소스 코드와 변경 이력 | 결과 파일과 업무 파일 |
| 주요 역할 | 코드 변경과 버전 관리 | 파일 처리와 대상 장비 반영 |
| 실행 시점 | 코드 작업과 이벤트 발생 | 설정한 작업 조건 충족 |
| 활용 환경 | 개발과 형상 관리 | 테스트, 배포, 업무 장비 |
이를 통해 각 파일을 목적에 맞게 관리하면서 필요한 시점에는 Git 작업 이후의 파일 전송을 자동으로 실행할 수 있습니다.
IT 엔지니어
환경 연결
Git 작업과 파일 전송 장비 연결하기
먼저 Git 작업과 파일 전송에 사용할 장비를 하나의 흐름으로 연결합니다.
Git에서 발생하는 작업과 빌드 또는 파일 처리 환경, 결과 파일을 사용할 대상 장비를 연결하면 전체 파일 흐름을 구성할 수 있습니다.
┌──────────────┐
│ Git │
└──────┬───────┘
│
▼
┌──────────────┐
│ Build Server │
└──────┬───────┘
│
▼
┌──────────────┐
│ File Transfer│
└──────┬───────┘
│
▼
┌──────────────┐
│Target Device │
└──────────────┘

각 장비와 작업을 연결하면 Git 작업 이후 생성되는 파일이 다음 전송 단계까지 이어지는 기본 환경이 준비됩니다.
전송 규칙
파일과 대상, 실행 조건을 하나의 기준으로 설정하기
별도로 관리할 파일과 전송 대상 장비를 지정하고, Git 또는 후속 작업의 어떤 시점에 파일 전송을 시작할지 함께 설정합니다.
전송할 파일의 경로와 종류를 지정하고, 대상 서버 또는 장비를 연결합니다. 이후 코드 변경, 빌드 완료, 결과 파일 생성과 같은 작업 조건을 전송 시작 기준으로 구성할 수 있습니다.
Git / Build Event
│
▼
Start Condition
│
├── Source
│ └── File / Path
│
└── Target
└── Device
│
▼
Transfer Run

| 구성 항목 | 설정 내용 |
|---|---|
| 시작 조건 | Git 작업 또는 빌드 완료 등 전송을 시작할 기준 |
| Source | 결과 파일과 파일 경로 |
| Filter | 전송할 파일 이름과 확장자 조건 |
| Target | 파일을 사용할 서버 또는 장비 |
| Transfer | 설정된 조건에 따라 실행할 파일 전송 |
이렇게 구성하면 어떤 작업 이후 어떤 파일을 어느 장비로 전송할지를 하나의 실행 기준으로 관리할 수 있습니다.
자동 흐름
Git 작업 이후 파일을 지정된 장비로 자동 전송하기
앞에서 구성한 장비와 전송 규칙을 기준으로 Git 작업부터 파일 전송까지 자동 흐름을 완성합니다.
Git 작업이 발생하면 연결된 빌드 또는 처리 작업이 진행되고, 설정한 조건에 따라 준비된 파일을 지정된 장비로 전송합니다.
Git Push
│
▼
Build
│
▼
Output Ready
│
├──────────────┐
│ │
▼ ▼
Test Server Deploy Server
│ │
└──────┬───────┘
▼
Complete

하나의 결과 파일을 여러 테스트 또는 배포 환경으로 전송하도록 구성하면, Git 작업 이후 필요한 업무 환경까지 파일을 자동으로 연결할 수 있습니다.
결과 관리
전송 상태와 재처리 흐름을 함께 관리하기
실행된 파일 전송 작업은 Runs와 실행 상세 정보를 통해 확인합니다.
Git 작업을 기준으로 어떤 전송 작업이 실행되었는지 확인하고, 대상 장비별 처리 상태와 전송된 파일 정보를 함께 관리할 수 있습니다.
Git Workflow
│
▼
Transfer Run
│
┌────┼───────┐
▼ ▼ ▼
Files Status Progress
│
▼
Result Review
│
┌────┴─────────────┐
▼ ▼
Completed Check Required
│
▼
Run Details
│
▼
Condition Check
│
▼
Retry
│
▼
Complete

주요 확인 항목은 다음과 같습니다.
| 확인 항목 | 확인 내용 |
|---|---|
| Trigger | 파일 전송을 시작한 Git 또는 후속 작업 |
| Source | 전송한 파일과 파일 경로 |
| Target | 파일을 사용할 대상 장비 |
| Progress | 전송 진행 상태 |
| Status | 실행 상태와 처리 결과 |
| Files | 처리된 파일 수와 용량 |
| Audit Log | 작업별 실행 기록과 상세 정보 |
실행 결과에서 추가 확인이 필요한 경우에는 해당 Run의 상세 정보와 Audit Log를 통해 파일 준비 상태, 전송 경로, 대상 장비의 연결 상태를 확인한 후 필요한 작업을 다시 실행할 수 있습니다.
개발자
빌드 산출물을 여러 배포 대상에 보내고 전송 결과를 파이프라인 종료 코드로 반영하기
연동 준비
공통 호출 코드와 CI 자격 준비하기
import os
import requests
BASE_URL = os.getenv("INNORIX_BASE_URL", "https://app.innorix.com").rstrip("/")
TOKEN = os.environ["INNORIX_ACCESS_TOKEN"]
WORKSPACE_ID = os.getenv("INNORIX_WORKSPACE_ID") # optional; falls back to the current workspace
STATUS_COMPLETE = 2
TERMINAL = {2, 4, 5, 9, 99} # complete / error / cancelled / partial / failed
NOT_SUCCEEDED = {4, 5, 9, 99}
def api(method, path, body=None, params=None):
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {TOKEN}",
}
if WORKSPACE_ID:
headers["x-workspace-id"] = WORKSPACE_ID
response = requests.request(
method, BASE_URL + path,
headers=headers, json=body, params=params, timeout=30,
)
payload = response.json() if response.content else {}
if not response.ok:
raise RuntimeError(payload.get("message") or f"HTTP {response.status_code}")
return payload.get("data")
def is_terminal(detail):
return detail.get("isTerminal", detail.get("status") in TERMINAL)전송 상태는 아래 값으로 판단합니다. 종료 상태는 다섯 개이고 성공에 해당하는 값은 완료(2)입니다.
| 상태 값 | 의미 | 종료 |
|---|---|---|
| 2 | 완료 | 예 |
| 4 | 오류 | 예 |
| 5 | 취소 | 예 |
| 9 | 부분 완료 | 예 |
| 99 | 실패 | 예 |
| 1 · 6 · 12 · 13 | 시작·전송 중·동기화 중·수신 중 | 아니오 |
CI에서 호출한다면 토큰을 파이프라인 시크릿으로 주입합니다. 저장소에 커밋하지 않습니다.
# GitHub Actions example
env:
INNORIX_BASE_URL: https://app.innorix.com
INNORIX_ACCESS_TOKEN: ${{ secrets.INNORIX_ACCESS_TOKEN }}
산출물 경로 설계
커밋이나 태그를 경로에 담아 배포 이력 남기기
어느 코드에서 나온 산출물인지 경로에 기록합니다. 버전 폴더를 따로 두면 이전 산출물이 남아 있어 문제가 생겼을 때 되돌리기 쉽습니다.
import os
import subprocess
def git_ref():
try:
sha = subprocess.check_output(
["git", "rev-parse", "--short", "HEAD"], text=True).strip()
except (subprocess.CalledProcessError, FileNotFoundError):
sha = "unknown"
return os.getenv("GIT_TAG") or sha
def target_path(base, ref):
return f"{base}/{ref}"한 경로에 덮어쓰면 롤백할 대상이 없습니다.
산출물 전송
빌드 결과 파일을 대상 장비로 보내기
파일을 보낼 때는 sourceItem에 isDir: false로 명시합니다. sourcePaths는 모든 경로를 폴더로 취급하므로, 산출물 파일을 넣으면 서버가 폴더로 스캔하려 해 느려지거나 시간이 초과됩니다.
def deploy_artifact(source, target, artifact_path, base):
ref = git_ref()
transfer = api("POST", "/api/transfers/manual", {
"sourceDevice": source,
"targetDevice": target,
"targetPath": target_path(base, ref),
"sourceItem": [{
"path": artifact_path,
"isDir": False,
"isFolder": False,
"fileSize": os.path.getsize(artifact_path),
}],
"sendAllFolder": False,
"transferOptions": {"target-action": "overwrite"},
})
return transfer["monitorId"], target_path(base, ref)빌드 디렉터리를 폴더 단위로 보낼 때는 sourcePaths와 sendAllFolder: True를 씁니다.
api("POST", "/api/transfers/manual", {
"sourceDevice": source,
"targetDevice": target,
"targetPath": target_path(base, ref),
"sourcePaths": ["/build/output"],
"sendAllFolder": True,
"transferOptions": {"target-action": "overwrite"},
})같은 참조로 다시 빌드해 올리는 경우가 대부분이므로 overwrite를 씁니다. numbering을 쓰면 재배포할 때마다 사본이 쌓입니다.
여러 대상 배포
하나의 산출물을 여러 환경으로 보내기
하나의 전송은 대상 장비 하나를 다룹니다. 테스트와 스테이징으로 함께 보내려면 전송을 나눠 만듭니다.
TARGETS = [
("device-test-01", "/deploy/app"),
("device-stage-01", "/deploy/app"),
]
transfers = {
target: deploy_artifact("device-build-01", target,
"/build/output/app.tar.gz", base)[0]
for target, base in TARGETS
}def wait(monitor_id, timeout=3600, interval=3):
deadline = time.time() + timeout
while time.time() < deadline:
detail = api("GET", f"/api/transfers/{monitor_id}")
if is_terminal(detail):
return detail
time.sleep(interval)
raise TimeoutError(monitor_id)
def failed_files(monitor_id):
result = api("GET", f"/api/transfers/{monitor_id}/files", params={
"state": "any", "size": 500,
}) or {}
return [r for r in (result.get("children") or [])
if r.get("status") in NOT_SUCCEEDED]
def retry_failed(monitor_id):
rows = failed_files(monitor_id)
if not rows:
return 0
api("POST", f"/api/transfers/{monitor_id}/retry", {
"filesRetry": [
{"filePath": r["sourceFilePath"], "isDir": bool(r.get("isFolder"))}
for r in rows
]
})
return len(rows)results = {}
for target, monitor_id in transfers.items():
results[target] = wait(monitor_id, timeout=3600)
failed = [t for t, d in results.items() if d["status"] != STATUS_COMPLETE]한 대상이 실패해도 나머지를 계속 확인해야 어디까지 반영됐는지 알 수 있습니다. 첫 실패에서 멈추면 재배포 범위를 정할 수 없습니다.
대용량 산출물 전송
대용량 산출물의 처리량을 속도·동시성 옵션으로 조절하기
컨테이너 이미지나 데이터셋처럼 크기가 큰 산출물은 처리량 옵션으로 전송 속도를 조절합니다.
api("POST", "/api/transfers/manual", {
"sourceDevice": source,
"targetDevice": target,
"targetPath": target_path(base, ref),
"sourcePaths": ["/build/output"],
"sendAllFolder": True,
"transferOptions": {
"target-action": "overwrite",
"networkLevel": 3, # throughput priority level
"concurrentTransfers": 8, # concurrent transfers
},
})CI 러너와 배포 대상이 같은 회선을 쓰는 환경이라면 속도 제한을 걸어 다른 작업에 영향이 가지 않게 합니다.
"transferOptions": {
"target-action": "overwrite",
"limitRate": 51200
}
limitRate의 단위는 KB/s입니다.
파이프라인 연결
전송 결과로 파이프라인 종료 코드 내기
CI는 종료 코드로 성공 여부를 판단합니다. 전송이 실패하면 파이프라인도 실패해야 합니다.
import sys
if __name__ == "__main__":
monitor_id, path = deploy_artifact(
os.environ["BUILD_DEVICE"],
os.environ["TARGET_DEVICE"],
os.environ["ARTIFACT_PATH"],
os.environ["DEPLOY_BASE"],
)
detail = wait(monitor_id)
if detail["status"] != STATUS_COMPLETE:
for row in failed_files(monitor_id)[:10]:
print(row["sourceFilePath"], row.get("errorCode"), file=sys.stderr)
print(f"retried {retry_failed(monitor_id)} files", file=sys.stderr)
sys.exit(1)
print(f"deployed: {path}")배포 이력 연결
monitorId와 커밋 정보를 짝지어 기록하기
monitorId와 커밋 정보를 함께 남기면 배포 이력과 코드 이력이 연결됩니다.
record = {
"monitorId": monitor_id,
"commit": subprocess.check_output(
["git", "rev-parse", "HEAD"], text=True).strip(),
"branch": os.getenv("GIT_BRANCH"),
"buildNumber": os.getenv("BUILD_NUMBER"),
"targetPath": path,
}
db.insert("deployments", record)| 기록 항목 | 내용 |
|---|---|
monitorId | 전송 식별자 |
commit · branch | 산출물이 나온 코드 지점 |
buildNumber | CI 실행 번호 |
targetPath | 대상 장비의 반영 경로 |
배포에 문제가 생겼을 때 monitorId로 이력을 찾고, 거기서 커밋까지 거슬러 올라갈 수 있습니다.
최근 배포 이력은 기간으로 조회합니다.
from datetime import datetime, timedelta, timezone
def paginate(path, params=None, limit=200, max_pages=50):
query = dict(params or {})
query["limit"] = limit
cursor = None
for _ in range(max_pages):
if cursor:
query["cursor"] = cursor
result = api("GET", path, params=query) or {}
for record in result.get("data") or []:
yield record
pagination = result.get("pagination") or {}
if not pagination.get("hasMore"):
return
cursor = pagination.get("nextCursor")
if not cursor:
return
end = datetime.now(timezone.utc)
fmt = "%Y-%m-%dT%H:%M:%SZ"
for row in paginate("/api/transfer-history", params={
"startDate": (end - timedelta(days=7)).strftime(fmt),
"endDate": end.strftime(fmt),
}):
print(row["monitorId"], row.get("statusName"),
row.get("targetDeviceName"), row.get("startDate"))| 확인 항목 | 확인 내용 |
|---|---|
| 산출물 | 전송한 파일과 경로 |
| 참조 | 커밋 또는 태그 |
| 대상 | 반영된 장비와 경로 |
| 상태 | 대상별 성공과 실패 |
| 실패 파일 | 파일별 오류와 재전송 결과 |