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

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 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.

1. Vấn đề với user data :: 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 Vào cloudformation service

1.2. Chọn Create StackWith new resources

  • Upload template for UserData case
  • Mình đã chuẩn bị sẵn một cloudformation template sử dụng
  • Các bạn lưu lại và upload lên nhé
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. Specify stack details đặt tên như hình dưới nhé

1.4. Configure stack options cứ để default và Next

1.5. Submit

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

1.7. Vào resource click vào Instance

1.8. Confirm state là Running và copy public ip

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

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

1.11. Vấn đề

  • Ở 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ệ lụy:
      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. Vấn đề với user data :: Application Update

2.1. Bây giờ chúng ta thực hiện update stack

2.2. Chọn exiting template

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

UserData second problem!!!

2.4. Ấn Next

2.5. Ấn Submit

2.6. Đợi update hoàn thành

2.7. Làm tương tự step 8 -> 10 của phần trên

  • 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

2.8. Để 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.9 Clean up UserData

  • Chọn Delete stack

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

  • 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 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 cf biết là application đã ready.
/opt/aws/bin/cfn-signal -e $? --stack AWS-StackId --resource Instance --region AWS-Region
  • Thử luôn nhé !

3.1. 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)

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

3.3 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.9

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

  • Ngoài sử dụng UserData chúng ta có thể dùng cfn-init để cài đặt app
  • 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
  • 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"
  • 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 cf biết là application cần cập nhật.
    /opt/aws/bin/cfn-init -v --stack ${AWS::StackId} --resource Instance --region ${AWS::Region}
  • Thử luôn nhé !

4.1. 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)

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

4.3 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.4 SSH vào instance

4.5 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

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

  • Volla!! đã được update

4.7 Vậy vấn đề ở đây là gì:

  • UserData chỉ chạy đúng một lần khi instance được khởi tạo , à 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-hub

4.8 Clean up

  • Làm tương tự 2.9

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

  • 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:
            /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-hub 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.1. 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)

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

5.3 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.4 Clean up

  • Làm tương tự 2.9