Microsoft MVP성태의 닷넷 이야기
개발 환경 구성: 696. C# - 리눅스용 AOT 빌드를 docker에서 수행 [링크 복사], [링크+제목 복사],
조회: 15165
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 2개 있습니다.)
(시리즈 글이 7개 있습니다.)
개발 환경 구성: 386. .NET Framework Native compiler 프리뷰 버전 사용법
; https://www.sysnet.pe.kr/2/0/11563

.NET Framework: 2069. .NET 7 - AOT(ahead-of-time) 컴파일
; https://www.sysnet.pe.kr/2/0/13162

닷넷: 2175. C# - DllImport 메서드의 AOT 지원을 위한 LibraryImport 옵션
; https://www.sysnet.pe.kr/2/0/13466

닷넷: 2184. C# - 하나의 resource 파일을 여러 프로그램에서 (AOT 시에도) 사용하는 방법
; https://www.sysnet.pe.kr/2/0/13483

개발 환경 구성: 696. C# - 리눅스용 AOT 빌드를 docker에서 수행
; https://www.sysnet.pe.kr/2/0/13487

닷넷: 2202. C# - PublishAot의 glibc에 대한 정적 링킹하는 방법
; https://www.sysnet.pe.kr/2/0/13529

오류 유형: 913. C# - AOT StaticExecutable 정적 링킹 시 빌드 오류
; https://www.sysnet.pe.kr/2/0/13669




C# - 리눅스용 AOT 빌드를 docker에서 수행

현재 AOT 빌드는 해당 플랫폼 환경에서만 할 수 있습니다. 간단하게 실습하면서 설명해 볼까요? ^^ 예제로 Console App을 하나 구성하고,

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <PublishAot>true</PublishAot>
    <InvariantGlobalization>true</InvariantGlobalization>
  </PropertyGroup>

</Project>

using System.Runtime.InteropServices;

namespace ConsoleApp1;

internal class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine($"{Environment.OSVersion}, {RuntimeEnvironment.GetSystemVersion()}");
    }
}

(윈도우 머신에서) 리눅스를 대상으로 AOT 빌드를 하면 이런 오류가 발생합니다.

c:\temp\ConsoleApp1\ConsoleApp1> dotnet publish -r linux-x64 -c Release
MSBuild version 17.8.3+195e7f5a3 for .NET
  Determining projects to restore...
  Restored C:\temp\ConsoleApp1\ConsoleApp1\ConsoleApp1.csproj (in 3.6 sec).
  ConsoleApp1 -> C:\temp\ConsoleApp1\ConsoleApp1\bin\Release\net8.0\linux-x64\ConsoleApp1.dll
C:\...[생략]...\Microsoft.NETCore.Native.Publish.targets(60,5): error : Cross-OS native compilation is not supported. [C:\temp\ConsoleApp1\ConsoleApp1\ConsoleApp1.csproj]

즉, Linux 환경에서 해야 한다는 것이고 간편하게는 WSL 환경에서 /mnt 경로를 이용해 빌드하는 것도 좋은 선택입니다.




WSL을 이용하는 경우 shell을 이동해야 한다는 불편함을 수반하는데요, 그렇다면 wsl.exe를 이용해 다음과 같이 처리하는 것도 가능합니다.

C:\temp> wsl /bin/bash -c "cd /mnt/c/temp/ConsoleApp1/ConsoleApp1 && dotnet publish -r linux-x64 -c Release"

혹은, docker를 이용해 해결하는 것도 가능합니다. 이런 경우 약간 준비할 것이 있는데요, 우선, Native 빌드 환경을 위해 전용 빌드 이미지를 하나 만들어야 합니다. 대략 요구되는 dockerfile은 아래와 같은 정도면 충분합니다.

c:\temp> type publish.dockerfile
FROM mcr.microsoft.com/dotnet/sdk:8.0

RUN apt-get update && apt-get install -y --no-install-recommends clang zlib1g-dev

그다음, 재사용할 이미지로 생성해 두고,

c:\temp> docker build -t net80-native-build-machine -f publish.dockerfile .

이후, dotnet publish가 필요할 때마다 다음과 같이 실행해 줍니다.

c:\temp> SET PROJECT_DIR=/C/temp/ConsoleApp1/ConsoleApp1

c:\temp> docker rm -f net80-native-build-machine-instance

c:\temp> docker run -v %PROJECT_DIR%:/app --name net80-native-build-machine-instance --rm -it net80-native-build-machine /bin/bash -c "cd /app && dotnet publish -r linux-x64 -c Release"

그럼, C:\temp\ConsoleApp1\ConsoleApp1\bin\Release\net8.0\linux-x64\publish 디렉터리에 linux-x64용으로 빌드된 결과가 생성됩니다.




그런데, 이렇게 빌드한 실행 파일을 Ubuntu 20.04에서 실행하면 이런 오류 메시지가 발생하는군요. ^^;

// WSL + Ubuntu 20.04에서 실행

$ ./ConsoleApp1
./ConsoleApp1: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found (required by ./ConsoleApp1)
./ConsoleApp1: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by ./ConsoleApp1)

$ ldd --version
ldd (Ubuntu GLIBC 2.31-0ubuntu9.14) 2.31

반면 22.04에서는 잘됩니다.

// WSL + Ubuntu 22.04에서 실행

$ ./ConsoleApp1
Unix 5.15.133.1, v8.0.0

$ ldd --version
ldd (Ubuntu GLIBC 2.35-0ubuntu3.5) 2.35

$ objdump -p ConsoleApp1

ConsoleApp1:     file format elf64-x86-64

...[생략]...

Dynamic Section:
  NEEDED               libm.so.6
  NEEDED               libc.so.6
  NEEDED               ld-linux-x86-64.so.2
...[생략]...

Version References:
  required from ld-linux-x86-64.so.2:
    0x0d696913 0x00 14 GLIBC_2.3
  required from libm.so.6:
    0x06969189 0x00 15 GLIBC_2.29
    0x09691a75 0x00 06 GLIBC_2.2.5
  required from libc.so.6:
    0x06969190 0x00 13 GLIBC_2.10
    0x09691974 0x00 12 GLIBC_2.3.4
    0x0d696914 0x00 11 GLIBC_2.4
    0x06969197 0x00 10 GLIBC_2.17
    0x06969194 0x00 09 GLIBC_2.14
    0x069691b2 0x00 08 GLIBC_2.32
    0x0d696919 0x00 07 GLIBC_2.9
    0x0d696916 0x00 05 GLIBC_2.6
    0x069691b4 0x00 04 GLIBC_2.34
    0x09691a75 0x00 03 GLIBC_2.2.5
    0x09691972 0x00 02 GLIBC_2.3.2

검색해 보면,

GLIBC_2.32 not found
; https://copyprogramming.com/howto/glibc-2-32-not-found

Ubuntu 20.10부터 2.32 버전이 제공된다고 합니다. 그러니까... "mcr.microsoft.com/dotnet/sdk:8.0" 이미지가 그 버전 이상의 리눅스 이미지를 기반으로 한다는 것인데요, 다소 번거롭지만 이런 문제를 해결하려면 dockerfile을 다음과 같이 구성해야 합니다.

FROM ubuntu:20.04

RUN apt-get update
RUN apt-get install -y wget

RUN wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
RUN dpkg -i packages-microsoft-prod.deb
RUN rm packages-microsoft-prod.deb

RUN apt-get update
RUN apt-get install -y dotnet-sdk-8.0
RUN apt-get install -y --no-install-recommends clang zlib1g-dev

저 dockerfile로 다시 빌드를 하면 이제 GLIBC 버전 문제는 없어집니다. 즉, Ubuntu 20.04, 22.04에서도 잘 동작합니다. 혹시 리눅스 전문가가 계신다면 이에 대한 이유를 좀 알 수 있을까요? 리눅스 계열에서는 GLIBC를 하위 호환성을 지키기 때문에 그런 것인지, 아니면 이전 버전을 모두 유지해서 포함시켜 두기 때문에 괜찮은 것인지, 그 이유를 잘 모르겠습니다. (glibc는 하위 호환성이 있어 낮은 버전과 링크하는 경우에는 범용성이 좋아진다고 합니다.)




다시 예상할 수 있듯이, 이제는 ubuntu 18에서 유사한 오류가 발생합니다.

$ ./ConsoleApp1
./ConsoleApp1: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.29' not found (required by ./ConsoleApp1)

그런데 아쉽게도 .NET 8의 공식 리눅스 지원 버전이 Ubuntu로는 20.04+라고 명시돼 있는데요,

.NET 8 - Supported OS versions - Linux
; https://github.com/dotnet/core/blob/main/release-notes/8.0/supported-os.md#linux

재미있는 건 18.04에서도 설치 및 실행이 모두 잘됩니다. ^^ 따라서 다음과 같이 이미지 버전만 바꿔주고,

FROM ubuntu:18.04

RUN apt-get update
RUN apt-get install -y wget

RUN wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
RUN dpkg -i packages-microsoft-prod.deb
RUN rm packages-microsoft-prod.deb

RUN apt-get update
RUN apt-get install -y dotnet-sdk-8.0
RUN apt-get install -y --no-install-recommends clang zlib1g-dev

AOT 빌드하면 Ubuntu 18.04 ~ 22.04에서 잘 동작하고 심지어 CentOS 7에서도 정상적으로 실행됩니다.
// docker ubuntu:18.04 환경에서 실행
$  ldd --version
ldd (Ubuntu GLIBC 2.27-3ubuntu1.6) 2.27
...[생략]...

// CentOS 7 환경에서 실행
$ ldd --version
ldd (GNU libc) 2.17
...[생략]...

$ lsb_release -a
LSB Version:    :core-4.1-amd64:core-4.1-noarch
Distributor ID: CentOS
Description:    CentOS Linux release 7.6.1810 (Core) 
Release:        7.6.1810
Codename:       Core

$ ./ConsoleApp1
Unix 5.1.15.1, v8.0.0

왜냐하면, 이렇게 빌드했을 때의 링크된 버전을 보면,

$ objdump -p ConsoleApp1
...[생략]...
Version References:
  required from ld-linux-x86-64.so.2:
    0x0d696913 0x00 18 GLIBC_2.3
  required from libdl.so.2:
    0x09691a75 0x00 11 GLIBC_2.2.5
  required from librt.so.1:
    0x09691a75 0x00 10 GLIBC_2.2.5
  required from libm.so.6:
    0x09691a75 0x00 06 GLIBC_2.2.5
  required from libc.so.6:
    0x06969196 0x00 19 GLIBC_2.16
    0x06969190 0x00 17 GLIBC_2.10
    0x09691974 0x00 16 GLIBC_2.3.4
    0x0d696914 0x00 15 GLIBC_2.4
    0x06969194 0x00 13 GLIBC_2.14
    0x0d696919 0x00 12 GLIBC_2.9
    0x09691972 0x00 09 GLIBC_2.3.2
    0x0d696913 0x00 07 GLIBC_2.3
    0x0d696916 0x00 05 GLIBC_2.6
    0x09691a75 0x00 03 GLIBC_2.2.5
  required from libpthread.so.0:
    0x09691973 0x00 14 GLIBC_2.3.3
    0x06969192 0x00 08 GLIBC_2.12
    0x09691a75 0x00 04 GLIBC_2.2.5
    0x09691972 0x00 02 GLIBC_2.3.2

보는 바와 같이 2.16 버전이 최고입니다.





마지막으로, 당연히 위와 같은 문제는 WSL을 경유한 빌드에서도 동일하게 발생합니다. wsl을 그냥 실행하는 경우 "Default"로 지정한 인스턴스에 명령을 전달하기 때문에,

c:\temp> wsl -l
Linux용 Windows 하위 시스템 배포:
Ubuntu-20.04(기본값)
Ubuntu-18.04
docker-desktop-data
docker-desktop

따라서, 다음과 같이 배포본 이름을 지정해 빌드를 해야 합니다.

C:\temp> wsl -d Ubuntu-18.04 --exec ldd --version
ldd (Ubuntu GLIBC 2.27-3ubuntu1.6) 2.27
...[생략]...

C:\temp> wsl -d Ubuntu-18.04 --exec lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.6 LTS
Release:        18.04
Codename:       bionic

C:\temp> wsl -d Ubuntu-18.04 --exec /bin/bash -c "cd /mnt/c/temp/ConsoleApp1/ConsoleApp1 && dotnet publish -r linux-x64 -c Release"

그런데, 이상한 점이 있는데요, WSL에 설치되는 Ubuntu 18.04에서 AOT 빌드한 결과물은 해당 환경에서 빌드했음에도 불구하고 실행이 안 됩니다.

$ ./ConsoleApp1
./ConsoleApp1: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.29' not found (required by ./ConsoleApp1)

18.04의 GLIBC 환경이 2.27로 2.29보다 더 낮은 환경이었는데 저렇게 실행이 안 되는 것입니다. docker 환경에서 빌드했을 때는 괜찮았는데, 왜 WSL + Ubuntu 18.04에서 빌드했을 때는 안 되는 것인지도 참 이상합니다. ^^; (혹시 이유를 아신 분은 덧글 부탁드립니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/13/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2023-12-18 09시39분
Using Native AOT
; https://github.com/dotnet/runtime/tree/main/src/coreclr/nativeaot/docs

Building native AOT apps in containers
; https://github.com/dotnet/runtime/blob/main/src/coreclr/nativeaot/docs/containers.md

---------------------------------------------------------
아래와 같은 노력과,

Compiling with Native AOT
; https://github.com/dotnet/runtime/blob/main/src/coreclr/nativeaot/docs/compiling.md#using-statically-linked-icu

StaticallyLinked 옵션 등이 있었던 것을 보면,

Statically Linked NativeAOT HelloWorld throws Segmentation fault with Globalization Cultural Data #70848
; https://github.com/dotnet/runtime/issues/70848

아마도 향후에는 glibc에 대한 의존성을 없애는 정적 링킹도 지원하지 않을까 싶습니다.

정성태

... 61  62  63  64  65  66  67  68  69  70  71  72  73  74  [75]  ...
NoWriterDateCnt.TitleFile(s)
12153정성태2/23/202024427.NET Framework: 898. Trampoline을 이용한 후킹의 한계파일 다운로드1
12152정성태2/23/202021429.NET Framework: 897. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 세 번째 이야기(Trampoline 후킹)파일 다운로드1
12151정성태2/22/202024062.NET Framework: 896. C# - Win32 API를 Trampoline 기법을 이용해 C# 메서드로 가로채는 방법 - 두 번째 이야기 (원본 함수 호출)파일 다운로드1
12150정성태2/21/202024169.NET Framework: 895. C# - Win32 API를 Trampoline 기법을 이용해 C# 메서드로 가로채는 방법 [1]파일 다운로드1
12149정성태2/20/202021073.NET Framework: 894. eBEST C# XingAPI 래퍼 - 연속 조회 처리 방법 [1]
12148정성태2/19/202025753디버깅 기술: 163. x64 환경에서 구현하는 다양한 Trampoline 기법 [1]
12147정성태2/19/202021052디버깅 기술: 162. x86/x64의 기계어 코드 최대 길이
12146정성태2/18/202022252.NET Framework: 893. eBEST C# XingAPI 래퍼 - 로그인 처리파일 다운로드1
12145정성태2/18/202023859.NET Framework: 892. eBEST C# XingAPI 래퍼 - Sqlite 지원 추가파일 다운로드1
12144정성태2/13/202024035.NET Framework: 891. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 두 번째 이야기파일 다운로드1
12143정성태2/13/202018455.NET Framework: 890. 상황별 GetFunctionPointer 반환값 정리 - x64파일 다운로드1
12142정성태2/12/202022376.NET Framework: 889. C# 코드로 접근하는 MethodDesc, MethodTable파일 다운로드1
12141정성태2/10/202021384.NET Framework: 888. C# - ASP.NET Core 웹 응용 프로그램의 출력 가로채기 [2]파일 다운로드1
12140정성태2/10/202022731.NET Framework: 887. C# - ASP.NET 웹 응용 프로그램의 출력 가로채기파일 다운로드1
12139정성태2/9/202022417.NET Framework: 886. C# - Console 응용 프로그램에서 UI 스레드 구현 방법
12138정성태2/9/202028625.NET Framework: 885. C# - 닷넷 응용 프로그램에서 SQLite 사용 [6]파일 다운로드1
12137정성태2/9/202020279오류 유형: 592. [AhnLab] 경고 - 디버거 실행을 탐지했습니다.
12136정성태2/6/202021924Windows: 168. Windows + S(또는 Q)로 뜨는 작업 표시줄의 검색 바가 동작하지 않는 경우
12135정성태2/6/202027718개발 환경 구성: 468. Nuget 패키지의 로컬 보관 폴더를 옮기는 방법 [2]
12134정성태2/5/202024977.NET Framework: 884. eBEST XingAPI의 C# 래퍼 버전 - XingAPINet Nuget 패키지 [5]파일 다운로드1
12133정성태2/5/202022738디버깅 기술: 161. Windbg 환경에서 확인해 본 .NET 메서드 JIT 컴파일 전과 후 - 두 번째 이야기
12132정성태1/28/202025768.NET Framework: 883. C#으로 구현하는 Win32 API 후킹(예: Sleep 호출 가로채기) [1]파일 다운로드1
12131정성태1/27/202024476개발 환경 구성: 467. LocaleEmulator를 이용해 유니코드를 지원하지 않는(한글이 깨지는) 프로그램을 실행하는 방법 [1]
12130정성태1/26/202022049VS.NET IDE: 142. Visual Studio에서 windbg의 "Open Executable..."처럼 EXE를 직접 열어 디버깅을 시작하는 방법
12129정성태1/26/202029069.NET Framework: 882. C# - 키움 Open API+ 사용 시 Registry 등록 없이 KHOpenAPI.ocx 사용하는 방법 [3]
12128정성태1/26/202023180오류 유형: 591. The code execution cannot proceed because mfc100.dll was not found. Reinstalling the program may fix this problem.
... 61  62  63  64  65  66  67  68  69  70  71  72  73  74  [75]  ...