가이드 세션을 듣기 전에는, 세션에서 알려주는 대로 방향성을 정하면 된다고 생각했는데, 가이드 세션에서의 유일한 가이드는 목표 지정 부터 개발 까지 모두 스스로의 방식으로 하면 된다는 것이었다. 재밌겠다라고 생각한 동시에 어떻게 해야되는거지? 라는 생각이 들었다. 그래서 우선, 회사에서 프로젝트를 진행하면서 해결하면 가장 큰 Impact를 만들 수 있는 작업을 찾아 보기로했다.
1. 필요한 작업
1. CI/CD 도입
2. 로그기반 모니터링 시스템 구축
3. 테스트 자동화
4. 정적 리소스 서버 (파일 서버) 분리
프로젝트를 정리해보니 위 4가지 아이템이 가장 Impact가 클 것으로 보였다.
2. 항목 평가
평가 지표는 '안정성 위협', '리소스 낭비', '팀에 미치는 영향', ' 개발 생산성 향상', '비즈니스 연계 효과'를 기준으로 정했다.
각각의 지표로 위 항목을 평가하였다.
| 구분 | 안정성 확보 | 리소스 절감 | 팀에 미치는 영향 | 개발 생산성 향상 | 비즈니스 연계 효과 | 총점 |
| CI/CD 도입 | 4 | 4 | 4 | 5 | 4 | 21 |
| 로그 기반 모니터링 시스템 구축 | 5 | 3 | 4 | 3 | 4 | 19 |
| 테스트 자동화 | 4 | 3 | 3 | 4 | 3 | 17 |
| 정적 리소스 서버 분리 | 3 | 4 | 1 | 1 | 2 | 11 |
위 지표를 토대로 각 항목의 점수를 매겨보니 위와 같았다. 특히 CI/CD는 한 번 구축해 놓으면 앞으로 새로 시작할 프로젝트들에도 적용할 수 있어 추가적인 효과들이 많을 것이라고 생각된다.
시간이 허락하는 한 로그 기반 모니터링 시스템 구축 부터 정적 리소스 서버 분리까지 다 개발을 하고 싶지만 우선 CI/CD 시스템을 어떻게 만들지에 대해 고민해야할 것 같다.
3. 시작 전 고려사항
우선 도커를 사용해 내부 Gitlab을 만드려고 했으나, 서버에서 wsl install이 되지 않았다.. 인프라팀 문의해보니 역시나 정책상 불가하다는 답변을 받았다.
시스템을 도입하기 이전에 인터넷 필요 유무, 오픈소스 라이선스, 인프라에 대해 고민할 필요가 있을 것 같다. 사용가능한 기술 스택을 확정을 짓고, 온프레미스 서버에서 구성이 가능한지까지 확인을 해야 한다. 회사의 개발환경이 굉장히 폐쇄적이기 때문에 섣부르게 개발을 시작했다가는 낭패를 볼 수 밖에 없다.
- 소스 코드 관리
- CI서버 (빌드 & 테스트)
- 빌드/배포 환경 관리
- 배포 자동화 도구
- 배포 대상 인프라 (Windows 온프레미스 서버 확정)
우선 이 항목들에 대한 스택을 픽스하여 배포자동화 까지의 흐름을 구성해보는 것을 목표로 했다.
4. 시스템 구성
개발부터 배포까지의 과정 중 필수적인 부분을 프로세스화 하였다.

개발자가 코드를 푸쉬 혹은 머지하고 Gitea의 web hook과 runner를 사용해 빌드를 하고 산출물을 배포하는 과정을 만들었다. 서버 리소스가 한정적이기 때문에 Gitea서버 내에서 빌드를 진행했다.
*Gitea서버 구축 및 사용 이유
2025.12.29 - [Git] - 폐쇄망(Windows) Gitea 서버 구축, runner 설정
폐쇄망(Windows) Gitea 서버 구축, runner 설정
CI/CD 파이프라인을 만들기 위한 첫 단계로, Git 서버를 구축하였다. 기존에는 사내망 Gitlab을 사용했었는데 해당 서버는 연구소에서 빌려서 사용을 했기 때문에 서버 방화벽 관리나 여타 작업들에
maybe-developer.tistory.com
우선 개인 pc에서 위 파이프라인이 잘 돌아갈 수 있는지 프로토타입을 만들어 테스트를 해 볼 필요가 있을 것 같다.
4. 프로토 타입
위에서 설정한 아키텍쳐를 PC 1대에서 구현을 해보았다.
Gitea서버는 도커로 띄우고, NAS는 onedrive로 대체하여 IIS에 샘플 .NET 프로젝트를 호스팅 하는 것을 목표로 했다.
생각보다 Gitea서버를 띄우고 onedrive를 연동하는데서 우여곡절이 많았다.
딱히 어려울 일이 없을 거라 생각했는데, 대부분의 시간을 잡아 먹은 작업이 2가지 있었다.
1. Gitea runner 연동
초기에는 Windows PC에서 gitea서버와 act_runner를 동시에 실행하려고 했다. 둘 다 windows에서 사용이 된다고 공식문서에 나와있었고(권장한다고는 하지 않았지만), runner와 gitea 서버를 연동하는 동안 구현하면서 에러가 발생하지 않았다.
그러나 빌드 파이프라인 yaml을 작성한 뒤, actions를 트리거 했을 때 문제가 발생했다. 기본 step인 Set up job에서 원인을 알 수 없는 에러가 발생했다. Gitea포럼에서 검색해 봤을때, 동일한 증상이 있는 사람들도 있었지만 solved된 자료가 전혀 없었다.
수 많은 시도 끝에 Windows에서 직접 두 가지 모두 호스팅하는 것은 불가능하다고 생각하고 Gitea서버를 Docker에, runner만 windows에서 호스팅하니 문제 없이 실행이 되었다.

2. Onedrive 연동
서버나 가상환경, 클라우드에 대한 지식이 부족하지만, 로컬 내에서만 테스트하는 것과 외부 스토리지, 서버와 통신하는 것은 완전히 다르다고 생각한다. 그냥 기존의 NAS를 c드라이브로 대체하여 프로토타입을 만들 수도 있지만, 나중에 NAS로 실적용을 할 때 문제가 발생할 수도 있다고 생각하여 나름 비슷한 클라우드 스토리지로 대체하는 것이 좋겠다고 생각했다.
내가 고려하지 못했던 건, 난 act_runner를 windows에서 실행하면 runner가 내 머신에서 돌아가는 것이라고 생각했다. 하지만 알고보니 windows에서 직접 실행해도 가상환경에서 작업들이 진행된다는 점이었다. 많은 시도 끝에 build.yaml을 완성할 수 있었다.
// build.yaml
name: Deploy to OneDrive
on: [push]
jobs:
build:
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/dotnet/sdk:8.0
steps:
# Node.js 설치
- name: Install Node.js
run: |
apt-get update
apt-get install -y nodejs npm
# 코드 체크아웃
- name: Checkout
uses: actions/checkout@v4
# .NET 빌드 및 배포 폴더 생성
- name: Build project
run: dotnet publish Server.csproj -c Release -o ./publish
# 빌드 결과 압축
- name: Zip build output
run: |
apt-get update
apt-get install -y zip
zip -r build.zip ./publish
# 최신 rclone 설치 (SSL 인증서 포함)
- name: Install latest rclone
run: |
apt-get update && apt-get install -y ca-certificates curl unzip
curl -O https://downloads.rclone.org/rclone-current-linux-amd64.zip
unzip -o rclone-current-linux-amd64.zip
cd rclone-*-linux-amd64
cp rclone /usr/bin/
rclone version
# rclone 설정 복원 및 연결 확인.
- name: Configure rclone
run: |
mkdir -p ~/.config/rclone
echo "${{ secrets.RCLONE_CONF_B64 }}" | base64 -d > ~/.config/rclone/rclone.conf
echo "=== Remote List ==="
rclone listremotes
echo "=== OneDrive Connection Test ==="
rclone about onedrive: -v || (echo "OneDrive 연결 실패" && exit 1)
# OneDrive로 업로드
- name: Upload to OneDrive
run: |
echo "Uploading build.zip to OneDrive /Backup ..."
rclone copy ./build.zip onedrive:/Backup -v --retries 1 --low-level-retries 1
# 업로드 성공 후 Webhook 호출
- name: Trigger Webhook
if: success()
run: |
echo "Sending deploy call..."
curl -X POST ${{ secrets.URL }} \
-H "Content-Type: application/json" \
-d '{
"status": "success",
"source": "GitHub Actions",
"message": "OneDrive upload completed successfully",
"timestamp": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'"
}'

덕분에 docker에 대해 공부해야 겠다고 굳은 결심을 할 수 있었다.
어쨋든 build.yaml이 runner 데몬에서 트리거 되면, Onedrive에 빌드 산출물을 업로드 하고 Deploy api를 호출한다.
// controller
[HttpPost("gitea")]
public async Task<IActionResult> OnGiteaWebhook([FromBody] dynamic payload)
{
Console.WriteLine("Webhook received from Gitea..");
await _deployService.HandleBuildDeployAsync();
return Ok(new { message = "Build deploy triggered." });
}
//service
public async Task HandleBuildDeployAsync()
{
string buildName = "latest_build";
// NAS에서 빌드 파일 가져오기
var buildBytes = await _nasService.GetBuildFileAsync(buildName);
string tempZip = Path.Combine(Path.GetTempPath(), $"{buildName}.zip");
await File.WriteAllBytesAsync(tempZip, buildBytes);
// 배포 스크립트 실행
string psScriptPath = @"<path>\deploy.ps1"; // 실행할 PowerShell 스크립트 경로
string arguments = $"-ExecutionPolicy Bypass -File \"{psScriptPath}\" \"{tempZip}\"";
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "powershell.exe",
Arguments = arguments,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
WorkingDirectory = Path.GetDirectoryName(psScriptPath)
}
};
process.Start();
string output = await process.StandardOutput.ReadToEndAsync();
string error = await process.StandardError.ReadToEndAsync();
process.WaitForExit();
if (process.ExitCode == 0)
Console.WriteLine($"Deployment completed.\n{output}");
else
Console.WriteLine($"Deployment failed (ExitCode: {process.ExitCode})\n{error}");
}
테스트기 때문에 호스팅 되는 프로젝트에 간단하게 배포스크립트를 실행시키는 형태로 api를 구현하였다.
// deploy.ps1
param(
[string]$buildZip
)
$deployPath = "C:\deploy\publish"
$backupPath = "C:\deploy\backup_$(Get-Date -Format yyyyMMddHHmmss)"
Write-Host "🚀 Starting deployment..."
Write-Host "Source build: $buildZip"
# Stop IIS
iisreset /stop | Out-Null
Write-Host "🛑 IIS stopped."
# Backup existing deployment
if (Test-Path $deployPath) {
Move-Item $deployPath $backupPath
Write-Host "📦 Backup completed: $backupPath"
}
# Extract new build
Expand-Archive -Force $buildZip $deployPath
Write-Host "📦 Build extracted to $deployPath"
# Start IIS
iisreset /start | Out-Null
Write-Host "♻️ IIS restarted."
Write-Host "✅ Deployment completed successfully!"
4. 결과
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\deploy\publish
warn: Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware[3]
Failed to determine the https port for redirect.
Webhook received from Gitea..
start deployDeployment completed.
Starting deployment...
Source build: C:\WINDOWS\TEMP\latest_build.zip
IIS stopped.
Backup completed: C:\deploy\backup_20260106222144
Build extracted to C:\deploy\publish
IIS restarted.
Deployment completed successfully!
성공적으로 배포가 진행된 것을 확인할 수 있다. 현업에서는 100% 같은 아키텍쳐를 만들 수 없겠지만 프로토타입을 만들면서 기본적인 작동원리를 배우고 시행착오를 겪었기 때문에 테스트보다 빠르게 개발이 가능할 것 같다. (테스트를 만드는게 너무 오래 걸렸다.)
'러너스하이' 카테고리의 다른 글
| 토스 러너스하이 (4) - 마무리 및 회고 (1) | 2026.01.19 |
|---|---|
| 토스 러너스하이 (4) - 피드백 (0) | 2026.01.14 |
| 토스 러너스하이 (3) - 구현 (0) | 2026.01.13 |
| 토스 러너스하이 (1) - 개인적 회고 및 목표 설정 (1) | 2025.12.14 |