Author: khanhcd92

  • Giả lập môi trường phát triển AWS với LocalStack

    Giả lập môi trường phát triển AWS với LocalStack

    Khi chúng ta xây dựng các ứng dụng với AWS, chúng ta truy cập các dịch vụ AWS khác nhau cho nhiều mục đích: lưu trữ tệp trong S3, lưu một số dữ liệu trong DynamoDB, gửi tin nhắn tới SQS, viết trình xử lý sự kiện bằng các hàm lambda và nhiều mục đích khác.

    Tuy nhiên, trong những ngày đầu phát triển, chúng ta thích tập trung vào việc viết mã ứng dụng thay vì dành thời gian cho việc thiết lập môi trường để truy cập các dịch vụ AWS. Việc thiết lập môi trường phát triển để sử dụng các dịch vụ này tốn nhiều thời gian và phát sinh chi phí không mong muốn với AWS.

    Để tránh bị sa lầy bởi những tác vụ thông thường này, chúng ta có thể sử dụng LocalStack để phát triển và kiểm tra các ứng dụng của mình với các triển khai mô phỏng của các dịch vụ này.


    Tại sao nên dùng LocalStack?

    Chúng ta thường hay sử dụng phương thức dummy như mock, fake, proxy object để chạy Unit test cho ứng dụng với việc sử dụng các liên kết bên ngoài như database, aws, …

    Với LocalStack, chúng ta sẽ giả lâp môi trường trường AWS phía local. LocalStack hỗ trợ:

    • Chạy các ứng dụng tương tác với các dịch vụ AWS ở phía local mà không cần kết nối môi trường thật.

    • Tránh sự phức tạp của cấu hình AWS và tập trung vào phát triển.

    • Tích hợp chạy thử nghiệm với CI / CD pipeline.

    • Cấu hình và thử nghiệm các kịch bản lỗi.


    Cách sử dụng LocalStack?

    Việc sử dụng LocalStack của chúng tôi tập trung vào hai nhiệm vụ:

    • Chạy LocalStack.
    • Ghi đè URL điểm cuối AWS bằng URL của LocalStack.

    LocalStack thường chạy bên trong vùng chứa Docker, nhưng thay vào đó chúng ta cũng có thể chạy nó dưới dạng ứng dụng Python.


    Chạy Localstack với Python

    Trước tiên, chúng ta cài đặt LocalStack bằng pip:

    pip install localstack
    

    Sau đó, chúng ta bắt đầu localstack bằng lệnh start:

    localstack start
    

    Thao tác này sẽ khởi động LocalStack bên trong vùng chứa Docker.


    Chạy Localstack với Docker

    Bạn cũng có thể sử dụng tệp docker-compose.yml từ Github và sử dụng lệnh này (hiện yêu cầu docker-compile phiên bản 1.9.0+):

    docker-compose up
    

    Tùy chỉnh LocalStack

    Mặc định của LocalStack là tạo ra tất cả các dịch vụ được hỗ trợ với mỗi dịch vụ trong số chúng đang lắng nghe trên cổng 4566. Chúng ta có thể ghi đè hành vi này của LocalStack bằng cách thiết lập một vài biến môi trường.

    Port mặc định 4566 có thể được ghi đè bằng cách đặt biến môi trường EDGE_PORT. Chúng tôi cũng có thể định cấu hình LocalStack để tạo ra một nhóm dịch vụ hạn chế bằng cách đặt danh sách tên dịch vụ được phân tách bằng dấu phẩy làm giá trị cho biến môi trường SERVICES:

    version: '2.1'
    
    services:
      localstack:
        container_name: "${LOCALSTACK_DOCKER_NAME-localstack_main}"
        image: localstack/localstack
        ports:
          - "4566-4599:4566-4599"
          - "${PORT_WEB_UI-8080}:${PORT_WEB_UI-8080}"
        environment:
          - SERVICES=s3,dynamodb
    

    Trong file docker-compose.yml này, chúng ta đặt biến môi trường SERVICES thành tên của dịch vụ chúng ta muốn sử dụng trong ứng dụng của mình (S3 và DynamoDB).


    Kết nối với LocalStack

    Chúng ta truy cập các dịch vụ AWS thông qua AWS CLI hoặc từ các ứng dụng của chúng ta bằng AWS SDK.

    AWS SDK và CLI là một phần không thể thiếu trong bộ công cụ của chúng ta để xây dựng ứng dụng với các dịch vụ AWS. SDK cung cấp các thư viện máy khách bằng tất cả các ngôn ngữ lập trình phổ biến như Java, Node js hoặc Python để truy cập các dịch vụ AWS khác nhau.

    Cả AWS SDK và CLI đều cung cấp tùy chọn ghi đè URL của AWS API. Chúng ta thường sử dụng điều này để chỉ định URL của máy chủ proxy của chúng ta khi kết nối với các dịch vụ AWS từ phía sau máy chủ proxy của công ty. Chúng ta sẽ sử dụng tính năng tương tự này trong môi trường local của chúng tôi để kết nối với LocalStack.

    Chúng ta thực hiện việc này trong AWS CLI bằng các lệnh như sau:

    aws --endpointurl http://localhost:4566 s3 ls
    

    Việc thực thi lệnh này sẽ gửi các yêu cầu đến URL của LocalStack được chỉ định làm giá trị của tham số dòng lệnh URL điểm cuối (localhost trên port 4566) thay vì đến AWS thật.

    Chúng ta sử dụng cách tiếp cận tương tự khi sử dụng SDK:

    URI endpointOverride = new URI("http://localhost:4566");
    S3Client s3 = S3Client.builder()
      .endpointOverride(endpointOverride )  // <-- Overriding the endpoint
      .region(region)
      .build();
    

    Ở đây, chúng ta đã ghi đè điểm cuối AWS của S3 bằng cách cung cấp giá trị của URL của LocalStack làm tham số cho phương thức endpointOverride trong lớp S3ClientBuilder.

    Toàn bộ bài viết là giới thiệu về framework LocalStack. Trong bài viết sau, mình sẽ hướng dẫn mọi người cách tích hợp nó vào source code như nào nhé.

  • Upload file dung lượng lớn tới S3 với Multipart và Presign-url

    Upload file dung lượng lớn tới S3 với Multipart và Presign-url

    Nếu như bình thường ta thực hiện single upload tới s3 thì sẽ có 2 cách sau:

    • Upload file lên s3 qua server của chúng ta: Cái này thì reject vì chúng ta phải tốn thời gian upload lên server của mình rồi từ đó mới upload lên S3. Với large file thì gần như là không nên.

    • Upload từ client bằng cách sử dụng presign-url. Thay vì phải qua server trung gian thì chúng ta upload thẳng lên S3. Tốc độ cải thiện rất nhiều vì cách 1 đa phần thời gian tốn ở khâu upload lên server của chúng ta.

    Nhưng nói gì thì nói single upload to S3 thì sẽ có những hạn chế sau dưới Maximum size chỉ là 5GB . Điều này thực sự hạn chế. Hơn nữa AWS cũng suggest chúng ta là file >100MB thì nên sử dụng Multipart Upload . Vậy ưu điểm chính của nó là:

    • Maximum size là 5TB
    • Tốc độ upload tốt hơn

    Điều đó mình lựa chọn multipart và presign-url cho bài toán upload file dung lượng lớn. Server ở đây mình sử dụng là python. Client thì dùng angular.

    part1

    Server

    Phần server này mình sử dụng kiến trúc serverless để triển khai với ngôn ngữ Python Flask trên môi trường AWS Cloud

    Các bạn tham khảo project trên Github của mình để hiểu cách setup nhé.

    Phần Server chủ yếu sẽ có 3 api chính:

    • Đầu tiên là api start-upload, với logic là request tới s3 để lấy uploadId

      @app.route("/start-upload", methods=["GET"])
      def start_upload():
          file_name = request.args.get('file_name')
          response = s3.create_multipart_upload(
              Bucket=BUCKET_NAME, 
              Key=file_name
          )
      
          return jsonify({
              'upload_id': response['UploadId']
          })
      
    • Tiếp theo là api get-upload-url để lấy presignurl cho từng part của file khi upload

      @app.route("/get-upload-url", methods=["GET"])
      def get_upload_url():
           file_name = request.args.get('file_name')
           upload_id = request.args.get('upload_id')
           part_no = request.args.get('part_no')
           signed_url = s3.generate_presigned_url(
               ClientMethod ='upload_part',
               Params = {
                   'Bucket': BUCKET_NAME,
                   'Key': file_name, 
                   'UploadId': upload_id, 
                   'PartNumber': int(part_no)
               }
           )
      
           return jsonify({
               'upload_signed_url': signed_url
           })
      
    • Cuối cùng đây là api để kiểm tra xem việc upload đã hoàn thành chưa.

       @app.route("/complete-upload", methods=["POST"])
       def complete_upload():
           file_name = request.json.get('file_name')
           upload_id = request.json.get('upload_id')
           print(request.json)
           parts = request.json.get('parts')
           response = s3.complete_multipart_upload(
               Bucket = BUCKET_NAME,
               Key = file_name,
               MultipartUpload = {'Parts': parts},
               UploadId= upload_id
           )
           
           return jsonify({
               'data': response
           })
      

    Client

    Phần client này mình dùng Angular để triển khai 1 trang web upload file đơn giản.

    Các bạn tham khảo project trên Github của mình để hiểu cách setup nhé.

    Khi có action upload đầu tiên ta sẽ gọi tới function uploadMultipartFile. Function uploadMultipartFile có chức năng với các step sau:

    • Call api start-upload để lấy được uploadId.

      const uploadStartResponse = await this.startUpload({
          fileName: file.name,
          fileType: file.type
      });
      
    • Split file upload thành các chunks, ở đây chia thành 10MB/chunk nhé. Ở đây mình chia là 10MB vì minimum size của Multipart Upload là 10MB.

    • Thực hiện call api get-upload-url để lấy preSignurl cho từng chunk ở trên.

    • Upload các chunks bằng signurl.

    • Khi tất cả các chunks upload xong sẽ thực hiện call api complete-upload để xác nhận với S3 mình đã upload đầy đủ các part. Done.

      try {
          const FILE_CHUNK_SIZE = 10000000; // 10MB
          const fileSize = file.size;
          const NUM_CHUNKS = Math.floor(fileSize / FILE_CHUNK_SIZE) + 1;
          let start, end, blob;
      
          let uploadPartsArray = [];
          let countParts = 0;
      
          let orderData = [];
      
          for (let index = 1; index < NUM_CHUNKS + 1; index++) {
            start = (index - 1) * FILE_CHUNK_SIZE;
            end = (index) * FILE_CHUNK_SIZE;
            blob = (index < NUM_CHUNKS) ? file.slice(start, end) : file.slice(start);
      
            // (1) Generate presigned URL for each part
            const uploadUrlPresigned = await this.getPresignUrl({
              fileName: file.name,
              fileType: file.type,
              partNo: index.toString(),
              uploadId: uploadStartResponse.upload_id
            });
      
            // (2) Puts each file part into the storage server
      
            orderData.push({
              presignedUrl: uploadUrlPresigned.upload_signed_url,
              index: index
            });
      
            const req = new HttpRequest('PUT', uploadUrlPresigned.upload_signed_url, blob, {
              reportProgress: true
            });
      
            this.httpClient
              .request(req)
              .subscribe((event: HttpEvent<any>) => {
                switch (event.type) {
                  case HttpEventType.UploadProgress:
                    const percentDone = Math.round(100 * event.loaded / FILE_CHUNK_SIZE);
                    this.uploadProgress$.emit({
                      progress: file.size < FILE_CHUNK_SIZE ? 100 : percentDone,
                      token: tokenEmit
                    });
                    break;
                  case HttpEventType.Response:
                    console.log('Done!');
                }
      
                // (3) Calls the CompleteMultipartUpload endpoint in the backend server
      
                if (event instanceof HttpResponse) {
                  const currentPresigned = orderData.find(item => item.presignedUrl === event.url);
      
                  countParts++;
                  uploadPartsArray.push({
                    ETag: event.headers.get('ETag').replace(/[|&;$%@"<>()+,]/g, ''),
                    PartNumber: currentPresigned.index
                  });
                  if (uploadPartsArray.length === NUM_CHUNKS) {
                    console.log(file.name)
                    console.log(uploadPartsArray)
                    console.log(uploadStartResponse.upload_id)
                    this.httpClient.post(`${this.url}/complete-upload`, {
                      file_name: encodeURIComponent(file.name),
                      parts: uploadPartsArray.sort((a, b) => {
                        return a.PartNumber - b.PartNumber;
                      }),
                      upload_id: uploadStartResponse.upload_id
                    }).toPromise()
                      .then(res => {
                        this.finishedProgress$.emit({
                          data: res
                        });
                      });
                  }
                }
              });
          }
        } catch (e) {
          console.log('error: ', e);
        }
      

    Có những chú ý sau.

    • Minimum size của Multipart Upload là 10MB. Maximum là 5TB
    • Được upload tối đã 10000 chunks cho 1 file thôi. Ví dụ ở đây mình chia 1 chunk là 10MB thì mình chỉ upload tối đa 200GB. Tương tự 1 chunk là 5GB => upload tối đa là 5TB.

    Demo

    • Cài đặt server với serverless. Vào project serverless và gõ lệnh

      sls deploy
      

      Có những chú ý sau:

      • Sau khi cài đặt NodeJS thì cài Serverless framework

        npm i -g serverless
        
      • Cần có account AWS

      • Config ở file serverless đang sử dụng aws profile mà mình đã setup ở local(khanhcd92). Nếu bạn dùng default hoặc cấu hình profile riêng thì hãy thay đổi nó nhé. Cách cấu hình aws profile.

        provider:
          name: aws
          runtime: python3.6
          stage: dev
          region: ap-southeast-1
          profile: khanhcd92
        
    • Thay đường dẫn API endpoint từ output của cài đặt server cho url trong class UploadService của project web

    • Cài đặt thư viện trong project web

      npm i
      
    • Chạy web angular ở local với lệnh

      npm start
      

      part2

    • Kéo file cần upload vào trình duyệt. Chỗ này mình sẽ dùng 1 file có dụng lượng khoảng 75M để upload.

      part3

    • Kiểm tra kết quả trên S3 part4

    Chi tiết source code các bạn xem ở đây nhé.

  • Thiết kế khả năng phục hồi cho một trang web tĩnh trên AWS Cloud

    Thiết kế khả năng phục hồi cho một trang web tĩnh trên AWS Cloud

    Khi bạn thiết kế một hệ thống thì bạn nên có suy nghĩ rằng không hệ thống nào có thể đảm bảo vận hành trơn chu mà không có vấn đề gì xảy ra. Hệ thống có thể sập vì rất nhiều lý do. Chúng ta luôn phải có phương án có thể khôi phục lại ngay tức thì để độ tổn thất cho khách hàng và công ty ở mức thấp nhất. Đặc biệt là các hệ thống trên Cloud.

    Werner Vogels, CTO & VP, Amazon says:

    Everything fails all the time.

    Bài viết hôm nay mình sẽ giới thiệu về cách thiết kế 1 trang web tĩnh trên S3 mà đảm bảo khả năng hồi phục(Resiliency).

    Khả năng phục hồi (Resiliency) là gì?

    Khả năng phục hồi là khả năng hệ thống phục hồi sau lỗi do hệ thống quá tải, bị tấn công, các lỗi liên quan phần cứng hoặc phần mềm.

    Amazon S3 rất khả dụng và có khả năng phục hồi cao đối với sự cố theo vùng nhưng liệu trang web có khả năng chống chịu với thảm họa khu vực như nguồn điện, thời tiết.,? đám mây có những cách tốt hơn để giúp cung cấp tính liên tục cho hoạt động kinh doanh và đây là một trong những cách.

    Thiết kế khả năng phục hồi cho một trang web tĩnh trên AWS Cloud

    s3dr

    Các dịch vụ sử dụng

    • Route 53
    • CloudFront
    • S3
    • IAM

    Triển khai

    Do chưa có domain có sẵn nên bước triển khai này mình sẽ chỉ sử dụng các dịch vụ:

    • CloudFront
    • S3
    • IAM

    Bước 1: Tạo S3 bucket name static-web-sample-s3 với region Singapore và enable tính năng Versioning. Chi tiết

    Bước 2: Tạo S3 bucket name static-web-sample-dr-s3 với region Tokyo và enable tính năng Versioning Chi tiết

    Ghi chú: Ở hướng dẫn này, web chính của mình triển khai lưu trên S3 với region Singapore, và web khôi phục khi xảy ra thảm hoạ sẽ được lưu trên S3 với region Tokyo.

    Bước 3: Cấu hình replication giữa 2 bucket name

    • Ở bucket name static-web-sample-s3 chọn Managemet tab. Tiếp theo, ở phần Replication rules chọn Create replication rule.

      • Nhập rule name rep1

      • Phần rule scope thì chọn option thứ 2 rep2

      • Ấn Browse S3 xong chọn đến bucket name static-web-sample-dr-s3 rep3

      • Phần IAM role, chọn Create new role rep4

      • Phần Additional replication options thì không chọn gì cả rồi ấn Save rep5

      • Kết quả sau khi cấu hình xong rep6

    Bước4: Tạo 1 trang page index.html đơn giản rồi upload nó lên S3 bucket name

    <!DOCTYPE html>
    <html lang='en'>
    
    <head>
        <meta charset='UTF-8'>
        <title>Static HTML</title>
    </head>
    
    <body>
        <div class="container">
            <h1>Hey there</h1>
        </div>
    </body>
    
    </html>
    

    Bước 5: Tạo CloudFront liên kết với S3 bucket name static-web-sample-s3. Chi tiết

    origin

    Ghi chú: Sử dụng OAI cho CloudFront access đến S3 và chọn Yes, update the bucket policy

    Bước 6: Tạo thêm origin của CloudFront liên kết tới S3 bucket name static-web-sample-dr-s3 dro2

    Ghi chú: Sử dụng OAI cho CloudFront access đến S3 và chọn Yes, update the bucket policy

    Bước 7: Tạo Origin Group của CloudFront. Nhập thông như hình bên dưới.

    • Chọn Create origin group og1
    • Chọn các origin cần sử dụng rồi thêm vào. Chú ý origin web chính sẽ được đặt lên đầu tiên. og2

    Bước 8: Cập nhật Behavior default của CloudFront Vì behavior default của CloudFront đang liên kết với origin của S3 bucket name static-web-sample-s3, nên chúng ta sẽ cần cập nhật sang sử dụng origin group.

    • Chọn Behavior Default rồi chọn Edit beha1
    • Ở màn hình Edit, cập nhật origin sang origin group (DR-Group) beha2

    Bước 9: Đợi CloudFront Deploy re1

    Bước 10: Sau khi CloudFront đã deploy xong, thì chúng ta vào brownser và nhập DNS link của CloudFront. web

    Bước 11: Xoá bucket name static-web-sample-s3 hoặc chỉ xoá file index.html trong bucket.

    Bước 12: Bạn vào lại trình duyệt reload lại trang web nhé. Bạn sẽ thấy trang web vẫn hoạt động bình thường dù đã xoá file hoặc S3 bucket chưa source chính. web

  • Triển khai angular web với AWS CDK

    Triển khai angular web với AWS CDK

    Ở phần đầu của series, mình đã giới thiệu AWS CDK là gì. Nay để hiểu rõ nó hơn mình sẽ giới thiệu cách triển khai 1 web angular với AWS CDK.

    Chuẩn bị

    • Chuẩn bị sẵn account AWS
    • Cài đặt NodeJS
    • Cài đặt Typescript: npm i -g typescript
    • Cài đặt AWS CLI
    • Cấu hình AWS client: aws configure. Bạn sẽ cần nhập thông tin access key ID, secret access key và aws region. Chi tiết cấu hình bạn có tìm ở đây
    • Cài đẳt AWS CDK: npm install -g aws-cdk

    Thực hiện

    • Tạo một thư mục static_web_cdk.

    • Trong thư mục này tạo 2 thư mục con, 1 cái angular-sample-web cho website và 1 cái infrastructure cho phần infra.

    Build source code Angular

    • Di chuyển đến thư mục ngular-sample-web. Trong thư mục này mình sẽ download sample từ trên trang chủ Angular về để sử dung Xem chi tiết ở đây.

    web1

    • Di chuyển đến thư mục infrastructure

    • Khơi tạo source code infrastructure: cdk init app --language typescript. Lệnh này sẽ gen ra sample code cho chúng ta.

    web2

    • Mở file bin/infrastructure.ts, thêm stackName và uncomment phần env. Phần env này chúng sẽ sử dụng mặc định như bên dưới.
    #!/usr/bin/env node
    import 'source-map-support/register';
    import * as cdk from '@aws-cdk/core';
    import { InfrastructureStack } from '../lib/infrastructure-stack';
    
    const app = new cdk.App();
    new InfrastructureStack(app, 'InfrastructureStack', {
      /* If you don't specify 'env', this stack will be environment-agnostic.
       * Account/Region-dependent features and context lookups will not work,
       * but a single synthesized template can be deployed anywhere. */
      stackName: 'web-static-stack',
      /* Uncomment the next line to specialize this stack for the AWS Account
       * and Region that are implied by the current CLI configuration. */
      env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
    
      /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
    });
    
    • Bây giờ mở file /lib/infrastructure-stack.ts nơi chúng ta sẽ khai báo các dịch vụ của stack.

    Khai báo các thành phần của Stack

    Để triển khai Angular web static mình sử dụng các dịch vụ CloudFront và S3. CloudFront sẽ access vào private S3 thông qua OAI (Origin Access Identity). Vậy thì Stack cần phải code những gì mọi người theo dõi bên dưới nhé.

    • Cài đặt thư viện @aws-cdk/aws-s3 để sử dụng được dịch vụ s3.
    • Cài đặt thư viện @aws-cdk/aws-cloudfront để sử dụng dịch vụ cloudfront.
    • Cài đặt thư viện @aws-cdk/aws-iam để sử dụng khai báo Policy cho S3
    • Trong file lib/infrastructure-stack.ts, chúng ta sẽ cần import các thư viện phía trên vào. Sau đó ở sau ta sẽ cần khai báo các dịch vụ bên duới dòng comment //The code that defines your stack goes here:
    import * as cdk from '@aws-cdk/core';
    import { BlockPublicAccess, Bucket, BucketEncryption } from '@aws-cdk/aws-s3';
    import { CloudFrontWebDistribution, OriginAccessIdentity } from '@aws-cdk/aws-cloudfront';
    import { PolicyStatement } from '@aws-cdk/aws-iam';
    
    export class InfrastructureStack extends cdk.Stack {
      constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
        super(scope, id, props);
    
        // The code that defines your stack goes here
        // Khai báo S3
        const myBucket = new Bucket(this, "static-website-bucket", {
          bucketName: 'static-web-sample-s3',
          versioned: true,
          encryption: BucketEncryption.S3_MANAGED,
          blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
          websiteIndexDocument: "index.html"
        });
    
        // Khai báo OAI
        const oia = new OriginAccessIdentity(this, 'OIA', {
          comment: "Created by CDK"
        });
    
        // Khai báo Policy cho S3
        const policyStatement = new PolicyStatement();
        policyStatement.addActions('s3:GetObject');
        policyStatement.addResources(`${myBucket.bucketArn}/*`);
        policyStatement.addCanonicalUserPrincipal(oia.cloudFrontOriginAccessIdentityS3CanonicalUserId);
        myBucket.addToResourcePolicy(policyStatement);
    
        // Khai báo CloudFront
        new CloudFrontWebDistribution(this, 'static-website-cf', {
          originConfigs: [
            {
              s3OriginSource: {
                s3BucketSource: myBucket,
                originAccessIdentity: oia
              },
              behaviors: [
                { isDefaultBehavior: true }
              ]
            }
          ]
        });
    
        // Output giá trị DNS của CloudFront
        new CfnOutput(this, 'CloudFrontDNS', {
          value: cfWebStatic.distributionDomainName
        })
      }
    }
    
    
    • Tiêp theo chúng ta sẽ phần khai báo deploy. Bạn sẽ cần cài thư viện @aws-cdk/aws-s3-deployment và thêm import * as s3Deployment from '@aws-cdk/aws-s3-deployment' vào file stack và khai báo deploy như bên dưới:
    const deployment = new s3Deployment.BucketDeployment(this, "deployStaticWebsite", {
          sources: [s3Deployment.Source.asset("../angular-sample-web/dist")],
          destinationBucket: myBucket,
        });
    
    • Thuộc tính s3Deployment.Source.asset("../angular-sample-web/dist") là nơi mọi người sẽ cần update tới đúng vị trị source code mình đang dùng.

    Triển khai

    Bây giờ chúng ta sẽ cần triển khai stack này tới AWS:

    • Di chuyển đến thư mục angular-sample-web

    • Cài đặt các thư viện npm i

    • Build Angular app ng build --prod. Lúc này bundle source code sẽ được gen ra trong thư mục dist

    • Sau khi build xong Angular app thì di chuyển đến thư mục infrastructure

    • Trên cửa sổ command gõ lệnh: cdk boostrap. Thư mục cdk.out sẽ được tạo ra. Nó sẽ bao gồm code phần website và infrastructure template.

    The first time you deploy an AWS CDK app into an environment (account/region), you’ll need to install a “bootstrap stack”. This stack includes resources that are needed for the toolkit’s operation. For example, the stack includes an S3 bucket that is used to store templates and assets during the deployment process.

    • Chạy lệnh cdk synth. Nó sẽ tổng hợp lại CloudFormation template cho stack.
    • Chạy lệnh cdk deploy
    • Nhập y để tiếp tục triển khai
    • Sau khi đã hoàn thành, chúng ta tới console của dịch vụ CloudFormation. Chúng ta sẽ thấy có 2 stack được tạo ra là CDKToolkit và web-static-stack

    web4

    • Bây giờ vào tiếp stack web-static-stack để lấy link web.

    web4

    • Click vào link CloudFrontDNS trên tab Output của màn hình stack. Bạn sẽ thấy kết quả như bên dưới.

    web5

    NOTE:

    • Khi bạn không dùng nữa thì có thể chạy lệnh cdk destroy để xoá stack.

    • Ví dụ bên trên là dùng cài đặt AWS client với giá trị default. Khi bạn đã thiết lập profile cho AWS thì tham khảo các câu lệnh để chạy cdk bên dưới đây nhé.

      cdk boostrap --profile profile_name
      cdk synth --profile profile_name
      cdk deploy --profile profile_name
      cdk destroy --profile profile_name
      

    Bạn có thể tham khao code ở Github

    Tôi hy vọng nó sẽ có ích. Bạn cũng có thể thay thế Angular với ReactJS hoặc VueJS. Xin vui lòng cho tôi biết phản hồi của bạn.

  • AWS CDK là gì ?

    AWS CDK là gì ?

    Infrastructure as Code

    Khái niệm này đã có rất nhiều bài viết nói về. Nên mình chỉ trích dẫn nội dung chính như bên dưới.

    Infrastructure as code (IaC) is the process of managing and provisioning computer data centers through machine-readable definition files, rather than physical hardware configuration or interactive configuration tools. The IT infrastructure managed by this process comprises both physical equipment, such as bare-metal servers, as well as virtual machines, and associated configuration resources. The definitions may be in a version control system. It can use either scripts or declarative definitions, rather than manual processes, but the term is more often used to promote declarative approaches.

    Để hiểu rõ hơn về IaC, mọi người đọc link bên dưới nhé. https://magz.techover.io/2021/08/19/infrastructure-as-code/

    AWS CDK

    • AWS (AWS CDK) là một framework phát triển phần mềm mã nguồn mở để xác định cơ sở hạ tầng đám mây trong mã và cung cấp nó thông qua AWS CloudFormation.

    • Nó cung cấp tính trừu tượng hướng đối tượng mức cao để xác định tài nguyên AWS một cách phân cấp bằng cách sử dụng sức mạnh của các ngôn ngữ lập trình hiện đại. Sử dụng thư viện cấu trúc cơ sở hạ tầng của CDK, bạn có thể dễ dàng đóng gói các phương pháp hay nhất của AWS vào định nghĩa cơ sở hạ tầng của mình và chia sẻ nó mà không cần lo lắng về logic bản soạn sẵn.

    • AWS CDK có hỗ trợ hạng nhất cho TypeScript, JavaScript, Python, Java và C #.

    cdk

    Tại sao nên sử dụng AWS CDK ?

    • AWS CDK cho phép phát triển hạ tầng với code và triển khai nó lên AWS qua CloudFormation. Bạn sẽ nhận được toàn bộ lợi ích của CloudFormation.

    • Sử dụng các ngôn ngữ lập trình, công cụ và quy trình làm việc quen thuộc – AWS CDK cho phép bạn phát triển cơ sở hạ tầng bằng TypeScript, Python, Java và .NET.

    • Triển khai cơ sở hạ tầng và runtime code cùng nhau – AWS CDK cho phép bạn tham chiếu các runtime code của mình trong cùng một dự án với cùng một ngôn ngữ lập trình

    • Sử dụng logic (câu lệnh if, vòng lặp for, v.v.) khi xác định cơ sở hạ tầng của bạn

    • Sử dụng các kỹ thuật hướng đối tượng để tạo mô hình hệ thống của bạn

    • Xác định các nội dung tóm tắt ở cấp độ cao, chia sẻ và xuất bản chúng cho nhóm, công ty hoặc cộng đồng của bạn

    • Tổ chức dự án của bạn thành các mô-đun hợp lý

    • Chia sẻ và sử dụng lại cơ sở hạ tầng của bạn làm thư viện

    • Kiểm tra mã cơ sở hạ tầng của bạn bằng các giao thức tiêu chuẩn ngành

    cdk

    Cấu trúc CDK Code

    Apps

    • Chạy chương trình
    • Render and triển khai cfn templates

    Stacks

    • Đơn vị triển khai
    • Gồm thông tin region và account id

    Constructs

    • Đại diện các tài nguyên AWS
    • Có thể hình thành cấu trúc cây phân cấp

    cdk

    • Chúng là các tệp bao gồm mọi thứ cần thiết để triển khai ứng dụng của bạn lên môi trường đám mây.

    • Đơn vị triển khai trong AWS CDK được gọi là stack. Tất cả các tài nguyên AWS được xác định trong phạm vi của một stack, trực tiếp hoặc gián tiếp, đều được cung cấp như một đơn vị duy nhất. Vì AWS CDK stack được triển khai thông qua AWS CloudFormation stack nên chúng có những hạn chế giống như trong AWS CloudFormation.

    • Construct là các khối xây dựng cơ bản của ứng dụng AWS CDK. Một cấu trúc đại diện cho một "thành phần đám mây" và đóng gói mọi thứ AWS CloudFormation cần để tạo thành phần đó.

    CDK Lifecycle

    • CDK (với sự trợ giúp của AWS CLI) biên dịch App của nó (App sẽ chạy chương trình có chứa các StackConstruct)
    • CDK tổng hợp mã để chuyển nó thành mẫu CloudFormation. Khi bạn chạy cdk, hãy triển khai mã mà chúng tôi đã đặt trong lib / <code_name> .ts được phân tích cú pháp bởi CDK Framework, sau đó tạo một mẫu CloudFormation nằm trong thư mục cdk.out và cuối cùng triển khai nó vào CloudFormation.
    • Mẫu CloudFormation được triển khai trong tài khoản AWS và chạy để tạo tất cả các thành phần AWS

    cdk

    AWS workflow

    cdk

    Đây chỉ là phần giới thiệu về AWS CDK trong chuỗi bài viết về AWS CDK, mình sẽ làm demo ở bài viết sau nhé. Mong mọi người ủng hộ.

    Tham khảo: https://dev.to/aws-builders/everything-about-aws-cdk-489m

  • AWS Certificate Manager là gì ?

    AWS Certificate Manager là gì ?

    Chứng chỉ SSL miễn phí

    Khi nói đến chứng chỉ SSL miễn phí, chắc hẳn bạn sẽ nghĩ ngay đến Let’s Encrypt. Nhưng thật ra thì, AWS Certificate Manager cũng cung cấp chứng chỉ SSL miễn phí. Bản chất ACM là một dịch vụ sẵn có miễn phí, cung cấp chức năng quản lý chứng chỉ SSL. Bạn không cần phải trả thêm gì khác ngoài chi phí cho các tài nguyên AWS mà bạn tạo ra để chạy ứng dụng.

    AWS Certificate Manager

    Với AWS Certificate Manager, bạn có thể nhanh chóng yêu cầu một chứng chỉ và triển khai chứng chỉ đó trên các tài nguyên AWS được tích hợp ACM( chẳng hạn như các dịch vụ Elastic Load Balancer, Amazon CloudFront và API Gateway,…) đồng thời cho phép AWS Certificate Manager xử lý các yêu cầu gia hạn chứng chỉ. Dịch vụ này còn cho phép bạn tạo các chứng chỉ riêng cho các tài nguyên nội bộ trong hệ thống mạng của mình và quản lý vòng đời của chứng chỉ một cách tập trung.

    acm

    Cách cấu hình

    Ở phần này mình sẽ không nói cách cấu hình như nào vì các bạn có thể tham khảo trên tài liệu của AWS đã có hướng dẫn rất đầy đủ. Mình sẽ tập trung nói về các điểm lợi điểm hại của các cách cấu hình.Với ACM chúng ta sẽ có 3 cách cấu hình chính:

    Cách 1: Import certificate

    Cách này chỉ dùng khi chúng ta đăng kí domain từ bên thứ 3 khác không phải là AWS. Với domain mà bạn tự mua thì có thể dễ dàng sẽ lấy được certificate và key chain. Nhưng nếu domain của khách hàng của bạn thì họ sẽ rất khó để cung cấp chúng cho bạn. Đôi khi họ cũng chỉ là nguời mới không lưu trữ cái đó chả hạn. Đặc biệt khi domain hết hạn cần gia hạn lại thì họ cũng phải cung cấp lại các thông tin certificate mới của domain để mình import lại. Nó sẽ làm tốn thêm công sức nhiều. Cho nên cách này mình không khuyến cáo sử dụng.

    case1

    • Certificate body: Certificate issued for a particular domain name
    • Certificate private key: Unencrypted (created without a password) private key.
    • Certificate chain: CA bundle of the issuing Certificate Authority

    Cách cấu hình: https://docs.aws.amazon.com/acm/latest/userguide/import-certificate-api-cli.html

    Cách 2: Request a certificate with email

    Cách này chúng ta sẽ cần điền thông tin email mà mình hoặc khách hàng đã dùng để đăng kí domain từ bên thứ 3 khác. Đôi khi khách hàng họ sẽ quên mất email đăng kí domain là email nào. Hoặc đơn giản họ chỉ là người mới tiếp quản công việc không thể nào biết được. Đặc biệt cách này cũng cần xử lý khi domain hết hạn mà gia hạn lại. Cho nên cách này mình không khuyến cáo sử dụng.

    case1

    Cách cấu hình: https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html

    Cách 3: Request a certificate with DNS validation

    Sau khi đã hoàn thành các bước thì chúng ta sẽ cần export DNS từ phần đăng ký ACM. Chúng ta cần thêm thông tin này trên trang quản trị domain. Với cách này sẽ đảm bảo khách hàng dễ xử lý với việc thêm nó và an toàn cho thông tin certificate domain của khách hàng.

    case1

    Ví dụ: Hệ thống khách hàng xây dựng trên AWS, nhưng lại đăng kí domain với Godday. Chúng ta có thể vào GoDaddy thêm 1 record với type là CNAME. Sau đó lấy các thông tin export từ ACM để điền vào. Hoàn thành bước này thì chúng ta đã đảm bảo phần có thể active được ACM. Ngoài ra với cách này chúng ta sẽ không cần lo lắng về việc khi domain hết hạn và gia hạn lại. Mình sẽ không phải làm gì thêm từ phía ACM. Mình khuyến khích nên sử dụng cách này. Nó sẽ vừa dễ dàng cho mình và cho khách có thể xử lý được mà không bị phụ thuộc certificate của domain hoặc email đã đăng kí.

    godady

    Cách cấu hình: https://docs.aws.amazon.com/acm/latest/userguide/dns-validation.html

  • Sử dụng Nginx để truy cập tới một Private S3 Bucket

    Sử dụng Nginx để truy cập tới một Private S3 Bucket

    Bài toán

    Mình được giao nhiệm vụ phát triển một ứng dụng quản lý chi tiêu cho khách hàng. Thông tin các hoá đơn và ảnh trong ứng dụng sẽ được lưu trên S3. Khách hàng có yêu cầu có thể xem được ảnh lưu trên S3 bằng domain của họ thay vì bằng đường link trực tiếp từ S3. Hệ thống Backend của họ đang sử dụng Nginx với Python Flask và mong muốn không sử dụng đến dịch vụ CloudFront.

    Giải pháp

    Sau hồi nguyên cứu mình đã tìm ra giải pháp là có thể dùng Nginx để truy cập tới một private S3 bucket.

    Để có thể demo được giải pháp nên mình sẽ lược bỏ hệ thống cho nó đơn giản hơn so với hệ thống của khách hàng. Đầu tiên giả sử hệ thống trên AWS đã được cấu hình như bên dưới:

    • VPC(10.0.0.0/16) với public subnet và private subnet
    • VPC Endpoint cho s3
    • A static web on ECS Fargate with Nginx
    • 1 trang web tĩnh được triển khai trên dịch vụ ECS Fargate với việc dùng Nginx
    • 1 private S3 bucket để lưu ảnh của trang web.

    Alt Text

    Kết quả sau khi cấu hình xong chúng ta sẽ có 1 trang web như bên dưới. Nhưng hình ảnh mọi nguời thấy là hình ảnh được tải từ trên mạng. Alt Text

    Bây giờ mình sẽ cần thay thể ảnh trên mạng này với ảnh trên S3. Để làm được điều đó các bạn theo dõi tiếp bên dưới nhé. Alt Text

    Thiết lập Nginx và S3

    1. Thay đổi S3 bucket policy để chỉ cho phép lấy dữ liệu từ VPC Endpoint.

    {
        "Version": "2008-10-17",
        "Id": "PolicyForCloudFrontPrivateContent",
        "Statement": [
            {
                "Sid": "Access-to-specific-VPCE-only",
                "Effect": "Allow",
                "Principal": "*",
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::demo-static-s3/*",
                "Condition": {
                    "StringEquals": {
                        "aws:sourceVpce": "vpce-0d92e50f230bc8070"
                    }
                }
            }
        ]
    }
    

    2. Tiếp theo là việc quan trọng nhất, chúng ta sẽ cần cấu hình Nginx để có thể tải ảnh từ private S3

    location ~^/image/(.+)$ {
            resolver 10.0.0.2;
            proxy_pass http://s3-ap-southeast-1.amazonaws.com/demo-static-s3/image/$1; 
    }
    
    • resolver: Địa chỉ IP của DNS server trong VPC mà mình đã tạo.

      Ví dụ, VPC của mình tạo có CIRD là 10.0.0.0/16. AWS sẽ dùng 5 IPs bên dưới cho các mục đích bên của họ và mình sẽ không thể sử dụng các IP đó.

      • 10.0.0.0: Network address.
      • 10.0.0.1: Reserved by AWS for the VPC router.
      • 10.0.0.2: Reserved by AWS. The IP address of the DNS server is the base of the VPC network range plus two. For VPCs with multiple CIDR blocks, the IP address of the DNS server is located in the primary CIDR. We also reserve the base of each subnet range plus two for all CIDR blocks in the VPC. For more information, see Amazon DNS server.
      • 10.0.0.3: Reserved by AWS for future use.
      • 10.0.0.255: Network broadcast address. We do not support broadcast in a VPC, therefore we reserve this address.

      => Resolver sẽ có giá trị là 10.0.0.2

      Tham khảo: https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Subnets.html

    • proxy_pass: s3 link

      Định dạng: http://s3-ap-southeast-1.amazonaws.com/[main_bucket]/[sub_bucket]/$1

    3. Thay đổi dường dẫn của tải ảnh của trang web

    <div class="container">
        <h1>Hey there</h1>
        <img src="https://cleandevs.com/image/668c1d479e27bb8750823655c83a6c9bd90263f9_hq.jpg"/>
      </div>
    

    4. Cuối cùng mình sẽ deploy lại trang web lên dịch vụ ECS Fargate. Kết quả sẽ như hình bên dưới. Trang web đã hiển thị ảnh được tải từ private S3 bucket

    Alt Text

  • Cách cấu hình Gitlab CI chỉ kiểm tra các file thay đổi với merge request

    Cách cấu hình Gitlab CI chỉ kiểm tra các file thay đổi với merge request

    Chúng ta sẽ thường hay cấu hình chạy kiểm tra code quality cho tất các các nhánh và merge request trên Gitlab CI. Nhưng Gitlab runner job sẽ chạy scan toàn bộ source code nên sẽ mất thời gian đối với merge request. Ngoài ra đôi khi do source code cũ của khách hàng lỏm sẽ dẫn tới lúc nào merge request đó cũng sẽ bị failed.

    Dưới đây là sample về Gitlab CI chạy python lint với cấu hình chạy cho nhánh develop và merge request.

    pylint:
      stage: lint
      before_script:
        - mkdir -p public/badges public/lint
        - echo undefined > public/badges/$CI_JOB_NAME.score
        - pip install pylint-gitlab
      script:
        - pip install -r requirements.txt
        - pylint --rcfile=.pylintrc --exit-zero --output-format=text $(find -type f -name "*.py" ! -path "**/.venv/**") | tee /tmp/pylint.txt
        - sed -n 's/^Your code has been rated at \([-0-9.]*\)\/.*/\1/p' /tmp/pylint.txt > public/badges/$CI_JOB_NAME.score
        - pylint --exit-zero --output-format=pylint_gitlab.GitlabCodeClimateReporter $(find -type f -name "*.py" ! -path "**/.venv/**") > codeclimate.json
        - pylint --exit-zero --output-format=pylint_gitlab.GitlabPagesHtmlReporter $(find -type f -name "*.py" ! -path "**/.venv/**") > public/lint/index.html
      after_script:
        - anybadge --overwrite --label $CI_JOB_NAME --value=$(cat public/badges/$CI_JOB_NAME.score) --file=public/badges/$CI_JOB_NAME.svg 4=red 6=orange 8=yellow 10=green
        - |
          echo "Your score is: $(cat public/badges/$CI_JOB_NAME.score)"
      artifacts:
        paths:
          - public
        reports:
          codequality: codeclimate.json
        when: always
      only:
        - develop
        - merge_requests
      tags:
        - docker
    

    Để giải quyết các vấn đề trên và có thể tập trung vào chất lượng code của các bạn member tốt hơn, chúng ta có thể tạo thêm 1 config mới cho merge request event. Với config mới chỉ cần kiểm tra code quality trên các file thay đổi là được.

    pylint_merge_request:
      stage: lint
      before_script:
        - pip install pylint-gitlab
      script:
        - echo CI_COMMIT_SHA=${CI_COMMIT_SHA}
        - echo CI_MERGE_REQUEST_TARGET_BRANCH_NAME=${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}
        - git fetch origin ${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}
        - FILES=$(git diff --name-only ${CI_COMMIT_SHA} origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME} | grep '\.py'$)
        - echo "Changed files are $FILES"
        - pip install -r requirements.txt
        - pylint --rcfile=.pylintrc --output-format=text $FILES | tee /tmp/pylint.txt
      only:
        - merge_requests
      tags:
        - docker
    

    Hi vọng nó hữu ích với mọi người và có thể apply nó vào dự án của mình.

  • Kiểm soát chi phí sử dụng AWS thông qua việc tạo AWS Budget

    Kiểm soát chi phí sử dụng AWS thông qua việc tạo AWS Budget

    Bạn đã trải nghiệm với các dịch vụ trên AWS, nhưng không may bạn nhận được một bất ngờ không mong muốn trên hóa đơn AWS vào cuối tháng?

    Hoặc có lẽ ai đó trong tổ chức của bạn đã hỏi bạn về việc chi phí AWS tăng cao, có cách nào nhận thông báo chi phí đã sử dụng trên AWS theo hằng ngày được không.

    Đây là việc mà có lẽ hầu hết các kĩ sư cloud đã trải qua.

    AWS Budget sẽ giúp các bạn giải quyết các vấn đề đó. Theo dõi các bước bên dưới để thiết lập nó nhé.


    (1) Đăng nhập vào tài khoản AWS của bạn. Nhập AWS Budgets ở ô tìm kiếm. Sau khi hiển thị kết quả chọn AWS Budgets.

    budget1

    (2) Ở màn hình giao diện Budgets, chọn Create budget.

    budget2

    (3) Ở Step 1, chọn Budget types là Cost budget, sau đó ấn Next step.

    budget3

    (4) Ở Step2, nhập các thông tin budget như hình. Ở chỗ nhập Enter your budgeted amount nên nhập giá trị nhỏ, để Budget sẽ dễ dàng kiểm tra và thông báo cho bạn được thường xuyên. Mình hay để chỗ này tầm 5 hoặc 10$.

    budget4

    (5) Ở Step3, chọn Add an alert threadhold để cài đặt alert.

    budget5

    Trên giao diện Alert, mình sẽ cần nhập thông tin như bên dưới:

    • Threshold: Phần trăm cost sử dụng so với giá trị budget đã set ở Step2 để trigger thông báo. Mình hay để chỗ này tầm 50 hoặc 60.
    • Email recipients: Nhập các email bạn mong muốn để nhận thông báo cost. Các email sẽ cách nhau bởi dấu (,)

    budget6 Sau khi đã nhập xong thì mình chuyển sang step tiếp theo.

    (6) Ở Step4, mình sẽ để default không thay đổi gì cả và chuyển sang step tiếp theo. budget7

    (7) Ở Step5, mình sẽ nhìn lại các cài đặt ở các bước lúc trước. Nếu không có vấn đề gì thì mình chọn Create budget. budget8

    (8) Bạn sẽ nhận email có nội dung như bên dưới. budget9