はじめに
概要
Python 本体およびライブラリをシステム管理者が一括で管理し、利用するバージョンのみをユーザ各自が管理できる環境を pyenv で構築する方法を紹介します。
背景
Python 2 系はいまだシステム標準として利用されていますが、2020年にサポートが切れることから、Python 3 系の利用環境を整えておくのが望ましいと考えられます。そこで、システム側に Python 3 系をインストールし、python コマンドを python3.x へのシンボリックリンクに変更したところ、常駐している tuned や yum コマンドに影響を及ぼします (tuned は syntax error で起動しませんし、また yum install ができなくなります)。
そのため pyenv や virtualenv の利用が推奨されております。それらはシステムに影響を及ぼさないように 3 系をインストールできますが、その設計思想が故に、それをセットアップしたユーザでしか利用できません。すなわち、マルチユーザ環境では、ユーザが増えるたびに pyenv のインストールからしなくてはならず面倒です (pyenv がセットアップされたホームディレクトリのテンプレートを作っておいて、それをコピーすればいいのかもしれませんが)。
ここで紹介する環境は、これらの問題を回避する一つの手段となります。
制限事項
通常の pyenv の環境とは異なり、管理者権限がなければ、様々なバージョンの Python を各自で自由にインストールできなくなる点がデメリットになります。
構築
対象プラットフォーム
- CentOS 7
手順
必要なパッケージのインストール
pyenv は Python のソースパッケージを入手してビルドしますので、下記の開発用パッケージが必要となります。
sudo yum -y install gcc gcc-c++ make git openssl-devel bzip2-devel zlib-devel readline-devel sqlite-devel
pyenv のインストール
mkdir ~/tmp
cd ~/tmp
git clone https://github.com/yyuu/pyenv.git
sudo cp -rupv pyenv /usr/local
chown -R root:root /usr/local/pyenv
pyenv の修正
ユーザが利用する Python のバージョンは ${PYENV_ROOT}/version を参照することで管理されています。デフォルトのまま利用すると、この PYENV_ROOT をシステム共通の場所に設定することになるので、利用者各自でバージョン変更できるようにしようと思うと都合が悪くなります。そこで環境変数 PYENV_VERSION_FILE が設定されていたら、そちらを参照するように pyenv を変更します。
--- ./libexec/pyenv-global 2017-05-10 13:29:55.444777132 +0900
+++ /usr/local/pyenv/libexec/pyenv-global 2017-05-10 16:35:48.281371082 +0900
@@ -22,7 +22,14 @@
fi
versions=("$@")
-PYENV_VERSION_FILE="${PYENV_ROOT}/version"
+if [ -z "${PYENV_VERSION_FILE}" ]; then
+ PYENV_VERSION_FILE="${PYENV_ROOT}/version"
+else
+ PYENV_VERSION_FILE_DIR=${PYENV_VERSION_FILE%/*}
+ if [ ! -d ${PYENV_VERSION_FILE_DIR} ]; then
+ mkdir -p ${PYENV_VERSION_FILE_DIR}
+ fi
+fi
if [ -n "$versions" ]; then
pyenv-version-file-write "$PYENV_VERSION_FILE" "${versions[@]}"
--- ./libexec/pyenv-version-file 2017-05-10 13:29:55.444777132 +0900
+++ /usr/local/pyenv/libexec/pyenv-version-file 2017-05-10 16:31:36.042696438 +0900
@@ -19,10 +19,16 @@
return 1
}
+if [ -z "${PYENV_VERSION_FILE}" ]; then
+ PYENV_VERSION_FILE_DIR=${PYENV_ROOT}
+else
+ PYENV_VERSION_FILE_DIR=${PYENV_VERSION_FILE%/*}
+fi
+
if [ -n "$target_dir" ]; then
find_local_version_file "$target_dir"
else
find_local_version_file "$PYENV_DIR" || {
[ "$PYENV_DIR" != "$PWD" ] && find_local_version_file "$PWD"
- } || echo "${PYENV_ROOT}/version"
+ } || echo "${PYENV_VERSION_FILE_DIR}/version"
fi
--- ./libexec/pyenv-rehash 2018-10-04 16:06:44.456347899 +0900
+++ /usr/local/pyenv/libexec/pyenv-rehash 2019-03-25 10:13:36.493066034 +0900
@@ -4,7 +4,7 @@
set -e
[ -n "$PYENV_DEBUG" ] && set -x
-SHIM_PATH="${PYENV_ROOT}/shims"
+SHIM_PATH="${HOME}/.pyenv/shims"
PROTOTYPE_SHIM_PATH="${SHIM_PATH}/.pyenv-shim"
# Create the shims directory if it doesn't already exist.
--- libexec/pyenv-init 2018-10-04 16:06:44.456347899 +0900
+++ /usr/local/pyenv/libexec/pyenv-init 2019-03-25 11:08:13.948005781 +0900
@@ -82,15 +82,16 @@
exit 1
fi
-mkdir -p "${PYENV_ROOT}/"{shims,versions}
+mkdir -p "${PYENV_ROOT}/versions"
+mkdir -p "${HOME}/.pyenv/shims"
case "$shell" in
fish )
- echo "set -gx PATH '${PYENV_ROOT}/shims' \$PATH"
+ echo "set -gx PATH '${HOME}/.pyenv/shims' \$PATH"
echo "set -gx PYENV_SHELL $shell"
;;
* )
- echo 'export PATH="'${PYENV_ROOT}'/shims:${PATH}"'
+ echo 'export PATH="${HOME}/.pyenv/shims:${PATH}"'
echo "export PYENV_SHELL=$shell"
;;
esac
--- libexec/pyenv-which 2019-03-15 16:34:52.087265948 +0900
+++ /usr/local/pyenv/libexec/pyenv-which 2019-03-26 10:57:13.635188148 +0900
@@ -19,10 +19,12 @@
local path_to_remove="$1"
local path_before
local result=":${PATH//\~/$HOME}:"
+ shopt -s extglob
while [ "$path_before" != "$result" ]; do
path_before="$result"
- result="${result//:$path_to_remove:/:}"
+ result="${result//:*([^:])$path_to_remove:/:}"
done
+ shopt -u extglob
result="${result%:}"
echo "${result#:}"
}
@@ -40,7 +42,7 @@
for version in "${versions[@]}"; do
if [ "$version" = "system" ]; then
- PATH="$(remove_from_path "${PYENV_ROOT}/shims")"
+ PATH="$(remove_from_path "/.pyenv/shims")"
PYENV_COMMAND_PATH="$(command -v "$PYENV_COMMAND" || true)"
else
PYENV_COMMAND_PATH="${PYENV_ROOT}/versions/${version}/bin/${PYENV_COMMAND}"
全ユーザ共通の設定
sudo vi /etc/profile.d/pyenv.sh
export PYENV_ROOT="/usr/local/pyenv"
if [ -d "${PYENV_ROOT}" ]; then
export PATH=${PYENV_ROOT}/bin:$PATH
eval "$(pyenv init -)"
export PYENV_VERSION_FILE=${HOME}/.pyenv/version
fi
ディレクトリ作成
最初に root でログインすれば自動的に作成されますが、ここでは手動で作成しておきます。
sudo mkdir /usr/local/pyenv/shims
sudo mkdir /usr/local/pyenv/versions
Python のインストール
ここでは Python 3.6.8 のみをインストールします。必要に応じて様々なバージョンをインストールすることになります。
sudo -E pyenv install 3.6.8
pip のアップデート
pyenv global 3.6.8
sudo -E pip install --upgrade pip
自分が利用する Python のバージョンを 3.6.8 に設定して、sudo -E で自分の環境変数を継承させれば、 root が利用する Python のバージョンを変更しなくても大丈夫です。
その他
pip の設定
pip のバージョンを 9.x 以降にアップデートするとリスト表示時に警告メッセージが出るので以下の設定をしておきます。
sudo vi /etc/pip.conf
[list]
format = columns
Python 3.7.x インストール時の追加対応
@hitochan777 さんの pyenvで3.7系のインストールに失敗したときのメモ を参考に以下のパッケージも事前にインストールしておきます。
yum install libffi-devel
ruby からの警告対応
もともと /usr/local/pyenv/shims ディレクトリは world writable にしていましたが、ruby の一部の gem から以下のような警告が出るようになりました。
/opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/ruby-progressbar-1.8.3/lib/ruby-progressbar/calculators/length.rb:76: warning: Insecure world writable dir /usr/local/pyenv/shims in PATH, mode 042777
そこで pyenv-init, pyenv-which, pyenv-rehash を修正して shims ディレクトリのみ個人で持たせるように修正し、/usr/local/pyenv/shims を使用しないようにしました。この修正に伴い、例えば管理者が awscli をインストールした場合、各自が
pyenv rehash
を実行しないと反映されないという副作用が生じます。もちろん再ログインすれば pyenv init から pyenv rehash が実行されるので問題はありません。