AWS CloudShellで無限のストレージを自動マウントする

# はじめに

[AWS CloudShell](https://aws.amazon.com/cloudshell/) は、管理コンソールからシェルセッションを開き、アクセスキー無しでAWS CLIやSDKが利用できる便利なサービスです。

しかしながら、利用可能な永続ストレージは1GBしかなく、それ以上のファイルを扱おうとすると取り扱いが少し面倒になります。そこで容量無制限のS3バケットをCloudShellのローカルファイルシステムとして利用する方法、またその環境をCloudShellセッション立ち上げ時に自動セットアップする方法を紹介します。

なおCloudShellからS3を利用する目的として、広大なストレージ領域以外にも、外部とのデータ共有のために利用することも可能です。

# mount-s3 (Mountpoint for Amazon S3)

S3バケットをファイルシステムとして利用できるようにする[Mountpoint for Amazon S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/mountpoint.html) (以降mount-s3と呼びます) というツールがあります。これを利用してCloudShellでS3バケットをマウントします。

## インストール

mount-s3はAWS製のツールですが、本稿執筆時点で、デフォルトではCloudShellで利用できないのでインストールします。

```console
$ curl -sLO https://s3.amazonaws.com/mountpoint-s3-release/latest/x86_64/mount-s3.rpm
$ sudo dnf install -y ./mount-s3.rpm
```

※説明の簡略化のためRPM検証手順はスキップしています。

## S3バケットのマウント

マウントしたいS3バケット名を `xxxxxxxx` と仮定します。

```console
$ bucket=xxxxxxxx
$ mkdir ~/mnt-s3  # マウントポイント作成
$ mount-s3 "${bucket}" ~/mnt-s3
```

## 動作確認

以下の動作確認例では、初期状態でバケットは空とします。

```console
## マウントポイントのディレクトリにファイルなし
$ ls -l ~/mnt-s3
total 0
$ aws s3 ls "${bucket}"

## ファイルシステム経由で書き込み
$ echo 'hello mount-s3' > ~/mnt-s3/hello-mount-s3.txt

## 今書き込んだファイルがS3バケットに保存されている
$ aws s3 ls "${bucket}"
2025-05-14 09:10:07         15 hello-mount-s3.txt
$ aws s3 cp "s3://${bucket}/hello-mount-s3.txt" -
hello mount-s3

## S3 API経由でオブジェクトを作成
$ echo 'hello S3 API' | aws s3 cp - "s3://${bucket}/hello-api.txt"

## それをファイルシステムから読み出す
$ ls ~/mnt-s3
hello-api.txt  hello-mount-s3.txt
$ cat ~/mnt-s3/hello-api.txt 
hello S3 API
```

## Tips. ファイル削除/更新の許可

mount-s3では、S3オブジェクト (ファイル) の削除や上書きはデフォルトで禁止されており、EPERM (Operation not permitted) で失敗します。

```console
$ echo update > ~/mnt-s3/hello-mount-s3.txt 
bash: /home/cloudshell-user/mnt-s3/hello-mount-s3.txt: Operation not permitted
$ rm ~/mnt-s3/hello-mount-s3.txt 
rm: cannot remove '/home/cloudshell-user/mnt-s3/hello-mount-s3.txt': Operation not permitted
```

削除、上書きを許可したい場合、マウント時に以下のオプションを指定します。

| オプション             | 効果         |
|-------------------|------------|
| --allow-delete    | 削除操作を許可する  |
| --allow-overwrite | 上書き操作を許可する |

```console
$ mount-s3 --allow-delete --allow-overwrite "${bucket}" ~/mnt-s3

$ cat ~/mnt-s3/hello-mount-s3.txt 
hello mount-s3

## ファイルの上書き
$ echo update > ~/mnt-s3/hello-mount-s3.txt 
$ cat ~/mnt-s3/hello-mount-s3.txt 
update

## ファイル削除
$ rm ~/mnt-s3/hello-mount-s3.txt 
$ cat ~/mnt-s3/hello-mount-s3.txt
cat: /home/cloudshell-user/mnt-s3/hello-mount-s3.txt: No such file or directory
```

## アンマウント

CloudShellセッションが終了するときに環境がリセットされるので、自動でアンマウントされますが、手動でアンマウントしたいときは以下のコマンドを実行してください。

```console
$ umount ~/mnt-s3
```

# CloudShell起動時に自動マウントする

CloudShellは、セッションが切断されるとホームディレクトリ以外は初期状態にリセットされます。`dnf` でインストールしたパッケージも未インストール状態に戻ってしまいます。環境を使い捨てにできるという点でこれはCloudShellの利点ですが、その都度mount-s3をインストールしてS3バケットをマウントするのはとても面倒です。

:::note info
CloudShellのセッションが[自動切断](https://docs.aws.amazon.com/cloudshell/latest/userguide/limits.html#session-lifecycle-limitations)されるケースは以下の通りです。

- キーボードやポインティングデバイスによるインタラクティブなユーザー操作が約20 - 30分連続して無かった場合
- セッション実行時間が約12時間を超えた場合

管理コンソールのブラウザを閉じたり、サインアウトしただけではセッションは切断されません。
:::

上記のとおりセッションはこまめに切断されるので、CloudShell起動時に自動でS3バケットがマウントされるように設定します。以下のスクリプトを `~/.bashrc.d/auto-mount-s3.sh` に保存してください (`~/.bashrc.d/` 配下であればファイル名は任意です)。

```sh:~/.bashrc.d/auto-mount-s3.sh
# S3バケット自動マウントスクリプト

# 変数/関数定義でロード元のシェルを汚さないようにサブシェルで実行
(
  # エラー時は終了させる
  set -eu

  # パラメータとそのデフォルト値定義. パラメータ未定義の場合にデフォルト値(右辺)が使用される
  # 各パラメータは同名の(環境)変数で上書き可能
  # S3バケット名
  : "${ams3_bucket:=xxxxxxxx}"  # TODO: 実際のバケットを指定してください
  # マウントポイント
  : "${ams3_mount_point:=${HOME}/mnt-s3}"
  # mount-s3 オプション
  : "${ams3_options:=--allow-delete --allow-overwrite}"
  # mount-s3 RPMのURL
  : "${ams3_rpm_url:=https://s3.amazonaws.com/mountpoint-s3-release/latest/x86_64/mount-s3.rpm}"
  # mount-s RPMのローカル保存パス
  : "${ams3_rpm_local_path:=${HOME}/rpm/mount-s3.rpm}"
  
  _has_command() {
    type "$@" &> /dev/null
  }
  
  _download_mount_s3() {
    # RPMが存在すれば即リターン
    if [[ -f "${ams3_rpm_local_path}" ]]; then
      return
    fi
    local rpm_local_dir=$(dirname "${ams3_rpm_local_path}")
    mkdir -p "${rpm_local_dir}"
    curl -L "${ams3_rpm_url}" -o "${ams3_rpm_local_path}"
  }
  
  _install_mount_s3() {
    # mount-s3がインストール済みなら即リターン
    if _has_command mount-s3; then
      return
    fi
    _download_mount_s3
    sudo dnf install -y "${ams3_rpm_local_path}"
  }
  
  _s3mount() {
    # マウント済みなら何もせず即終了
    if mountpoint "${ams3_mount_point}" &> /dev/null; then
      return
    fi
    _install_mount_s3
    mkdir -p "${ams3_mount_point}"
    mount-s3 ${ams3_options} "${ams3_bucket}" "${ams3_mount_point}"
  }
  
  # S3バケットをマウント. 時間がかかるのでバックグランドで実行
  # 初期化スクリプトを汚さないように出力を抑制
  _s3mount &> /dev/null &
)
```

スクリプトの内容はコメントで説明しているのでコード以外の部分を補足します。
- CloudShellは、起動時にデフォルトで `~/.bashrc.d/` 配下のシェルスクリプトを実行する (`~/.bashrc`から読み出される) ので、そこにS3自動マウントスクリプトを配置します。もちろん `~/.bashrc` などの初期化スクリプトに同じ内容を直接記述することも可能です。`~/.bashrc.d` ディレクトリはデフォルトで存在しないので作成してください。
- 各パラメータのデフォルト値をスクリプト中で定義していますが、ユーザーの好みに応じて変更可能です。スクリプト中の定義を直接変更してもよいですし、スクリプト読み出し前に初期化スクリプトで変数を定義してもかまいません。パラメータは `ams3_` (Auto Mount S3の略) のプレフィクスを付けています。
- ダウンロードしたRPMは、次回セッション時にも保持され再ダウンロードを不要にするために、デフォルトでHOME配下に保存しています。新しいバージョンmount-s3がリリースされたときダウンロードからやり直したい場合は、ローカルのRPMファイルを削除してセッションを再開してください。
    - なお、CloudShellでは最終セッション終了後120日経過すれば永続ストレージのHOME配下であっても[自動削除](https://docs.aws.amazon.com/cloudshell/latest/userguide/limits.html#persistent-storage-limitations)されます。
- 上記の自動マウントスクリプトは、バケット名のパラメータ値以外はどの環境でもそのまま実行可能です。

# まとめ

[mount-s3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/mountpoint.html)を用いて、CloudShellから容量無制限のS3バケットをファイルシステムとして利用する方法、およびその自動設定方法を紹介しました。快適なCloudShellライフのお役に立てば幸いです。