AWS LambdaからAWS AppConfigを呼び出してみた

## 1. 初めに 
皆さん、AWS AppConfigを利用されていますか？ AWS AppConfig は 2019年12月にリリースされた機能で、[こちらのブログ](https://aws.amazon.com/jp/blogs/news/safe-deployment-of-application-configuration-settings-with-aws-appconfig/)が出た際には私は記事を何度も読み直し、一番の売りは何だろう？他のデータストア(例えば、DynamoDBやParameter Store）に設定情報を格納するのと何が違うのだろう？と考えたものです。
ちなみに、AWS Configとは違います。AWS **App**Configについて取り扱う記事です。AWS ConfigはAWS リソースの設定状況を記録するサービスで変更を検知し記録するものです。また、AWS Config RulesはAWSサービスのリソースの状況が事前に設定されたルール通りに作られているか検証するサービスとなります。


繰り返しになりますが AWS **App**Configについて簡単にご紹介したいと思います。



## 2.　要約
- より安全なデプロイをしたい場合に活用すると効果的
- 設定はアプリ側でキャッシュすることでレイテンシーやAWS サービス利用料の削減につながる
- 設定のリリースにカナリア・リニアデプロイを実現

追記  2020/10/17
ambda Extensions の登場に伴い、当記事で記載したキャッシュ部分を自身で実装しなくても自動的にできるようになった、ぜひこちらも検討してほしい
https://aws.amazon.com/jp/blogs/mt/introducing-aws-appconfig-lambda-extension-deploying-application-configuration-serverless/


## 3. AWS AppConfigとは
冒頭にもリンクを付けましたが、AWS AppConfigは、Amazonの内部で使われていた仕組みをAWSのサービスとして提供したサービスで、AWS Systems Manager の機能の１つとして AWS AppConfig がリリースされました。AWS AppConfig を使用すると、Amazon Elastic Compute Cloud (EC2) インスタンス、コンテナ(ECS/EKS)、およびLambda等で実行されているアプリケーションが、**コードやインフラのビルドやデプロイに関係なく**、アプリケーション設定の変更を迅速に、より安全に、デプロイすることを支援する仕組みです。

AWSのマネージドなサービスとして提供されているため、利用者による運用の手間が軽減されています。

## 4. AWS AppConfigを使う最大のメリットは何か？

私は、個人的には、**「より安全に」** という部分がポイントだと思っています。

以前から私がアプリケーションを構築する際に、アプリケーションで必要な設定情報は多くのケースで以下のいずれかに配置していました。

1. 実行モジュール内に同梱する定義ファイル
2. 実行モジュール外に配置し、ランタイムから参照できるファイル
3. 環境変数
4. データベース


つまり、AWS AppConfigがなくても簡単な手法でコードに設定情報を注入し動作させていました。例えば、以下の二つの目的だけであれば、従来から行われていた方法で十分機能すると思います。
- アプリを動かす
- 設定を外部から反映する

もし、皆さんが設定情報の反映に対して以下の運用上の課題をお持ちであれば、AWS AppConfigを利用する価値がでてくると考えます。
・設定情報を直接反映しているために、入力値・フォーマットの検証が不十分でエラーになることがある。
・設定を反映した後でエラーになった場合の影響範囲が大きい
・エラー時の切り戻しが手動で手間がかかる・ミスが発生する。
  
ということで、「設定値の検証」「設定値のデプロイ」「設定値の切り戻し」についてAWS AppConfigがどのような支援をするのか書きたいと思います。

### 4.1 設定値の検証
設定値を皆さんはどのように入力しているでしょうか？例えばアプリケーションで利用する設定値の値をデータベースに保持していた場合、直接データベースにSQLやCSVのLoad等をしているでしょうか？あるいは、アプリケーションを実装して、入力値のチェックを実装しているでしょうか？
もし、何等か検証方法を実装したうえで、**必ず、その検証を実施後に設定値が格納**されているのであれば、この機能はそれほど既存の方法と大差はないかもしれません。
検証されていることが保証できない入力方法を採用している場合、
AWS AppConfigでは、設定値をAWS AppConfigにデプロイする際に、二種類のバリデーションをサポートしており、そのバリデーションでエラーが発生するとデプロイされません。
二種類とは、
1. JSONスキーマ（JSON スキーマバージョン 4.X）による検証
2. AWS Lambda関数による検証
です。詳細は[こちら](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/appconfig-creating-configuration-and-profile-validators.html)を参照してください。

複数の設定値の組み合わせによるチェック等、ロジックや他のデータソースへのアクセスをして検証するような場合には、AWS Lambdaを利用したバリデーションを実装いただくとデプロイ前に不適切な値を検知できます。


### 4.2 設定値のデプロイ
アプリケーションをデプロイする際には、B/Gデプロイメントや、Rollingによるデプロイまた、カナリアデプロイなど、デプロイ戦略を考え、
- 切り戻しの迅速さ
- 影響範囲の最小化
- コスト効率性
等を考慮して決定するかと思います。では、そのデプロイメントを実行するパイプラインについて少し考えたいと思います。パイプラインは、アプリケーションと設定は同一のパイプラインを使っているでしょうか？その場合、アプリケーションの設定のみの変更でビルドやデプロイが不要という場合はないでしょうか？もし、そういう状況があり、迅速に簡単にデプロイをしたいということであれば、まず、「パイプライン」の分割を検討してみましょう。アプリケーションモジュールのデプロイと設定値のデプロイのサイクルが同じである場合、分割する必要はないかもしれませんが、個別でデプロイがあるのであれば、分割することで、軽量な設定値のデプロイが可能になります。ただ、パイプラインを分割しただけでは、「設定を反映した後でエラーが発生した場合の影響範囲」の最小化には何も貢献していません。パイプラインを分けても分けなくても、設定値に問題があった場合のランタイム（実行環境）への影響は同じです。そことで、登場するのがAWS AppConfigです。AWS AppConfigでは、アプリケーションがAWS AppConfigに設定情報を取得するAPIを呼び出す際に”ClientId"というパラーメータを渡して呼び出します。AWS AppConfigでは、設定変更のデプロイが行われれると一定期間、ClientIdを利用して、応答する値を調整します。具体的には、変更前の既存の値として応答するケースと、デプロイ要求された新バージョンの値の応答を、ClientIdをみて指定された割合で応答するようにします。したがって、100種類のClientIdで設定情報の取得が行われ、10％のみ新バージョンを応答する設定を仮にした場合、10個のClientIdは新バージョン、90個は旧バージョンの値を応答される形で試すことができます。この割合も徐々に増やすことをAWS AppConfigはサポートしており、アプリケーションモジュールのデプロイと同様に設定値のデプロイにおいても、徐々に増やし安全なデプロイをすることが可能です。

設定方法は[こちらのドキュメント](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/appconfig-creating-deployment-strategy.html)を見ていただければと思います。


なお、設定情報を実際にデプロイする際には、設定情報をCode Commitで管理しその変更をトリガーにCodePipelineを起動して設定をAWS AppConfigに反映する方法がサポートされております。
その統合の構成手順は[こちら](https://aws.amazon.com/jp/blogs/news/safe-deployment-of-application-configuration-settings-with-aws-appconfig/)で書かれているので実際に動かす際にご覧いただければと思います。

### 4.3 設定値の切り戻し
さて、設定のデプロイをAWS AppConfigの機能と、アプリケーションのコーディングでClientIdを指定して呼び出すことにより徐々に範囲を拡大していったとしても何等かの不具合が発生し、設定を戻したいというケースがある場合、AWS AppConfigを利用すると、一定時間の間、事前に指定されたCloudWatch Alarmを監視し、Alarm状態になった場合は、次のステップには進まず、設定をもとの値に戻す動きが可能となります。

人の判断ではなく、CloudWatch Alarmを利用した判断であり、迅速に自動で元に戻すことができるため、より安全なデプロイを設定情報に対して実施することが可能になります。


## 5. AppConfigに反映された設定値の取得の注意事項
アプリケーションが設定値をAppConfigから取得するLambda関数内のコード（Pyhton）の例は以下の通りです。

``` python:app.py
client = boto3.client('appconfig')
id=str(uuid.uuid4())
envId=os.environ['EnvId']
def callAppConfig(version):
    config = client.get_configuration(
      Application="SampleApp",
      Environment=envId,
      Configuration="SampleConfigProfile01",
      ClientConfigurationVersion=version,
      ClientId=id
    )
```
ポイントは2つあります。
1. ClientConfigurationVersionパラメータを指定していること
2. ClientIdを指定していること
です。
### 5.1 AppConfigのAWSサービス利用料とget_configuration時のパラメータ
AWS AppConfigの利用料金は[こちら](https://aws.amazon.com/jp/systems-manager/pricing/#AWS_AppConfig)です。2020年8月1日時点では、以下の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535037/d6bff7bf-98a6-4e97-1214-b5a6d853fea5.png)

100万回、get_configuration　APIコール当たり、0.2USD発生します。また、「受信した構成」については、1回につき「0.0008USD」発生します。


**受信した構成**について注意が必要です。

AWS AppConfigのget_configurationを呼び出す際には、「ClientConfigurationVersion」を指定できます。ただ、これは、事実上、必須パラメータだと誤認識いただいたほうが良いと私は考えます。
なぜなら、これを指定し忘れるとget_configurationを呼び出す都度、「構成情報を受信」、つまり、「受信した構成」の料金が0.0008USDが発生するためです。

AWS AppConfigのget_configurationは、Version指定がない場合、現在の設定情報を応答します。その中には以下のような形式で現在のバージョン情報が含まれています。

`ConfigurationVersion': 'e5aa23ff-0b57-49cd-bc10-f551cf245cc8'`

このバージョン情報を指定してget_configurationを呼び出す場合、それが現在のAWS AppConfigのバージョンと一致していればNoneを応答し、「100万回、get_configuration　APIコール」の1回には該当しますが、「受信した構成」の対象にはなりません。

ということで、AWS AppConfigのAWSサービス利用料の最適化の観点から2回目以降の呼び出しについては、Version情報を設定することが重要です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535037/91925f4b-4642-277e-2232-577bc596f3da.png)
参考まで

### 5.2 受信した構成情報のキャッシュの実装
私は、2020年8月1日時点で東京リージョンでget_configurationを呼び出した結果、応答時間は平均して200ms程度でした。
つまり、毎回get_configurationを呼び出していると処理の遅延が大きくなりますし、前述した通りAWS サービス利用料にも影響がでます。そこで、重要なのがアプリケーション側でのキャッシュです。get_configurationを呼び出し、Noneではなく設定情報を受け取った場合、TTL(Time to Live)を自身で管理・設定し、一定時間経過したら（例えば10秒）AppConfigのget_configurationを呼び出しNoneまたは、新たな設定を受け取る工夫をすることが重要だと考えます。AWS Lambdaの場合ではあれば、Global 変数としてConfigとConfigの有効期限を保持し、有効期限が切れた場合は、get_configrationするような関数を実装しておくことによって（例えばLayerを利用して)効率のよい設定値の取得が可能になると考えます。

### 5.3 設定情報の取得時のClientId指定によるデプロイ戦略の恩恵
既に開設しましたが、設定情報を一括で反映する場合、影響範囲が大きくなる可能性があります。AWS AppConfigでは、一括反映もサポートしていますが、Exponetialなデプロイやリニアなデプロイが可能になっています。仕組みは応答する値をClientIdの値をみて判断するというものです。つまり、全インスタンスが同じClientIdを指定すると意味がありません。Lambdaであれば関数のインスタンスごとに異なるIdを指定することで効果を発揮します。例えば、UUIDをグローバル変数で関数初期化時に生成することで、関数インスタンスごとにユニークなIDとなり、デプロイ戦略の恩恵を受けることができるようになると思います。

---
設定値の取得の注意事項については以上です。

## 6. AppConfigの単位
当記事では解説しませんでしたが、設定情報を皆さんはバージョン管理していますか？バージョン管理をすることで、いつの時点でだれが変更したのか、過去の値は何が設定されていたのか確認することもできます。ですので、バージョン管理可能なGitHubやAWS Code Commitをご活用いただければと思います。CodeCommitからCodePipeline、そして、AppConfigの連携は[こちら](https://aws.amazon.com/jp/blogs/news/automating-feature-release-using-aws-appconfig-integration-with-aws-codepipeline/)で解説されていますのでご確認ください。
さて、AppConfigをCodeCommitでバージョン管理し、デプロイメントパイプライン（CodePipeline)を利用し、かつ、AppConfigをつかってデプロイすると気になるのが、設定情報の単位です。どの単位で設定情報を構成するとよいのか？ということを悩むことになるかもしれません。「これがベスト！」という回答を私は現時点で持っていませんが、設定情報を利用するアプリケーション側の粒度と設定情報のコンテキストを考慮して決定するのが良いかなと思います。大量の設定を1つのファイルにまとめてデプロイすることももちろん可能ですが、メンテナンス性は関係ない変更を受け取るといった影響もでますので、「設定の管理」や「アプリケーション側の共通性」等を考慮して決めるとよいかと思います。




## 7. まとめ。
AWS AppConfigについて使いどころを解説してみました。サービスの可用性を高めるためには「予防」と「検知」と「対応」が重要になると考えます。設定値を事前に検証してデプロイし予防をしておくこと、設定をリリースする範囲を限定化しておき全停止を予防しておくこと、そして、影響が出ていないかCloudWatch Alarmを利用して検知し迅速に戻す等の対応をすること、これが可用性を高めることにつながると考えます。せひ、運用性の向上の観点からAppConfigをご検討いただければと思います。