Thiết kế
Yêu cầu
- API tạo URL rút gọn: input là URL cần rút gọn, output là URL được rút gọn
- API đọc URL rút gọn: input là URL được rút gọn
- redirect tới trang cần được rút gọn
- trả về lỗi nếu URL không tồn tại
Giải pháp
- Gồm 3 thành phần chính:
- API Gateway: API endpoint, nhận request từ user và chuyển cho Lambda funtion xử lý
- Lambda funtion: Xử lý logic đọc, ghi URL
- DynamoDB: Lưu trữ dữ liệu URL đã được rút gọn
Ngoài ra có nếu có thêm Route53 Hosted Zone và AWS Certificate Manager cho tuỳ chọn gắn API Gateway với custom domain.
Điều kiện trước tiên
Trước khi bắt đầu các bạn nên :
- AWS CDK (có thể tham khảo Hướng dẫn chi tiết cài đặt AWS CDK)
- Cài đặt AWS CLI và cài đặt AWS profile (có thể tham khảo hướng dẫn tại đây)
Cài đặt AWS CDK project
Sau khi cài đặt profile AWS CLI và AWS CDK, chúng ta tạo project AWS CDK python để xây dựng ứng dụng AWS CDK.
1 2 3 4 5 6 7 8 | <span class="token function">mkdir</span> url-shortener cdk init --language python <span class="token comment"># cài đặt môi trường ảo</span> python3 -m venv .venv <span class="token builtin class-name">source</span> .venv/bin/activate <span class="token comment"># cài đặt các dependencies cần thiết</span> pip <span class="token function">install</span> -r requirements.txt |
Cấu trúc file sau khi project được tạo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ./ ├── README.md ├── app.py ├── cdk.json ├── requirements-dev.txt ├── requirements.txt ├── source.bat ├── tests │ ├── __init__.py │ └── unit │ ├── __init__.py │ └── test_url_shortener_stack.py └── url_shortener ├── __init__.py └── url_shortener_stack.py |
DynamoDB
Tiến hành tạo bảng mapping-table
với partition_key là id
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <span class="token keyword">from</span> aws_cdk <span class="token keyword">import</span> <span class="token punctuation">(</span> Stack<span class="token punctuation">,</span> aws_dynamodb <span class="token keyword">as</span> dynamodb<span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token keyword">from</span> constructs <span class="token keyword">import</span> Construct <span class="token keyword">class</span> <span class="token class-name">UrlShortenerStack</span><span class="token punctuation">(</span>Stack<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">def</span> <span class="token function">__init__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> scope<span class="token punctuation">:</span> Construct<span class="token punctuation">,</span> construct_id<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">,</span> <span class="token operator">**</span>kwargs<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token boolean">None</span><span class="token punctuation">:</span> <span class="token builtin">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>__init__<span class="token punctuation">(</span>scope<span class="token punctuation">,</span> construct_id<span class="token punctuation">,</span> <span class="token operator">**</span>kwargs<span class="token punctuation">)</span> table <span class="token operator">=</span> dynamodb<span class="token punctuation">.</span>Table<span class="token punctuation">(</span>self<span class="token punctuation">,</span> <span class="token string">"mapping-table"</span><span class="token punctuation">,</span> partition_key<span class="token operator">=</span>dynamodb<span class="token punctuation">.</span>Attribute<span class="token punctuation">(</span> name<span class="token operator">=</span><span class="token string">"id"</span><span class="token punctuation">,</span> <span class="token builtin">type</span><span class="token operator">=</span>dynamodb<span class="token punctuation">.</span>AttributeType<span class="token punctuation">.</span>STRING<span class="token punctuation">)</span> <span class="token punctuation">)</span> |
Deploy với AWS CDK
1 2 | cdk deploy |
Khi kiểm tra trên AWS console, chúng ta thấy DynamoDB table đã được tạo
Lambda & API Gateway
Tạo folder chứa code cho lambda function
1 2 3 | <span class="token function">mkdir</span> lambda <span class="token function">touch</span> handler.py |
Viết hàm xử lý handler.py
cho lambda function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | <span class="token keyword">import</span> logging <span class="token keyword">import</span> json <span class="token keyword">import</span> os <span class="token keyword">import</span> uuid <span class="token keyword">import</span> boto3 LOG <span class="token operator">=</span> logging<span class="token punctuation">.</span>getLogger<span class="token punctuation">(</span><span class="token punctuation">)</span> LOG<span class="token punctuation">.</span>setLevel<span class="token punctuation">(</span>logging<span class="token punctuation">.</span>INFO<span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">main</span><span class="token punctuation">(</span>event<span class="token punctuation">,</span> context<span class="token punctuation">)</span><span class="token punctuation">:</span> LOG<span class="token punctuation">.</span>info<span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"EVENT: + </span><span class="token interpolation"><span class="token punctuation">{</span>json<span class="token punctuation">.</span>dumps<span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span> query_string_params <span class="token operator">=</span> event<span class="token punctuation">[</span><span class="token string">"queryStringParameters"</span><span class="token punctuation">]</span> <span class="token keyword">if</span> query_string_params <span class="token keyword">is</span> <span class="token keyword">not</span> <span class="token boolean">None</span><span class="token punctuation">:</span> target_url <span class="token operator">=</span> query_string_params<span class="token punctuation">[</span><span class="token string">'targetUrl'</span><span class="token punctuation">]</span> <span class="token keyword">if</span> target_url <span class="token keyword">is</span> <span class="token keyword">not</span> <span class="token boolean">None</span><span class="token punctuation">:</span> <span class="token keyword">return</span> create_short_url<span class="token punctuation">(</span>event<span class="token punctuation">)</span> path_parameters <span class="token operator">=</span> event<span class="token punctuation">[</span><span class="token string">'pathParameters'</span><span class="token punctuation">]</span> <span class="token keyword">if</span> path_parameters <span class="token keyword">is</span> <span class="token keyword">not</span> <span class="token boolean">None</span><span class="token punctuation">:</span> <span class="token keyword">if</span> path_parameters<span class="token punctuation">[</span><span class="token string">'proxy'</span><span class="token punctuation">]</span> <span class="token keyword">is</span> <span class="token keyword">not</span> <span class="token boolean">None</span><span class="token punctuation">:</span> <span class="token keyword">return</span> read_short_url<span class="token punctuation">(</span>event<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token string">'statusCode'</span><span class="token punctuation">:</span> <span class="token number">200</span><span class="token punctuation">,</span> <span class="token string">'body'</span><span class="token punctuation">:</span> <span class="token string">'usage: ?targetUrl=URL'</span> <span class="token punctuation">}</span> <span class="token comment"># Tạo URL</span> <span class="token keyword">def</span> <span class="token function">create_short_url</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">:</span> target_url <span class="token operator">=</span> event<span class="token punctuation">[</span><span class="token string">'queryStringParameters'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'targetUrl'</span><span class="token punctuation">]</span> <span class="token builtin">id</span> <span class="token operator">=</span> <span class="token builtin">str</span><span class="token punctuation">(</span>uuid<span class="token punctuation">.</span>uuid4<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">:</span><span class="token number">8</span><span class="token punctuation">]</span> table_name <span class="token operator">=</span> os<span class="token punctuation">.</span>environ<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">'TABLE_NAME'</span><span class="token punctuation">)</span> dynamo_db <span class="token operator">=</span> boto3<span class="token punctuation">.</span>resource<span class="token punctuation">(</span><span class="token string">'dynamodb'</span><span class="token punctuation">)</span> table <span class="token operator">=</span> dynamo_db<span class="token punctuation">.</span>Table<span class="token punctuation">(</span>table_name<span class="token punctuation">)</span> table<span class="token punctuation">.</span>put_item<span class="token punctuation">(</span> Item<span class="token operator">=</span><span class="token punctuation">{</span> <span class="token string">'id'</span><span class="token punctuation">:</span> <span class="token builtin">id</span><span class="token punctuation">,</span> <span class="token string">'target_url'</span><span class="token punctuation">:</span> target_url<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> url <span class="token operator">=</span> <span class="token string">"https://"</span> <span class="token operator">+</span> event<span class="token punctuation">[</span><span class="token string">"requestContext"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"domainName"</span><span class="token punctuation">]</span> <span class="token operator">+</span> event<span class="token punctuation">[</span><span class="token string">"requestContext"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"path"</span><span class="token punctuation">]</span> <span class="token operator">+</span> <span class="token builtin">id</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token string">'statusCode'</span><span class="token punctuation">:</span> <span class="token number">200</span><span class="token punctuation">,</span> <span class="token string">'headers'</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token string">'Content-Type'</span><span class="token punctuation">:</span> <span class="token string">'text/plain'</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">'body'</span><span class="token punctuation">:</span> <span class="token string">"Created URL: %s"</span> <span class="token operator">%</span> url <span class="token punctuation">}</span> <span class="token comment"># Redirect tới URL đã được tạo</span> <span class="token keyword">def</span> <span class="token function">read_short_url</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token builtin">id</span> <span class="token operator">=</span> event<span class="token punctuation">[</span><span class="token string">'pathParameters'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'proxy'</span><span class="token punctuation">]</span> table_name <span class="token operator">=</span> os<span class="token punctuation">.</span>environ<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">'TABLE_NAME'</span><span class="token punctuation">)</span> dynamo_db <span class="token operator">=</span> boto3<span class="token punctuation">.</span>resource<span class="token punctuation">(</span><span class="token string">'dynamodb'</span><span class="token punctuation">)</span> table <span class="token operator">=</span> dynamo_db<span class="token punctuation">.</span>Table<span class="token punctuation">(</span>table_name<span class="token punctuation">)</span> response <span class="token operator">=</span> table<span class="token punctuation">.</span>get_item<span class="token punctuation">(</span>Key<span class="token operator">=</span><span class="token punctuation">{</span><span class="token string">'id'</span><span class="token punctuation">:</span> <span class="token builtin">id</span><span class="token punctuation">}</span><span class="token punctuation">)</span> LOG<span class="token punctuation">.</span>debug<span class="token punctuation">(</span><span class="token string">"RESPONSE: "</span> <span class="token operator">+</span> json<span class="token punctuation">.</span>dumps<span class="token punctuation">(</span>response<span class="token punctuation">)</span><span class="token punctuation">)</span> item <span class="token operator">=</span> response<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">'Item'</span><span class="token punctuation">,</span> <span class="token boolean">None</span><span class="token punctuation">)</span> <span class="token keyword">if</span> item <span class="token keyword">is</span> <span class="token boolean">None</span><span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token string">'statusCode'</span><span class="token punctuation">:</span> <span class="token number">400</span><span class="token punctuation">,</span> <span class="token string">'headers'</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token string">'Content-Type'</span><span class="token punctuation">:</span> <span class="token string">'text/plain'</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">'body'</span><span class="token punctuation">:</span> <span class="token string">'No redirect found for '</span> <span class="token operator">+</span> <span class="token builtin">id</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token string">'statusCode'</span><span class="token punctuation">:</span> <span class="token number">301</span><span class="token punctuation">,</span> <span class="token string">'headers'</span><span class="token punctuation">:</span> <span class="token punctuation">{</span> <span class="token string">'Location'</span><span class="token punctuation">:</span> item<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">'target_url'</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Thêm hàm Lambda, APIGateway vào stack và phân quyền phù hợp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <span class="token keyword">from</span> aws_cdk <span class="token keyword">import</span> <span class="token punctuation">(</span> Stack<span class="token punctuation">,</span> aws_dynamodb <span class="token keyword">as</span> dynamodb<span class="token punctuation">,</span> aws_lambda <span class="token keyword">as</span> lambda_<span class="token punctuation">,</span> aws_apigateway <span class="token keyword">as</span> apigw<span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token keyword">from</span> constructs <span class="token keyword">import</span> Construct <span class="token keyword">import</span> os <span class="token keyword">class</span> <span class="token class-name">UrlShortenerStack</span><span class="token punctuation">(</span>Stack<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">def</span> <span class="token function">__init__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> scope<span class="token punctuation">:</span> Construct<span class="token punctuation">,</span> construct_id<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">,</span> <span class="token operator">**</span>kwargs<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token boolean">None</span><span class="token punctuation">:</span> <span class="token builtin">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>__init__<span class="token punctuation">(</span>scope<span class="token punctuation">,</span> construct_id<span class="token punctuation">,</span> <span class="token operator">**</span>kwargs<span class="token punctuation">)</span> <span class="token comment"># DynamoDB table</span> table <span class="token operator">=</span> dynamodb<span class="token punctuation">.</span>Table<span class="token punctuation">(</span>self<span class="token punctuation">,</span> <span class="token string">"mapping-table"</span><span class="token punctuation">,</span> partition_key<span class="token operator">=</span>dynamodb<span class="token punctuation">.</span>Attribute<span class="token punctuation">(</span> name<span class="token operator">=</span><span class="token string">"id"</span><span class="token punctuation">,</span> <span class="token builtin">type</span><span class="token operator">=</span>dynamodb<span class="token punctuation">.</span>AttributeType<span class="token punctuation">.</span>STRING<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token comment"># Lambda Function</span> function <span class="token operator">=</span> lambda_<span class="token punctuation">.</span>Function<span class="token punctuation">(</span>self<span class="token punctuation">,</span> <span class="token string">"backend"</span><span class="token punctuation">,</span> runtime<span class="token operator">=</span>lambda_<span class="token punctuation">.</span>Runtime<span class="token punctuation">.</span>PYTHON_3_9<span class="token punctuation">,</span> handler<span class="token operator">=</span><span class="token string">"handler.main"</span><span class="token punctuation">,</span> code<span class="token operator">=</span>lambda_<span class="token punctuation">.</span>Code<span class="token punctuation">.</span>from_asset<span class="token punctuation">(</span><span class="token string">"./lambda"</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token comment"># Cấp quyền đọc, ghi vào DynamoDB table cho lambda function mới được tạo</span> table<span class="token punctuation">.</span>grant_read_write_data<span class="token punctuation">(</span>function<span class="token punctuation">)</span> <span class="token comment"># Add ENV to lambda function</span> function<span class="token punctuation">.</span>add_environment<span class="token punctuation">(</span><span class="token string">"TABLE_NAME"</span><span class="token punctuation">,</span> table<span class="token punctuation">.</span>table_name<span class="token punctuation">)</span> <span class="token comment"># API gateway</span> api <span class="token operator">=</span> apigw<span class="token punctuation">.</span>LambdaRestApi<span class="token punctuation">(</span>self<span class="token punctuation">,</span> <span class="token string">"api"</span><span class="token punctuation">,</span> handler<span class="token operator">=</span>function<span class="token punctuation">)</span> |
grant_read_write_data
sẽ cấp quyền đọc và ghi của dynamodb table cho lambda function, một lần nữa AWS CDK chức tỏ tính ưu việt khi làm điều này với 1 dòng lệnh thay vì phải mapping phức tạp khi sử dụng CloudFormation template.add_environment
tạo biến môi trường cho Lambda functionLambdaRestApi
thêm một oneliner của AWS CDK cho việc tạp Rest API cho Lambda function + tạo API Gateway và map 2 thứ lại với nhau
Deploy với AWS CDK CLI
- Chạy lệnh
diff
để thấy được changeset của stack
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | cdk <span class="token function">diff</span> <span class="token comment"># 👇 output</span> Stack UrlShortenerStack IAM Statement Changes ┌───┬──────────────────────┬────────┬───────────────────────────────────────────────┬───────────────────────────────────────────────┬───────────┐ │ │ Resource │ Effect │ Action │ Principal │ Condition │ ├───┼──────────────────────┼────────┼───────────────────────────────────────────────┼───────────────────────────────────────────────┼───────────┤ │ + │ <span class="token variable">${mapping-table.Arn}</span> │ Allow │ dynamodb:BatchGetItem │ AWS:<span class="token variable">${backend<span class="token operator">/</span>ServiceRole}</span> │ │ │ │ │ │ dynamodb:BatchWriteItem │ │ │ │ │ │ │ dynamodb:ConditionCheckItem │ │ │ │ │ │ │ dynamodb:DeleteItem │ │ │ │ │ │ │ dynamodb:DescribeTable │ │ │ │ │ │ │ dynamodb:GetItem │ │ │ │ │ │ │ dynamodb:GetRecords │ │ │ │ │ │ │ dynamodb:GetShardIterator │ │ │ │ │ │ │ dynamodb:PutItem │ │ │ │ │ │ │ dynamodb:Query │ │ │ │ │ │ │ dynamodb:Scan │ │ │ │ │ │ │ dynamodb:UpdateItem │ │ │ └───┴──────────────────────┴────────┴───────────────────────────────────────────────┴───────────────────────────────────────────────┴───────────┘ <span class="token punctuation">(</span>NOTE: There may be security-related changes not <span class="token keyword">in</span> this list. See https://github.com/aws/aws-cdk/issues/1299<span class="token punctuation">)</span> Resources <span class="token punctuation">[</span>+<span class="token punctuation">]</span> AWS::IAM::Policy backend/ServiceRole/DefaultPolicy backendServiceRoleDefaultPolicyxxx <span class="token punctuation">[</span>~<span class="token punctuation">]</span> AWS::Lambda::Function backend backendCBA98286 ├─ <span class="token punctuation">[</span>+<span class="token punctuation">]</span> Environment │ └─ <span class="token punctuation">{</span><span class="token string">"Variables"</span>:<span class="token punctuation">{</span><span class="token string">"TABLE_NAME"</span>:<span class="token punctuation">{</span><span class="token string">"Ref"</span><span class="token builtin class-name">:</span><span class="token string">"mappingtablexxx"</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">}</span> └─ <span class="token punctuation">[</span>~<span class="token punctuation">]</span> DependsOn └─ @@ -1,3 +1,4 @@ <span class="token punctuation">[</span> <span class="token punctuation">]</span> <span class="token punctuation">[</span> <span class="token punctuation">[</span>+<span class="token punctuation">]</span> <span class="token string">"backendServiceRoleDefaultPolicyxxx"</span>, <span class="token punctuation">[</span> <span class="token punctuation">]</span> <span class="token string">"backendServiceRolexxx"</span> <span class="token punctuation">[</span> <span class="token punctuation">]</span> <span class="token punctuation">]</span> |
- Deloy stack lên AWS Cloud
1 2 3 4 5 6 7 8 9 10 11 12 13 | cdk deploy <span class="token comment"># 👇 output</span> <span class="token punctuation">..</span>. ✅ UrlShortenerStack ✨ Deployment time: <span class="token number">51</span>.6s Outputs: UrlShortenerStack.apiEndpoint9349E63C <span class="token operator">=</span> https://xxx.execute-api.ap-southeast-1.amazonaws.com/prod/ Stack ARN: arn:aws:cloudformation:ap-southeast-1:xxx:stack/UrlShortenerStack/xxxx |
Test
- Bạn có thể thử sử dụng ứng dụng này với URL trong phần output
1 2 3 4 5 6 7 8 9 10 11 12 13 | <span class="token function">curl</span> https://xxx.execute-api.ap-southeast-1.amazonaws.com/prod/?targetUrl<span class="token operator">=</span>https://dev.vntechies.com/ <span class="token comment"># 👇 output</span> Created URL: https://xxx.execute-api.ap-southeast-1.amazonaws.com/prod/7deb07cb <span class="token function">curl</span> https://xxx.execute-api.ap-southeast-1.amazonaws.com/prod/7deb07cb -v <span class="token comment"># 👇 output</span> <span class="token punctuation">..</span>. <span class="token operator"><</span> HTTP/2 <span class="token number">301</span> <span class="token operator"><</span> content-type: application/json <span class="token operator"><</span> content-length: <span class="token number">0</span> <span class="token operator"><</span> location: https://dev.vntechies.com/ <span class="token punctuation">..</span>. |
Vậy là chúng ta đã hoàn thành việc xây dựng ứng dụng rút gọn URL sử dụng API Gateway, Lambda và DynamoDB được triển khai nhờ AWS CDK.
Nếu bạn muốn tiếp tục sử dụng với custom domain, bạn có thể tiếp tục làm theo bước sau.
API Gateway & Custom domain
Tạo hosted zone trên Route53
- Bạn cần có một tên miền và tạo hosted zone trên AWS Route53 bằng console với tên miền đó. Hướng dẫn cụ thể bạn có thể tham khảo tại đây
Cài đặt biến môi trường
- Sau khi hoàn tất việc tạp hosted zone với custom domain, bạn cần cài đặt biến môi trường (hãy export các biến tại session bạn chạy AWS CDK CLI)
- Nếu bạn sử dụng CI/CD pipeline khi triển khai ứng dụng AWS CDK, hãy chắc chắn cài đặt các biến môi trường trước đó
AWS Route53 Hosted Zone, Targets và AWS Certificate Manager
1 2 3 4 | <span class="token builtin class-name">export</span> <span class="token assign-left variable">ZONE_NAME</span><span class="token operator">=</span><span class="token string">'vntechies.com'</span> <span class="token comment"># thay bằng domain của bạn</span> <span class="token builtin class-name">export</span> <span class="token assign-left variable">CDK_DEFAULT_ACCOUNT</span><span class="token operator">=</span><span class="token string">'xxx'</span> <span class="token builtin class-name">export</span> <span class="token assign-left variable">CDK_DEFAULT_REGION</span><span class="token operator">=</span><span class="token string">'ap-southeast-1'</span> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | <span class="token keyword">from</span> aws_cdk <span class="token keyword">import</span> <span class="token punctuation">(</span> Stack<span class="token punctuation">,</span> aws_dynamodb <span class="token keyword">as</span> dynamodb<span class="token punctuation">,</span> aws_lambda <span class="token keyword">as</span> lambda_<span class="token punctuation">,</span> aws_apigateway <span class="token keyword">as</span> apigw<span class="token punctuation">,</span> aws_route53 <span class="token keyword">as</span> route53<span class="token punctuation">,</span> aws_route53_targets <span class="token keyword">as</span> targets<span class="token punctuation">,</span> aws_certificatemanager <span class="token keyword">as</span> acm<span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token keyword">from</span> constructs <span class="token keyword">import</span> Construct <span class="token keyword">import</span> os ZONE_NAME <span class="token operator">=</span> os<span class="token punctuation">.</span>getenv<span class="token punctuation">(</span><span class="token string">'ZONE_NAME'</span><span class="token punctuation">)</span> <span class="token keyword">class</span> <span class="token class-name">UrlShortenerStack</span><span class="token punctuation">(</span>Stack<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">def</span> <span class="token function">__init__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> scope<span class="token punctuation">:</span> Construct<span class="token punctuation">,</span> construct_id<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">,</span> <span class="token operator">**</span>kwargs<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token boolean">None</span><span class="token punctuation">:</span> <span class="token builtin">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>__init__<span class="token punctuation">(</span>scope<span class="token punctuation">,</span> construct_id<span class="token punctuation">,</span> <span class="token operator">**</span>kwargs<span class="token punctuation">)</span> <span class="token comment"># DynamoDB table</span> table <span class="token operator">=</span> dynamodb<span class="token punctuation">.</span>Table<span class="token punctuation">(</span>self<span class="token punctuation">,</span> <span class="token string">"mapping-table"</span><span class="token punctuation">,</span> partition_key<span class="token operator">=</span>dynamodb<span class="token punctuation">.</span>Attribute<span class="token punctuation">(</span> name<span class="token operator">=</span><span class="token string">"id"</span><span class="token punctuation">,</span> <span class="token builtin">type</span><span class="token operator">=</span>dynamodb<span class="token punctuation">.</span>AttributeType<span class="token punctuation">.</span>STRING<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token comment"># Lambda Function</span> function <span class="token operator">=</span> lambda_<span class="token punctuation">.</span>Function<span class="token punctuation">(</span>self<span class="token punctuation">,</span> <span class="token string">"backend"</span><span class="token punctuation">,</span> runtime<span class="token operator">=</span>lambda_<span class="token punctuation">.</span>Runtime<span class="token punctuation">.</span>PYTHON_3_9<span class="token punctuation">,</span> handler<span class="token operator">=</span><span class="token string">"handler.main"</span><span class="token punctuation">,</span> code<span class="token operator">=</span>lambda_<span class="token punctuation">.</span>Code<span class="token punctuation">.</span>from_asset<span class="token punctuation">(</span><span class="token string">"./lambda"</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token comment"># Cấp quyền đọc, ghi vào DynamoDB table cho lambda function mới được tạo</span> table<span class="token punctuation">.</span>grant_read_write_data<span class="token punctuation">(</span>function<span class="token punctuation">)</span> <span class="token comment"># Add ENV to lambda function</span> function<span class="token punctuation">.</span>add_environment<span class="token punctuation">(</span><span class="token string">"TABLE_NAME"</span><span class="token punctuation">,</span> table<span class="token punctuation">.</span>table_name<span class="token punctuation">)</span> <span class="token comment"># API gateway</span> api <span class="token operator">=</span> apigw<span class="token punctuation">.</span>LambdaRestApi<span class="token punctuation">(</span>self<span class="token punctuation">,</span> <span class="token string">"api"</span><span class="token punctuation">,</span> handler<span class="token operator">=</span>function<span class="token punctuation">)</span> <span class="token comment"># Map API gateway với custom domain</span> self<span class="token punctuation">.</span>map_subdomain<span class="token punctuation">(</span><span class="token string">"go"</span><span class="token punctuation">,</span> api<span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">map_subdomain</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> subdomain<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">,</span> api<span class="token punctuation">:</span> apigw<span class="token punctuation">.</span>RestApi<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">str</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" Maps a sub-domain of {domain_name} to an API gateway :param subdomain: The sub-domain (e.g. "www") :param api: The API gateway endpoint :return: The base url (e.g. "https://www.{domain_name}") """</span> domain_name <span class="token operator">=</span> subdomain <span class="token operator">+</span> <span class="token string">'.'</span> <span class="token operator">+</span> ZONE_NAME url <span class="token operator">=</span> <span class="token string">'https://'</span> <span class="token operator">+</span> domain_name <span class="token comment"># Lấy hosted zone vừa mới tạo</span> hosted_zone <span class="token operator">=</span> route53<span class="token punctuation">.</span>HostedZone<span class="token punctuation">.</span>from_lookup<span class="token punctuation">(</span>self<span class="token punctuation">,</span> <span class="token string">"zone"</span><span class="token punctuation">,</span> domain_name<span class="token operator">=</span>ZONE_NAME <span class="token punctuation">)</span> <span class="token comment"># Tạo một certificate mới cho custom domain</span> cert <span class="token operator">=</span> acm<span class="token punctuation">.</span>Certificate<span class="token punctuation">(</span>self<span class="token punctuation">,</span> <span class="token string">"Certificate"</span><span class="token punctuation">,</span> domain_name<span class="token operator">=</span>domain_name<span class="token punctuation">,</span> validation<span class="token operator">=</span>acm<span class="token punctuation">.</span>CertificateValidation<span class="token punctuation">.</span>from_dns<span class="token punctuation">(</span> hosted_zone<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token comment"># Add domain vào API và tạo một A record tại hosted zone trên Route53</span> domain <span class="token operator">=</span> api<span class="token punctuation">.</span>add_domain_name<span class="token punctuation">(</span> <span class="token string">'Domain'</span><span class="token punctuation">,</span> certificate<span class="token operator">=</span>cert<span class="token punctuation">,</span> domain_name<span class="token operator">=</span>domain_name<span class="token punctuation">)</span> route53<span class="token punctuation">.</span>ARecord<span class="token punctuation">(</span> self<span class="token punctuation">,</span> <span class="token string">'UrlShortenerDomain'</span><span class="token punctuation">,</span> record_name<span class="token operator">=</span>subdomain<span class="token punctuation">,</span> zone<span class="token operator">=</span>hosted_zone<span class="token punctuation">,</span> target<span class="token operator">=</span>route53<span class="token punctuation">.</span>RecordTarget<span class="token punctuation">.</span>from_alias<span class="token punctuation">(</span>targets<span class="token punctuation">.</span>ApiGatewayDomain<span class="token punctuation">(</span>domain<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> url |
HostedZone.from_lookup
tìm hosted zone theo config tên miền (domain_name)acm.CertificateValidation.from_dns
sẽ tự động tạo một dns record cho việc chứng thực quyền sở hữu tên miền trên hosted zone của bạn, nếu bạn không quản lý tên miền bằng Route53, bạn cần tạo record này một cách thủ công.
Sau khi hoàn tất việc chuẩn bị source code, chúng ta có thể deploy stack lên AWS Cloud
1 2 | cdk deploy |
Demo
Sau khi truy cập vào URL rút gọn được đã tạo, chúng ta sẽ được redirect tới trang đã được yêu cầu trước đó.
Vậy là ứng dụng rút gọn URL nhỏ gọn đã được hoàn thành.
Tuy bài hướng dẫn này chỉ xây dựng một ứng dụng đơn giản, nhưng bạn có thể thấy việc sử dụng AWS CDK đã rút gọn đáng kể thời gian triển khai các tài nguyên trên AWS và giúp bạn có thể tập trung vào logic của ứng dụng.
Hãy thử bắt đầu sử dụng ngay từ bây giờ bạn nhé