初めてのAWSでNoCredentialsError: Unable to locate credentialsが出たのでDockerを設定する

# はじめに

私は2021年現在新卒入社から2年目で主に自然言語処理を扱う会社で仕事をしています。
普段はAWSとは無縁(だいたいは先輩がやってくれている)の生活を送っているため、触る機会がありませんでしたが、AWSのSageMarkerを今後使ったらどうかという話になり調査をする必要がありました。

そこで重い腰をあげ、AWSをやろうとしたところ、さっそくエラーに当たり色々調べて対処したのでまとめたいと思います。

エラー内容は基本的なことになります。
Dockerにそこまで詳しくなく、AWSを初めての方でしたら直面するかもしれない問題でした。

# 環境
- windows
- Docker
- Python3
- JupyterNotebook

# 問題
Dockerを利用して、JupyterNotebook環境を立ち上げて、AWS SDK for pythonを使おうと以下のコードを実行しました。

```
!pip install --upgrade boto3
import boto3

client = boto3.client('s3', region_name='ap-northeast-1')
client.list_buckets()
```

すると、`client.list_buckets()`でエラーになりました。

```
apiuser 
AKIA5YFBRBZR23P37GGM
S8PNL4hhV5wWqkvZgpQIrR1YW/7codZHE7oZHry7


---------------------------------------------------------------------------
NoCredentialsError                        Traceback (most recent call last)
<ipython-input-2-e72cca7ea9ce> in <module>
      2 
      3 client = boto3.client('s3', region_name='ap-northeast-1')
----> 4 client.list_buckets()

/opt/conda/lib/python3.8/site-packages/botocore/client.py in _api_call(self, *args, **kwargs)
    384                     "%s() only accepts keyword arguments." % py_operation_name)
    385             # The "self" in this scope is referring to the BaseClient.
--> 386             return self._make_api_call(operation_name, kwargs)
    387 
    388         _api_call.__name__ = str(py_operation_name)

/opt/conda/lib/python3.8/site-packages/botocore/client.py in _make_api_call(self, operation_name, api_params)
    689             http, parsed_response = event_response
    690         else:
--> 691             http, parsed_response = self._make_request(
    692                 operation_model, request_dict, request_context)
    693 

/opt/conda/lib/python3.8/site-packages/botocore/client.py in _make_request(self, operation_model, request_dict, request_context)
    709     def _make_request(self, operation_model, request_dict, request_context):
    710         try:
--> 711             return self._endpoint.make_request(operation_model, request_dict)
    712         except Exception as e:
    713             self.meta.events.emit(

/opt/conda/lib/python3.8/site-packages/botocore/endpoint.py in make_request(self, operation_model, request_dict)
    100         logger.debug("Making request for %s with params: %s",
    101                      operation_model, request_dict)
--> 102         return self._send_request(request_dict, operation_model)
    103 
    104     def create_request(self, params, operation_model=None):

/opt/conda/lib/python3.8/site-packages/botocore/endpoint.py in _send_request(self, request_dict, operation_model)
    130     def _send_request(self, request_dict, operation_model):
    131         attempts = 1
--> 132         request = self.create_request(request_dict, operation_model)
    133         context = request_dict['context']
    134         success_response, exception = self._get_response(

/opt/conda/lib/python3.8/site-packages/botocore/endpoint.py in create_request(self, params, operation_model)
    113                 service_id=service_id,
    114                 op_name=operation_model.name)
--> 115             self._event_emitter.emit(event_name, request=request,
    116                                      operation_name=operation_model.name)
    117         prepared_request = self.prepare_request(request)

/opt/conda/lib/python3.8/site-packages/botocore/hooks.py in emit(self, event_name, **kwargs)
    354     def emit(self, event_name, **kwargs):
    355         aliased_event_name = self._alias_event_name(event_name)
--> 356         return self._emitter.emit(aliased_event_name, **kwargs)
    357 
    358     def emit_until_response(self, event_name, **kwargs):

/opt/conda/lib/python3.8/site-packages/botocore/hooks.py in emit(self, event_name, **kwargs)
    226                  handlers.
    227         """
--> 228         return self._emit(event_name, kwargs)
    229 
    230     def emit_until_response(self, event_name, **kwargs):

/opt/conda/lib/python3.8/site-packages/botocore/hooks.py in _emit(self, event_name, kwargs, stop_on_response)
    209         for handler in handlers_to_call:
    210             logger.debug('Event %s: calling handler %s', event_name, handler)
--> 211             response = handler(**kwargs)
    212             responses.append((handler, response))
    213             if stop_on_response and response is not None:

/opt/conda/lib/python3.8/site-packages/botocore/signers.py in handler(self, operation_name, request, **kwargs)
     88         # this method is invoked to sign the request.
     89         # Don't call this method directly.
---> 90         return self.sign(operation_name, request)
     91 
     92     def sign(self, operation_name, request, region_name=None,

/opt/conda/lib/python3.8/site-packages/botocore/signers.py in sign(self, operation_name, request, region_name, signing_type, expires_in, signing_name)
    160                     raise e
    161 
--> 162             auth.add_auth(request)
    163 
    164     def _choose_signer(self, operation_name, signing_type, context):

/opt/conda/lib/python3.8/site-packages/botocore/auth.py in add_auth(self, request)
    371     def add_auth(self, request):
    372         if self.credentials is None:
--> 373             raise NoCredentialsError()
    374         datetime_now = datetime.datetime.utcnow()
    375         request.context['timestamp'] = datetime_now.strftime(SIGV4_TIMESTAMP)

NoCredentialsError: Unable to locate credentials
```

ユーザーへの権限がないようでした。
また、この時点でIAMユーザーを作成してあり、`AmazonS3FullAccess`の権限を追加してあり、手元には`アクセスキーID`と`シークレットアクセスキー`がある状態です。

この状態でない方は、そちらの準備を調べて行ってください。
簡単ですので、説明は省略します。

`C:\Users\<ユーザー名>/.aws/credentials`に以下を記入して認証情報の保存をします。

```
[default]
aws_access_key_id = <アクセスキーID>
aws_secret_access_key = <シークレットアクセスキー>
```

この設定をしているので、先ほどのJupyterのコードで認証されるかと思いましたがされませんでした。

# 解決方法

そもそも、Dockerとローカルの環境は別ですので、認証情報は読み取れないのが当たり前でした。

ですので、Dockerfileの最後に環境変数として記述することにしました。

`Dockerfile`

```Dockerfile
ENV AWS_ACCESS_KEY_ID <アクセスキーID>
ENV AWS_SECRET_ACCESS_KEY <シークレットアクセスキー>
```

この状態で先ほどのエラーが出たコードを実施したところ成功しました。

# 注意点

この方法で行うと、Dockerfileに直接アクセスキーを記述するためGitなどにPushした際に、他の人からキーを確認することができてしまいます。

ですので、`.env`と`docker-compose.yml`、`.gitignore`を利用して、他人にわからないように設定したほうが良いです。

[こちら](https://qiita.com/Sicut_study/items/84a079f495a3b5419758)は別記事にまとめていますので、ご確認いただければと思います。

# おわりに

以前Azureの初めてのデプロイでかなり苦戦したので、次はAWSの基本もこれを機会に抑えていきたいなと思っています。

Dockerもわかった気でいて、案外わからないことばかりですので記事にまとめていきたいです。

# 参考記事

https://circleci.com/docs/ja/2.0/custom-images/

https://qiita.com/Sicut_study/items/84a079f495a3b5419758