네이티브 AOT 빌드를 한다고 해도 닷넷 런타임만 필요 없을 뿐, glibc 같은 네이티브 종속성들은 여전히 필요합니다. 물론 “glibc는 대부분의 리눅스 배포판에서 채택하고 있으니 문제가 없지 않나요?” 라고 질문하실 수도 있는데, 아치 리눅스같은 최신 glibc에서 빌드한 바이너리는 데비안같은 구형 glibc에서 실행이 안 될수도 있습니다.
그래서 glibc 버전에 상관 없이 실행시키고 싶다면 정적 바이너리를 만들어야 하는데, 보통 정적 링킹에는 glibc 대신 musl을 사용합니다. 그래서 이 글에서도 musl을 사용하겠습니다.
0. 프로젝트 준비하기
먼저 프로젝트를 준비해야 겠죠.
dotnet new console --aot -o Test
이렇게 하면 다음과 같은 프로젝트가 생성됩니다.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
</Project>
여기서 PropertyGroup 안에 다음 내용을 추가합니다.
<PropertyGroup>
...
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
<RuntimeIdentifier>linux-musl-x64</RuntimeIdentifier>
<StaticExecutable>true</StaticExecutable>
</PropertyGroup>
이제 빌드를 하면 되는데, 방법은 두 가지가 있습니다.
1. 그냥 로컬에서 빌드하기
로컬에서 빌드하려면 준비물이 필요합니다.
아치 리눅스는
sudo pacman -S clang lld llvm musl
우분투는
sudo apt install clang lld musl-dev
그리고 프로젝트 파일을 다음과 같이 수정합니다.
<PropertyGroup>
...
<StaticExecutable>true</StaticExecutable>
<LinkerFlavor>lld</LinkerFlavor>
</PropertyGroup>
<ItemGroup>
<LinkerArg Include="--rtlib=compiler-rt" />
<LinkerArg Include="--sysroot=/usr/lib/musl" />
</ItemGroup>
여기서 --sysroot가 중요한데, 위는 아치 리눅스 기준입니다. 우분투는 좀 복잡해서 다음과 같이 해야 합니다.
<LinkerArg Include="--sysroot=(프로젝트 루트)/musl" />
<LinkerArg Include="-L/usr/lib/gcc/x86_64-linux-gnu/15" />
그리고 프로젝트 루트에서 다음 명령어를 입력해야 합니다.
mkdir musl
ln -s /usr/lib/x86_64-linux-musl musl/lib
또 gcc 라이브러리 경로를 지정하는 부분이 있는데, 위는 현재 개발 중인 우분투 26.04 LTS 기준입니다. gcc 버전에 따라 경로가 다릅니다.
어쨌든 이렇게 하고 dotnet publish를 하면 되는데, 솔직히 말해서 좀 복잡하기 때문에 저는 2번 방법을 더 추천합니다.
2. Docker를 이용하여 빌드하기
그냥 알파인 리눅스 Docker 이미지에서 빌드하는게 더 속 편할 수 있습니다. 그냥 프로젝트 루트에서 아래 명령어 실행하기만 하면 됩니다.
docker run --rm -v "${PWD}:/src" -w /src mcr.microsoft.com/dotnet/sdk:10.0-alpine-aot dotnet publish
이 방식의 단점은 bin 폴더와 obj 폴더가 root 계정의 소유로 되어 있기 때문에 관리가 좀 귀찮다는 것 정도?
3. 정적 링크 확인하기
file Test
Test: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), static-pie linked, BuildID[sha1]=d8a82efa9d1316daee5f90899c4c50341ea6611f, stripped
ldd Test
statically linked
이렇게 나오면 성공적으로 정적 링크가 된 것입니다.