เล่นกับ AWS X-Ray เบื้องต้น
Tags: awsthai, awsxray, monitoring
Published: 2021-10-21 07:54:55

_Level: Overview - Basic_

งานสำคัญอย่างนึงในการพัฒนาระบบคือการตรวจสอบและติดตามหา Root cause ของ error หรือ bug ในโปรแกรม ซึ่งโดยปกติจะมีการนั่งดู log ของ service ต่างๆที่เกี่ยวข้อง แต่สำหรับกรณีที่เราใช้ Serverless หรือ Service ใน AWS นอกจากที่จะเข้าไปตรวจสอบ Log ใน Cloudwatch หรือจุดอื่นๆแล้ว AWS มี Service อีกตัวที่หลายๆคนอาจจะไม่เคยได้ลองเล่น คือ AWS X-Ray ที่ช่วยให้เราสามารถติดตามการทำงานของระบบได้สะดวกมาก และมีประโยชน์ในการวิเคราะห์ว่าการทำงานมีส่วนไปนที่ทำงานผิดพลาดหรือมีคอขวดตรงไหนหรือไม่

โดยในตัวอย่างที่ยกมาจะลองเล่นกับ AWS X-Ray เบื้องต้นซึ่งจะมี Service ต่างๆที่เกี่ยวข้องดังนี้
* API Gateway
* Lambda Function
* S3
* DynamoDB

## Architecture 
![Architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b4kzhunm1mlaqakwhu5g.png)

นอกจากการทดสอบระหว่าง API Gateway กับ Lambda แล้ว ในตัวอย่างจะเพิ่มการเชื่อมต่อกับ DynamoDB และ S3 รวมไปถึงการสร้าง DynamoDB Stream และ S3 Event Notification เพื่อเรียก Lambda เพื่อทดสอบดูว่า Trace ไปได้ถึงไหน ในเบื้องต้น

## Setup
* สร้าง DynamoDB Table ชื่อ __xray-demo__ โดยมี partition key ชื่อ __id__
![DynamoDB xray-demo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eas64jifgiz6pw690vsg.png)

* สร้าง S3 Bucket ชื่อ __xray-demo-20211020__ (ถ้าไปทำตามเปลี่ยนชื่อและเปลี่ยนชื่อใน code function xray-demo-s3 ด้วยนะครับ)
![S3 xray-demo-20211020](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uh3crptx21fqlyvo5rb3.png)

* สร้าง AWS Lambda Function __xray-demo-dynamo__ ทำการเขียนข้อมูลลงบน dynamoDB (อย่าลืมเพิ่ม DynamoDB Policy ให้ LambdaExecution Role)
```python
import json
import boto3
import uuid

def lambda_handler(event, context):
    client = boto3.resource('dynamodb')
    table = client.Table('xray-demo')
    table.put_item(
        Item={ 
            'id': str(uuid.uuid4()), 
            'data': event
        }
    )
    
    return {
        'statusCode': 200,
        'body': 'Wrote to DynamoDB'
    }

```

* สร้าง AWS Lambda Function __xray-demo-s3__ ทำการเขียนข้อมูลลงบน s3 (อย่าลืมเพิ่ม S3 Policy ให้ LambdaExecution Role)
```python
import json
import boto3
import uuid

def lambda_handler(event, context):
    filename = str(uuid.uuid4())
    s3 = boto3.resource('s3')
    s3.Object('xray-demo-20211020', 
        f'{filename}.json').put(
            Body=bytes(json.dumps(event).encode('UTF-8'))
            )
    return {
        'statusCode': 200,
        'body': 'Wrote to S3'
    }
```

* สร้าง AWS Lambda Function __xray-demo-logging__ ทำการเขียน Log บน CloudWatch 
```python
import json

def lambda_handler(event, context):
    print(event)
    return {
        'Status': 'Done'
    }

```

* สร้าง API Gateway 2 Methods แล้วทำการ Deploy เพื่อใช้ทดสอบ
  * __GET /dynamodb__ เพื่อเรียก Lambda Proxy Integrationไปยัง Function __xray-demo-dynamo__
  * __GET /s3__ เพื่อเรียก Lambda Proxy Integrationไปยัง Function __xray-demo-s3__

![API Gateway Setup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y0wgxazz1gutmzf7omv2.png)

* เปิดใช้งาน DynamoDB Stream แล้วให้ Trigger เป็น Lambda Function __xray-demo-loging__

![Enable DynamoDB Stream](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/61gu05fhcyekfwvvj11s.png)

* เปิดใช้งาน S3 Event Notification แล้วให้เรียก Lambda Function __xray-demo-logging__

![Enable S3 Event Notification](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mswfc4tmhlhohfesbzw2.png)

## การเปิดใช้ X-Ray สำหรับ AWS Lambda
สำหรับ AWS Lambda ทั้ง 3 Functions จะต้องทำการเปิดใช้ X-Ray บน AWS Lambda ก่อน โดยสามารถเปิดใช้งานได้ที่หน้า Configuration > Monitoring and operations tools โดยตัว X-Ray จะใช้ชื่อว่า Active Tracing ซึ่งปกติจะขึ้น Not enabled อยู่ และสามารถกด Edit เพื่อเข้าไปเปิดได้
![Lambda Config](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/chk5cfon3bgw51w5uu1v.png)

โดยในการเปิด Active tracing ตัว Lambda จะเพิ่ม Permission ให้โดยอัตโนมัติ
![Enable Active tracing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cicf5xxcg51w11kq01ti.png)


## การเปิดใช้ X-Ray สำหรับ API Gateway
สำหรับ API Gateway เมื่อทำการ Deploy API แล้วสามารถเปิดใช้งาน AWS X-Ray ได้ที่หน้า Logs/Tracing ของ Stage นั้นๆ
![Enable API Gateway X-Ray](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iizxaq0agn1xqx39lgx6.png)

สำหรับ X-Ray Sampling Rule เพื่อให้ง่ายต่อการทดสอบเฉยๆจะใช้ Default ที่มีอยู่แล้ว (จริงๆคือยังไม่ได้ลอง แหะๆ) ถ้าสนใจรายละเอียดอ่านที่เอกสารของ AWS [ที่นี่](https://docs.aws.amazon.com/xray/latest/devguide/xray-console-sampling.html?icmpid=docs_xray_console)
![X-Ray Sampling](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kl5ls59puhgj4pguqagv.png)

## ทดลองเรียกใช้งานรอบที่ 1
ลองเรียกทั้ง 2 Function ดูแล้วเข้าไปดูที่ AWS X-Ray เพื่อดูผลลัพธ์ดู โดยจะเรียกจาก Postman และดู Header ที่ตอบกลับมา
* เรียก __API GET /dynamodb__ 
![Postman GET /dynamodb](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ya1pgpxb78zekbw609gm.png)
จากรูปข้างบนจะเห็นว่าจะมี Header __X-Amzn-Trace-Id__ กลับมาด้วย ซึ่งตัวข้อมูลนี้คือค่าที่ X-Ray ใช้ Trace ข้อมูล Request/Response ต่างๆในระบบ ซึ่งเมื่อมีการส่งต่อเลขนี้ไปด้วย
เมื่อเข้าไปดูที่หน้าจอ X-Ray จะสามารถดูรายการ Trace ที่เกิดขึ้นในช่วงระยะเวลาที่เราเลือกได้ (ขวาบน) ซึ่งจะสังเกตได้ว่าเลขอ้างอิง หรือ ID ที่แสดงใน Trace List จะเป็นเลขเดียวกับค่าของ __X-Amzn-Trace-Id__ โดยสามารถเข้าไปดูรายละเอียดของ Request นั้นๆและ Service Map ที่แสดงภาพรวมระยะเวลาในการทำงานของ Node ต่างๆได้ด้วย
![Dynamo Trace](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xxdbjk1b5b6am7xryg1u.png)
![Dynamo Trace Detail](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/me35av1fbrksjx2dceil.png)
![Dynamo Service Map](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mz71s7n3ric7h9agr9v9.png)

* เรียก __API GET /s3__
เพื่อให้เห็นตัวอย่างที่ชัดเจนขึ้นจะทำการแก้ Lambda Function ให้ทำงานผิดพลาด (Lambda Error และ Return 502 จาก API Gateway) 1 รอบ และแก้กลับให้ทำงานปกติ ซึ่งเมื่อดู Trace และ Service Map บน X-Ray จะขึ้นสี เหลือง - Errors (4XX) และ สีแดง - Faults (5XX) ตามลำดับ สำหรับรายการที่ไม่สำเร็จ
![Trace S3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y18rp0xtizxcnh6rt9mi.png)
![Service Map S3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0i990jmdca261gjzaog9.png)

## การเพิ่มรายละเอียดการ Trace ด้วย Code
จากตัวอย่างการเรียกทั้ง 2 แบบด้านบน จะเห็นว่าข้อมูลการ Trace จบที่ AWS Lambda ไม่แสดงรายละเอียดการเรียกไปยัง DynamoDB และ S3 รวมไปถึงไม่สามารถแสดงกรณีที่ S3 Event หรือ DynamoDB Stream เป็นผู้ Trigger Lambda __xray-demo-logging__ ได้ ซึ่งการจะแสดงผลให้ละเอียดขึ้นจะต้องทำการเรียกใช้ __aws-xray-sdk__ เพื่อให้ทำการเพิ่ม Trace ID ลงบน Header ของการเรียกใช้งาน Service อื่นๆด้วย
ในตัวอย่างจะแสดงการเรียกใช้แบบง่าย ซึ่งถ้าต้องการรายละเอียดเชิงลึกเพิ่มเติมสามารถดูได้ที่เอกสารของ AWS [ที่นี่](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python.html) ส่วนถ้าใช้ภาษาอื่นๆนอกเหนือจาก Python สามารถหาข้อมูลต่อได้จาก Link ข้างต้นเหมือนกัน

ในการเรียก __aws-xray-sdk__ บน Lambda เนื่องจาก AWS ไม่ได้ include library ตัวนี้ไว้ใน default runtime ดังนั้นในเบื้องต้นจะมี 2 ทางเลือกคือ
1. เขียน Code บนเครื่องพร้อม install package แล้ว upload .zip ขึ้นมายัง Lambda
2. เพิ่ม Lambda Layer ที่ติดตั้ง __aws-xray-sdk__ ไว้
ซึ่งในตัวอย่างจะใช้การทำงานแบบที่ 2 ซึ่งวิธีการสร้าง Lambda Layer ง่ายๆอ่านได้ [ที่นี่](https://towardsdatascience.com/python-packages-in-aws-lambda-made-easy-8fbc78520e30)

เมื่อทำการสร้าง Lambda Layer แล้ว ให้ทำการ Attach Layer และแก้ Lambda function สำหรับ dynamodb และ s3 โดยเพิ่ม 3 บรรทัดด้านบนดังนี้
```python
from aws_xray_sdk.core import xray_recorder
from aws_xray_sdk.core import patch_all
patch_all()
```
ซึ่งเมื่อแก้ไขแล้วจะได้ Code ของ. Function ที่แก้ไขแล้ว
* แก้ไข AWS Lambda Function __xray-demo-dynamo__ 
```python
import json
import boto3
import uuid

from aws_xray_sdk.core import xray_recorder
from aws_xray_sdk.core import patch_all
patch_all()

def lambda_handler(event, context):
    client = boto3.resource('dynamodb')
    table = client.Table('xray-demo')
    table.put_item(
        Item={ 
            'id': str(uuid.uuid4()), 
            'data': event
        }
    )
    
    return {
        'statusCode': 200,
        'body': 'Wrote to DynamoDB'
    }

```
* แก้ไข Function __xray-demo-s3__ 
```python
import json
import boto3
import uuid

from aws_xray_sdk.core import xray_recorder
from aws_xray_sdk.core import patch_all
patch_all()

def lambda_handler(event, context):
    filename = str(uuid.uuid4())
    s3 = boto3.resource('s3')
    s3.Object('xray-demo-20211020', 
        f'{filename}.json').put(
            Body=bytes(json.dumps(event).encode('UTF-8'))
            )
    return {
        'statusCode': 200,
        'body': 'Wrote to S3'
    }
```
ซึ่งการทำงานของคำสั่ง `patch_all()` จะไปทำการเพิ่ม Trace Id ใน Header ของทุกคำสั่งที่ X-Ray รองรับ เพื่อให้สามารถ Trace การทำงานได้

## ทดลองเรียกใช้งานรอบที่ 2
* เรียก __API GET /dynamodb__
เมื่อเรียก API และเปิดดู​ Service Map จะเห็นว่ามี Node การทำงานของ DynamoDB Table เพิ่มขึ้นมา ซึ่งถ้าดูใน Trace จะแสดง API ที่ถูกเรียกด้วย คือ คำสั่ง PutItem แต่การแสดงข้อมูลการเรียก __xray-demo-logging__ จะถูกแยกออกจาก Trace ของ API ที่เรียก
![X-Ray with Dynamo Table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ya56d2m945px0a1z6ogc.png)
![X-Ray Dynamo PutItem](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vvx86lhhxa5vmc0irnlk.png)

* เรียก __API GET /s3__
เมื่อเรียก API และเปิดดู Service Map จะเห็นว่ามี Node การทำงานของ S3 Bucket เพิ่มขึ้นมา ซึ่งถ้าดูใน Trace จะแสดง API ที่ถูกเรียกด้วย คือ คำสั่ง PutObject และการเรียก __xray-demo-logging__ จะแสดงเป็นการถูกเรียกโดย Lambda function __xray-demo-s3__ แทน โดยไม่แยกออกจากกันเหมือนกรณี DynamoDB
![X-Ray With S3 Bucket](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/afnqhxljdn3am7lx9jr8.png)
![X-Ray S3 PutObject](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/790qyy7cdy1vll67s1u4.png)

## Limitation
อย่างไรก็ตาม จากตัวอย่างข้างบน 2 รูปแบบ จะเห็นว่าการเรียก xray-demo-logging ซึ่งถูกเรียกโดย DynamoDB Stream จะขึ้น Trace Id แยกออกจากการคำสั่งชุดแรกที่ผ่าน API แต่การเรียกผ่าน S3 Event Notification จะมีการส่งต่อ TraceID เพื่อให้เห็นว่าที่มาของการเกิด Trigger เกิดจากที่ใด 
ซึ่งรูปแบบที่แตกต่างกันนี้เกิดขึ้นเนื่องจาก X-Ray ยังไม่รองรับการส่ง Trace Id ผ่าน Dynamo DB แต่สำหรับ S3 มีการรองรับการส่งต่อ Trace ID ไปยังคำสั่งต่อไปแล้ว เพียงแต่จะไม่เห็นว่าต้นทางที่เรียกมาจาก S3 Event Notification โดยรายละเอียดการรองรับการทำงานร่วมกับ X-Ray สามารถอ่านเพิ่มเติมได้ที่เอกสารของ AWS [ที่นี่](https://docs.aws.amazon.com/xray/latest/devguide/xray-services.html)

## Cost
AWS X-Ray เปิดให้ใช้ฟรีสำหรับการเก็บข้อมูลเดือนละ 100,000 trace และฟรีสำหรับการเรียกข้อมูลหรือสแกนข้อมูลที่บันทึกไว้ 1,000,000 trace หลังจากนั้นจึงคิดเงินเพิ่มตามรูปด้านล่าง
![X-Ray Pricing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/exbktf39qsrj2ks123uh.png)


## Conclusion
> Software developers spend 35-50 percent of their time validating and debugging software. The cost of debugging, testing, and verification is estimated to account for 50-75 percent of the total budget of software development projects
_Devon H. O'Dell_ [ACMQueue](https://queue.acm.org/detail.cfm?id=3068754)

งาน Monitor & Debugging เป็นงานที่สำคัญและกินเวลาของ Developer ค่อนข้างมาก ซึ่งการเลือกเครื่องมือต่างๆมาช่วยให้สามารถตรวจสอบหรือติดตามผลการทำงานของ Software ที่พัฒนาขึ้นมาได้อย่างเหมาะสมจะช่วยลดเวลาและค่าใช้จ่ายในการพัฒนาระบบได้ด้วย ดังนั้น AWS X-Ray จึงเป็นหนึ่งในตัวเลือกที่นักพัฒนาบน AWS ควรจะศึกษาไว้เพื่อให้การทำงานง่ายขึ้น