AppDynamics Lambda Extension による Python 関数性能監視

この記事はシスコシステムズ有志による Cisco Systems Japan Advent Calendar 2020 (2枚目) の 3 日目として投稿しています。

2017年版: https://qiita.com/advent-calendar/2017/cisco
2018年版: https://qiita.com/advent-calendar/2018/cisco
2019年版: https://qiita.com/advent-calendar/2019/cisco
2020年版: https://qiita.com/advent-calendar/2020/cisco
2020年版(2枚目): https://qiita.com/advent-calendar/2020/cisco2

# はじめに

2020年10月にプレビューとなった AWS Lambda Extensions により，コード変更なしに Lambda 関数の AppDynamics での監視が可能となりました。

[AWS Lambda Extensions(プレビュー)を構築する](https://aws.amazon.com/jp/blogs/news/building-extensions-for-aws-lambda-in-preview/)
[Step-by-Step Guide: Enhancing Lambda Performance Monitoring with AppDynamics]
(https://www.appdynamics.com/blog/product/enhancing-lambda-performance-monitoring/)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/901251/8e3dc886-8b87-6239-b3cb-416c1d1c13cf.png)


これまでは，[AppDynamics Tracer SDK](https://docs.appdynamics.com/display/PRO45/Serverless+APM+for+AWS+Lambda) を用いるため Lambda 関数のコード変更が必要でしたが， AppDynamics Extension Layer を追加することによりコード変更なしでモニタリング可能となりました。

本記事では，[AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html#serverless-sam-cli) を用いて Lambda 関数のテスト/デプロイ， AppDynamics による性能監視を試してみます。


# AppDynamics Lambda Extension 利用前提条件
- Node.js 10+, Python 3.6+ で書かれた Lambda 関数がインストゥルメント可能 
- [Serverless APM for AWS Lambda subscription](https://aws.amazon.com/marketplace/saas/fulfillment?productId=ae903929-4d20-4de6-b3f1-533ffb29f3e7&ref_=saas_pdp_header_continue) へサブスクライブ済み
- AppDynamics SaaS Controller : バージョン 4.5.11 以降が利用可能

# 利用可能な AWS リージョン
- ap-northeast-1 (東京)
- ap-northeast-2 (ソウル)
- ap-south-1
- ap-southeast-1
- ap-southeast-2
- eu-central-1
- eu-north-1
- eu-west-1
- eu-west-2
- eu-west-3
- ca-central-1
- sa-east-1
- us-east-1
- us-east-2
- us-west-1


# インストゥルメント手順公式ドキュメント

2020年12月時点で英語版のみとなりますが，Extension を用いた Lambda 関数のインストゥルメント手順の公式ドキュメントはこちらになります。

[Use the AppDynamics AWS Lambda Extension to Instrument Serverless APM at Runtime]
(https://docs.appdynamics.com/display/PRO45/Use+the+AppDynamics+AWS+Lambda+Extension+to+Instrument+Serverless+APM+at+Runtime)

## Serverless APM for AWS Lambda subscription へサブスクライブ

[Serverless APM for AWS Lambda subscription](https://aws.amazon.com/marketplace/saas/fulfillment?productId=ae903929-4d20-4de6-b3f1-533ffb29f3e7&ref_=saas_pdp_header_continue) から Continue to Subscribe をクリックし，

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/901251/8d22d8d4-a37e-b600-e95d-514ac783b632.png)

次の画面で Subscribe をクリック，

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/901251/a805eb3b-1a35-51bf-876f-a262f16793e0.png)

次に Set Up Your Account をクリックします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/901251/da651512-38fc-4cba-9af4-b534b3b3d5a7.png" width=60%>

最後に AppDynamics SaaS コントローラURL, コントローラアクセスキーを入力します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/901251/9c1e993d-de65-cc1e-67a7-809f0f901541.png" width=40%>

サブスクリプションの確認は [AWS Marketplace > Manage subscriptions](https://console.aws.amazon.com/marketplace/home?region=us-east-1#/subscriptions?Feedback=true) から行うことができます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/901251/707a68f7-beb5-b648-3c35-de02929a34e4.png" width=40%>


# AWS SAM CLI による Lambda 関数のデプロイと AppDynamics Extension インストゥルメント

今回は AWS マネジメントコンソールを極力用いず，[AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) を用いて監視対象の Lambda 関数のデプロイを行ってみます。AWS SAM CLI のインストールをされていない方は以下のリンクからご自分の利用環境に合わせてインストールを事前に行ってください。

https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html

## template.yaml の編集

SAM のテンプレートを編集し，Layers に 以下のように ARN, ```arn:aws:lambda:ap-northeast-1:716333212585:layer:appdynamics-lambda-extension:9``` を設定します(東京リージョンの場合)。利用リージョンが異なる場合は __ap-northeast-1__ を適宜変更してください。

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: AppDynamics Lambda extension
Resources:
  Function:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: lambda_function.lambda_handler
      Runtime: python3.8
      CodeUri: code
      Handler: app.lambda_handler
      MemorySize: 128
      Timeout: 3
      Environment:
        Variables:
          APPDYNAMICS_CONTROLLER_HOST: your-account.saas.appdynamics.com
          APPDYNAMICS_SERVERLESS_API_ENDPOINT: 'https://syd-sls-agent-api.saas.appdynamics.com'
          APPDYNAMICS_ACCOUNT_NAME: your-account
          APPDYNAMICS_AGENT_ACCOUNT_ACCESS_KEY: your-access-key
          APPDYNAMICS_APPLICATION_NAME: your-application
          APPDYNAMICS_TIER_NAME: your-tier
          AWS_LAMBDA_EXEC_WRAPPER: /opt/appdynamics-extension-script
      Layers:
        - >-
          arn:aws:lambda:ap-northeast-1:716333212585:layer:appdynamics-lambda-extension:9
```

各環境変数はご利用の AppDynamics コントローラへのアクセス情報，アプリケーション名，Tier名に適宜置き換えてください。なお，アクセスキーは SaaS コントローラ の右上の歯車アイコン - License - Account で確認することができます。



## サンプル Python コード

今回は以下の非常にシンプルなサンプルコードを用います。ifconfig.io に GET リクエストを送り，グローバル IP アドレスを取得します。

```python

import urllib.request

def lambda_handler(event, context):
    url = 'https://ifconfig.io/'
    req = urllib.request.Request(url)
    req.add_header("User-Agent", 'curl')
    with urllib.request.urlopen(req) as res:
        body = res.read()
        return {
            'statusCode': 200,
            'body': body.decode().strip()
        }
```

このスクリプトは ```code/app.py``` として保存しておきます。

## ビルド

今回は Python 3.8 Runtime を用いますが，ローカル環境で 正常に SAM から Python 3.8 を実行できない場合は，```sam build -u``` でDocker コンテナを利用することにより抽象化/仮想化することができます。

```bash
$ sam build -u
Starting Build inside a container
Building codeuri: code runtime: python3.8 metadata: {} functions: ['SampleFunction']

Fetching amazon/aws-sam-cli-build-image-python3.8 Docker container image......
Mounting /Users/foo/sample_app/code as /tmp/samcli/source:ro,delegated inside runtime container

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided

Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource
```

## ローカルテスト実行

```sam local invoke``` でローカルテストを実行することができます。

```bash
$ sam local invoke
Invoking app.lambda_handler (python3.8)
arn:aws:lambda:ap-northeast-1:716333212585:layer:appdynamics-lambda-extension:9 is already cached. Skipping download
Skip pulling image and use local one: samcli/lambda:python3.8-f00964ae55accef74c51e9ff8.

Mounting /Users/foo/sample_app/.aws-sam/build/SampleFunction as /var/task:ro,delegated inside runtime container
[Appdynamics wrapper script] AWS_Execution_Env is
[Appdynamics wrapper script]  not supported, running lambda with default arguments
START RequestId: db5cd0c4-3cf3-10a1-8fde-bf6711cc3df0 Version: $LATEST
END RequestId: db5cd0c4-3cf3-10a1-8fde-bf6711cc3df0
REPORT RequestId: db5cd0c4-3cf3-10a1-8fde-bf6711cc3df0	Init Duration: 241.18 ms	Duration: 371.69 ms	Billed Duration: 400 ms	Memory Size: 128 MB	Max Memory Used: 27 MB

{"statusCode":200,"body":"xx.xxx.xxx.xxx"}
```



## Lambda 関数，AppDynamics Extension Layer のデプロイ

正常にグローバル IP アドレスを取得可能なことが確認できたら，```sam deploy --guided``` で自動デプロイを行うことができます。


```bash
$ sam deploy --guided

Configuring SAM deploy
======================

	Looking for config file [samconfig.toml] :  Found
	Reading default arguments  :  Success

	Setting default arguments for 'sam deploy'
	=========================================
	Stack Name [Python]:
	AWS Region [ap-northeast-1]:
	#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
	Confirm changes before deploy [Y/n]: y
	#SAM needs permission to be able to create roles to connect to the resources in your template
	Allow SAM CLI IAM role creation [Y/n]: y
	Save arguments to configuration file [Y/n]: y
	SAM configuration file [samconfig.toml]:
	SAM configuration environment [default]:

	Looking for resources needed for deployment: Found!

		Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-xxxxxxxxx
		A different default S3 bucket can be set in samconfig.toml

	Saved arguments to config file
	Running 'sam deploy' for future deployments will use the parameters saved above.
	The above parameters can be changed by modifying samconfig.toml
	Learn more about samconfig.toml syntax at
	https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html

	Deploying with following values
	===============================
	Stack name                 : Python
	Region                     : ap-northeast-1
	Confirm changeset          : True
	Deployment s3 bucket       : aws-sam-cli-managed-default-samclisourcebucket-xxxxxxxxx
	Capabilities               : ["CAPABILITY_IAM"]
	Parameter overrides        : {}
	Signing Profiles           : {}

Initiating deployment
=====================

Waiting for changeset to be created..

CloudFormation stack changeset
-------------------------------------------------------------------------------------------------------------------------------------
Operation                         LogicalResourceId                 ResourceType                      Replacement
-------------------------------------------------------------------------------------------------------------------------------------
+ Add                             SampleFunctionRole                AWS::IAM::Role                    N/A
+ Add                             SampleFunction                    AWS::Lambda::Function             N/A
-------------------------------------------------------------------------------------------------------------------------------------

Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:679382613873:changeSet/samcli-deploy1606793756/cda913e6-946d-4713-b0b2-71c816716825


Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y

2020-12-01 12:36:12 - Waiting for stack create/update to complete

CloudFormation events from changeset
-------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus                    ResourceType                      LogicalResourceId                 ResourceStatusReason
-------------------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS                AWS::IAM::Role                    SampleFunctionRole                -
CREATE_IN_PROGRESS                AWS::IAM::Role                    SampleFunctionRole                Resource creation Initiated
CREATE_COMPLETE                   AWS::IAM::Role                    SampleFunctionRole                -
CREATE_IN_PROGRESS                AWS::Lambda::Function             SampleFunction                    -
CREATE_IN_PROGRESS                AWS::Lambda::Function             SampleFunction                    Resource creation Initiated
CREATE_COMPLETE                   AWS::Lambda::Function             SampleFunction                    -
CREATE_COMPLETE                   AWS::CloudFormation::Stack        Python                               -
-------------------------------------------------------------------------------------------------------------------------------------

Successfully created/updated stack - Python in ap-northeast-1
```

# AWS CLI によるLambda 関数の実行

```aws lambda list-functions --query Functions[].FunctionName``` を実行することにより，デプロイされている Lambda 関数のリストを取得することができます。

```
$ aws lambda list-functions --query Functions[].FunctionName
[
    "Python-Function-123456ABCDEF"                
]
```

次に，以下のように ```aws lambda invoke``` により，コマンドラインから Lambda 関数を実行することが可能です。

```bash
$ aws lambda invoke --function-name Python-Function-123456ABCDEF /dev/stdout
{"statusCode": 200, "body": "xx.xxx.xxx.xxx"}
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}
```



# ログの確認

```sam logs``` コマンドで Lambda 関数のログを確認します。

```
sam logs -n Python-Function-123456ABCDEF
...
2020/12/01/[$LATEST]6e06e51410a146fab5b645091d7ce510 2020-12-01T04:28:41.708000 START RequestId: e0db5107-9048-45b6-a2d6-c9e586417891 Version: $LATEST
2020/12/01/[$LATEST]6e06e51410a146fab5b645091d7ce510 2020-12-01T04:28:41.715000 2020-12-01 04:28:41,715 AppDynamics - transaction_service.py [INFO]: Starting the business transaction.
2020/12/01/[$LATEST]6e06e51410a146fab5b645091d7ce510 2020-12-01T04:28:42.095000 2020-12-01 04:28:42,094 AppDynamics - transaction_service.py [INFO]: Stopping the business transaction.
2020/12/01/[$LATEST]6e06e51410a146fab5b645091d7ce510 2020-12-01T04:28:42.095000 2020-12-01 04:28:42,095 AppDynamics - event_service.py [INFO]: Total number of items in the events queue are 4
2020/12/01/[$LATEST]6e06e51410a146fab5b645091d7ce510 2020-12-01T04:28:42.095000 2020-12-01 04:28:42,095 AppDynamics - event_service.py [INFO]: Successfully scheduled the sending of events to downstream
2020/12/01/[$LATEST]6e06e51410a146fab5b645091d7ce510 2020-12-01T04:28:42.754000 2020-12-01 04:28:42,754 AppDynamics - http_service.py [INFO]: HTTP post request was successful for url https://syd-sls-agent-api.saas.appdynamics.com/v1/events
2020/12/01/[$LATEST]6e06e51410a146fab5b645091d7ce510 2020-12-01T04:28:42.756000 END RequestId: e0db5107-9048-45b6-a2d6-c9e586417891
2020/12/01/[$LATEST]6e06e51410a146fab5b645091d7ce510 2020-12-01T04:28:42.756000 REPORT RequestId: e0db5107-9048-45b6-a2d6-c9e586417891   	Duration: 1041.72 ms	Billed Duration: 1042 ms	Memory Size: 128 MB	Max Memory Used: 81 MB
```

```AppDynamics - http_service.py [INFO]: HTTP post request was successful for url https://syd-sls-agent-api.saas.appdynamics.com/v1/events``` を含む行が確認でき，正常にメトリックが AppDynamics SaaS プラットフォームに送信されていることが確認できます。

# Application Flow Map の確認

AppDynamics SaaS コントローラで template.yaml の APPDYNAMICS_APPLICATION_NAME で指定したアプリケーションの Application Flow Map を確認すると，以下のように Lambda 関数(Python) Tier からリモートサービスとして，ifconfig.io がコールされていることが確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/901251/991f01af-e56b-028a-2e56-04a7afe5bbb3.png)

# Lambda 関数，S3 バケット, CloudFormation スタックの削除

以下のコマンドで SAM により作成した Lambda 関数，S3 バケット, CloudFormation スタックを削除することができます(Python: CloudFormation スタック名)。

```bash
aws cloudformation delete-stack --stack-name Python
```



# まとめ

今回は [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) を用い，2020年10月に導入された [AppDynamics Lambda Extension](https://docs.appdynamics.com/display/PRO45/Use+the+AppDynamics+AWS+Lambda+Extension+to+Instrument+Serverless+APM+at+Runtime) で性能監視を行う Python で書かれた Lambda 関数のデプロイ，テストを実施しました。
Extension Layer により，従来必要であった Lambda 関数のコード変更が不要となり，より利便性が高くなったと考えます。