스마트 업데이트 개발 스토리

1. 개요

CSLEE 개발자 박소은입니다. 저는 이번에 CSLEE의 자체 솔루션인 QMon 이라는 SQL Server를 모니터링 하는 프로그램을 맡아 개발하였습니다. 특히, 그 중에서도 저는 QMon에 ‘온라인 업데이트 기능’을 구축하는 역할을 맡게 되었습니다. 온라인 업데이트 기능은 QMon을 사용하는 고객들이 QMon의 새로운 기능의 업데이트나 버그 수정이 있을 때 마다 복잡한 절차를 거치지 않고도 손쉽게 솔루션을 최신 상태로 유지할 수 있도록 하였습니다. 이 온라인 업데이트 기능은 ERgrin에 구축되어있는 DMUpdater를 이용하여 개발하였고, 이를 구축하며 개발 과정을 남기고 공유하고자 CSLEE 기술 블로그에 글을 남기게 되었습니다. 

2. 프로젝트 이식 및 구축 단계

먼저 기존의 Updater 프로젝트를 사용하기 위해, 구축하고자 하는 프로젝트에 Add 하여 이식합니다. 그리고 이제 Updater와 프로젝트를 연결시켜야 합니다. 하지만 이 둘은 서로 다른 프로젝트이기에 매개체가 필요합니다. 이 때, 기존 프로젝트에 Receiver 클래스를 추가하여 두 프로젝트가 통신할 수 있도록 합니다. 

상황에 따른 Updater 구축 방법

이식이 완료된 Updater는 2가지 상황에서 실행되어야 합니다.

1. 프로그램이 구동되기 전 자동으로 실행

2. 프로그램 내부 메뉴에서 수동으로 실행

각각의 두 필요한 상황에 따라 Updater가 적절히 실행되도록 해야합니다. 

첫번째 경우에 대해서는, 주 진입점을 찾고 Updater가 인스턴스가 시작되기 전에 Updater를 먼저 호출하도록 합니다.

두번째 경우에 대해서는 만들고자 하는 메뉴 위치에 Node를 삽입하고, 해당 View를 만들어서 수동으로 Updater를 실행할 수 있도록 합니다.

3. 서버 구축 단계

Updater를 다른 프로젝트에 이식하였습니다. 하지만 이 상태로는 Updater가 동작하지 않습니다. 왜일까요? Updater의 동작 원리는 FTP 서버와의 통신을 이용한 프로그램 업데이트입니다. GetUpdateComparison()이란 메서드를 자세히 살펴보면 알 수 있습니다. 코드는 다음과 같습니다. 

public UpdateComparison GetUpdateComparison()
{
    try
    {
        var remoteUpdateInfo = UpdateInfo.FromUri($"{_localUpdateInfo.Uri}/{UpdateInfoFilename}");
        return new UpdateComparison(_localUpdateInfo, remoteUpdateInfo);
    }
    ...
}

이 메서드를 통해 localUpdateInfo와 remoteUpdateInfo를 비교한다는 것을 확인할 수 있습니다. 버전을 비교하기 위해서는 버전 정보 메타 파일을 이용합니다.

즉, 아직 저희는 프로젝트 이식만 했을 뿐 서버 설정을 하지 않았습니다. 그럼 먼저 서버 생성 및 설정을 해 보겠습니다. 

3.1 Caddy 서버 구축

먼저 AWS EC2 서비스를 통해 ubuntu 인스턴스를 생성하고 PuTTY를 통해 할당받은 IP로 접속합니다. 이제 Caddy를 설치합니다. 다음은 Caddy 안정화 버전 설치 스크립트 입니다.

$ sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
$ curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
$ curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
$ sudo apt update
$ sudo apt install caddy

그리고 도메인 서비스를 통해 사용하고자 하는 도메인을 할당받은 IP를 AWS에 적용해두었습니다. Caddy 설정 파일을 사용하고자 하는 도메인으로 수정해야 합니다. 이를 설정할 수 있는 파일은 /etc/caddy/Caddyfile입니다

$ sudo vi /etc/caddy/Caddyfile

위의 명령어를 치고 원하는 도메인 명으로 Caddyfile을 수정합니다. 버전 정보를 기록하는 undateinfo.xml 파일의 경로를 /var/www/yourproject에 위치할 것이기 때문에 다음과 같이 수정하였습니다. 

yourDomain.com {
    root * /var/www/yourproject
    encode zstd gzip
    try_files {path} /index.html
    file_server
}

모든 설정을 마쳤다면 재설정을 위하여 restart를 해줍니다.

$ systemctl caddy restart

이제 updateinfo.xml 파일 및 업데이트 파일들이 저장될 경로를 생성하겠습니다. 

$ mkdir -p /var/www/yourproject/update

ubuntu 계정으로 접속하고 파일들을 업데이트 하기 때문에 폴더 소유자 및 소유 그룹을 ubuntu로 변경하고, 소유자에게 읽기, 쓰기, 실행권한을 주고 나머지 사용자들에게는 읽기, 실행권한만 부여하였습니다. 

$ sudo chown ubuntu:ubuntu /var/www/yourproject/update
$ sudo chmod 755 /var/www/yourproject/update

3.2 코드 내 서버 정보

이제 Caddy 서버와 통신하기 위해서 코드를 수정하겠습니다. Caddy 서버와 통신하기 위한 URI 정보는 UpdateInfo.cs 라는 파일 내에 존재합니다. Line 43 부분을 수정하겠습니다

public Uri Uri { get; set; } = new Uri("https://yourDomain.com/update");

4. 업데이트 파일 업로드 및 테스트 단계

서버까지 구축했으니 이제 본격적으로 파일들을 업로드하고 업데이트가 잘 되는 지 테스트를 해 볼 차례입니다. 

4.1 로컬 버전 정보 

로컬 버전 정보를 가지고 있는 updateinfo.xml 파일이 필요합니다. Main 프로젝트에 updateinfo.xml 파일을 생성 합니다. 다음 .xml 파일은 1.1.5.0 버전 정보를 가지고 있습니다. <Keep> 태그 안에는 업데이트 프로세스와 관계 없이 이전 버전의 파일을 유지할 파일들의 리스트를 적습니다. <Ignore> 태그 안에는 파일이 업데이트 프로세스가 진행될 때 이전에 존재하든 존재안하든 무관하게 업데이트가 진행될 수 있도록 무시할 파일들의 리스트를 적습니다. 

<?xml version="1.0" encoding="utf-8"?>
<... Version="1.1.5.0" ...>
<Keep>
        ...
</Keep>
...
<Ignore>
        ...
</Ignore>

4.2 서버에 파일 업로드

서버에 업로드 할 파일들의 확장자는 .gz 형태가 되어야 합니다. 이는 Updater에서 간단한 Generate라는 명령어를 수행하면 자동적으로 생성되게 되어있는데요. 그 전에 먼저 프로젝트를 publish 해서 업로드 할 파일들의 목록을 만들어야 합니다. 

Main 프로젝트를 우클릭하여 Properties 를 열어줍니다. 여러 메뉴 중 Publish 메뉴를 선택합니다. Publish Location과 Publish Version을 지정합니다.

그 후 Publish Now를 눌러서 Publish를 진행하는 방법도 있지만, 저는 업데이트 버전을 서버에 업로드 하는 중 실수가 생기지 않도록 One Click Build Script를 생성하여 한번에 진행하고자 하였습니다. 

4.3 One Click Build

제가 구성한 One Click Build는 총 4가지의 파일로 이루어져 있습니다. 

첫번째는 Build 파일, 두번째 파일은 버전을 기록하는 .json 파일, 세번째는 버전에 맞게 Publish를 진행하고 .gz파일을 생성하는 Generate 명령어를 포함하는 스크립트 파일입니다. 그리고 마지막으로 FTP 서버에 접속하여 기존 폴더에 있는 모든 파일들을 지우고 Generate를 통해 생성한 현재 버전의 .gz파일들을 FTP 서버에 업로드 하는 스크립트 파일입니다.

1. 

cd DMUpdater
msbuild DMUpdater.csproj /p:Configuration=Release /p:Platform=x64
cd ..
msbuild Your.Project.sln /p:Configuration=Release /p:Platform=x64
pause

Updater를 먼저 빌드하고 전체 프로젝트를 빌드합니다.

2.currentVersion.

{
    "version": "1.1.5.0",
    "version_": "1_1_5_0",
    "last_updated": "2024-02-13"
}

현재 버전을 포맷에 맞게 두 가지 방식으로 기록하고, 참고용으로 날짜를 기록합니다. 

3. 

cd Your.Project.UI.Main
msbuild Your.Project.UI.Main.csproj /t:Publish /p:Configuration=Release /p:Platform=x64 /p:BaseOutputPath=bin\ /p:PublishDir=publish\
cd ..
@echo off
for /f %%A in ('powershell -Command "(Get-Content 2.currentVersion.json | ConvertFrom-Json).version_"') do set version_=%%A
echo The version_ is %version_%
xcopy "DMUpdater\bin\x64\Release\net46-windows\DMUpdater.exe" "Your.Project.UI.Main\publish\Application Files\Your.Project.UI.Main_%version_%" /y
xcopy "DMUpdater\bin\x64\Release\net46-windows\DMUpdater.exe.config" "Your.Project.UI.Main\publish\Application Files\Your.Project.UI.Main_%version_%" /y
cd "Your.Project.UI.Main\publish\Application Files\Your.Project.UI.Main_%version_%"
del "Your.Project.UI.Main.exe.config"
del "Your.Project.UI.Main.exe.manifest"
.\DMUpdater.exe Generate
pause

프로젝트의 Main 디렉토리로 이동 후 Publish 합니다. 그리고 2번 파일에서 버전을 업데이트 하는 것을 깜빡하지 않았는지 버전에 대한 echo를 합니다. Publish가 끝난 후 업데이트에 필요한 DMUpdater.exe 파일과 DMUpdater.exe.config 파일을 publish 폴더로 복사합니다. 그 후, 업데이트에 필요하지 않은 파일인 Main.exe.config파일과 Main.exe.manifest 파일을 삭제합니다. 

모든 준비가 끝났으니 이제 .gz 파일을 생성하는 DMUpdater.exe Generate 명령어로 서버에 업로드 할 파일들을 생성합니다.

4. 

set "original_dir=%CD%"
@echo off
for /f %%A in ('powershell -Command "(Get-Content 2.currentVersion.json | ConvertFrom-Json).version_"') do set version_=%%A
echo The version_ is %version_%
cd "C:\\Program Files\PuTTY"
plink.exe -i "C:\where\Your\ppk\is\yourproject.ppk" ubuntu@1.1.1.1 "sudo rm -r /var/www/yourproject/update*"
echo files deleted!
pscp.exe -r -i "C:\where\Your\ppk\is\yourproject.ppk" "%original_dir%\Your.Project.UI.Main\publish\Application Files\Your.Project.UI.Main_%version_%\publish\*" ubuntu@1.1.1.1:/var/www/yourproject/update
echo files copied!
pause

업로드할 버전을 최종적으로 확인하고, PuTTY가 설치되어 있는 위치에서 .ppk 파일을 통해 서버에 접속합니다. 서버에 접속 후 업로드 할 디렉토리에 디렉토리를 포함한 모든 파일들을 지웁니다. 그 후 Generate를 통해 생성 한 .gz파일을 전체 파일을 타겟 위치에 복사합니다. 

이렇게 각각의 파일을 순서대로 실행과 수정을 진행하면 빠뜨리는 일 없이 서버에 업데이트 파일을 업로드 할 수 있습니다. 

이를 통해 프로젝트에서 스마트 업데이트를 구축하는 방법에 대해 알아보았습니다. CSLEE의 SQL Server 모니터링 솔루션인 QMon은 온라인 업데이트 기능을 갖추었습니다. 이 기능은 앞으로의 유지보수에 큰 도움이 될 것으로 기대됩니다. 이를 통해 CSLEE는 고객들에게 더 신속하고 효율적인 서비스를 제공할 수 있게 되었습니다.

저희 CSLEE는 고객들의 다양한 요구를 충족시키기 위해 끊임없이 소통하고, 새로운 기술과 기능을 도입하고 발전시켜 나갈 것을 약속드립니다.