CLI và SDK là thứ bạn sẽ dùng trong mọi production debug session, nhưng ít ai hiểu hết credential chain hoặc JMESPath query phức tạp. Trước khi đụng đến S3, Lambda, DynamoDB, có một skill nền tảng cần làm chủ: AWS CLI và TypeScript SDK v3.
Bài này không phải “CLI tutorial” — mà là những pattern thực chiến: JMESPath query phức tạp, debug credential chain, hiểu SDK v3 middleware stack, và tối ưu performance.
flowchart TB
subgraph "CLI Data Flow"
CMD["aws s3 ls"] --> JSON["JSON Response"]
JSON --> JMESPath["--query JMESPath<br/>filter · transform · sort"]
end
subgraph "SDK v3 Middleware Stack"
App["App Code"] --> Cmd["Command Object"]
Cmd --> Ser["1. Serialize<br/>build HTTP request"]
Ser --> Sig["2. Sign (SigV4)"]
Sig --> Retry["3. Retry<br/>exponential backoff"]
Retry --> Send["4. Send HTTP"]
Send --> Deser["5. Deserialize<br/>parse response"]
end
subgraph "Credential Chain (Priority Order)"
Env["1. Env Variables"]
SSO["2. SSO (AWS SSO)"]
Cfg["3. ~/.aws/config"]
WebId["4. Web Identity (OIDC)"]
ECS["5. ECS Task Role"]
EC2["6. EC2 Instance Profile"]
end
```text
---
## JMESPath: query JSON như SQL
### Filter + transform + sort (pattern hàng ngày)
```bash
# EC2 instances đang chạy: ID, type, launch time, private IP — sort theo launch time
aws ec2 describe-instances \
--query 'sort_by(Reservations[*].Instances[?State.Name==`running`], &LaunchTime)[].[InstanceId,InstanceType,LaunchTime,PrivateIpAddress]' \
--output table
# Lambda functions > 512MB memory — sort từ cao xuống thấp
aws lambda list-functions \
--query 'reverse(sort_by(Functions[?MemorySize > `512`], &MemorySize))[].[FunctionName,MemorySize,Runtime]' \
--output table
# IAM users chưa từng login (CreateDate > PasswordLastUsed)
aws iam list-users \
--query 'Users[?PasswordLastUsed == ``].[UserName,CreateDate]' \
--output table
```text
### Multi-level filtering
```bash
# Security groups có rule open 0.0.0.0/0
aws ec2 describe-security-groups \
--query 'SecurityGroups[?IpPermissions[?IpRanges[?CidrIp==`0.0.0.0/0`]]].[GroupName,GroupId]' \
--output table
# DynamoDB tables có GSIs, lấy tên table + tên GSI + throughput
aws dynamodb list-tables --query 'TableNames' --output text | while read table; do
aws dynamodb describe-table --table-name $table \
--query '{Table:Table.TableName,GSIs:Table.GlobalSecondaryIndexes[*].IndexName}' \
--output json
done
```text
### JMESPath vs jq
| | JMESPath (`--query`) | jq (pipe sau CLI) |
|---|---------------------|-------------------|
| **Xử lý** | Server-side một phần | Client-side 100% |
| **Filter** | Rất mạnh | Rất mạnh |
| **Transform** | Mạnh | Mạnh hơn (math, string ops) |
| **Group/Aggregate** | **Không hỗ trợ** | Hỗ trợ (`group_by`, `reduce`) |
| **Bandwidth** | Giảm (server filter) | Không giảm |
| **Best for** | Filter + transform đơn giản, giảm output size | Complex aggregation, grouping |
Pattern: --query để filter + jq để aggregate. --query giảm 90% output size trước khi pipe qua jq → nhanh hơn nhiều so với pipe toàn bộ JSON vào jq.
---
## SDK TypeScript v3: middleware stack
SDK v3 kiến trúc **middleware pipeline** — mỗi API call đi qua chuỗi middleware có thứ tự cố định:
```text
client.send(command)
→ Serialize Middleware (build HTTP request từ command object)
→ Signer Middleware (SigV4 signature với credential)
→ Retry Middleware (exponential backoff cho transient errors)
→ Deserialize Middleware (parse HTTP response → typed output)
→ Return Promise<OutputType>
```text
```typescript
import { S3Client, ListBucketsCommand, paginateListObjectsV2 } from "@aws-sdk/client-s3";
import { NodeHttpHandler } from "@smithy/node-http-handler";
import { Agent } from "https";
const s3 = new S3Client({
region: "ap-southeast-1",
maxAttempts: 5, // Retry tối đa 5 lần (default: 3)
retryMode: "adaptive", // standard | adaptive
requestHandler: new NodeHttpHandler({
requestTimeout: 10_000, // 10s timeout
httpsAgent: new Agent({ keepAlive: true, maxSockets: 50 }), // Connection pooling
}),
});
// Paginator: tự động paginate qua tất cả pages
const paginator = paginateListObjectsV2(
{ client: s3, pageSize: 100 },
{ Bucket: "my-bucket", Prefix: "logs/2026/" }
);
let totalSize = 0;
for await (const page of paginator) {
for (const obj of page.Contents ?? []) {
totalSize += obj.Size ?? 0;
}
}
console.log(`Total: ${(totalSize / 1e9).toFixed(2)} GB`);
```text
### Retry modes: standard vs adaptive
| Mode | Strategy | Khi nào dùng |
|------|----------|-------------|
| **standard** | Exponential backoff cố định (base delay = 100ms, max = 20s) | Hầu hết use case |
| **adaptive** | Tự động điều chỉnh rate dựa trên throttling response từ server | High-throughput, multi-tenant app |
Retry tự động cho: 429 (throttling), 5xx (server errors), network errors (timeout, reset). `maxAttempts` = 3 mặc định, nên tăng lên 5 cho production.
---
## Credential provider chain: SDK tìm credential ở đâu?
Mỗi lần gọi AWS API, SDK duyệt danh sách credential source theo thứ tự. **Dừng ở source đầu tiên có credential hợp lệ:**
| # | Source | Dùng khi |
|---|--------|----------|
| 1 | **Environment variables** `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` | CI/CD, Docker container |
| 2 | **SSO** `aws sso login` token cache | Local dev với SSO |
| 3 | **Shared config file** `~/.aws/credentials` + `~/.aws/config` | Local dev |
| 4 | **Web Identity Token** `AWS_WEB_IDENTITY_TOKEN_FILE` + `AWS_ROLE_ARN` | EKS Pod Identity, GitHub Actions OIDC |
| 5 | **ECS Container Credentials** `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` | ECS task role |
| 6 | **EC2 Instance Profile** IMDSv2 | EC2 instance |
### Debug credential
```bash
# Lệnh đầu tiên khi debug: "Tôi là ai?"
aws sts get-caller-identity
# Output: Account, UserId, Arn, Region
# Kiểm tra credential source
aws configure list
# Output dạng bảng: Key, Value, Type, Location
# Test credential với API đơn giản nhất (global service)
aws s3 ls 2>&1 | head -5
```text
```typescript
async function debugCredential() {
const sts = new STSClient({ region: "us-east-1" });
try {
const { Account, Arn, UserId } = await sts.send(new GetCallerIdentityCommand({}));
console.log("✓ Authenticated:", { Account, Arn, UserId });
} catch (err: any) {
const messages: Record<string, string> = {
ExpiredToken: "✗ Token hết hạn — refresh SSO hoặc lấy temp credential mới",
InvalidClientTokenId: "✗ Access key không tồn tại hoặc đã bị xóa",
AccessDenied: "✗ Credential OK nhưng không có quyền sts:GetCallerIdentity",
CredentialsProviderError: "✗ Không tìm thấy credential source nào — kiểm tra env vars, config file",
};
console.error(messages[err.name] || `✗ Unknown: ${err.message}`);
}
}
```text
---
## SSO credential management
```bash
aws sso login --profile myorg
# Mở browser → login Google Workspace/Okta → auto cache token
# ~/.aws/config:
[profile myorg-admin]
sso_start_url = https://myorg.awsapps.com/start
sso_region = ap-southeast-1
sso_account_id = 111111111111
sso_role_name = AdministratorAccess
region = ap-southeast-1
```text
Token cache ở `~/.aws/sso/cache/`, tự động expire sau 8-24h. `aws sso login` refresh khi hết hạn.
---
JMESPath `--query` filter + sort + transform, pipe qua `jq` khi cần group/aggregate. SDK v3 middleware: serialize → sign → retry → deserialize, `maxAttempts=5` + `retryMode=adaptive` cho production. Credential chain 6 nguồn: env vars → SSO → config file → web identity → ECS task → EC2 instance profile. `aws sts get-caller-identity` là lệnh đầu tiên khi debug credential. SSO: `aws sso login` một lần, token cache, không access key.
Bài sau: [Phần 5: CloudTrail & auditing fundamentals](/posts/aws/05-cloudtrail-auditing-fundamentals/)