Tổng hợp cách sử dụng và chức năng của 3 loại Helper Script thường dùng trong AWS CloudFormation

Tổng hợp cách sử dụng và chức năng của 3 loại Helper Script thường dùng trong AWS CloudFormation

avatar

Long Ngo

2025.06.27

Tìm hiểu về 3 loại Helper Script của AWS CloudFormation: cfn-init, cfn-hup, và cfn-signal.

Giới thiệu

Trong bài viết này, chúng ta sẽ tìm hiểu về 3 loại Helper Script của AWS CloudFormation: cfn-init, cfn-hup, và cfn-signal. Các script này hỗ trợ việc xây dựng, quản lý và đồng bộ hóa tài nguyên EC2 trong CloudFormation stack một cách hiệu quả. Bài viết sẽ trình bày chức năng, cách sử dụng, và ví dụ cụ thể cho từng script, dựa trên thông tin từ bài đăng của Saori Kato.

Mục tiêu:

  • Hiểu rõ chức năng của từng helper script.
  • Biết cách áp dụng chúng trong template CloudFormation.
  • Minh họa bằng các ví dụ thực tế.

Lab Introduction

  • AWS experience: Beginner / Intermediate
  • Time to complete: 30 phút
  • AWS Region: Tokyo (ap-northeast-1) hoặc bất kỳ region nào hỗ trợ các dịch vụ sử dụng
  • Cost to complete: Free Tier eligible (sử dụng instance t3.micro và các tài nguyên cơ bản)
  • Services used: EC2, CloudFormation, Security Group

Architecture Diagram

  • Dưới đây là kiến trúc tổng quan của ứng dụng sau khi hoàn thành triển khai:

Giới thiệu Helper Scripts

AWS CloudFormation cung cấp 3 helper script để hỗ trợ quản lý tài nguyên EC2 trong stack:

  1. cfn-init: Dùng để cài đặt các gói phần mềm, tạo file, và khởi động dịch vụ dựa trên metadata được định nghĩa trong template.
  2. cfn-hup: Theo dõi thay đổi trong metadata và thực thi hành động tùy chỉnh khi phát hiện thay đổi.
  3. cfn-signal: Gửi tín hiệu đến CloudFormation để báo hiệu trạng thái hoàn thành của tài nguyên, thường dùng với CreationPolicy hoặc WaitCondition.

Tasks Details

  1. UserData và Hạn chế của nó (Bootloading)
  • 1.1. Access CloudFormation
  • 1.2. Deploy stack (UserData)
  • 1.3. Đặt tên Specify stack details
  • 1.4. Configure stack options
  • 1.5. Submit stack
  • 1.6. Đợi đến khi Stack sẵn sàng
  • 1.7. Kiểm tra trạng thái Webapp
  • 1.8. Vấn đề Bootloading
  1. UserData và Hạn chế của nó ( Application Update)
  • 2.1. Update Stack
  • 2.2. Vấn đề Application Update
  • 2.3. Clean up UserData
  1. Deploy stack sử dụng cfn-signal
  • 3.1. Deploy Stack
  • 3.2. Kiểm tra Ec2, application
  • 3.3. Test với vấn đề Application Update
  • 3.4. Clean up
  1. Deploy stack sử dụng cfn-init
  • 4.1. Deploy stack
  • 4.2. Kiểm tra Ec2, application
  • 4.3. Xử lý vấn đề (thủ công)
  • 4.4. Phân tích vấn đề
  • 4.5. Clean up
  1. Deploy stack sử dụng cfn-hup
  • 5.1 Deploy stack
  • 5.2. Kiểm tra Ec2, application
  • 5.3 Clean up

1.UserData và Hạn chế của nó (Bootloading)

Để chứng minh được lợi ích của việc sử dụng các cfn thì mình sẽ đi từ vấn đề trước các bạn nhé.

1.1 Access CloudFormation

1.2. Deploy stack (UserData)

  • Mình đã chuẩn bị sẵn một cloudformation template sử dụng

  • Upload template for UserData case

  • Download template : 1_userdata.yaml

  • Nội dung:

    Parameters:
      LatestAmiId:
        Description: "AMI for EC2"
        Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
        Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
      Message:
        Description: "Message for HTML page"
        Default: "UserData first problem!!!"
        Type: "String"
    Resources:
      InstanceSecurityGroup:
        Type: 'AWS::EC2::SecurityGroup'
        Properties:
          GroupDescription: Enable SSH and HTTP access via port 22 IPv4 & port 80 IPv4
          DeletionPolicy: Delete
          SecurityGroupIngress:
            - Description: 'Allow SSH IPv4 IN'
              IpProtocol: tcp
              FromPort: '22'
              ToPort: '22'
              CidrIp: '0.0.0.0/0'
            - Description: 'Allow HTTP IPv4 IN'
              IpProtocol: tcp
              FromPort: '80'
              ToPort: '80'
              CidrIp: '0.0.0.0/0'
      Bucket:
        Type: 'AWS::S3::Bucket'
        DeletionPolicy: Delete
      Instance:
        Type: 'AWS::EC2::Instance'
        Properties:
          DeletionPolicy: Delete
          InstanceType: "t2.micro"
          ImageId: !Ref "LatestAmiId"
          SecurityGroupIds: 
            - !Ref InstanceSecurityGroup
          Tags:
            - Key: Name
              Value: SOA-Lab4-UserData Test
          UserData:
            Fn::Base64: !Sub |
              #!/bin/bash -xe
              yum -y update
              yum -y upgrade
              # simulate some other processes here
              sleep 300
              # Continue
              yum install -y httpd
              systemctl enable httpd
              systemctl start httpd
              echo "<html><head><title>Amazing test page</title></head><body><h1><center>${Message}</center></h1></body></html>" > /var/www/html/index.html
    

1.3. Đặt tên Specify stack details

  • Đặt tên stackname : CMPUserData
  • Phần message : "UserData first problem!!!" (để default)

1.4. Configure stack options

  • Cứ để default và Next

1.5. Submit stack

1.6. Đợi đến khi sẵn sàng

1.7. Kiểm tra trạng thái Webapp

  • Vào resource click Instance

  • Confirm state là Running và copy public ip

  • Sử dụng ip để access thì kết quả không load được như bên dưới

  • Đợi 1 vài phút sau thì load được trang

1.8. Vấn đề Bootloading

  • Ở ví dụ trên chúng ta thấy server của chúng ta chưa sẵn sàng nhưng trạng thái stack đã báo là hoàng thành.
  • Điều này có nghĩa trạng thái thực tế của application đang không được đồng bộ với trạng thái của cloudformation
  • Điều này dẫn đến rất nhiều hậu quả:
      1. Trải nghiêm người dùng sẽ tệ vì người dùng có thể sẽ không access được service cho đến khi app thật sự được load
      1. Khi triển khai kiến trúc phức tạp nhiều thành phần sẽ gặp khó khăn vì không biết được tình trang ready của application

2. UserData và Hạn chế của nó ( Application Update)

2.1. Update Stack

  • Tiến hành update stack ở trạng thái hiện tại

  • Chọn exiting template

  • Cập nhật message hiển thị của application

      UserData second problem!!!
    

  • Ấn Next

  • Ấn Submit

  • Đợi update hoàn thành

  • Kiểm tra trạng thái Webapp

  • Vào resource click Instance

  • Sử dụng ip để access

2.2 Vấn đề Application Update

  • Chúng ta có thể dễ dàng nhận thấy là dù instance có thay đổi ip nhưng application data cũng không được cập nhật

  • Để giải quyết vấn đề này chúng ta sẽ dùng cfn-signal

  • Nhưng trước hết chúng ta cần clean up resource đã

2.3 Clean up UserData

  • Chọn Delete stack

3. Deploy stack sử dụng cfn-signal

3.1 Deploy Stack

  • Mình đã chuẩn bị template sử dụng cfn-signal như bên dưới .

  • Link download : 2_userdata_with_signal.yaml

  • Các bước deploy giống hệt bên trên nên mình không đi lại.

  • Mình sẽ note một số điểm khác biệt và cải thiện

    Parameters:
      LatestAmiId:
        Description: "AMI for EC2"
        Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
        Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
      Message:
        Description: "Message for HTML page"
        Default: "UserData first problem is resolved by signal"
        Type: "String"
    Resources:
      InstanceSecurityGroup:
        Type: 'AWS::EC2::SecurityGroup'
        Properties:
          GroupDescription: Enable SSH and HTTP access via port 22 IPv4 & port 80 IPv4
          SecurityGroupIngress:
            - Description: 'Allow SSH IPv4 IN'
              IpProtocol: tcp
              FromPort: '22'
              ToPort: '22'
              CidrIp: '0.0.0.0/0'
            - Description: 'Allow HTTP IPv4 IN'
              IpProtocol: tcp
              FromPort: '80'
              ToPort: '80'
              CidrIp: '0.0.0.0/0'
      Bucket:
        Type: 'AWS::S3::Bucket'
      Instance:
        Type: 'AWS::EC2::Instance'
        CreationPolicy:
          ResourceSignal:
            Timeout: PT15M
        Properties:
          InstanceType: "t2.micro"
          ImageId: !Ref "LatestAmiId"
          SecurityGroupIds: 
            - !Ref InstanceSecurityGroup
          Tags:
            - Key: Name
              Value: CMP-UserData Test
          UserData:
            Fn::Base64: !Sub |
              #!/bin/bash -xe
              yum -y update
              yum -y upgrade
              # simulate some other processes here
              sleep 300
              # Continue
              yum install -y httpd
              systemctl enable httpd
              systemctl start httpd
              echo "<html><head><title>Amazing test page</title></head><body><h1><center>${Message}</center></h1></body></html>" > /var/www/html/index.html
              /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackId} --resource Instance --region ${AWS::Region}
    
  • Điểm thay đổi

  • Mình đã thêm block

        CreationPolicy:
          ResourceSignal:
            Timeout: PT15M
    
  • Dòng cuối cùng trong userdata mình thêm dòng này. Command này sẽ sử dụng cfn-signal để báo cho cfn biết là application đã ready.

    /opt/aws/bin/cfn-signal -e $? --stack AWS-StackId --resource Instance --region AWS-Region
    
  • Thử luôn nhé !

3.2. Kiểm tra Ec2, application

  • Kiểm tra tình trạng ec2 instance khi mới deploy

  • Khác với chỉ dùng mỗi UserData stack sẽ không ready ngay mà sẽ phải đợi lâu hơn (khoảng 5-6 phút)

  • Application Được khởi động thành công đồng bộ với stack báo finish

3.3 Test với vấn đề Application Update

  • Thử update stack với UserData second problem!!!

  • chúng ta có thể thấy vấn đề vẫn còn nguyên

3.4 Clean up

  • Làm tương tự 2.3

4. Deploy stack sử dụng cfn-init

4.1 Deploy stack

  • Ngoài sử dụng UserData chúng ta có thể dùng cfn-init để cài đặt các ứng dụng bên trong Ec2 instance.

  • Mình đã chuẩn bị template như bên dưới .

  • Các bước deploy giống hệt bên trên nên mình không đi lại.

  • Mình sẽ note một số điểm khác biệt và cải thiện

    Parameters:
      LatestAmiId:
        Description: "AMI for EC2"
        Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
        Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
      Message:
        Description: "Message for HTML page"
        Default: "UserData first problem!!!"
        Type: "String"
    Resources:
      InstanceSecurityGroup:
        Type: 'AWS::EC2::SecurityGroup'
        Properties:
          GroupDescription: Enable SSH and HTTP access via port 22 IPv4 & port 80 IPv4
          SecurityGroupIngress:
            - Description: 'Allow SSH IPv4 IN'
              IpProtocol: tcp
              FromPort: '22'
              ToPort: '22'
              CidrIp: '0.0.0.0/0'
            - Description: 'Allow HTTP IPv4 IN'
              IpProtocol: tcp
              FromPort: '80'
              ToPort: '80'
              CidrIp: '0.0.0.0/0'
      Bucket:
        Type: 'AWS::S3::Bucket'
      Instance:
        Type: 'AWS::EC2::Instance'
        Metadata:
          'AWS::CloudFormation::Init':
            config:
              packages:
                yum:
                  httpd: []
              files:
                /var/www/html/index.html:
                  content: !Sub |
                    <html><head><title>Amazing test page</title></head><body><h1><center>${Message}</center></h1></body></html>
              commands:
                simulatebootstrap:
                  command: "sleep 300"
              services:
                sysvinit:
                  httpd:
                    enabled: "true"
                    ensureRunning: "true"
                    files:
                      - "/var/www/html/index.html"
        CreationPolicy:
          ResourceSignal:
            Timeout: PT15M
        Properties:
          InstanceType: "t2.micro"
          ImageId: !Ref "LatestAmiId"
          SecurityGroupIds: 
            - !Ref InstanceSecurityGroup
          Tags:
            - Key: Name
              Value: CMP-UserData Test
          UserData:
            Fn::Base64: !Sub |
              #!/bin/bash -xe
              /opt/aws/bin/cfn-init -v --stack ${AWS::StackId} --resource Instance --region ${AWS::Region}
              /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackId} --resource Instance --region ${AWS::Region}
    
    
  • Điểm thay đổi:

    1. Thay vì install app bằng block user data mình sử dụng cfn-init
        Metadata:
          'AWS::CloudFormation::Init':
            config:
              packages:
                yum:
                  httpd: []
              files:
                /var/www/html/index.html:
                  content: !Sub |
                    <html><head><title>Amazing test page</title></head><body><h1><center>${Message}</center></h1></body></html>
              commands:
                simulatebootstrap:
                  command: "sleep 300"
              services:
                sysvinit:
                  httpd:
                    enabled: "true"
                    ensureRunning: "true"
                    files:
                      - "/var/www/html/index.html"
    
    1. Dòng cuối cùng trong userdata mình thêm dòng này. Command này sẽ sử dụng cfn-init để báo cho cfn biết là application cần cập nhật.
        /opt/aws/bin/cfn-init -v --stack ${AWS::StackId} --resource Instance --region ${AWS::Region}
    

4.2. Kiểm tra Ec2, application

  • Kiểm tra tình trạng ec2 instance khi mới deploy

  • Khác với chỉ dùng mỗi UserData stack sẽ không ready ngay mà sẽ phải đợi lâu hơn (khoảng 5-6 phút)

  • Application Được khởi động thành công đồng bộ với stack báo finish

  • Thử update stack với UserData second problem!!!

  • chúng ta có thể thấy vấn đề vẫn còn nguyên

  • Vậy cfn-init đâu có khác gì với user data ?

  • Chưa hẳn chúng ta có thê cập nhật lại app bằng cách chạy cfn-init trong instance

4.3 Xử lý vấn đề (thủ công)

  • SSH vào instance

  • Chạy lệnh cfn-init

    /opt/aws/bin/cfn-init -v --stack ${AWS::StackId} --resource Instance --region ${AWS::Region}
    
  • Thay thế "AWS::StackId" và "AWS::Region" cho phù hợp nhé

    /opt/aws/bin/cfn-init -v --stack arn:aws:cloudformation:us-east-1:654654427784:stack/CMPUserData/880346c0-535f-11f0-9e4f-0affe69900f7 --resource Instance --region us-east-1
    

  • Xác nhận lại bằng các access vào IP instance

  • Voilà! đã được update

4.4 Phân tích vấn đề:

  • Vậy vấn đề ở đây là gì:
    • UserData chỉ chạy đúng một lần khi instance được khởi tạo , và không chạy lại khi stack được up date
    • cfn-init sẽ chạy mỗi khi được kích hoạt ở trong instance . Tuy nhiên việc này không được kích hoạt tự động.
    • Để tự động cập nhật application mỗi khi update stack thì chúng ta có thể sử dụng cfn-hup

4.5 Clean up

  • Làm tương tự 2.3

5. Deploy stack sử dụng cfn-hup

5.1. Deploy stack

  • Mình đã chuẩn bị template như bên dưới .

  • Download : 4_cfninit_with_signal_and_cfnhup.yaml

  • Các bước deploy giống hệt bên trên nên mình không đi lại.

  • Mình sẽ note một số điểm khác biệt và cải thiện

    Parameters:
      LatestAmiId:
        Description: "AMI for EC2"
        Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
        Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
      Message:
        Description: "Message for HTML page"
        Default: "UserData first problem!!!"
        Type: "String"
    Resources:
      InstanceSecurityGroup:
        Type: 'AWS::EC2::SecurityGroup'
        Properties:
          GroupDescription: Enable SSH and HTTP access via port 22 IPv4 & port 80 IPv4
          SecurityGroupIngress:
            - Description: 'Allow SSH IPv4 IN'
              IpProtocol: tcp
              FromPort: '22'
              ToPort: '22'
              CidrIp: '0.0.0.0/0'
            - Description: 'Allow HTTP IPv4 IN'
              IpProtocol: tcp
              FromPort: '80'
              ToPort: '80'
              CidrIp: '0.0.0.0/0'
      Bucket:
        Type: 'AWS::S3::Bucket'
      Instance:
        Type: 'AWS::EC2::Instance'
        Metadata:
          'AWS::CloudFormation::Init':
            config:
              packages:
                yum:
                  httpd: []
              files:
                /etc/cfn/cfn-hup.conf:
                  content: !Sub |
                    [main]
                    stack=${AWS::StackName}
                    region=${AWS::Region}
                    interval=1
                    verbose=true
                  mode: '000400'
                  owner: 'root'
                  group: 'root'
                /etc/cfn/hooks.d/cfn-auto-reloader.conf:
                  content: !Sub |
                    [cfn-auto-reloader-hook]
                    triggers=post.update
                    path=Resources.Instance.Metadata.AWS::CloudFormation::Init
                    action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackId} --resource Instance --region ${AWS::Region}
                    runas=root
                  mode: '000400'
                  owner: 'root'
                  group: 'root'
                /var/www/html/index.html:
                  content: !Sub |
                    <html><head><title>Amazing test page</title></head><body><h1><center>${Message}</center></h1></body></html>
              commands:
                simulatebootstrap:
                  command: "sleep 300"
              services:
                sysvinit:
                  cfn-hup:
                    enabled: "true"
                    ensureRunning: "true"
                    files:
                      - /etc/cfn/cfn-hup.conf
                      - /etc/cfn/hooks.d/cfn-auto-reloader.conf
                  httpd:
                    enabled: "true"
                    ensureRunning: "true"
                    files:
                      - "/var/www/html/index.html"
        CreationPolicy:
          ResourceSignal:
            Timeout: PT15M
        Properties:
          InstanceType: "t2.micro"
          ImageId: !Ref "LatestAmiId"
          SecurityGroupIds: 
            - !Ref InstanceSecurityGroup
          Tags:
            - Key: Name
              Value: CMP-UserData Test
          UserData:
            Fn::Base64: !Sub |
              #!/bin/bash -xe
              /opt/aws/bin/cfn-init -v --stack ${AWS::StackId} --resource Instance --region ${AWS::Region}
              /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackId} --resource Instance --region ${AWS::Region}
    
    
    
  • Điểm thay đổi

  • Mình thêm service cfn-hup vào trong blockcode cfn-init

        'AWS::CloudFormation::Init':
            config:
              packages:
                yum:
                  httpd: []
              files:
                /etc/cfn/cfn-hup.conf:
                  content: !Sub |
                    [main]
                    stack=${AWS::StackName}
                    region=${AWS::Region}
                    interval=1
                    verbose=true
                  mode: '000400'
                  owner: 'root'
                  group: 'root'
                /etc/cfn/hooks.d/cfn-auto-reloader.conf:
                  content: !Sub |
                    [cfn-auto-reloader-hook]
                    triggers=post.update
                    path=Resources.Instance.Metadata.AWS::CloudFormation::Init
                    action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackId} --resource Instance --region ${AWS::Region}
                    runas=root
                  mode: '000400'
                  owner: 'root'
                  group: 'root'
                /var/www/html/index.html:
                  content: !Sub |
                    <html><head><title>Amazing test page</title></head><body><h1><center>${Message}</center></h1></body></html>
              commands:
                simulatebootstrap:
                  command: "sleep 300"
              services:
                sysvinit:
                  cfn-hup:
                    enabled: "true"
                    ensureRunning: "true"
                    files:
                      - /etc/cfn/cfn-hup.conf
                      - /etc/cfn/hooks.d/cfn-auto-reloader.conf
                  httpd:
                    enabled: "true"
                    ensureRunning: "true"
                    files:
                      - "/var/www/html/index.html"
    
  • Thử luôn nhé !

5.2. Kiểm tra Ec2, application

  • Kiểm tra tình trạng ec2 instance khi mới deploy

  • Khác với chỉ dùng mỗi UserData stack sẽ không ready ngay mà sẽ phải đợi lâu hơn (khoảng 5-6 phút)

  • Application Được khởi động thành công đồng bộ với stack báo finish

  • Thử update stack với UserData second problem!!!

  • chúng ta có thể thấy đã được cập nhật ngay tức thì

5.3 Clean up

  • Làm tương tự 2.3