Open10

clangでビルドしたduckdbのdockerイメージ作成のメモ

ktz_aliasktz_alias

たまにしかdockerを触らないため、よく記憶から抜け落ちる。
clangでビルドしたduckdbのdockerイメージを介して作成の記録を残しておく。

※一連の手順は、CHATGPT大先生の指導のもとに実施しています。

ktz_aliasktz_alias

duckdbのソースからのビルドに必要なもの

  • llvm
  • cmake
  • ninja
    • duckdb公式で使用が推奨されている

手元の開発環境で、最新版のパーケージ取得が確認できているlinuxhomebrewを使う。

幸いdockerhub.ioにて公式提供されている。

https://hub.docker.com/u/homebrew

今回はhomebrew/brewを使用する。

ktz_aliasktz_alias

段階を踏んでイメージを作成するため、マルチステージビルドを採用する

  • ステージ1 (dev)- 開発環境の構築まで
  • ステージ2 (build) - duckdbのビルド・インストールまで
  • 最終ステージ - 後々必要となる成果物のみを残したイメージの作成
    • homebrewイメージをベース
      • 後々使う際にaptでは必要なバージョンのパッケージを取得できなかった
      • linuxhomebrewは最新版のパーケージ取得が確認できた
    • nodejs
      • これも後々Zigの導入で使用する
        • 時短のため組み込んでおく
ktz_aliasktz_alias

ステージ1

以下のDockerfileを記述した。

FROM homebrew/brew as dev

# Update and install common packages
RUN brew update 
RUN brew install llvm && \
    brew install cmake
    
# Default command
CMD ["bash"]
  • docker build -t tmp_build_duckdb:latest .でビルドできた
  • docker run -it tmp_build_duckdb:latestでコンテナ内に入れた
  • コンテナ内で、brew list
    • 期待通りパッケージがインストールされていることを確認した
ktz_aliasktz_alias

上で、ninjaの導入を忘れていたため追加でインストールする

  1. docker ps - 該当のコンテナを記録する
  2. `docker start -i <CONTAINER ID>
    • コンテナを停止していたため再起動
    • バックグラウンド起動で停止していなければ、docker exec -it <CONTAINER ID>
  3. コンテナ内でbrew install ninja
  4. コンテナから脱出
ktz_aliasktz_alias

ステージ2

手順再確認のため、コンテナ内に入りStp by Stepでビルドを進める。

  1. duckdbのclone
git clone --depth=1 https://github.com/duckdb/duckdb.git
  1. モンキーパッチを当てる
target_file="duckdb/extension/jemalloc/jemalloc/include/jemalloc/internal/jemalloc_internal_defs.h" \
    && line_number=485 \
    && sed -i "${line_number}s/^\/\/ #define JEMALLOC_STRERROR_R_RETURNS_CHAR_WITH_GNU_SOURCE/#define JEMALLOC_STRERROR_R_RETURNS_CHAR_WITH_GNU_SOURCE/" "$target_file"

issue 12657のため、linux環境下でのclangでのビルドに失敗するため。

  • 2024-7-15時点の対応。該当ファイルはしばしば変更されるため行番号は変更するだろう
  1. モンキーパッチが適用されたかどうかの確認
cat duckdb/extension/jemalloc/jemalloc/include/jemalloc/internal/jemalloc_internal_defs.h | head -n 485 | tail -n 1

viがいなかったためcatで代用。

  1. homebrewのパスを環境変数に入れておく
BREW_PREFIX="/home/linuxbrew/.linuxbrew/opt"
echo $BREW_PREFIX
  1. configureまでの実行
mkdir -p duckdb/_build \
    && cd duckdb/_build \
    && cmake -GNinja \
        -DCMAKE_C_COMPILER=$BREW_PREFIX/llvm/bin/clang \
        -DCMAKE_CXX_COMPILER=$BREW_PREFIX/llvm/bin/clang++ \
        -DCMAKE_BUILD_TYPE=Release \
        -DINSTALL_LIB_DIR=../_install/lib \
        -DINSTALL_INCLUDE_DIR=../_install/include \
        -DINSTALL_BIN_DIR=../_install/bin \
        -DINSTALL_CMAKE_DIR=../_install/lib/cmake/DuckDB \
        ..
  1. ビルドの実行(めっちゃ長い)
ninja
  1. 成果物のインストール
cmake --install .

5.の設定より、duckdb/_installフォルダに展開される

フォルダ構成

  • bin
    • duckdb - 対話環境
  • lib
    • cmake
    • libduckdb.so
    • libduckdb_fastpforlib.a
    • libduckdb_fmt.a
    • libduckdb_fsst.a
    • libduckdb_hyperloglog.a
    • libduckdb_mbedtls.a
    • libduckdb_miniz.a
    • libduckdb_pg_query.a
    • libduckdb_re2.a
    • libduckdb_skiplistlib.a
    • libduckdb_static.a
    • libduckdb_utf8proc.a
    • libduckdb_yyjson.a
    • libjemalloc_extension.a
    • libparquet_extension.a
  • include
    • duckdb - Parser, Binder, Plannerなどのコア機能を直接さわるためのAPI
    • duckdb.h - C用クライアントAPI
    • duckdb.hpp - C++用クライアントAPI
  1. ここまでの分を一度コミットする
docker commit <CONTAINER ID> tmp_duckdb_image
ktz_aliasktz_alias

ステージ2まとめ

手順をDockerfileに転記

# (snip) stage 1

FROM dev as build

WORKDIR /home/linuxbrew

# download duckdb source repository
RUN git clone --depth=1 https://github.com/duckdb/duckdb.git

# apply monky patch from issue 12657 (https://github.com/duckdb/duckdb/issues/12657)
RUN target_file="duckdb/extension/jemalloc/jemalloc/include/jemalloc/internal/jemalloc_internal_defs.h" \
    && line_number=485 \
    && sed -i "${line_number}s/^\/\/ #define JEMALLOC_STRERROR_R_RETURNS_CHAR_WITH_GNU_SOURCE/#define JEMALLOC_STRERROR_R_RETURNS_CHAR_WITH_GNU_SOURCE/" "$target_file"

# add homebre prefix to env
RUN BREW_PREFIX="/home/linuxbrew/.linuxbrew/opt"

# build duckdb
RUN mkdir -p duckdb/_build \
    && cd duckdb/_build \
    && cmake -GNinja \
        -DCMAKE_C_COMPILER=$BREW_PREFIX/llvm/bin/clang \
        -DCMAKE_CXX_COMPILER=$BREW_PREFIX/llvm/bin/clang++ \
        -DCMAKE_BUILD_TYPE=Release \
        -DINSTALL_LIB_DIR=../_install/lib \
        -DINSTALL_INCLUDE_DIR=../_install/include \
        -DINSTALL_BIN_DIR=../_install/bin \
        -DINSTALL_CMAKE_DIR=../_install/lib/cmake/DuckDB \
        ..

# install artifacts
RUN cmake --install .

CMD [bash]

WORKDIR コマンドを使用して、作業ディレクトリを明示的に設定すると、コマンドの実行やファイルのコピーがより簡潔になります。

とのこと。

ktz_aliasktz_alias

ステージ3

最終ビルドの構築をStep by Stepで

ステージ2の成果物を最終ステージに取り込むため、管理対象のためのvolumeを作成する

  1. ボリュームを作成する
docker volume create duckdb_volume
  1. ボリュームのマウントポイントを取得
VOLUME_MOUNTPOINT=$(docker volume inspect --format '{{.Mountpoint}}' duckdb_volume)

docker volume inspectの実行結果は以下のようになった。

[
    {
        "CreatedAt": "2024-07-15T08:16:50Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/duckdb_volume/_data",
        "Name": "duckdb_volume",
        "Options": null,
        "Scope": "local"
    }
]

配列形式なのは、複数のボリュームを同時に指定できるようにするため。

docker volume inspect volume_a volume_b

3-a. ステージ2のコンテナを起動する(すでに起動済みの場合はスキップ)

docker run -d --name tmp_duckdb_container tmp_duckdb_image

コンテナ -> ボリューム間で成果物を複製するため、起動しておく必要がある。
なんか起動後即終了する。
ttyでコンテナ内には入れる。

3-b. コンテナ内からボリュームにコピーするため、ボリュームをマウントした上でttyでコンテナに入る。

docker run -it --name tmp_duckdb_container -v duckdb_volume:/mnt tmp_duckdb_image bash

マウントしたボリュームに成果物をコピー

sudo cp -r duckdb/_install/. /mnt

duckdb/_install/.の指定、今まで知らんかった。さすCHATGPT先生。

確認

$ ls -ld /mnt/*
drwxr-xr-x 2 root root 4096 Jul 15 08:57 /mnt/bin
drwxr-xr-x 3 root root 4096 Jul 15 08:57 /mnt/include
drwxr-xr-x 3 root root 4096 Jul 15 08:57 /mnt/lib
ktz_aliasktz_alias

ステージ3 (後半戦)

homebrew/brewベースの新規のコンテナを起動し、ttyで入る。

docker run -it --name tmp_final_container -v duckdb_volume:/mnt homebrew/brew bash
``

成果物を取り込む

cp -r /mnt duckdb


nodejsをインストールする

brew install node

ktz_aliasktz_alias

最終的なDockerfile

# Build tool setup
FROM homebrew/brew as dev

# Update and install common packages
RUN brew update 
RUN brew install llvm && \
    brew install cmake && \
    brew install ninja

# Build stage
FROM dev as build

WORKDIR /home/linuxbrew

# download duckdb source repository
RUN git clone --depth=1 https://github.com/duckdb/duckdb.git

# apply monky patch from issue 12657 (https://github.com/duckdb/duckdb/issues/12657)
RUN target_file="duckdb/extension/jemalloc/jemalloc/include/jemalloc/internal/jemalloc_internal_defs.h" \
    && line_number=485 \
    && sed -i "${line_number}s/^\/\/ #define JEMALLOC_STRERROR_R_RETURNS_CHAR_WITH_GNU_SOURCE/#define JEMALLOC_STRERROR_R_RETURNS_CHAR_WITH_GNU_SOURCE/" "$target_file"

# add homebre prefix to env
RUN BREW_PREFIX="/home/linuxbrew/.linuxbrew/opt"

# build duckdb
RUN mkdir -p duckdb/_build \
    && cd duckdb/_build \
    && cmake -GNinja \
        -DCMAKE_C_COMPILER=$BREW_PREFIX/llvm/bin/clang \
        -DCMAKE_CXX_COMPILER=$BREW_PREFIX/llvm/bin/clang++ \
        -DCMAKE_BUILD_TYPE=Release \
        -DINSTALL_LIB_DIR=../_install/lib \
        -DINSTALL_INCLUDE_DIR=../_install/include \
        -DINSTALL_BIN_DIR=../_install/bin \
        -DINSTALL_CMAKE_DIR=../_install/lib/cmake/DuckDB \
        ..

# install artifacts
RUN cmake --install .

# Last stage
FROM homebrew/brew

WORKDIR /home/linuxbrew

# copy artifact
COPY --from=build /home/linuxbrew/duckdb/_install/. /home/linuxbrew/duckdb

# install package
RUN brew update && \
    brew install node