【AWS】手を動かしながら学ぶAWS AWS SDK for Python (boto3)

## この記事で伝えたいこと（ポイント）

- AWS SDKがどんなものだったか改めて振り返る
- 書き方の例をいくつかみる
- boto3の定義が書いてあるドキュメントの読み方

## はじめに

この記事ではAWSが提供するAWS SDK for Python (boto3)（以下、本文ではboto3）を手を動かしながら振り返る内容となっています。

主な内容としては実践したときのメモを中心に書きます。（忘れやすいことなど）
誤りなどがあれば修正していく想定です。

今回は重要だと思うポイントや実務で4年〜5年使ってみての見解を交えてAWS SDK for Pythonについて説明できたらと思います。

なお、解説記事ついては[【AWS】用語を整理しながら学ぶAWS AWS SDK for Python (Boto3)](https://qiita.com/ymd65536/items/2ea9edc38c9693e4066a)を参照していただけますと幸いです。

## 結論を言うとAWS SDKって何者

AWSが提供するAPIを手軽に実行できるようにした開発者ツールです。

## 復習：AWS SDK for Pythonとは

AWSの公式サイトには下記のように表現されています。

> AWS での構築ツール
> AWS でアプリケーションを開発および管理するためのツール
> 選択したプログラミング言語を使用して、AWS でアプリケーションを簡単に開発する
> Python 固有の API と有益なライブラリを使って、アプリケーションを開発する

[参考](https://aws.amazon.com/jp/developer/tools/)

主にアプリケーション開発者向けに提供されているソフトウェアと解釈できます。
AWSリソースの作成、削除、情報取得ができます。

### 実際に触ってみて思うこと

強みは`使い慣れたプログラミング言語でAWSリソースの作成・削除・情報の取得ができる`という理解でしたが、AWS SDKの強みは`リソース情報の取得`にあると思います。

リソースを作成するだけであれば、AWS CDKやIaC（CloudFormationやTerraformなど）で十分である場合がほとんどです。もちろん、これらはインフラストラクチャーに携わる人が主に利用するのでそうではないとも言えます。

また、アプリケーションから条件に合わせてリソースを作成する場合においてとても有効な手段になります。他にはAWSマネジメントコンソールから参照が難しいリソース情報を簡単に参照できるのは他にはない強みです。

## 環境の構築方法

では実際に使うためにはどのような環境を構築すれば良いのでしょうか。

以下の方法で環境構築が可能です。

- パッケージマネージャ(pip)を使ってそのままローカルPCにパッケージをインストールして利用（ローカル）
- Python仮想環境(venv)を構築して仮想環境にインストールして利用(venv)
- コンテナ開発環境にインストールして利用(コンテナ)

それ以外の方法ではクラウドIDEを使った方法があります。認証情報はホームディレクトリにある`.aws/credential`というところに保存されますので認証情報が読み取れれば、どんな環境でも構いません。

### 構築方法の違い

いくつか示した上でどんな環境でも良いという話をしました。構築方法によってどんな違いがあるのか経験を元に説明します。

結論から述べると、とくに理由がない限りはコンテナベースの開発環境を構築することをオススメします。理由としてはboto3およびPythonはバージョンとパッケージのマネジメントが肝となるからです。

|項番|構築方法|メリット|デメリット|
|:---|:---|:---|:---|
|1|ローカル|Pythonがインストールされていれば、すぐに扱える。|チーム開発では不向き|
|2|仮想環境|メインとなるPython環境とセパレートしてプロジェクトを分離できる。|ECSやLambdaを使ってアプリケーションを開発している場合は不向き|
|3|コンテナ|Pythonのバージョンも含め再現性の高い環境が構築できる。|コンテナイメージが必要|

#### 補足：ローカル開発環境について

`チーム開発に不向き`ということですが、最低でも以下の設定をメンバー間で共有できれば問題ありません。

- Pythonのバージョン
- pipの設定情報（pip.conf）
- インストールされたpipパッケージ情報

### pipの仕様に阻まれる

pip installの仕様でパッケージインストールに失敗する場合があります。経験上では以下のケースが多いです。

- `--break-system-packages`を必要とする場合
- `--user` インストールを必要とする場合
- `--trusted-host`を必要とする場合

### `--break-system-packages`を必要とする場合

システムでインストールされているパッケージがオーバーライドされるのを防ぐために導入されたオプションです。Python3.11、pip 23.0で導入された機能です。

参考：[Bump for release · pypa/pip@368c7b4](https://github.com/pypa/pip/commit/368c7b4c557e673b05b0f8cffc967d3e333eee19)

対策としてはシステムにインストールされたPythonパッケージに対して干渉しない形でPythonのパッケージをインストールする必要があります。
つまりは`仮想環境を構築して使いましょう`ということです。あるいは`--user`を使いましょう。（後述）

詳しくはPython Packaging User Guideを参照してください。

[外部から管理される環境 - Python Packaging User Guide](https://packaging.python.org/ja/latest/specifications/externally-managed-environments/#guide-users-towards-virtual-environments)

### `--user` インストールを必要とする場合

システムパッケージに干渉しない形でインストールするには`--user`オプションを利用します。管理者権限を使わない場合にも有効です。

詳しくはPython Packaging User Guideを参照してください。

参考：[パッケージをインストールする - Python Packaging User Guide](https://packaging.python.org/ja/latest/tutorials/installing-packages/#installing-to-the-user-site)

### `--trusted-host`を必要とする場合

信頼済みのパッケージレジストリからパッケージをインストールする時に利用します。
たとえば、SSLインスペクションが導入されている環境で利用することがあります。

#### 証明書を使っている場合

証明書を使ってboto3を扱う場合は`AWS_CA_BUNDLE`という環境変数に証明書のパスを設定する必要があります。あるいはboto.coreの設定にしておく必要があります。

参考：[Configuration - Boto3 1.38.18 documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html)

## AWS SDK for Pythonの基本

ここからは実際に触っていきたいと思いますが、まずは基本をおさえておきましょう。
boto3は主に以下の流れで利用します。

1.セッションを構築する
2.Client API / Resource APIオブジェクトを作成する
3.オブジェクトからメソッドを実行する
4.戻り値を処理して表示

### Client API / Resource APIって何

boto3には主にclientとresourceの２つがあります。

- client:アクセス元の操作について扱う場合
- resource:指定されたリソースについてダイレクトにアクセスしたい場合

実際のところclientが扱えれば、だいたいのことはできます。resource APIはほとんど利用しないです。（全く利用しないわけではないです）

#### 補足：resource APIについて

resource APIについては今後の機能追加を予定していないという言及があります。
また、一時は廃止するという話まで出てきましたが、すぐにアナウンス文が修正されるということもありました。
※リアルタイムで見ていたこともあって当時はとても驚いたことを覚えています。

参考：[More info about resource deprecation? #3563](https://github.com/boto/boto3/discussions/3563)

今後の利用についてはとくに理由がない限り、client APIの利用を検討したほうが良いでしょう。

## 書き方の例：サクッと書いてみる

では実際に書いてみましょう。

### アカウント情報を取得する

とてもベーシックで誰でも簡単に実行できるメソッドとしてAWS STSのAPIを呼び出すというのがあります。以下のコードを実行するとAWSのアカウントIDが表示されます。

```python
import boto3

boto3_session = boto3.Session()
sts = boto3_session.client('sts')
id_info = sts.get_caller_identity()

print(id_info['Account'])
```

一見して問題ないコードに思えますが、このコードには問題があります。
それは`実行するまでどのAWSアカウントのIDが取得されるかわからない`という点です。

認証情報を参照する優先順位についてはboto3に以下の記載があります。

> Boto3は認証情報の検索時に複数の場所を検索します。Boto3が認証情報を検索する仕組みは、可能性のある場所のリストを検索し、認証情報が見つかった時点で検索を停止することです。Boto3が認証情報を検索する順序は以下のとおりです。

参考：[認証情報 - Boto3 1.38.18 ドキュメント](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html#configuring-credentials)

この問題を解決する場合は`AWS_DEFAULT_PROFILE`の環境変数や`.aws/credential`を設定する必要があります。あるいはオススメはしませんが、以下のようにプロファイルを指定して書くことも可能です。

```python
from boto3 import Session

boto3_session = Session(profile_name="account_profile")
sts = boto3_session.client('sts')
id_info = sts.get_caller_identity()

print(id_info['Account'])
```

### Lambdaの関数一覧を取得する

次にLambdaの関数一覧を取得する例を見ていきましょう。

```python
import boto3

boto3_session = boto3.Session()
client = boto3_session.client('lambda')

for function in client.list_functions()['Functions']:
    print(function['FunctionName'])

```

先ほど同様にこのコードには認証情報に関する問題を抱えていますが、結論から先に述べると`リージョンが実行されるまで不明なため、どの関数一覧を取得するのかわからない`という問題があります。
この問題を解決するにはセッション構築時にリージョンを指定する必要があります。

たとえば、`us-east-1`の関数一覧を取得する例です。

```python
import boto3

boto3_session = boto3.Session(region_name='us-east-1')
client = boto3_session.client('lambda')

for function in client.list_functions()['Functions']:
    print(function['FunctionName'])
```

#### ページングとNextToken

さまざまなメソッドを持つboto3ですが、実は万能ではなく一度のリクエストですべてのデータを取れないケースがあります。たとえば、リソースが多い場合においては分割して取得する必要があります。

分割して取得する場合は取得件数の最大値を超えた時にNextTokenが発行されますのでNextTokenを使って再度、リクエストを送信します。

塊毎にちょっとずつデータを取っていく、この操作をページングと言います。150件あって100件までは一度に取得できる上限であれば、2回のリクエストが必要です。

具体的には以下のような実装例です。

```python
    if "NextToken" in table_list:
        next_token = table_list["NextToken"]
        has_next_token = True

        while has_next_token:
            next_data = session.client("glue").get_tables(NextToken=next_token)
            table_list["DatabaseList"].extend(next_data["DatabaseList"])

            if "NextToken" in next_data:
                next_token = next_data["NextToken"]
            else:
                has_next_token = False
```

あるいは各client APIに実装されているpagenatorを使うと良いでしょう。

### クロスアカウントアクセスを意識する

ここまででわかることとしてはboto3を扱う上ではセッションがより重要になるということです。
AWSではセキュアに認証する手段としてAWS STSが提供されています。普段意識することがないですが
これがboto3をクロスアカウントアクセスする際に役に立ちます。

以下のコードはAWS STSを使ってクロスアカウントアクセスを実行するメソッドです。

```python
def get_resource_details2(role, external_id, region):
    # ロールの引き受け
    sts = boto3.client("sts").assume_role(
        RoleArn=role,
        RoleSessionName="SessionName",
        ExternalId=external_id)
    # Sessionオブジェクトの生成
    boto3_session = boto3.Session(
        aws_access_key_id=sts["Credentials"]["AccessKeyId"],
        aws_secret_access_key=sts["Credentials"]["SecretAccessKey"],
        aws_session_token=sts["Credentials"]["SessionToken"],
        region_name=region)
    # クライアントオブジェクトの生成
    ec2 = boto3_session.client('ec2')
    rds = boto3_session.client('rds')
    s3 = boto3_session.client('s3')
```

アクセス時にはロール名と外部IDを使用しています。上記のように実装することで同一セッション、特定のリージョンに対して複数のAPIを実行できるようになります。
また、`RoleSessionName`を設定することでセッション名を明確に管理できるようになります。

## 補足：PEP8をうまく扱う

実際にコードを書いているとPEP8による警告が表示されてコーディングしにくいところが出てきます。とくにPythonのコーディング規約には1行の文字数を79文字に定めています。この規約を守るのがboto3では難しいです。

参考：[maximum-line-length　- PEP 8 – Style Guide for Python Code | peps.python.org](https://peps.python.org/pep-0008/#maximum-line-length)

なお、このPythonのコーディング規約は無視することが可能であり、flake8を利用されている場合は`.flake8`ファイルをリポジトリにおくことで無視することが可能です。

```
[flake8]
ignore = E501
```

参考：[Line too long (82 > 79 characters) (E501)](https://www.flake8rules.com/rules/E501.html)

あるいは1行の文字数を任意の文字数にすることも可能です。
※120文字にする場合

```
[flake8]
max-line-length = 120
```

## boto3のメソッド名を探索する

ここまででboto3の特徴・使い方をおさえたところで実際に使ってみると、とある壁にぶつかると思います。というのもboto3はAWSのサービスに対していろんなAPIを実行できるということでたくさんのメソッドがあります。

boto3はメソッド名がたくさんあるため、「自分の欲しい情報を取ってきてくれるメソッドはどれだ？」という状況になりがちです。

昨今においてはAIに聞くことでなんとなくやってくれるところがありますが、ドキュメントを読めるようになった方がいささか早いかもしれません。
※実際のところ、生成したコードを精査するためにドキュメントを探索することがあります。

そこでboto3ドキュメントを読むコツとしては個人的に以下のやり方がオススメです。

- 使いたいサービスを探す
    - たとえば、VPCならEC2のページを開く
- 英単語からやりたいことを想像する
    - ネットワークACLを作りたいなら`create`
- 単語を検索する
    - `create`から始まるものを文書内から検索する
- メソッドの詳細を追う
    - Request Syntaxを見る
    - parametersを見て、requiredになっているものメモする
    - Response SyntaxとResponse Structureをチェックする

### 補足：単語を検索する

[Lambda - Boto3 1.38.18 documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda.html)をベースに見ていくとわかるとおり、基本的に`動詞 + 名詞`の組み合わせでメソッド名が定義されています。

作成と削除は`create`と`delete`で大変わかりやすいですが、リソース情報の取得には少しだけクセがあります。
リソース情報の取得は`list`あるいは`get`を使うことで実現できますが、それぞれで使い方が異なります。

`list`は一覧の取得、`get`は特定のリソースに対して詳細情報を取得するといったニュアンスが強いです。

たとえば、Lambdaにおいては`list_functions`を実行して関数名を`get_function`で関数の詳細を取得します。具体的には以下のコードです。

```python
import boto3

boto3_session = boto3.Session(region_name='us-east-1')
client = boto3_session.client('lambda')

for function in client.list_functions()['Functions']:
    print(client.get_function(FunctionName=function['FunctionName'])['Configuration'])
```

## まとめ

AWS SDK for Python (boto3) とは何かや実践的な使い方や注意点を解説しました。
今までずっと触ってきてハマったところや内在する不具合に気づけるようになったのは使い始めて3年目くらいだということを覚えています。とくにインフラ周りまで面倒をみるとコードの悪いところに気づけたり、チームで開発しているときに気づけたりすることが多いです。

また、個人の環境ではかなり自由にやっているため、制約のある社内環境でやってみると動かないことがよくあります。そういった場合に細かい設定方法を調査して知ることがよくありました。
一番気を遣ったところとしてはソースコードをいかに修正せずに現在の環境に対応させるかというところです。
認証プロファイルやリージョン名、SSL対策はその典型と言えるでしょう。

なお、これらの設定はboto.coreの設定を変えることで対応が可能であり、具体的には`.aws/config`を修正することで対応可能です。次はコンフィグ周りについて調べてみようと思いました。

## おわり