CentOS 7 に pyenv を用いて最新バージョンの Python をインストールする

# はじめに
サーバーのPythonのアップグレードに挑戦しました。かなり時間を取られたので、備忘録としてここにまとめます。

# 環境
* CentOS 7
* python3.6とpython2.7が予め入っている

# 1. pyenvのインストール

```
$ pyenv versions
```
以下のように出力されれば、インストールされていないことになります。バージョンが表示される場合は、インストールされているので読み飛ばしてください。

> bash: pyenv: command not found...

pyenvのインストールは以下の記事を参考にしました。手順をまとめておきます。

https://qiita.com/ksugawara61/items/ba9a51ebfdaf8d1a1b48

#### 1-1. pyenvの使用に必要となるパッケージのインストール
```
$ sudo yum install gcc zlib-devel bzip2 bzip2-devel readline readline-devel sqlite sqlite-devel openssl openssl-devel git libffi-devel
```
#### 1-2.ホームディレクトリ下にpyenvのリポジトリをクローンする
```
$ git clone https://github.com/pyenv/pyenv.git ~/.pyenv
```
#### 1-3. .bash_profileにpyenvのパスを追加
```
$ echo 'export PATH="$HOME/.pyenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(pyenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile
```
#### 1-4. pyenvのバージョンを確認
```
$ pyenv --version
```
> pyenv 1.2.2-6-g694b551

# 2. pythonのインストール　（失敗）
入手できる最新のpythonのバージョンを確認。
```
$ pyenv install --list
```
3.11.3を選びました。
```
$ pyenv install 3.11.3
```
とすれば問題なくpython3.11.3がインストールできるはずですが
> ModuleNotFoundError: No module named '_ssl'
> ERROR: The Python ssl extension was not compiled. Missing the OpenSSL lib?
>
> Please consult to the Wiki page to fix the problem.
> https://github.com/pyenv/pyenv/wiki/Common-build-problems




とエラーが出てきてしまいました。どうやら、**_sslというモジュールが無い**というエラーのようです。
実際、以下のように打ち込み、インストールされているpythonのバージョンを確認してみても、`3.11.3`は出てきません。
```
$ pyenv versions
```

調べてみると、たくさん記事が出てきました。

https://qiita.com/nunnunnununun/items/76b9a6b8b9eadbe495fe

https://qiita.com/ami_yakuwa/items/dd4b3ffc981e9329741e

https://www.python.jp/news/wnpython310/require-openssl11.html

なるほど。python3.10.x以降はopen-sslのバージョンが1.1.1以上でないとダメらしいです。open-sslとはchatGPTによると以下のようなものだそうです。

>OpenSSLは、オープンソースの暗号ライブラリとツールキットです。データの暗号化、復号化、署名、証明書の作成、プロトコルの実装など、さまざまな暗号化関連の機能を提供します。

では、今入っているopen-sslのバージョンを確認してみます。
```
$ openssl version
```
> OpenSSL 1.0.2k-fips  26 Jan 2017

確かにバージョンは1.0.2です。ではアップグレードを試みましょう。

# 3. opensslのアップグレード
まず試したのは以下のスクリプトです。
```
sudo yum install openssl11-devel --allowerasing
```
> Command line error: no such option: --allowerasing

怒られました。

次に試したのは以下のコマンドです。
```
sudo yum install openssl11-devel
```
> 0 packages excluded due to repository protections
 No package openssl11-devel available.

また怒られました。

よく見ると、以下のように書いてあります。
> This system is not registered with an entitlement server. You can use subscription-manager to register.

なるほど、**エンタイトルメントサーバー**じゃないからダメ。みたいな話でしょうか。調べてみたら以下のような記事が出てきました。

https://bishop.hatenadiary.com/entry/2022/11/03/081615

`/etc/yum/pluginconf.d/subscription-manager.conf`の中身にある、`enabled`の値を1から0に書き換えたらいいんですね。こちらの方が、もし万が一のことがあったときに書き直せばいいので`sudo subscription-manager register`とするよりも心理的にやりやすいです。
ということで、

```
$ vi /etc/yum/pluginconf.d/subscription-manager.conf
```
として`enabled=0`に書き換えました。

登録を済ませた？ところで再び実行です。
`openssl11`もその開発用パッケージ`openssl11-devel`もまとめてインストールします。

```
sudo yum install openssl11 openssl11-devel
```

> No package openssl11 available.
> No package openssl11-devel available.

ダメでした。
仕方ないのでソースコードから直接インストールする方法がないか調べてみたところ、以下の記事が見つかりました。

https://qiita.com/Esfahan/items/4e2002f4a24589b2d0bf

良さそうなので早速試してみることに。
#### 3-1. 必要なパッケージのインストール
最初にインストールしたパッケージと一部被ってますが、気にせずインストールしました。
```
$ sudo yum install -y zlib-devel perl-core make gcc
```

#### 3-2. opensslのダウンロード
次に、ソースコードをおいてあるサイトから、tar.gzファイルをダウンロード。
```
$ sudo curl https://www.openssl.org/source/openssl-1.1.1.tar.gz -o /usr/local/src/openssl-1.1.1.tar.gz
```

#### 3-3. opensslのインストール
そしてOpenSSLのインストールです。
```
$ cd /usr/local/src
$ sudo tar xvzf openssl-1.1.1.tar.gz
$ cd openssl-1.1.1/
$ sudo ./config --prefix=/usr/local/openssl-1.1.1 shared zlib
$ sudo make depend
$ sudo make
$ sudo make test
$ sudo make install
```

インストールされたかを確認しましょう。以下のようになればOKです。
```
$ sudo ls -l /usr/local/openssl-1.1.1
total 0
drwxr-xr-x 2 root root  37 Jul 11 13:57 bin
drwxr-xr-x 3 root root  21 Jul 11 13:57 include
drwxr-xr-x 4 root root 159 Jul 11 13:57 lib
drwxr-xr-x 4 root root  28 Jul 11 13:58 share
drwxr-xr-x 5 root root 140 Jul 11 13:57 ssl
```

#### 3-4. opensslのライブラリ群を追加する
どうやら、ライブラリの追加という操作が必要なようです。
以下のサイトを参考にしました。

https://qiita.com/Esfahan/items/0064d845ca6faf7f3d47#%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E5%85%A8%E4%BD%93%E3%81%A7%E6%B0%B8%E7%B6%9A%E7%9A%84%E3%81%AB%E4%BD%BF%E3%81%84%E3%81%9F%E3%81%84%E5%A0%B4%E5%90%88

```
$ vi /etc/ld.so.conf.d/openssl-1.1.1.conf
```
として、
```
/usr/local/openssl-1.1.1/lib
```
と書き込みました。

#### 3-5. キャッシュファイルの更新
```
$ sudo ldconfig
```

#### 3-6. ライブラリが追加されたことを確認
```
$ sudo ldconfig -p | grep libssl
```
これで、

> libssl.so.1.1 (libc6,x86-64) => /usr/local/openssl-1.1.1/lib/libssl.so.1.1
> libssl.so (libc6,x86-64) => /usr/local/openssl-1.1.1/lib/libssl.so

が追加されていれば（あれば）OKです。

#### 3-7. 動作確認
以下のコマンドで問題なくインストールされていることがわかります。
```
$ sudo /usr/local/openssl-1.1.1/bin/openssl ciphers -v TLSv1.3
TLS_AES_256_GCM_SHA384  TLSv1.3 Kx=any      Au=any  Enc=AESGCM(256) Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256 TLSv1.3 Kx=any      Au=any  Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256  TLSv1.3 Kx=any      Au=any  Enc=AESGCM(128) Mac=AEAD
```

さて、これで最新のOpenSSLがインストールできたので、自信をもって`pyenv install`ができますね。

# pyenvの実行再び
```
$ pyenv install 3.11.3
```

> ModuleNotFoundError: No module named '_ssl'
> ERROR: The Python ssl extension was not compiled. Missing the OpenSSL lib?

ダメでした。
OpenSSLは問題なくインストールできたはず。インストールできてるよね？ってことで調べてみる。

```
$ openssl version
OpenSSL 1.0.2k-fips  26 Jan 2017
```

**できてない**。いやでも間違いなくサーバーの何処かにちゃんとしたOpenSSLはおいてあるはず。ということは従来のバージョンと競合してるかもしれない。ログアウトして再度ログインしても問題は解決しない。さっきインストールしたOpenSSLは`/usr/local/openssl-1.1.1/bin/openssl`にある。
ということで絶対パスで確認してみる。

```
$ /usr/local/openssl-1.1.1/bin/openssl version
OpenSSL 1.1.1  11 Sep 2018
```
ビンゴ。絶対パスで指定してあげればちゃんと機能するってことは、正しくインストールはできたけど、opensslとコマンドを打つときに従来のopensslがまず読み込まれるようになっているということでしょう。これは従来のopensslがおいてあるPATHよりも前に、`/usr/local/openssl-1.1.1/bin/openssl`をPATHとして指定すれば解決しそう。

ということで、

```
$ vi .bashrc
```
として、
```
export PATH="/usr/local/openssl-1.1.1/bin/:$PATH"
```
とすれば良いんじゃないだろうか。
```
source ~/.bashrc
```
として変更を反映させて、

```
$ openssl version
OpenSSL 1.1.1  11 Sep 2018
```

うまくいきました。これでようやくpyenvが使えそうです。

```
$  pyenv install 3.11.3

ModuleNotFoundError: No module named '_ssl'
ERROR: The Python ssl extension was not compiled. Missing the OpenSSL lib?
```

ダメでした。
調べてみたところ、opensslが標準の場所とは違うところにインストールされたせいで、pythonのビルドがうまく行かないそう（以下の記事参考）。そこで、OpenSSLのヘッダ・ライブラリを見つけられるように、`PYTHON_CONFIGURE_OPTS`という環境変数にOpenSSLの場所を代入する必要があるみたいです。

https://aifx.tech/centos7-python/

上記の記事とはインストール場所が違うので、そこだけ書き換えた以下のコマンドを実行します。
```
$ echo 'export PYTHON_CONFIGURE_OPTS="--with-openssl=/usr/local/openssl-1.1.1"' >> ~/.bashrc
```

# 達成
流石に次はうまくいくだろうと思い、再度`pyenv`を実行します。

```
$ pyenv install 3.11.3

Downloading Python-3.11.3.tar.xz...
-> https://www.python.org/ftp/python/3.11.3/Python-3.11.3.tar.xz
Installing Python-3.11.3...
Installed Python-3.11.3 to /root/.pyenv/versions/3.11.3
```

できました。
ようやくpython3.11.3がインストールできました。

以下のコマンドで、インストールができているか念のため確認します。アスタリスクの付いているものが現在の環境です。
```
$ pyenv versions
```
デフォルトで使用されるpythonを3.11.3にするために、

```
$ pyenv global 3.11.3
```
と実行して、
```
$ python -V
Python 3.11.3
```

と表示されればインストール終了です。

# おまけ（一般ユーザーにも変化を適用する）

以下のように普段Pythonが使われているディレクトリに、新しくインストールしたPythonのシンボリックリンクを作成しました。

```
sudo ln -s $(pyenv which python) /usr/bin/python3.11
```

これで、
`python3.11`というコマンドによって、誰でも`python3.11.3`を使うことが可能になりました。

# 結論
従来のPythonも残しつつ、新しいPythonも使えるようになりました。

# 追記：rootでやる必要はなかった
パッケージの保管場所など色々と面倒だったので、よく確認してみたら、上記のpyenvのインストールやpyenvの実行は別に一般ユーザーでもできることでした。なので、それぞれがｐｙenvをインストールしてopen-sslの問題をクリアして、好きなバージョンのpythonをインストールし管理する方がいい気がしました。
ですので、rootにインストールした新しいpyenvは
```
pyenb uninstall 3.11.3
```
として消しました。