boto3 の client は「AWS の API をそのまま呼ぶ低レベルの窓口」、resource は「バケットやテーブルをオブジェクトとして扱う高レベルの窓口」、Session は「認証情報とリージョンを持ち、client と resource を作る元」です。 boto3.client() と書いたときも、裏では既定の Session が 1 つ作られています。

この記事では、同じ処理を 3 通りで書いて戻り値の型と出力を並べ、どれを選ぶかを表にします。後半は認証情報の探索順を全段の一覧にし、「いまどの認証情報が使われたか」を session.get_credentials().method と sts get-caller-identity で確かめます。profile_name・region_name の指定、3 つの定番エラーの再現、AssumeRole で別の Session を作る定型コードまで扱います。

確認環境:Ubuntu 24.04、Python 3.11.15、boto3 1.43.103、AWS CLI 1.46.1。AWS の API をローカルで再現するツール(moto 5.2.3)で実行しました。掲載するコードとコマンドは、実際の AWS でそのまま使える形(接続先の指定なし)にしてあります。

client・resource・Sessionの違い 早見表

比べる点 client resource Session
役割 API を 1 対 1 で呼ぶ リソースをオブジェクトとして操作する 設定を持ち、client・resource を作る
作り方 boto3.client("s3") boto3.resource("s3") boto3.Session(profile_name=..., region_name=...)
戻り値 辞書(dict) Bucket・Object・Table などのインスタンス ―
対応サービス すべて(この環境では 434) 9 サービスのみ ―
新機能の追加 続く 追加されない(既存の機能は使い続けられる) ―
ページ分割 paginator を使う コレクション(.all() など)が自動で続きを取る ―
スレッド間の共有 おおむね可(公式に注意点あり) 共有しない 共有しない
例外の捕まえ方 client.exceptions.XXX resource.meta.client.exceptions.XXX ―
やりたいこと 書き方
既定の設定でそのまま使う boto3.client("s3")
profile を指定する boto3.Session(profile_name="dev").client("s3")
リージョンを指定する boto3.client("s3", region_name="ap-northeast-1")
いまの認証の出どころを見る session.get_credentials().method
誰として接続しているかを見る session.client("sts").get_caller_identity()["Arn"]
使える profile の一覧 boto3.Session().available_profiles
resource から client を取り出す resource.meta.client

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「boto3 client・resource・sessionの違いと認証の優先順位」

同じ処理をclient・resource・Sessionの3通りで書く

S3 のバケット rjc-demo-bucket(オブジェクトが 2 つ入っている)の一覧を、3 通りで取ります。

# three_ways.py
import boto3

BUCKET = "rjc-demo-bucket"

# 1. client:API をそのまま呼ぶ。戻り値は辞書
client = boto3.client("s3")
resp = client.list_objects_v2(Bucket=BUCKET)
print("client  :", type(client).__name__, "->", type(resp).__name__)
for obj in resp.get("Contents", []):
    print("   ", obj["Key"], obj["Size"])

# 2. resource:オブジェクトとして扱う。戻り値はリソースのインスタンス
resource = boto3.resource("s3")
bucket = resource.Bucket(BUCKET)
print("resource:", type(resource).__name__, "->", type(bucket).__name__)
for obj in bucket.objects.all():
    print("   ", obj.key, obj.size, f"({type(obj).__name__})")

# 3. Session:設定(認証情報・リージョン)を持ち、client も resource もここから作る
session = boto3.Session(region_name="ap-northeast-1")
print("session :", type(session).__name__, "region =", session.region_name)
s3 = session.client("s3")
keys = [o["Key"] for o in s3.list_objects_v2(Bucket=BUCKET).get("Contents", [])]
print("    session.client   ->", keys)
keys = [o.key for o in session.resource("s3").Bucket(BUCKET).objects.all()]
print("    session.resource ->", keys)

# resource の中には client が入っている
print("resource.meta.client:", type(resource.meta.client).__name__)
# boto3.client() は、既定の Session を作って使っている
print("既定の Session:", boto3.DEFAULT_SESSION)
client  : S3 -> dict
    a.txt 3
    members.csv 8
resource: s3.ServiceResource -> s3.Bucket
    a.txt 3 (s3.ObjectSummary)
    members.csv 8 (s3.ObjectSummary)
session : Session region = ap-northeast-1
    session.client   -> ['a.txt', 'members.csv']
    session.resource -> ['a.txt', 'members.csv']
resource.meta.client: S3
既定の Session: Session(region_name='ap-northeast-1')
  • client は list_objects_v2 という API 名そのままのメソッドを呼び、辞書を受け取ります。Contents というキーを自分でたどります。
  • resource は Bucket というオブジェクトを作り、bucket.objects.all() で ObjectSummary を順に受け取ります。属性は obj.key のように小文字です。
  • Session は、それ自体が API を呼ぶのではなく、client や resource を作る元です。最後の行のとおり、boto3.client() を呼ぶと既定の Session が作られます。

client を作れるサービスと resource を作れるサービスの数は、次のコードで確かめられます。

# count.py
import boto3

session = boto3.Session()
print("client を作れるサービス  :", len(session.get_available_services()))
print("resource を作れるサービス:", session.get_available_resources())
client を作れるサービス  : 434
resource を作れるサービス: ['cloudformation', 'cloudwatch', 'dynamodb', 'ec2', 'glacier', 'iam', 's3', 'sns', 'sqs']

どれを選ぶかの比較表

公式ドキュメントの resource のページには、「AWS の Python SDK チームは、resource のインターフェースに新機能を追加する予定がない。既存のインターフェースは boto3 のライフサイクルの間は動き続け、新しいサービス機能は client から利用できる」という趣旨の注記があります。これを踏まえた選び方です。

状況 選ぶもの 理由
これから書くコード client 全サービス・全 API に対応し、新機能も届く
resource に無いサービス(Lambda、SSM など) client resource は 9 サービスのみ
S3 や DynamoDB を短く書きたいスクリプト resource でもよい bucket.objects.all() や Table の型変換が手軽
resource で書かれた既存のコード そのまま使える 既存の機能は動き続ける。足りない操作だけ resource.meta.client で補う
profile・リージョン・ロールを切り替える Session を明示的に作る 設定を 1 か所に集められる
スレッドやプロセスを分ける スレッドごとに Session を作る Session と resource は共有しない、と公式にある

スレッドについて、公式ドキュメントは「client はおおむねスレッドセーフだが、プロセス間では共有できない」「Session と resource はスレッドセーフではないので、スレッドやプロセスごとに作る」と説明しています。

Sessionの役割:profile_nameとregion_nameの指定

Session が持つのは、主に認証情報・リージョン・profile に関する設定です。以降の検証では、次の 2 つのファイルを使います(キーの値は説明用の架空のものです)。

# ~/.aws/credentials
[default]
aws_access_key_id = AKIAFILEDEFAULT00001
aws_secret_access_key = secret-from-credentials-default

[dev]
aws_access_key_id = AKIAFILEDEV000000002
aws_secret_access_key = secret-from-credentials-dev

[both]
aws_access_key_id = AKIAFILEBOTH00000009
aws_secret_access_key = secret-from-credentials-both
# ~/.aws/config
[default]
region = ap-northeast-1

[profile dev]
region = us-west-2

[profile confonly]
aws_access_key_id = AKIACONFIGONLY000003
aws_secret_access_key = secret-from-config
region = eu-west-1

[profile admin]
role_arn = arn:aws:iam::123456789012:role/demo-admin
source_profile = dev
role_session_name = taro-session
region = ap-northeast-1

リージョンが決まる順番

# region.py
import boto3

session = boto3.Session()
print("Session()                         :", session.region_name)
print("Session(profile_name='dev')       :", boto3.Session(profile_name="dev").region_name)
print("Session(region_name='eu-central-1'):", boto3.Session(region_name="eu-central-1").region_name)

s = boto3.Session(profile_name="dev")
print("dev の client                     :", s.client("s3").meta.region_name)
print("dev の client(region_name=...)    :", s.client("s3", region_name="ap-southeast-1").meta.region_name)

環境変数なしで実行した結果と、AWS_DEFAULT_REGION を付けて実行した結果です。

$ python3 region.py
Session()                         : ap-northeast-1
Session(profile_name='dev')       : us-west-2
Session(region_name='eu-central-1'): eu-central-1
dev の client                     : us-west-2
dev の client(region_name=...)    : ap-southeast-1

$ AWS_DEFAULT_REGION=sa-east-1 python3 region.py
Session()                         : sa-east-1
Session(profile_name='dev')       : sa-east-1
Session(region_name='eu-central-1'): eu-central-1
dev の client                     : sa-east-1
dev の client(region_name=...)    : ap-southeast-1
優先 指定する場所 例
1 client・resource の引数 session.client("s3", region_name="ap-southeast-1")
2 Session の引数 boto3.Session(region_name="eu-central-1")
3 環境変数 AWS_DEFAULT_REGION AWS_DEFAULT_REGION=sa-east-1
4 設定ファイルの profile にある region [profile dev] の region = us-west-2

profile に region が書いてあっても、AWS_DEFAULT_REGION があるとそちらが使われます。 また、この環境の boto3 では、AWS_REGION という環境変数を設定しても session.region_name は変わりませんでした(AWS_DEFAULT_REGION だけが読まれました)。

認証情報の探索順(全段の一覧)

boto3 は、認証情報が見つかるまで決まった順に探し、最初に見つかったものを使います。公式ドキュメント(Credentials)に載っている順番は次の 12 段です。右の列は、その段で見つかったときに get_credentials().method が返した値です(「―」は今回動かしていない段)。

順 探す場所 指定のしかた method の値
1 client・resource の引数 boto3.client("s3", aws_access_key_id=..., aws_secret_access_key=..., aws_session_token=...) ―
2 Session の引数 boto3.Session(aws_access_key_id=..., aws_secret_access_key=...) explicit
3 環境変数 AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY、AWS_SESSION_TOKEN env
4 AssumeRole(profile) config の role_arn と source_profile(または credential_source) assume-role
5 Web ID トークンでの AssumeRole AWS_ROLE_ARN と AWS_WEB_IDENTITY_TOKEN_FILE、または config の web_identity_token_file assume-role-with-web-identity
6 IAM Identity Center config の sso_start_url、sso_region、sso_account_id、sso_role_name ―
7 共有 credentials ファイル ~/.aws/credentials shared-credentials-file
8 コンソールの認証情報でのログイン config の login_session ―
9 config ファイル ~/.aws/config の profile に書いたキー config-file
10 Boto2 の設定ファイル /etc/boto.cfg と ~/.boto の Credentials セクション ―
11 コンテナの認証情報 環境変数 AWS_CONTAINER_CREDENTIALS_RELATIVE_URI・AWS_CONTAINER_CREDENTIALS_FULL_URI が指す先 container-role
12 インスタンスメタデータ IAM ロールを付けた EC2 インスタンス iam-role

11 と 12 は、同じ形式の応答を返す検証用のサーバーを環境変数で指して確かめました。このほか、config の credential_process で外部コマンドから受け取る profile では、method は custom-process でした。

いまどの認証情報が使われたかを確かめる

session.get_credentials().method で出どころを見る

# which_cred.py
import sys
import boto3

profile = sys.argv[1] if len(sys.argv) > 1 else None
session = boto3.Session(profile_name=profile) if profile else boto3.Session()
cred = session.get_credentials()          # 見つからなければ None
print("profile   :", session.profile_name)
print("region    :", session.region_name)
print("method    :", cred.method if cred else None)
print("access key:", cred.access_key[:8] + "…" if cred else None)

設定を変えながら実行した結果です。

$ python3 which_cred.py
profile   : default
region    : ap-northeast-1
method    : shared-credentials-file
access key: AKIAFILE…

$ python3 which_cred.py confonly
profile   : confonly
region    : eu-west-1
method    : config-file
access key: AKIACONF…

$ AWS_ACCESS_KEY_ID=AKIAENV0000000000007 AWS_SECRET_ACCESS_KEY=x python3 which_cred.py
profile   : default
region    : ap-northeast-1
method    : env
access key: AKIAENV0…

$ python3 which_cred.py admin
profile   : admin
region    : ap-northeast-1
method    : assume-role
access key: ASIARZPU…

環境変数にキーを入れると、共有ファイルの default より先に使われました(3 が 7 より先)。admin は role_arn と source_profile を持つ profile で、boto3 が自動で AssumeRole を呼び、一時的なキー(ASIA で始まる)に変わっています。

環境変数のキーとprofileが両方あるとき

つまずきやすいのが、環境変数のキーと profile の組み合わせです。3 通りを比べます。

$ AWS_ACCESS_KEY_ID=AKIAENV0000000000007 AWS_SECRET_ACCESS_KEY=x AWS_PROFILE=dev python3 which_cred.py
profile   : dev
region    : us-west-2
method    : env
access key: AKIAENV0…

$ AWS_ACCESS_KEY_ID=AKIAENV0000000000007 AWS_SECRET_ACCESS_KEY=x python3 which_cred.py dev
profile   : dev
region    : us-west-2
method    : shared-credentials-file
access key: AKIAFILE…

$ python3 which_cred.py both
profile   : both
region    : None
method    : shared-credentials-file
access key: AKIAFILE…
状況 使われた認証情報
環境変数のキー + 環境変数 AWS_PROFILE=dev 環境変数のキー(region だけ dev のものになる)
環境変数のキー + コードで Session(profile_name="dev") dev の profile のキー
同じ profile 名が credentials と config の両方にある credentials ファイルのキー

コードで profile_name を渡した場合は、環境変数のキーよりその profile が使われます。 「profile を指定したのに別のキーで接続している」と感じたら、AWS_PROFILE ではなく Session の引数で渡しているか、環境変数にキーが残っていないかを method で確かめると早く分かります。

Sessionに直接キーを渡した場合

# explicit.py
import boto3

# 環境変数にも共有ファイルにも認証情報がある状態で実行する
session = boto3.Session(aws_access_key_id="AKIAARGS000000000010",
                        aws_secret_access_key="secret-from-args")
print("Session に引数   :", session.get_credentials().method,
      session.get_credentials().access_key[:8] + "…")

plain = boto3.Session()
print("引数なしの Session:", plain.get_credentials().method,
      plain.get_credentials().access_key[:8] + "…")
$ AWS_ACCESS_KEY_ID=AKIAENV0000000000007 AWS_SECRET_ACCESS_KEY=x python3 explicit.py
Session に引数   : explicit AKIAARGS…
引数なしの Session: env AKIAENV0…

引数で渡したキーが最優先です。キーをソースコードに直接書くと、リポジトリに残りやすいので、手元では profile、AWS 上ではロール(コンテナやインスタンスに付けるもの)に任せる形が扱いやすくなります。

sts get-caller-identity で「誰として接続しているか」を見る

method は「どこから見つけたか」、get-caller-identity は「AWS から見て誰か」を教えてくれます。

# whoami.py
import sys
import boto3

session = boto3.Session(profile_name=sys.argv[1])
identity = session.client("sts").get_caller_identity()
print("method :", session.get_credentials().method)
print("Account:", identity["Account"])
print("Arn    :", identity["Arn"])
$ python3 whoami.py admin
method : assume-role
Account: 123456789012
Arn    : arn:aws:sts::123456789012:assumed-role/demo-admin/taro-session

AWS CLI でも同じ profile で確かめられます。

aws sts get-caller-identity --profile admin
{
    "UserId": "AROARZPUZDIKAODWUFO6M:taro-session",
    "Account": "123456789012",
    "Arn": "arn:aws:sts::123456789012:assumed-role/demo-admin/taro-session"
}

Arn が assumed-role/ロール名/セッション名 の形なら、ロールに切り替わっています。

NoRegionError・NoCredentialsError・ProfileNotFoundを再現する

設定ファイルも環境変数も無い状態で、3 つのエラーを順に起こします。

# errors3.py
import boto3
from botocore.exceptions import NoCredentialsError, NoRegionError, ProfileNotFound

# 1. ProfileNotFound:Session を作る時点で起きる
try:
    boto3.Session(profile_name="nothing")
except ProfileNotFound as e:
    print("ProfileNotFound    :", e)
print("使える profile     :", boto3.Session().available_profiles)

# 2. NoRegionError:client を作る時点で起きる
try:
    boto3.Session().client("dynamodb")
except NoRegionError as e:
    print("NoRegionError      :", e)
print("region を渡せば作れる:", boto3.Session().client("dynamodb", region_name="ap-northeast-1").meta.region_name)

# 3. NoCredentialsError:client は作れて、API を呼んだ時点で起きる
s3 = boto3.Session().client("s3", region_name="ap-northeast-1")
print("client は作れる    :", type(s3).__name__)
try:
    s3.list_buckets()
except NoCredentialsError as e:
    print("NoCredentialsError :", e)
print("get_credentials()  :", boto3.Session().get_credentials())
ProfileNotFound    : The config profile (nothing) could not be found
使える profile     : []
NoRegionError      : You must specify a region.
region を渡せば作れる: ap-northeast-1
client は作れる    : S3
NoCredentialsError : Unable to locate credentials
get_credentials()  : None
エラー 起きる時点 原因 直し方
ProfileNotFound Session を作るとき profile 名が設定ファイルに無い(綴り、AWS_CONFIG_FILE の向き先、config 側の見出しに「profile 」を付け忘れ) available_profiles で一覧を確かめ、名前とファイルを合わせる
NoRegionError client・resource を作るとき リージョンがどこにも無い region_name を渡す、AWS_DEFAULT_REGION を設定する、profile に region を書く
NoCredentialsError API を呼んだとき 12 段のどこにも認証情報が無い 環境変数・profile・ロールのいずれかを用意する。get_credentials() が None かどうかで事前に分かる

3 つは起きる時点が違います。 認証情報が無くても client は作れて、最初の API 呼び出しで NoCredentialsError になります。起動時に確かめたい場合は、get_credentials() が None でないことを見るか、get_caller_identity を 1 回呼ぶと早い段階で気づけます。

AssumeRoleで別のSessionを作る定型コード

別のロールに切り替えて操作する方法は 2 つあります。

方法 書き方 向いている場面
profile に任せる config に role_arn と source_profile を書き、Session(profile_name="admin") 手元の作業、切り替え先が決まっている。期限が来ると boto3 が自動で更新する
コードで assume_role を呼ぶ 下の定型コード 切り替え先を実行時に決める、複数アカウントを順に処理する

公式ドキュメントによると、profile による AssumeRole で得た一時的な認証情報はディスクには書かれず、1 つの Session の中でメモリに保持され、その Session から作った client が共有します。

コードで切り替える定型です。

# assume_role.py
import boto3

def assumed_session(role_arn, session_name, base_session=None, duration=3600):
    """AssumeRole で得た一時的な認証情報から、別の Session を作る"""
    base = base_session or boto3.Session()
    resp = base.client("sts").assume_role(
        RoleArn=role_arn, RoleSessionName=session_name, DurationSeconds=duration)
    cred = resp["Credentials"]
    return boto3.Session(
        aws_access_key_id=cred["AccessKeyId"],
        aws_secret_access_key=cred["SecretAccessKey"],
        aws_session_token=cred["SessionToken"],
        region_name=base.region_name,
    )

base = boto3.Session(profile_name="dev")
admin = assumed_session("arn:aws:iam::123456789012:role/demo-admin", "taro-session", base)

cred = admin.get_credentials()
print("method :", cred.method)
print("token  :", "あり" if cred.token else "なし")
print("Arn    :", admin.client("sts").get_caller_identity()["Arn"])

# 切り替え先の Session から、いつもどおり client / resource を作る
s3 = admin.client("s3")
print("buckets:", [b["Name"] for b in s3.list_buckets()["Buckets"]])
method : explicit
token  : あり
Arn    : arn:aws:sts::123456789012:assumed-role/demo-admin/taro-session
buckets: ['rjc-demo-bucket']

ポイントは 3 つです。

  • AccessKeyId・SecretAccessKey・SessionToken の 3 つをそろえて渡します。 一時的な認証情報は、トークンが無いと使えません。
  • 元の Session と切り替え先の Session を別の変数で持ちます。 boto3.client() の既定の Session は元のままなので、切り替え先の操作は必ず admin.client(...) から作ります。
  • この書き方では method が explicit になり、期限が来ても自動では更新されません。 長く動く処理では、期限(ここでは 3600 秒を指定)の前に assumed_session を呼び直すか、profile に任せる方法を選びます。

複数のロールを順に処理する形にも、そのまま広げられます。

for role_arn in ["arn:aws:iam::123456789012:role/demo-admin"]:
    session = assumed_session(role_arn, "batch-session")
    print(session.client("sts").get_caller_identity()["Arn"])
arn:aws:sts::123456789012:assumed-role/demo-admin/batch-session

よくある質問

Q. boto3.client() と boto3.Session().client() は何が違いますか? A. boto3.client() は、既定の Session(boto3.DEFAULT_SESSION)を作ってそこから client を作る近道です。profile やリージョンを切り替えない小さなスクリプトなら前者、設定を明示したい・複数の設定を並べて使いたいときは後者が向いています。

Q. resource で書いたコードは書き直したほうがよいですか? A. 急ぐ必要はありません。公式の注記は「新機能を追加しない」で、既存のインターフェースは動き続けるとされています。新しく書く部分を client にし、足りない操作は resource.meta.client で補う進め方が無理なく続けられます。

Q. AWS CLI と boto3 で、設定ファイルは共通ですか? A. ~/.aws/credentials と ~/.aws/config を共通で読みます。この記事の admin profile も、boto3 と AWS CLI の両方で同じロールに切り替わりました。

Q. コンテナや EC2 インスタンスの上では、キーをどう渡せばよいですか? A. ロールを付けておけば、探索順の 11 段目(コンテナの認証情報)や 12 段目(インスタンスメタデータ)で自動的に見つかります。コードは boto3.client("s3") のままで動き、キーをファイルや環境変数に置かずに済みます。

まとめ

  • client は API を 1 対 1 で呼ぶ窓口、resource はオブジェクトとして扱う窓口、Session は設定を持って両方を作る元です。
  • resource は 9 サービスのみで、新機能は追加されません。 これから書くコードは client を基本にします。
  • リージョンは「client の引数 → Session の引数 → AWS_DEFAULT_REGION → profile の region」の順に決まります。
  • 認証情報は 12 段を上から探し、最初に見つかったものが使われます。 引数 → 環境変数 → AssumeRole 系 → 共有 credentials → config → コンテナ → インスタンスロールの順です。
  • session.get_credentials().method で出どころ、get_caller_identity で接続先から見た身元を確かめられます。
  • ProfileNotFound は Session 作成時、NoRegionError は client 作成時、NoCredentialsError は API 呼び出し時に起きます。
  • AssumeRole は、得た 3 つの値で別の Session を作るのが定型です。自動更新が必要なら profile の role_arn に任せます。

参考・出典

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「boto3 client・resource・sessionの違いと認証の優先順位」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?