Alpine Linux 環境への Linuxbrew の導入手法
はじめに
Alpine Linux とは、 GNU libc6 と互換性を持つ非常に軽量な C 標準ライブラリである musl と、標準的な UNIX 環境において重要なコマンド類を単一の実行ファイルとして提供するプログラムである Busybox をベースとした超軽量な Linux のディストリビューションです。
また、 Linuxbrew とは、 Mac OS X における、ソースコードの取得及びビルドに基づいたパッケージ管理システムである Homebrew を Linux の各ディストリビューション向けに移植したものであり、現在は Homebrew と統合されています。
ここで、 Alpine Linux が導入されている環境に Linuxbrew を導入する手法として、 Linuxbrew 公式ページで述べられている "Install Linuxbrew on Alpine Linux" に示す手法がありますが、記述内容が古い上に、導入の際に以下に述べる問題が発生します。
-
Busybox における
grep
やps
等のコマンドのオプションや引数が標準的な Linux ディストリビューション等におけるそれと幾つかの差異があるため、 Linuxbrew のインストールスクリプトが正常に動作しない。 -
Linuxbrew が内部で使用する ruby の処理系である
portable-ruby
の実行形式のバイナリファイルが、 musl に適合しないために Linuxbrew が適切に動作しない。
以上の問題を回避するために、予め Alpine Linux 上に Linuxbrew 環境を構築するために必要な apk パッケージを導入し、 ldd
コマンドが --version
オプションを受け取った時に適切なバージョンを返すように ldd
コマンドのラッパースクリプトの修正を行いました。
次に、ディレクトリ /home/linuxbrew
以下に手動で Linuxbrew のディレクトリツリーを作成し、ディレクトリ /home/linuxbrew/.linuxbrew/Homebrew
以下に Linuxbrew 本体の git リポジトリを git clone
コマンドを用いて取得しました。
その後、ディレクトリ /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/portable-ruby
を作成し、 /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/portable-ruby/2.6.3_2
以下に Linuxbrew の内部で使用する ruby の処理系をソースコードからビルドしました。
そして、 /home/linuxbrew/.linuxbrew/bin/brew tap homebrew/core
コマンドにより、 Tap リポジトリ homebrew/core
を取得しました。
最後に、 /home/linuxbrew/.linuxbrew/bin/brew shellenv
の出力に基づいて Linuxbrew 関連お環境変数の設定を行い、 Linuxbrew において使用する glibc, linux-headers, gcc
の導入を行いました。
以上の作業を行なった後、 brew doctor
コマンドによる Linuxbrew の診断等の結果、 Alpine Linux 環境上において Linuxbrew が正常に動作することを確認しました。
本稿では、手動による Linuxbrew のディレクトリツリーの作成に基づいた Alpine Linux 環境における Linuxbrew の導入手法について述べます。
本稿では、 導入手法 の章において、 Alpine Linux 環境における Linuxbrew の導入手法について順を追って具体的に述べ、 結論 の章において、本稿の結論について述べます。
なお本稿では、特段の断りが無い限り、 Linuxbrew の導入作業を行なった Alpine Linux 環境及び Linuxbrew の導入先等は以下の通りであるとします。
- Alpine Linux 環境 … Vagrant Cloud alpine/alpine64 より取得できる Alpine Linux 仮想環境
-
Linuxbrew の導入先 … ディレクトリ
/home/linuxbrew/.linuxbrew
- 一般ユーザアカウント …
vagrant
- シェルスクリプト …
/bin/bash
導入手法
本章では、 Alpine Linux 環境に Linuxbrew を導入する為の具体的な手法について述べます。
先ず、 "Linuxbrew に依存する apk パッケージの導入" の節において、 Linuxbrew を Alpine Linux 環境に導入する上で、 Linuxbrew の導入に必要となる apk パッケージの導入手法について述べ、 "ldd コマンドの修正" の節において、 ldd --version
コマンドの出力が適切なものとなるための ldd
コマンドのラッパースクリプトの修正手法について述べます。
次に、 "手動による Linuxbrew のツリーの作成" の節において、ディレクトリ /home/linuxbrew
以下に Linuxbrew を動作させるために必要なファイル群を格納するためのディレクトリツリーを手動で作成する手法について述べ、 "Linuxbrew の内部で使用する Ruby 処理系のビルド" の節において、 Linuxbrew 関連のコマンドを動作させるために使用する ruby 処理系をソースコードからビルドする手法について述べます。
そして、 "Tap リポジトリ homebrew/core
の取得と Linuxbrew ツリーの更新" の節において、 Linuxbrew の中核をなす Tap リポジトリである homebrew/core
を取得し、 Linuxbrew 本体を最新の状態に更新する為の具体的な手法について述べます。
また、 "Linuxbrew 関連の環境変数の設定" の節において、 Linuxbrew を動作させるのに必要となる環境変数の設定について述べ、 "glibc, linux-headers, gcc の導入" の節において、 Linuxbrew によって実行ファイルをビルドしたり、導入される実行ファイルを動作させるために必要となる Linuxbrew のパッケージである glibc, linux-headers
及び gcc
を導入するための具体的手法について述べます。
最後に、 "動作確認" の節において、以上で述べた手法により導入した Linuxbrew についての最終的な動作確認について述べます。
Linuxbrew に依存する apk パッケージの導入
Alpine Linux 環境に Linuxbrew を導入する為に、先ず、 Linuxbrew の動作に関して必要となる Alpine Linux 環境の apk パッケージを導入します。
導入する apk パッケージについては、 "Install Linuxbrew on Alpine Linux" に記述されたパッケージを参照しますが、 ruby-dbm
パッケージは既に obsolete となっているため、これに代えて ruby-sdbm, ruby-gdbm
を導入します。
また、 Alpine Linux 環境における Busybox で実装されている grep, ps
等の一部コマンドにおいて、標準の Linux ディストリビューションにおける同様のコマンドと引数及びオプションの仕様が異なるものがあるので、追加で grep, coreutils, procps
を導入する必要があります。
そして、 "Linuxbrew の内部で使用する Ruby 処理系のビルド" の節にて後述する Linuxbrew 内部で使用する ruby 処理系をソースコードからビルドする際に使用するヘッダファイル群を集めたパッケージである linux-headers
及び、 ruby 処理系から readline 及び zlib を扱うために必要となるパッケージである readline-dev, zlib-dev
も同時に導入する必要があります。
# apk update
# apk add bash build-base curl file git gzip libc6-compat ncurses
# apk add ruby ruby-sdbm ruby-gdbm ruby-etc ruby-irb ruby-json sudo
# apk add grep coreutils procps readline-dev zlib-dev linux-headers
ldd コマンドの修正
標準的な Linux ディストリビューションのコマンドの一つである ldd
コマンドは、指定した実行ファイル若しくは共有ライブラリについて、その実行ファイル及び共有ライブラリが依存している共有ライブラリを表示するためのコマンドです。
また、 ldd
コマンドに --version
オプションを指定すると、 ldd
コマンドを実行している環境において使用している標準 C ライブラリのバージョン番号を表示させることが出来ます。
例えば、 Ubuntu 20.04 環境において ldd --version
コマンドを実行すると、以下のようなメッセージが標準出力に出力されます。
$ ldd --version
ldd (Ubuntu GLIBC 2.31-0ubuntu9.2) 2.31
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
作者 Roland McGrath および Ulrich Drepper。
しかし、 Alpine Linux 環境では、標準 C ライブラリに musl が使用されており、 Alpine Linux 環境において ldd --version
コマンドを実行すると、以下のようなメッセージが標準エラーメッセージに出力されます。
# ldd --version
musl libc (x86_64)
Version 1.2.2
Dynamic Program Loader
Usage: /lib/ld-musl-x86_64.so.1 [options] [--] pathname
Linuxbrew の動作において、標準 C ライブラリのバージョンを検出する必要が有る場合には、コマンド ldd --version
の標準出力の結果が使用されるために、 Alpine Linux 環境で Linuxbrew を動作させる場合は、標準 C ライブラリのバージョンを検出が出来ずに不具合が発生します。
ここで、 Alpine Linux 環境において ldd
コマンドの実行ファイルの実体は、以下のように、共有ライブラリファイル /lib/ld-musl-x86_64.so.1
にオプション --list
と実行時に指定された引数とオプションを渡して直接実行するためのシェルスクリプト形式のラッパーであることが判ります。
# cat /usr/bin/ldd
#!/bin/sh
exec /lib/ld-musl-x86_64.so.1 --list "$@"
そこで、 Alpine Linux 環境において Linuxbrew を動作させる際には、スクリプトファイル /usr/bin/ldd
を別のファイルに退避させた上で、以下の通りに、 ldd
コマンドに --version
オプションを渡した時に適当な出力を返すように /usr/bin/ldd
を修正する必要があります。
#!/bin/sh
while test $# -gt 0; do
case "$1" in
--vers | --versi | --versio | --version)
echo 'ldd (Alpine MUSL 2.13) 2.13'
exit 0
;;
esac
done
exec /lib/ld-musl-x86_64.so.1 --list "$@"
手動による Linuxbrew のツリーの作成
通常の Linux ディストリビューションにおいて、 Linuxbrew を導入する際には、 Linuxbrew の公式ページに記述の有る通りに、 Linuxbrew のインストールスクリプトの起動を行いますが、 Alpine Linux 環境では、 Linuxbrew のインストールスクリプトを実行しても、以下のようなエラーメッセージを出力して起動に失敗します。
$ sh -c "$(curl -fsSL https://raw.githubusercontent.com/Linuxbrew/install/master/install.sh)"
...(略)...
==> Pouring portable-ruby-2.6.3_2.x86_64_linux.bottle.tar.gz
Error: Failed to install ruby 2.6.3_2!
Error: Failed to install Homebrew Portable Ruby and cannot find another Ruby 2.6.3!
...(略)...
以上のようなエラーメッセージが出力されるのは、 Linuxbrew 本体を起動するために使用される ruby 処理系を含む tarball であり、インストールスクリプトによってダウンロードされる tarball である portable-ruby-2.6.3_2.x86_64_linux.bottle.tar.gz
に同梱されている実行ファイル群が、 Linuxbrew によって導入される GNU glibc6 2.23 及びそれ以降のバージョンの標準 C ライブラリに適合するように構築されているのが原因です。
以上の問題を回避するには、 Alpine Linux 環境上で Linuxbrew のインストールスクリプトを起動せずに、次に述べる手順にて手動で Linuxbrew を導入する必要があります。
まずは、ディレクトリ /home/linuxbrew
以下に Linuxbrew 本体及び Tap リポジトリを格納するためのディレクトリツリーを以下の通りにして作成し、ディレクトリ /home/linuxbrew/.linuxbrew/Homebrew
以下に、 git clone
コマンドを用いて Linuxbrew 本体を構成するファイル群を取得します。
# mkdir -p /home/linuxbrew/.linuxbrew
# cd /home/linuxbrew/.linuxbrew
# mkdir Caskroom Cellar Frameworks bin etc include lib opt sbin share tmp
# mkdir -p var/homebrew/links
# git clone https://github.com/Homebrew/brew ./Homebrew
そして、ディレクトリ /home/linuxbrew/.linuxbrew/bin
以下に、 brew
コマンドの本体となるスクリプトファイル /home/linuxbrew/.linuxbrew/Homebrew/bin/brew
へのシンボリックリンクを張ります。
# cd /home/linuxbrew/.linuxbrew/bin
# ln -sf ../Homebrew/bin/brew .
Linuxbrew の内部で使用する Ruby 処理系のビルド
Linuxbrew の各種コマンドを起動するために使用される ruby 処理系は、ディレクトリ /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/portable-ruby
に置かれています。
"手動による Linuxbrew のツリーの作成" の節で前述した通り、インストールスクリプトからダウンロードした実行バイナリファイル形式の ruby 処理系が Linuxbrew 内部で使用する ruby 処理系として使用できないため、 Alpine Linux 環境においては、 ruby 処理系のソースコードを ruby の公式ページより入手し、ディレクトリ /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/portable-ruby/2.6.3_2
以下に、手動によるビルドに基づいて導入する必要があります。
Linuxbrew 内部で使用する ruby 処理系のビルドを行うには、まず、以下の通りにして、ディレクトリ /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/portable-ruby
以下に src
ディレクトリを作成します。
そして、ディレクトリ src
以下に ruby 公式ページより ruby 2.6.3 のソースコードを取得します。
# mkdir -p /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/portable-ruby
# cd /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/portable-ruby
# mkdir src
# cd src
# curl -L -o ruby-2.6.3.tar.gz https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.3.tar.gz
次に、以下の通りに ruby 2.6.3 のソースコードを展開し、ディレクトリ /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/portable-ruby/2.6.3_2
以下にソースコードのビルドに基づいて Linuxbrew 内部で使用する ruby 処理系を導入します。
なお、スクリプト ./configure
を起動する際は、インストール先を示すオプション --prefix
を指定する他に、以下のオプションも同時に指定します。
-
--enable-load-relative
… 実行ファイルruby
及びlibruby.so.*
の探索先を相対化する。 -
--with-static-linked-ext
… 外部ライブラリをlibruby.so.*
に静的に結合する。 -
--with-out-ext=openssl,tk,sdbm,gdbm,dbm,win32,win32ole
… 外部ライブラリopenssl,tk,sdbm,gdbm,dbm,win32,win32ole
を使用しない。 -
--without-gmp
… GMP による Bignum の演算の高速化を無効にする。 -
--disable-install-doc, --disable-install-rdoc
… ruby 処理系に関するドキュメント類の生成を抑止する。 -
--disable-dependency-tracking
… 依存性の追跡を無効にする。
# tar -xvf ruby-2.6.3.tar.gz
#
# cd ruby-2.6.3
# ./configure --prefix=/home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/portable-ruby/2.6.3_2 \
--enable-load-relative --with-static-linked-ext --with-out-ext=openssl,tk,sdbm,gdbm,dbm,win32,win32ole \
--without-gmp --disable-install-doc --disable-install-rdoc --disable-dependency-tracking
# make
# make install
また、 ruby 処理系のビルド中に発生する各種警告を抑止したい場合は、スクリプト ./configure
の実行前に予め以下の通りに環境変数 CFLAGS, CXXFLAGS
を設定します。
# export CFLAGS="$CFLAGS -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations"
# export CFLAGS="$CFLAGS -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wwrite-strings"
# export CFLAGS="$CFLAGS -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long"
# export CFLAGS="$CFLAGS -Wno-missing-field-initializers -Wno-overlength-strings -Wno-packed-bitfield-compat"
# export CFLAGS="$CFLAGS -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter"
# export CFLAGS="$CFLAGS -Wno-unused-value -Wsuggest-attribute=noreturn -Wno-unused-variable -Wno-implicit-fallthrough"
# export CFLAGS="$CFLAGS -Wno-address-of-packed-member -Wno-incompatible-pointer-types -Wno-declaration-after-statement"
# export CFLAGS="$CFLAGS -Wno-empty-body -Wno-sign-compare -Wno-unused-but-set-variable"
# export CXXFLAGS="$CXXFLAGS $CFLAGS"
その後、ディレクトリ /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/portable-ruby
に移動し、以下のようにしてディレクトリ 2.6.3_2
より current
に向けてシンボリックリンクを張ります。
そして、ディレクトリ src
に残っている ruby 処理系のソースコードを削除します。
# cd /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/portable-ruby
# ln -sf 2.6.3_2 current
# rm -rf src
Tap リポジトリ homebrew/core
の取得と Linuxbrew ツリーの更新
Linuxbrew は、一般ユーザの権限において各種パッケージを管理するシステムであるため、 Linuxbrew の各種コマンドは、管理者権限の環境において実行することは推奨されていません。
従って、 Linuxbrew の中核の Formula が納められている Tap リポジトリである homebrew/core
を取得するには、一般ユーザの権限で、コマンド /home/linuxbrew/.linuxbrew/bin/brew
を実行する必要があります。
そこで、以下のようにして、ディレクトリ /home/linuxbrew
以下の全てのファイルの所有者を一般ユーザに変更し、 su
コマンドにより、一般ユーザの環境に移ります。
# chown -R vagrant:vagrant /home/linuxbrew
# su - vagrant
そして、以下のようにして、/home/linuxbrew/.linuxbrew/bin/brew tap homebrew/core
コマンドにより、 Tap リポジトリ homebrew/core
を取得し、 /home/linuxbrew/.linuxbrew/bin/brew update
コマンドにより、 Linuxbrew 本体のリポジトリを更新します。
$ mkdir -p /home/linuxbrew/.linuxbrew/Homebrew/Library/Taps/homebrew/homebrew-core
$ /home/linuxbrew/.linuxbrew/bin/brew tap homebrew/core
$ /home/linuxbrew/.linuxbrew/bin/brew update
Linuxbrew 関連の環境変数の設定
Alpine Linux 環境において、 Linuxbrew を正常に動作させるためには、 Linuxbrew が使用する環境変数 HOMEBREW_PREFIX, PATH
等を適切な値に設定する必要があります。
ここで、 Linuxbrew を適切に動作させるための各種環境変数の値は /home/linuxbrew/.linuxbrew/bin/brew shellenv
コマンドによって出力されます。
従って、以下の通りにして /home/linuxbrew/.linuxbrew/bin/brew shellenv
コマンドの出力内容を、 bash の設定ファイルである ~/.bashrc, ~/.profile
に追加します。
$ /home/linuxbrew/.linuxbrew/bin/brew shellenv >> ~/.bashrc
$ /home/linuxbrew/.linuxbrew/bin/brew shellenv >> ~/.profile
$ eval $(/home/linuxbrew/.linuxbrew/bin/brew shellenv)
glibc, linux-headers, gcc の導入
Linuxbrew で管理される各種パッケージは、 Linuxbrew において導入される GNU glibc6 2.23 以降の標準 C ライブラリの存在を前提として構築が行われています。また、 Linuxbrew において、ソースコードのビルドが行われる際においても、 GNU glibc6 2.23 以降の標準 C ライブラリの存在を前提としたビルドが行われます。
従って、 Alpine Linux 環境において Linuxbrew を正常に動作させるには、 Linuxbrew を用いて GNU glibc6 及び GNU C コンパイラを予め導入する必要があります。
また、パッケージ Linuxbrew で導入される GNU C コンパイラを正常に動作させるためには、 Linux 関連の C 処理系のヘッダファイル群である linux-headers
も同時に導入する必要があります。
この際、全てのパッケージを導入する時は、 brew install
コマンドにオプション --ignore-dependencies, --force-bottle, --force
を指定し、全てのパッケージについて、強制的に実行形式のバイナリファイルからの導入を行うことに留意する必要があります。
$ brew install --ignore-dependencies --force-bottle --force glibc
$ brew install --ignore-dependencies --force-bottle --force linux-headers
$ for f in `brew deps -n gcc`; do brew install --ignore-dependencies --force-bottle --force $f; done
$ brew install --ignore-dependencies --force-bottle --force gcc
動作確認
以上の導入に関する作業が完了した後は、 Linuxbrew の動作確認を行います。まず、以下の通りにして brew doctor
コマンドにより、 Linuxbrew 全体の診断プログラムを起動させます。
ここで、標準出力に Your system is ready to brew.
が出力されていれば、正常に Linuxbrew が導入されています。
$ brew doctor
Your system is ready to brew.
最後に、以下のようにして hello, patchelf
パッケージ等をソースコードから導入し、正常にこれらのパッケージが正常に導入されることを確認します。
$ brew install -dvs hello
$ brew install -dvs patchelf
結論
本稿における Alpine Linux 環境上の Linuxbrew において、まず最初に Busybox における grep
や ps
等のコマンドのオプションや引数や ldd
の動作が標準的な Linux ディストリビューション等の仕様と異なるために、 Linuxbrew に依存する apk パッケージの導入において、 grep, procps
等のパッケージを追加で導入し、また、 ldd
コマンドのラッパースクリプトの修正を行いました。
次に、インストールスクリプトからダウンロードした実行バイナリファイル形式の ruby 処理系が Alpine Linux 環境での動作に適合しないため、インストールスクリプトによる Linuxbrew の導入に代えて、ディレクトリ /home/linuxbrew
以下に Linuxbrew のディレクトリツリーを手動で作成し、 Linuxbrew 本体の git リポジトリを git clone
コマンドを用いて取得しました。
その後、 Linuxbrew の内部で使用する ruby の処理系が置かれているディレクトリ /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/portable-ruby/2.6.3_2
以下に ruby 2.6.3 の処理系をソースコードからビルドしました。
そして、 Linuxbrew の中核の Formula が納められている Tap リポジトリ homebrew/core
を取得しました。
最後に、 /home/linuxbrew/.linuxbrew/bin/brew shellenv
の出力に基づいて Linuxbrew 関連お環境変数の設定を行い、 Linuxbrew の動作において前提となるパッケージである glibc, linux-headers, gcc
の導入を行いました。
以上の作業を行なった後、 brew doctor
コマンド等による Linuxbrew の動作確認の結果、 Alpine Linux 環境上において Linuxbrew が正常に動作することを確認しました。
本稿においては、 Alpine Linux 環境上における Linuxbrew の導入について、通常の [Linuxbrew][X005] のディストリビューションで用いるインストールスクリプトによる導入手法に代えて、 Linuxbrew を導入するためのディレクトリツリーを手動で作成し、 Linuxbrew の内部で使用する ruby 処理系を手動でソースコードからビルドすることにより導入する手法について述べました。
本稿で述べた Alpine Linux 環境での導入の他にも、標準的な GNU libc6 が導入されていない Linux ディストリビューション等のように、 Linuxbrew のインストールスクリプトからダウンロードされる Linuxbrew の動作用の ruby 処理系の実行形式のバイナリファイル群が、当該環境に適合しない場合においても、本稿で述べた Linuxbrew の導入手法が有効であると考えられます。
謝辞
まず最初に、超軽量な Linux のディストリビューションである Alpine Linux を開発した Alpine Linux の開発コミュニティの各位に心より感謝致します。
そして、 Linuxbrew 本体のリポジトリの開発を行っている Shaun Jackman 氏を始めとする Linuxbrew の開発コミュニティの各氏に心より感謝致します。また、 Linuxbrew の詳細に関しては、 Linuxbrew 公式ページ及び Linuxbrew のリポジトリに同梱される各種資料も併せて参考にしました。
最後に、 Linuxbrew 及び Alpine Linux 環境そして Linux 全体に関わる全ての皆様に心より感謝致します。
追記
本稿の付録として、 vagrant を用いて Alpine Linux 環境に導入された Linuxbrew を tarball で固めたものを以下に添付致します。