Windows Service를 컨테이너로 옮기기

Windows, Docker, Container

독립적인 서버에서 실행되던 애플리케이션을 컨테이너로 옮기는 작업은 매우 많은 고민을 필요로 합니다. 이 때의 고민들은 아키텍처 측면에서의 고민도 있겠지만, 실질적인 문제를 포함하는 경우도 있습니다.

이번에 소개하려는 내용은 컨테이너로 옮기기 까다로운 유형에 해당하는 Windows Service 타입의 서버 애플리케이션을 옮기는 방법입니다. 콘솔이나 RDP 등을 통한 사용자의 상호작용을 필요로 하지 않는 서버의 경우 오늘 소개하는 내용을 통하여 쉽게 컨테이너로 서버를 패키징할 수 있을 것입니다.

준비하기

Windows Serer 2016에 이어서 차기 LTSC 버전의 서버 운영 체제로 Windows Server 2019가 출시되었습니다. 여기에 맞추어 이번 글에서는 Windows Server 2019 (1809)를 기준으로 내용을 설명하며, Windows 10 2018년 10월 업데이트 (17763 빌드)를 설치한 상태에서 테스트해보실 수 있는 내용입니다.

그리고 빠른 실습을 위하여 Windows Container Base Image를 미리 다운로드받아두시는 것을 추천합니다. 이미지 파일의 크기가 매우 크기 때문에, 별도 요금이 발생하지 않는 Wi-Fi 등을 이용하여 다운로드하시는 것이 좋습니다. 다음과 같이 명령을 실행합니다.

docker pull mcr.microsoft.com/windows/servercore:1809

서비스를 컨테이너화하는 방법

서버 응용프로그램을 컨테이너화하는 것은 보통 단독으로 실행되는 프로그램을 기준으로 합니다. 리눅스 및 유닉스 환경에서는 거의 모든 응용프로그램들이 그 즉시 다른 도우미 프로그램을 경유하지 않고 실행되므로 컨테이너화를 하는데 큰 고민을 할 것이 없습니다.

반면 Windows의 경우 서비스 제어 관리자 (Service Control Manager, 이하 SCM)에 의하여 백그라운드 서비스를 다루게 됩니다. 그리고 SCM이 인식할 수 있는 서비스는 통상적인 응용프로그램들과는 동작 방식이 다릅니다. 컨테이너화를 하는데 있어서 허들이 되는 부분은 즉 SCM이라고 할 수 있습니다.

Windows 서비스를 컨테이너화한다는 것은, 컨테이너화할 서비스가 정상적으로 시작되어 SCM에 의하여 종료를 맞이할 때까지 컨테이너가 계속 실행되어야 함을 뜻합니다. 하지만 컨테이너 런타임은 SCM의 실행 상태를 고려하지 않고, 또한 SCM을 고려한다는 것은 플랫폼에 지나치게 종속적인 일입니다.

이 문제를 해결하기 위하여 Microsoft에서는 IIS용으로 ServiceMonitor 라는 오픈 소스 유틸리티를 공개하였습니다. 하지만 IIS만이 아니라 SCM에 의하여 제어할 수 있는 서비스에는 모두 적용할 수 있습니다.

위의 리포지터리에서 제공하는 릴리스된 버전의 ServiceMonitor 유틸리티를 Docker로 이미지를 빌드하는 도중에 추가하여 ENTRYPOINT로 지정하면 손쉽게 활용할 수 있습니다.

실제 Dockerfile 예시 살펴보기

이번 글에서의 관심사는 순수하게 ServiceMonitor의 활용에 관한 것이므로 저는 Windows 운영 체제에 이미 포함되어있는 예제 TCP 서비스를 컨테이너화하는 것으로 시나리오를 잡았습니다.

예제 TCP 서비스를 활성화하기 위한 제반 사항을 포함하는 SKU는 Windows Server Core이므로, 해당 이미지를 기본 이미지로 사용하고, PowerShell 명령어를 자주 써야 하므로 기본 Shell을 PowerShell로 변경하기 위하여 Dockerfile의 첫 시작은 다음과 같이 만들었습니다.

FROM mcr.microsoft.com/windows/servercore:1809
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'Continue'; $verbosePreference='Continue';"]

앞에서 이야기한 ServiceMonitor를 미리 빌드한 버전을 다음과 같이 루트 디렉터리 (C:\)에 추가합니다.

ADD https://dotnetbinaries.blob.core.windows.net/servicemonitor/2.0.1.3/ServiceMonitor.exe /

예제 TCP 서비스를 Windows 부가 기능에서 찾아 활성화하고, 서비스를 시작 상태로 변경하는 PowerShell 명령을 다음과 같이 추가합니다.

RUN Enable-WindowsOptionalFeature -Online -FeatureName SimpleTCP
RUN Start-Service -ServiceName SimpTcp

Echo (7/tcp), Discard (9/tcp), Daytime (13/tcp), QotD (17/tcp), chargen (19/tcp)가 SimpTcp 서비스로 인하여 한 번에 시작됩니다. 컨테이너 외부에서 이들 서비스에 접속할 수 있도록 설정합니다.

# Echo
EXPOSE 7
# Discard
EXPOSE 9
# Daytime
EXPOSE 13
# Quote of the Day (QOTD)
EXPOSE 17
# Character Generator (chargen)
EXPOSE 19

그리고 SimpTcp 서비스가 실행되는 동안 컨테이너가 종료되지 않도록 ServiceMonitorSimpTcp 서비스를 바라보도록 ENTRYPOINT 설정을 다음과 같이 추가합니다.

ENTRYPOINT C:\ServiceMonitor SimpTcp

지금까지 만든 Dockerfile은 다음과 같습니다.

FROM mcr.microsoft.com/windows/servercore:1809
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'Continue'; $verbosePreference='Continue';"]
ADD https://dotnetbinaries.blob.core.windows.net/servicemonitor/2.0.1.3/ServiceMonitor.exe /
RUN Enable-WindowsOptionalFeature -Online -FeatureName SimpleTCP
RUN Start-Service -ServiceName SimpTcp
# Echo
EXPOSE 7
# Discard
EXPOSE 9
# Daytime
EXPOSE 13
# Quote of the Day (QOTD)
EXPOSE 17
# Character Generator (chargen)
EXPOSE 19
ENTRYPOINT C:\ServiceMonitor SimpTcp

컨테이너 이미지 빌드 및 테스트

컨테이너 이미지를 빌드하기 위하여 Dockerfile이 있는 디렉터리에서 다음과 같이 명령어를 실행합니다.

docker build -t simpletcp:latest .

그 후, 다음과 같이 컨테이너를 실행합니다.

docker run --rm -d -p 7:7 -p 9:9 -p 13:13 -p 17:17 -p 19:19 --name=simpletcp simpletcp:latest .

컨테이너가 정상적으로 계속 실행되는지 확인한 후에, 13, 17, 19번 포트 중 하나를 골라서 telnet 명령으로 접속해봅니다.

telnet localhost 13
telnet localhost 17
telnet localhost 19

각각에 해당되는 서비스가 정상 작동하는 모습을 볼 수 있습니다.

참고로 telnet 유틸리티는 기본으로 설치되지 않으므로 호스트 PC에서 관리자 권한으로 아래 명령을 실행하여 해당 기능을 설치해야 합니다.

Install-WindowsFeature Telnet-Client

더 생각해볼 것

일단 컨테이너화된 Windows 서비스가 잘 작동하는 것을 볼 수 있었습니다. 하지만 stdout으로 로그를 내보내는 기능이 없어 동작 상태를 즉시 파악할 수 없는 문제가 있습니다.

ServiceMonitor는 IIS를 컨테이너화할 목적으로 만들어진 제한된 목적의 도우미 유틸리티입니다.

그러나 최소한의 기능만 제공하면서도 오픈 소스로 공개되어있어서 여러분의 입맛대로 기능을 커스터마이징할 수 있습니다. 약간의 기능 개선을 적용하면 서비스의 동작 상태를 ServiceMonitor로 하여금 stdout에 표시하게 하는 등의 개발을 좀 더 진행하는 것을 고려해볼 수도 있습니다.

또는 fluentd 등의 솔루션을 이용하여 파일로 기록되는 로그를 중앙으로 계속해서 올려보내는 전략을 생각해 볼 수도 있겠습니다.