Amazon EC2 のユーザーデータで Ubuntu インスタンスに Pyenv を導入して別バージョンの Python を利用可能にする

2024.05.07

こんにちは、CX 事業本部製造ビジネステクノロジー部の若槻です。

Amazon EC2の AMI Catalog で公開されている Ubuntu の AMI には既定でPythonがインストールされていますが、その既定のものとは別のバージョンの Python を利用したい場合があります。その際にPyenvを導入することで、任意の Python バージョンを簡単に利用可能になります。

今回は、Amazon EC2 のユーザーデータで Ubuntu インスタンスに Pyenv を導入して、別バージョンの Python を利用可能にする実装をAWS CDKで行ってみました。

やってみた

プリインストールされた Python バージョンを確認

AMI Ubuntu Server 22.04 LTS (HVM), SSD Volume Type ami-0595d6e81396a9efb (64-bit (x86)) で起動したインスタンスでは、既定では Python 3.10.12 がプリインストールされていました。

$ python3 --version
Python 3.10.12

ユーザーデータ

結論として以下のスクリプトをユーザーデータとして実行することにより、Pyenv を導入して任意の Python バージョンを利用可能になりました。Python のバージョンは 3.7.17 をインストールし、グローバルバージョンに設定しています。

lib/cdk-sample-stack.ts

# Ubuntu ユーザー向けの Pyenv の導入

## 必要な依存パッケージをインストール
sudo apt update
sudo apt install -y build-essential libssl-dev zlib1g-dev libbz2-dev \
  libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \
  xz-utils tk-dev libffi-dev liblzma-dev python3-openssl git

## ubuntu ユーザーのホームディレクトリを取得
UBUNTU_HOME=$(getent passwd "ubuntu" | cut -d: -f6)

## pyenv をダウンロード
git clone https://github.com/pyenv/pyenv.git "$UBUNTU_HOME/.pyenv"

## ubuntu ユーザーに pyenv ディレクトリの所有権を付与する
sudo chown -R ubuntu:ubuntu "$UBUNTU_HOME/.pyenv"

## 設定ファイルに必要な処理を追記する
echo 'export PATH="$HOME/.pyenv/bin:$PATH"' >> "$UBUNTU_HOME/.bashrc"
echo 'eval "$(pyenv init --path)"' >> "$UBUNTU_HOME/.bashrc"
echo 'eval "$(pyenv init -)"' >> "$UBUNTU_HOME/.bashrc"

## 設定ファイルを読み込む
source "$UBUNTU_HOME/.bashrc"

## Pyenv コマンドのパスを取得(PATH が反映されない場合があるため)
PYENV_DIR=$UBUNTU_HOME/.pyenv/bin

## 確認コマンド:バージョン情報が出力される(pyenv が実行可能である)ことを確認する
$PYENV_DIR/pyenv -v

## Python 3.7.17 をインストールし、グローバルバージョンに設定する
## NOTE: Pyenv を使用するユーザーでインストールするため、sudo -u ubuntu -i を付与する
sudo -u ubuntu -i $PYENV_DIR/pyenv install 3.7.17
sudo -u ubuntu -i $PYENV_DIR/pyenv global 3.7.17

## 確認コマンド:pyenv の現在のバージョンが 3.7.17 であることを確認する
$PYENV_DIR/pyenv versions
$PYENV_DIR/pyenv which python --version

## ユーザーデータでの実行用に pyenv が管理する Python バージョンへのパスを通す
eval "$($PYENV_DIR/pyenv init --path)"
eval "$($PYENV_DIR/pyenv init -)"

## 確認コマンド:Python バージョンが 3.7.17 であることを確認する
python --version

ポイントとしては、ユーザーデータの実行ユーザーは root となるため、ubuntu ユーザーのホームディレクトリを使うようにしています。また Pyenv コマンドの実行を sudo -u ubuntu -i を付与して行うことで、ubuntu ユーザーで実行されるようにしています。

CDK コード

AWS CDK で、前述のユーザーデータを使った EC2 インスタンスを構築するためのコード(TypeScript)を作成します。

lib/cdk-sample-stack.ts

import { aws_ec2, Stack } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as fs from 'fs';
import * as path from 'path';

export class CdkSampleStack extends Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    const vpcCidr = '10.100.0.0/16';
    const ec2InstanceConnectSourceIpRange = '3.112.23.0/29';
    const instanceAmiId = 'ami-0595d6e81396a9efb'; // Ubuntu Server 22.04 LTS (HVM), SSD Volume Type (64-bit (x86))
    const region = 'ap-northeast-1';

    // VPC を作成
    const vpc = new aws_ec2.Vpc(this, 'Vpc', {
      ipAddresses: aws_ec2.IpAddresses.cidr(vpcCidr),
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: 'public',
          subnetType: aws_ec2.SubnetType.PUBLIC,
        },
      ],
      maxAzs: 1,
    });

    // セキュリティグループを作成
    const securityGroup = new aws_ec2.SecurityGroup(this, 'SecurityGroup', {
      vpc,
    });

    // セキュリティグループに EC2 Instance Connect 用のインバウンドルールを追加
    securityGroup.addIngressRule(
      aws_ec2.Peer.ipv4(ec2InstanceConnectSourceIpRange),
      aws_ec2.Port.tcp(22),
      `SSH by EC2 Instance Connect in ${region}`
    );

    // EC2 インスタンスを作成
    const instance = new aws_ec2.Instance(this, 'Instance', {
      vpc,
      securityGroup,
      instanceType: aws_ec2.InstanceType.of(
        aws_ec2.InstanceClass.T2,
        aws_ec2.InstanceSize.MICRO
      ),
      machineImage: aws_ec2.MachineImage.genericLinux({
        [region]: instanceAmiId,
      }),
      requireImdsv2: true,
    });

    // Pyenvのセットアップスクリプトを読み込み
    const setupPyenvCommands = fs
      .readFileSync(path.resolve(__dirname, '../script/setupPyenv.sh'), 'utf-8')
      .split('\n');

    // インスタンスのユーザデータにコマンドを追加
    instance.userData.addCommands(...setupPyenvCommands);
  }
}

上記を CDK デプロイして、EC2 インスタンスを作成します。

動作確認

インスタンスが作成されたら、EC2 インスタンスコネクトでログインします。

pyenv には Python 3.7.17 がインストールされ、グローバルバージョンに設定されていることが確認できます。

$ pyenv versions
  system
* 3.7.17 (set by /home/ubuntu/.pyenv/version)

pyenv で管理されている Python の実行バージョンは 3.7.17 が設定されていることが分かります。

$ pyenv which python --version
/home/ubuntu/.pyenv/versions/3.7.17/bin/python

python3 および python コマンドで実行される Python バージョンが 3.7.17 になっていることが確認できます。

$ python3 --version
Python 3.7.17
$ python --version
Python 3.7.17

これで、構築された EC2 インスタンスで Python 3.7.17 を利用することができるようになりました。

既存の Python は削除しない方が良さそう

今回 Ubuntu を例にして Pyenv を導入しました。この場合プリインストールされた Python は削除しても良さそうですが、Ubuntu で Python を削除すると画面がクラッシュするなどの問題が報告されているので、特段の理由が無ければ削除せずに残しておく方が良いかもしれません。

おわりに

Amazon EC2 のユーザーデータで Pyenv を導入して別バージョンの Python を利用可能にしてみました。

ユーザーデータの実行ユーザーと、利用者がログインするユーザーが異なる場合の対応で苦労しましたが、なんとか解決できました。Pyenv に限らず使える手法だと思うので参考になれば幸いです。

参考

以上