265 lines
14 KiB
Markdown
265 lines
14 KiB
Markdown
## Task 47
|
|
|
|
The Nautilus DevOps team needs to implement priority queuing using Amazon SQS and SNS. The goal is to create a system where messages with different priorities are handled accordingly. You are required to use AWS CloudFormation to deploy the necessary resources in your AWS account. The CloudFormation template should be created on the AWS client host at /root/datacenter-priority-stack.yml, the stack name must be datacenter-priority-stack and it should create the following resources:
|
|
|
|
Two SQS queues named datacenter-High-Priority-Queue and datacenter-Low-Priority-Queue.
|
|
An SNS topic named datacenter-Priority-Queues-Topic.
|
|
A Lambda function named datacenter-priorities-queue-function that will consume messages from the SQS queues. The Lambda function code is provided in /root/index.py on the AWS client host.
|
|
An IAM role named lambda_execution_role that provides the necessary permissions for the Lambda function to interact with SQS and SNS.
|
|
Once the stack is deployed, to test the same you can publish messages to the SNS topic, invoke the Lambda function and observe the order in which they are processed by the Lambda function. The high-priority message must be processed first.
|
|
|
|
topicarn=$(aws sns list-topics --query "Topics[?contains(TopicArn, 'datacenter-Priority-Queues-Topic')].TopicArn" --output text)
|
|
|
|
aws sns publish --topic-arn $topicarn --message 'High Priority message 1' --message-attributes '{"priority" : { "DataType":"String", "StringValue":"high"}}'
|
|
|
|
aws sns publish --topic-arn $topicarn --message 'High Priority message 2' --message-attributes '{"priority" : { "DataType":"String", "StringValue":"high"}}'
|
|
|
|
aws sns publish --topic-arn $topicarn --message 'Low Priority message 1' --message-attributes '{"priority" : { "DataType":"String", "StringValue":"low"}}'
|
|
|
|
aws sns publish --topic-arn $topicarn --message 'Low Priority message 2' --message-attributes '{"priority" : { "DataType":"String", "StringValue":"low"}}'
|
|
|
|
### Solution
|
|
|
|
# SQS/SNS Priority Queue via CloudFormation Task (datacenter) — KodeKloud-Constrained
|
|
|
|
A CloudFormation task — deploy SNS → (filtered fan-out) → two SQS queues, plus a Lambda that drains high-priority first. The priority logic is: SNS **message-attribute filter policies** route `priority=high` messages to the high queue and `priority=low` to the low queue; the Lambda (provided `index.py`) reads the high queue before the low queue on invocation.
|
|
|
|
> ⚠️ **KodeKloud + CloudFormation caveat (HIGHLIGHT):** KodeKloud's general FAQ warns that arbitrary CloudFormation/Terraform "will almost certainly not work due to IAM restrictions." **This task explicitly requires CloudFormation**, which means the lab has curated the IAM permissions for *this specific* scenario. So it works here — but it's why the IAM role below uses **managed policies, not inline** (inline `PutRolePolicy` is the restricted path). Don't add custom/inline policies to the role.
|
|
|
|
## Where KodeKloud limits are specifically applied
|
|
|
|
| Limit | Where it lands in this task | Highlighted below |
|
|
|---|---|---|
|
|
| **Lambda timeout ≤ 10s** | `Timeout: 10` on the function | ⚠️ HIGHLIGHT |
|
|
| **Lambda memory ≤ 256 MB** | `MemorySize: 256` on the function | ⚠️ HIGHLIGHT |
|
|
| **IAM: managed policies only, no inline/custom** | Role uses `ManagedPolicyArns` | ⚠️ HIGHLIGHT |
|
|
| **IAM: can't exceed login user's scope** | AWS-managed policies (SQS/SNS/Logs Full) are within scope | ⚠️ HIGHLIGHT |
|
|
| **Lambda config set at create, not updated later** | All props set in the template | ⚠️ HIGHLIGHT |
|
|
| SQS/SNS "basic operations only" | Standard queues, standard topic — nothing advanced | (implicit) |
|
|
|
|
## Step 1 — Write the template (everything except the embedded code)
|
|
|
|
The Lambda code from `/root/index.py` gets **injected** into the template (inline `ZipFile`) in Step 2, so here we write the template *up to* the `ZipFile: |` line, with the Lambda as the last resource and `Code` its last property.
|
|
|
|
```bash
|
|
cat > /root/datacenter-priority-stack.yml << 'YAML'
|
|
AWSTemplateFormatVersion: '2010-09-09'
|
|
Description: Priority queuing with SNS filter-policy fan-out to SQS + Lambda consumer
|
|
|
|
Resources:
|
|
|
|
HighPriorityQueue:
|
|
Type: AWS::SQS::Queue
|
|
Properties:
|
|
QueueName: datacenter-High-Priority-Queue
|
|
|
|
LowPriorityQueue:
|
|
Type: AWS::SQS::Queue
|
|
Properties:
|
|
QueueName: datacenter-Low-Priority-Queue
|
|
|
|
PriorityTopic:
|
|
Type: AWS::SNS::Topic
|
|
Properties:
|
|
TopicName: datacenter-Priority-Queues-Topic
|
|
|
|
HighSubscription:
|
|
Type: AWS::SNS::Subscription
|
|
Properties:
|
|
TopicArn: !Ref PriorityTopic
|
|
Protocol: sqs
|
|
Endpoint: !GetAtt HighPriorityQueue.Arn
|
|
RawMessageDelivery: true
|
|
FilterPolicy:
|
|
priority:
|
|
- high
|
|
|
|
LowSubscription:
|
|
Type: AWS::SNS::Subscription
|
|
Properties:
|
|
TopicArn: !Ref PriorityTopic
|
|
Protocol: sqs
|
|
Endpoint: !GetAtt LowPriorityQueue.Arn
|
|
RawMessageDelivery: true
|
|
FilterPolicy:
|
|
priority:
|
|
- low
|
|
|
|
# SPLIT: one QueuePolicy per queue — SQS requires exactly one resource per statement
|
|
HighQueuePolicy:
|
|
Type: AWS::SQS::QueuePolicy
|
|
Properties:
|
|
Queues:
|
|
- !Ref HighPriorityQueue
|
|
PolicyDocument:
|
|
Version: '2012-10-17'
|
|
Statement:
|
|
- Effect: Allow
|
|
Principal: "*"
|
|
Action: sqs:SendMessage
|
|
Resource: !GetAtt HighPriorityQueue.Arn
|
|
Condition:
|
|
ArnEquals:
|
|
aws:SourceArn: !Ref PriorityTopic
|
|
|
|
LowQueuePolicy:
|
|
Type: AWS::SQS::QueuePolicy
|
|
Properties:
|
|
Queues:
|
|
- !Ref LowPriorityQueue
|
|
PolicyDocument:
|
|
Version: '2012-10-17'
|
|
Statement:
|
|
- Effect: Allow
|
|
Principal: "*"
|
|
Action: sqs:SendMessage
|
|
Resource: !GetAtt LowPriorityQueue.Arn
|
|
Condition:
|
|
ArnEquals:
|
|
aws:SourceArn: !Ref PriorityTopic
|
|
|
|
LambdaExecutionRole:
|
|
Type: AWS::IAM::Role
|
|
Properties:
|
|
RoleName: lambda_execution_role
|
|
AssumeRolePolicyDocument:
|
|
Version: '2012-10-17'
|
|
Statement:
|
|
- Effect: Allow
|
|
Principal:
|
|
Service: lambda.amazonaws.com
|
|
Action: sts:AssumeRole
|
|
ManagedPolicyArns:
|
|
- arn:aws:iam::aws:policy/AmazonSQSFullAccess
|
|
- arn:aws:iam::aws:policy/AmazonSNSFullAccess
|
|
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
|
|
|
|
PriorityLambda:
|
|
Type: AWS::Lambda::Function
|
|
Properties:
|
|
FunctionName: datacenter-priorities-queue-function
|
|
Handler: index.lambda_handler
|
|
Runtime: python3.13
|
|
Role: !GetAtt LambdaExecutionRole.Arn
|
|
Timeout: 10
|
|
MemorySize: 256
|
|
Environment:
|
|
Variables:
|
|
high_priority_queue: !Ref HighPriorityQueue
|
|
low_priority_queue: !Ref LowPriorityQueue
|
|
Code:
|
|
ZipFile: |
|
|
YAML
|
|
```
|
|
|
|
⚠️ **HIGHLIGHTED limit applications in that template:**
|
|
|
|
- **`Timeout: 10`** — KodeKloud auto-resets Lambda timeouts > 10s to 3s and suspends the session for > 30s. 10 is the ceiling. **This is a hard limit you must set here.**
|
|
- **`MemorySize: 256`** — the lab cap; > 256 gets reset to 128. Set it explicitly.
|
|
- **`ManagedPolicyArns` instead of inline `Policies`** — the lab restricts custom/inline policy creation (your `PutRolePolicy` denial earlier). Attaching AWS-managed policies is permitted because they don't exceed your login user's scope. `AmazonSQSFullAccess` + `AmazonSNSFullAccess` + `AWSLambdaBasicExecutionRole` cover "interact with SQS and SNS" + logging.
|
|
- All function config is set **at create time in the template** — KodeKloud blocks post-hoc `update-function-configuration`, so getting it right in the stack matters.
|
|
|
|
## Step 2 — Inject `/root/index.py` into the template (indented under `ZipFile: |`)
|
|
|
|
The inline `ZipFile` block scalar needs each code line indented to 10 spaces (deeper than the `ZipFile: |` at 8). `sed` handles it; appending is all that's needed since the Lambda is the last resource.
|
|
|
|
```bash
|
|
sed 's/^/ /' /root/index.py >> /root/datacenter-priority-stack.yml
|
|
|
|
# Sanity-check the tail of the template shows your indented code
|
|
tail -20 /root/datacenter-priority-stack.yml
|
|
```
|
|
|
|
Two things to verify:
|
|
|
|
- **Handler match:** the template uses `Handler: index.lambda_handler`. If the function inside `/root/index.py` is named something other than `lambda_handler`, change the handler to `index.<actual_function_name>`. Check with `grep '^def ' /root/index.py`.
|
|
- **Inline code size limit:** CloudFormation inline `ZipFile` caps at ~4096 bytes. `wc -c /root/index.py` — if it's over 4 KB, you can't inline it; you'd zip it to S3 and use `Code: {S3Bucket, S3Key}` instead. A simple priority processor is almost always well under 4 KB.
|
|
|
|
## Step 3 — Deploy the stack
|
|
|
|
```bash
|
|
REGION=us-east-1
|
|
|
|
aws cloudformation deploy \
|
|
--template-file /root/datacenter-priority-stack.yml \
|
|
--stack-name datacenter-priority-stack \
|
|
--capabilities CAPABILITY_NAMED_IAM \
|
|
--region $REGION
|
|
```
|
|
|
|
⚠️ **`--capabilities CAPABILITY_NAMED_IAM` is mandatory** — the stack creates an IAM role with an *explicit name* (`lambda_execution_role`). CloudFormation refuses to create named IAM resources without this acknowledgment (`CAPABILITY_IAM` alone isn't enough for *named* ones). Omit it and the deploy fails with an `InsufficientCapabilities` error.
|
|
|
|
## Step 4 — Test (publish to SNS, invoke Lambda)
|
|
|
|
```bash
|
|
topicarn=$(aws sns list-topics \
|
|
--query "Topics[?contains(TopicArn, 'datacenter-Priority-Queues-Topic')].TopicArn" \
|
|
--output text)
|
|
|
|
aws sns publish --topic-arn $topicarn --message 'High Priority message 1' \
|
|
--message-attributes '{"priority":{"DataType":"String","StringValue":"high"}}'
|
|
aws sns publish --topic-arn $topicarn --message 'High Priority message 2' \
|
|
--message-attributes '{"priority":{"DataType":"String","StringValue":"high"}}'
|
|
aws sns publish --topic-arn $topicarn --message 'Low Priority message 1' \
|
|
--message-attributes '{"priority":{"DataType":"String","StringValue":"low"}}'
|
|
aws sns publish --topic-arn $topicarn --message 'Low Priority message 2' \
|
|
--message-attributes '{"priority":{"DataType":"String","StringValue":"low"}}'
|
|
|
|
# Give SNS a couple seconds to fan out to the queues, then invoke the Lambda
|
|
sleep 5
|
|
aws lambda invoke --function-name datacenter-priorities-queue-function \
|
|
--region $REGION /tmp/out.json
|
|
cat /tmp/out.json
|
|
```
|
|
|
|
⚠️ **Don't loop the invoke** — KodeKloud deletes any Lambda invoked **> 300 times in an hour**. A handful of test invokes is fine; don't script a hammer loop.
|
|
|
|
The `priority` message attribute is what the SNS **filter policy** keys on: `high` messages match `HighSubscription`'s filter and land in the high queue; `low` in the low queue. The Lambda reads high first, so high-priority messages process ahead of low — the task's success condition.
|
|
|
|
## Verify
|
|
|
|
```bash
|
|
# Stack created cleanly
|
|
aws cloudformation describe-stacks --stack-name datacenter-priority-stack \
|
|
--region $REGION --query 'Stacks[0].StackStatus'
|
|
|
|
# All resources present
|
|
aws cloudformation describe-stack-resources --stack-name datacenter-priority-stack \
|
|
--region $REGION --query 'StackResources[].{Type:ResourceType,Status:ResourceStatus,Id:PhysicalResourceId}' \
|
|
--output table
|
|
|
|
# Messages fanned out to the queues (before Lambda drains them)
|
|
for Q in datacenter-High-Priority-Queue datacenter-Low-Priority-Queue; do
|
|
URL=$(aws sqs get-queue-url --queue-name $Q --region $REGION --query QueueUrl --output text)
|
|
echo "$Q:"; aws sqs get-queue-attributes --queue-url $URL \
|
|
--attribute-names ApproximateNumberOfMessages --region $REGION \
|
|
--query 'Attributes.ApproximateNumberOfMessages' --output text
|
|
done
|
|
|
|
# Lambda processing order — check its logs
|
|
aws logs tail /aws/lambda/datacenter-priorities-queue-function --region $REGION --since 5m
|
|
```
|
|
|
|
Want: stack `CREATE_COMPLETE`, all resources `CREATE_COMPLETE`, messages present in both queues after publish, and the Lambda logs showing **high-priority messages processed before low**. That ordering in the logs is the task's proof.
|
|
|
|
## Debug order
|
|
|
|
1. **Deploy fails `InsufficientCapabilities`** → missing `--capabilities CAPABILITY_NAMED_IAM`.
|
|
2. **Deploy fails on the IAM role (`AccessDenied`/`not authorized`)** → the lab didn't grant role creation for this scenario, *or* you left inline `Policies` in the template. Confirm you're using `ManagedPolicyArns` only. If it still fails, the role may be pre-created — remove the role resource and reference the existing `lambda_execution_role` ARN in the function's `Role`.
|
|
3. **Messages don't reach the queues** → SNS filter policy attribute mismatch (must be `priority`), or the `QueuePolicy` didn't allow SNS `SendMessage`. Also `RawMessageDelivery: true` matters so the SQS message body is the raw message, not SNS-wrapped JSON — check what `index.py` expects.
|
|
4. **Lambda errors on invoke** → `ImportModuleError` (handler/filename mismatch — must be `index.lambda_handler` for `index.py`), or `AccessDenied` (role missing SQS/SNS perms — the managed policies cover it).
|
|
5. **Inline code rejected at deploy** → `index.py` > 4096 bytes; switch to S3-packaged `Code`.
|
|
|
|
## Full KodeKloud compliance checklist
|
|
|
|
| Constraint | Applied |
|
|
|---|---|
|
|
| Region us-east-1 | ✅ |
|
|
| Lambda timeout ≤ 10s | ⚠️✅ `Timeout: 10` |
|
|
| Lambda memory ≤ 256 MB | ⚠️✅ `MemorySize: 256` |
|
|
| IAM managed policies (no inline/custom) | ⚠️✅ `ManagedPolicyArns` |
|
|
| IAM within login-user scope | ⚠️✅ AWS-managed policies |
|
|
| Config at create, not updated later | ⚠️✅ all in template |
|
|
| SQS/SNS basic ops only (standard) | ✅ standard queue/topic |
|
|
| ≤ 300 Lambda invocations/hr | ⚠️✅ single test invoke |
|
|
| CFN named-IAM capability | ✅ `CAPABILITY_NAMED_IAM` | |