如何使 cloud-init 启动脚本在每次 EC2 实例启动时运行?

发布于 2024-11-17 06:10:41 字数 845 浏览 1 评论 0原文

我有一个运行基于 Amazon Linux AMI 的 AMI 的 EC2 实例。与所有此类 AMI 一样,它支持 cloud-init 系统,用于基于用户数据运行启动脚本传递到每个实例中。在这种特殊情况下,我的用户数据输入恰好是一个包含文件,该文件提供了几个其他启动脚本:

#include
http://s3.amazonaws.com/path/to/script/1
http://s3.amazonaws.com/path/to/script/2

我第一次启动实例时,cloud-init 启动脚本正确运行。但是,如果我对实例进行软重启(例如,通过运行 sudo shutdown -r now),实例将重新启动,而无需在第二次运行启动脚本周围的时间。如果我进入系统日志,我可以看到:

Running cloud-init user-scripts
user-scripts already ran once-per-instance
[  OK  ]

这不是我想要的 - 我可以看到启动脚本在每个实例生命周期中只运行一次的实用程序,但在我的情况下,这些脚本应该在每次实例启动时运行,就像普通的启动脚本一样。

我意识到一个可能的解决方案是在第一次运行后手动将我的脚本插入到 rc.local 中。然而,这似乎很麻烦,因为 cloud-init 和 rc.d 环境略有不同,我现在必须在首次启动和所有后续启动时分别调试脚本。

有谁知道我如何告诉 cloud-init 始终运行我的脚本?这听起来确实像是 cloud-init 的设计者会考虑到的事情。

I have an EC2 instance running an AMI based on the Amazon Linux AMI. Like all such AMIs, it supports the cloud-init system for running startup scripts based on the User Data passed into every instance. In this particular case, my User Data input happens to be an Include file that sources several other startup scripts:

#include
http://s3.amazonaws.com/path/to/script/1
http://s3.amazonaws.com/path/to/script/2

The first time I boot my instance, the cloud-init startup script runs correctly. However, if I do a soft reboot of the instance (by running sudo shutdown -r now, for instance), the instance comes back up without running the startup script the second time around. If I go into the system logs, I can see:

Running cloud-init user-scripts
user-scripts already ran once-per-instance
[  OK  ]

This is not what I want -- I can see the utility of having startup scripts that only run once per instance lifetime, but in my case these should run every time the instance starts up, like normal startup scripts.

I realize that one possible solution is to manually have my scripts insert themselves into rc.local after running the first time. This seems burdensome, however, since the cloud-init and rc.d environments are subtly different and I would now have to debug scripts on first launch and all subsequent launches separately.

Does anyone know how I can tell cloud-init to always run my scripts? This certainly sounds like something the designers of cloud-init would have considered.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

魔法少女 2024-11-24 06:10:42

另一种方法是在用户数据脚本中使用#cloud-boothook。来自 文档

云 Boothook

  • 以 #cloud-boothook 或内容类型开头:text/cloud-boothook。
  • 此内容是 Bootook 数据。它存储在 /var/lib/cloud 下的文件中,然后立即执行。
  • 这是最早可用的“钩子”。 没有提供仅运行一次的机制。 Bootook 必须小心
    这本身。它由环境中的实例 ID 提供
    变量 INSTANCE_ID。使用此变量提供每个实例一次
    Bootook 数据集。

Another approach is to use #cloud-boothook in your user data script. From the docs:

Cloud Boothook

  • Begins with #cloud-boothook or Content-Type: text/cloud-boothook.
  • This content is boothook data. It is stored in a file under /var/lib/cloud and then executed immediately.
  • This is the earliest "hook" available. There is no mechanism provided for running it only one time. The boothook must take care
    of this itself. It is provided with the instance ID in the environment
    variable INSTANCE_ID. Use this variable to provide a once-per-instance
    set of boothook data.
落叶缤纷 2024-11-24 06:10:42

请使用 bash 脚本上方的以下脚本。

之前,我将 hello world 打印到我的文件

停止实例

示例:在添加到 userdata脚本

Content-Type: multipart/mixed; boundary="//"
MIME-Version: 1.0

--//
Content-Type: text/cloud-config; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="cloud-config.txt"

#cloud-config
cloud_final_modules:
- [scripts-user, always]

--//
Content-Type: text/x-shellscript; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="userdata.txt"

#!/bin/bash
/bin/echo "Hello World." >> /var/tmp/sdksdfjsdlf
--//

please use the below script above your bash script.

example: here m printing hello world to my file

stop instance before adding to userdata

script

Content-Type: multipart/mixed; boundary="//"
MIME-Version: 1.0

--//
Content-Type: text/cloud-config; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="cloud-config.txt"

#cloud-config
cloud_final_modules:
- [scripts-user, always]

--//
Content-Type: text/x-shellscript; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="userdata.txt"

#!/bin/bash
/bin/echo "Hello World." >> /var/tmp/sdksdfjsdlf
--//
泪眸﹌ 2024-11-24 06:10:42

在 Amazon Linux 2 上对我有用的是删除 /var/lib/cloud/instance/sem/ 下的信号量:

sudo rm /var/lib/cloud/instance/sem/config_write_files
sudo rm /var/lib/cloud/instance/sem/config_runcmd
sudo rm /var/lib/cloud/instance/sem/config_scripts_user

理论上,可以创建一个 cron 来定期删除这些文件

What worked for me on Amazon Linux 2 was removing semaphores under /var/lib/cloud/instance/sem/:

sudo rm /var/lib/cloud/instance/sem/config_write_files
sudo rm /var/lib/cloud/instance/sem/config_runcmd
sudo rm /var/lib/cloud/instance/sem/config_scripts_user

In theory one could create a cron to remove these files periodically

情归归情 2024-11-24 06:10:41

在 11.10、12.04 及更高版本中,您可以通过使“scripts-user”“始终”运行来实现此目的。
在 /etc/cloud/cloud.cfg 中,您将看到类似以下内容:

cloud_final_modules:
 - rightscale_userdata
 - scripts-per-once
 - scripts-per-boot
 - scripts-per-instance
 - scripts-user
 - keys-to-console
 - phone-home
 - final-message

这可以在启动后修改,或者可以通过用户数据插入覆盖此节的云配置数据。即,在用户数据中,您可以提供:

#cloud-config
cloud_final_modules:
 - rightscale_userdata
 - scripts-per-once
 - scripts-per-boot
 - scripts-per-instance
 - [scripts-user, always]
 - keys-to-console
 - phone-home
 - final-message

也可以是“#included”,正如您在描述中所做的那样。
不幸的是,现在您无法修改“cloud_final_modules”,而只能覆盖它。我希望在某个时候添加修改配置部分的功能。

云配置文档中有更多关于此的信息:
https://github.com/canonical/cloud-init/tree/master /doc/examples

或者,您可以将文件放入 /var/lib/cloud/scripts/per-boot 中,它们将通过 'scripts-per-boot' 路径运行。

In 11.10, 12.04 and later, you can achieve this by making the 'scripts-user' run 'always'.
In /etc/cloud/cloud.cfg you'll see something like:

cloud_final_modules:
 - rightscale_userdata
 - scripts-per-once
 - scripts-per-boot
 - scripts-per-instance
 - scripts-user
 - keys-to-console
 - phone-home
 - final-message

This can be modified after boot, or cloud-config data overriding this stanza can be inserted via user-data. Ie, in user-data you can provide:

#cloud-config
cloud_final_modules:
 - rightscale_userdata
 - scripts-per-once
 - scripts-per-boot
 - scripts-per-instance
 - [scripts-user, always]
 - keys-to-console
 - phone-home
 - final-message

That can also be '#included' as you've done in your description.
Unfortunately, right now, you cannot modify the 'cloud_final_modules', but only override it. I hope to add the ability to modify config sections at some point.

There is a bit more information on this in the cloud-config doc at
https://github.com/canonical/cloud-init/tree/master/doc/examples

Alternatively, you can put files in /var/lib/cloud/scripts/per-boot , and they'll be run by the 'scripts-per-boot' path.

那请放手 2024-11-24 06:10:41

/etc/init.d/cloud-init-user-scripts 中,编辑此行:

/usr/bin/cloud-init-run-module once-per-instance user-scripts execute run-parts ${SCRIPT_DIR} >/dev/null && success || failure

 /usr/bin/cloud-init-run-module always user-scripts execute run-parts ${SCRIPT_DIR} >/dev/null && success || failure

你好运!

In /etc/init.d/cloud-init-user-scripts, edit this line:

/usr/bin/cloud-init-run-module once-per-instance user-scripts execute run-parts ${SCRIPT_DIR} >/dev/null && success || failure

to

 /usr/bin/cloud-init-run-module always user-scripts execute run-parts ${SCRIPT_DIR} >/dev/null && success || failure

Good luck !

后eg是否自 2024-11-24 06:10:41

cloud-init 现在原生支持此功能,请参阅文档中的 runcmd 与 bootcmd 命令描述 (http://cloudinit.readthedocs.io/en/latest/topics/examples.html#run-commands-on-first-boot):

"runcmd":

#cloud-config

# run commands
# default: none
# runcmd contains a list of either lists or a string
# each item will be executed in order at rc.local like level with
# output to the console
# - runcmd only runs during the first boot
# - if the item is a list, the items will be properly executed as if
#   passed to execve(3) (with the first arg as the command).
# - if the item is a string, it will be simply written to the file and
#   will be interpreted by 'sh'
#
# Note, that the list has to be proper yaml, so you have to quote
# any characters yaml would eat (':' can be problematic)
runcmd:
 - [ ls, -l, / ]
 - [ sh, -xc, "echo $(date) ': hello world!'" ]
 - [ sh, -c, echo "=========hello world'=========" ]
 - ls -l /root
 - [ wget, "http://slashdot.org", -O, /tmp/index.html ]

“bootcmd”:

#cloud-config

# boot commands
# default: none
# this is very similar to runcmd, but commands run very early
# in the boot process, only slightly after a 'boothook' would run.
# bootcmd should really only be used for things that could not be
# done later in the boot process.  bootcmd is very much like
# boothook, but possibly with more friendly.
# - bootcmd will run on every boot
# - the INSTANCE_ID variable will be set to the current instance id.
# - you can use 'cloud-init-per' command to help only run once
bootcmd:
 - echo 192.168.1.130 us.archive.ubuntu.com >> /etc/hosts
 - [ cloud-init-per, once, mymkfs, mkfs, /dev/vdb ]

另请注意 bootcmd 中的“cloud-init-per”命令示例。从它的帮助:

Usage: cloud-init-per frequency name cmd [ arg1 [ arg2 [ ... ] ]
   run cmd with arguments provided.

   This utility can make it easier to use boothooks or bootcmd
   on a per "once" or "always" basis.

   If frequency is:
      * once: run only once (do not re-run for new instance-id)
      * instance: run only the first boot for a given instance-id
      * always: run every boot

cloud-init supports this now natively, see runcmd vs bootcmd command descriptions in the documentation (http://cloudinit.readthedocs.io/en/latest/topics/examples.html#run-commands-on-first-boot):

"runcmd":

#cloud-config

# run commands
# default: none
# runcmd contains a list of either lists or a string
# each item will be executed in order at rc.local like level with
# output to the console
# - runcmd only runs during the first boot
# - if the item is a list, the items will be properly executed as if
#   passed to execve(3) (with the first arg as the command).
# - if the item is a string, it will be simply written to the file and
#   will be interpreted by 'sh'
#
# Note, that the list has to be proper yaml, so you have to quote
# any characters yaml would eat (':' can be problematic)
runcmd:
 - [ ls, -l, / ]
 - [ sh, -xc, "echo $(date) ': hello world!'" ]
 - [ sh, -c, echo "=========hello world'=========" ]
 - ls -l /root
 - [ wget, "http://slashdot.org", -O, /tmp/index.html ]

"bootcmd":

#cloud-config

# boot commands
# default: none
# this is very similar to runcmd, but commands run very early
# in the boot process, only slightly after a 'boothook' would run.
# bootcmd should really only be used for things that could not be
# done later in the boot process.  bootcmd is very much like
# boothook, but possibly with more friendly.
# - bootcmd will run on every boot
# - the INSTANCE_ID variable will be set to the current instance id.
# - you can use 'cloud-init-per' command to help only run once
bootcmd:
 - echo 192.168.1.130 us.archive.ubuntu.com >> /etc/hosts
 - [ cloud-init-per, once, mymkfs, mkfs, /dev/vdb ]

also note the "cloud-init-per" command example in bootcmd. From it's help:

Usage: cloud-init-per frequency name cmd [ arg1 [ arg2 [ ... ] ]
   run cmd with arguments provided.

   This utility can make it easier to use boothooks or bootcmd
   on a per "once" or "always" basis.

   If frequency is:
      * once: run only once (do not re-run for new instance-id)
      * instance: run only the first boot for a given instance-id
      * always: run every boot
岁月静好 2024-11-24 06:10:41

一种可能(虽然有些黑客行为)是删除 cloud-init 用于确定用户脚本是否已运行的锁定文件。就我而言 (Amazon Linux AMI),此锁定文件位于 /var/lib/cloud/sem/ 中,名为 user-scripts.i-7f3f1d11 (最后的哈希部分每次启动都会改变)。因此,添加到包含文件末尾的以下用户数据脚本将起到作用:

#!/bin/sh
rm /var/lib/cloud/sem/user-scripts.*

我不确定这是否会对其他任何内容产生不利影响,但它在我的实验中有效。

One possibility, although somewhat hackish, is to delete the lock file that cloud-init uses to determine whether or not the user-script has already run. In my case (Amazon Linux AMI), this lock file is located in /var/lib/cloud/sem/ and is named user-scripts.i-7f3f1d11 (the hash part at the end changes every boot). Therefore, the following user-data script added to the end of the Include file will do the trick:

#!/bin/sh
rm /var/lib/cloud/sem/user-scripts.*

I'm not sure if this will have any adverse effects on anything else, but it has worked in my experiments.

半衬遮猫 2024-11-24 06:10:41

我在这个问题上苦苦挣扎了近两天,尝试了所有我能找到的解决方案,最后结合多种方法,提出了以下方案:

MyResource:
  Type: AWS::EC2::Instance
  Metadata:
    AWS::CloudFormation::Init:
      configSets:
        setup_process:
          - "prepare"
          - "run_for_instance"
      prepare:
        commands:
          01_apt_update:
            command: "apt-get update"
          02_clone_project:
            command: "mkdir -p /replication && rm -rf /replication/* && git clone https://github.com/awslabs/dynamodb-cross-region-library.git /replication/dynamodb-cross-region-library/"
          03_build_project:
            command: "mvn install -DskipTests=true"
            cwd: "/replication/dynamodb-cross-region-library"
          04_prepare_for_apac:
            command: "mkdir -p /replication/replication-west && rm -rf /replication/replication-west/* && cp /replication/dynamodb-cross-region-library/target/dynamodb-cross-region-replication-1.2.1.jar /replication/replication-west/replication-runner.jar"
      run_for_instance:
        commands:
          01_run:
            command: !Sub "java -jar replication-runner.jar --sourceRegion us-east-1 --sourceTable ${TableName} --destinationRegion ap-southeast-1 --destinationTable ${TableName} --taskName -us-ap >/dev/null 2>&1 &"
            cwd: "/replication/replication-west"
  Properties:
    UserData:
      Fn::Base64:
        !Sub |
          #cloud-config
          cloud_final_modules:
           - [scripts-user, always]
          runcmd:
           - /usr/local/bin/cfn-init -v -c setup_process --stack ${AWS::StackName} --resource MyResource --region ${AWS::Region}
           - /usr/local/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource MyResource --region ${AWS::Region}

这是 DynamoDb 跨区域复制过程的设置。

I struggled with this issue for almost two days, tried all of the solutions I could find and finally, combining several approaches, came up with the following:

MyResource:
  Type: AWS::EC2::Instance
  Metadata:
    AWS::CloudFormation::Init:
      configSets:
        setup_process:
          - "prepare"
          - "run_for_instance"
      prepare:
        commands:
          01_apt_update:
            command: "apt-get update"
          02_clone_project:
            command: "mkdir -p /replication && rm -rf /replication/* && git clone https://github.com/awslabs/dynamodb-cross-region-library.git /replication/dynamodb-cross-region-library/"
          03_build_project:
            command: "mvn install -DskipTests=true"
            cwd: "/replication/dynamodb-cross-region-library"
          04_prepare_for_apac:
            command: "mkdir -p /replication/replication-west && rm -rf /replication/replication-west/* && cp /replication/dynamodb-cross-region-library/target/dynamodb-cross-region-replication-1.2.1.jar /replication/replication-west/replication-runner.jar"
      run_for_instance:
        commands:
          01_run:
            command: !Sub "java -jar replication-runner.jar --sourceRegion us-east-1 --sourceTable ${TableName} --destinationRegion ap-southeast-1 --destinationTable ${TableName} --taskName -us-ap >/dev/null 2>&1 &"
            cwd: "/replication/replication-west"
  Properties:
    UserData:
      Fn::Base64:
        !Sub |
          #cloud-config
          cloud_final_modules:
           - [scripts-user, always]
          runcmd:
           - /usr/local/bin/cfn-init -v -c setup_process --stack ${AWS::StackName} --resource MyResource --region ${AWS::Region}
           - /usr/local/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource MyResource --region ${AWS::Region}

This is the setup for DynamoDb cross-region replication process.

浪荡不羁 2024-11-24 06:10:41

如果有人想在 CDK 上执行此操作,这里有一个 python 示例。

对于 Windows,用户数据具有特殊的 persist标签,但对于Linux,需要使用多部分用户数据 首先设置cloud-init。此 Linux 示例使用 cloud-config(请参阅 参考博客)部分类型而不是 cloud-boothook 需要一个 cloud-init-per (另请参阅 bootcmd) 调用我无法测试(例如:cloud-init-pre always)。

Linux 示例:

    # Create some userdata commands
    instance_userdata = ec2.UserData.for_linux()
    instance_userdata.add_commands("apt update")
    # ...
    # Now create the first part to make cloud-init run it always
    cinit_conf = ec2.UserData.for_linux();
    cinit_conf .add_commands('#cloud-config');
    cinit_conf .add_commands('cloud_final_modules:');
    cinit_conf .add_commands('- [scripts-user, always]');
    multipart_ud = ec2.MultipartUserData()
    #### Setup to run every time instance starts
    multipart_ud.add_part(ec2.MultipartBody.from_user_data(cinit_conf , content_type='text/cloud-config'))
    #### Add the commands desired to run every time
    multipart_ud.add_part(ec2.MultipartBody.from_user_data(instance_userdata));

    ec2.Instance(
        self, "myec2",
        userdata=multipart_ud,
        #other required config...
    )

Windows 示例:

    instance_userdata = ec2.UserData.for_windows()
    # Bootstrap
    instance_userdata.add_commands("Write-Output 'Run some commands'")
    # ...
    # Making all the commands persistent - ie: running on each instance start
    data_script = instance_userdata.render()
    data_script += "<persist>true</persist>"
    ud = ec2.UserData.custom(data_script)
    ec2.Instance(
        self, "myWinec2",
        userdata=ud,
        #other required config...
    )

If someone wants to do this on CDK, here's a python example.

For Windows, user data has a special persist tag, but for Linux, you need to use MultiPart User data to setup cloud-init first. This Linux example worked with cloud-config (see ref blog) part type instead of cloud-boothook which requires a cloud-init-per (see also bootcmd) call I couldn't test out (eg: cloud-init-pre always).

Linux example:

    # Create some userdata commands
    instance_userdata = ec2.UserData.for_linux()
    instance_userdata.add_commands("apt update")
    # ...
    # Now create the first part to make cloud-init run it always
    cinit_conf = ec2.UserData.for_linux();
    cinit_conf .add_commands('#cloud-config');
    cinit_conf .add_commands('cloud_final_modules:');
    cinit_conf .add_commands('- [scripts-user, always]');
    multipart_ud = ec2.MultipartUserData()
    #### Setup to run every time instance starts
    multipart_ud.add_part(ec2.MultipartBody.from_user_data(cinit_conf , content_type='text/cloud-config'))
    #### Add the commands desired to run every time
    multipart_ud.add_part(ec2.MultipartBody.from_user_data(instance_userdata));

    ec2.Instance(
        self, "myec2",
        userdata=multipart_ud,
        #other required config...
    )

Windows example:

    instance_userdata = ec2.UserData.for_windows()
    # Bootstrap
    instance_userdata.add_commands("Write-Output 'Run some commands'")
    # ...
    # Making all the commands persistent - ie: running on each instance start
    data_script = instance_userdata.render()
    data_script += "<persist>true</persist>"
    ud = ec2.UserData.custom(data_script)
    ec2.Instance(
        self, "myWinec2",
        userdata=ud,
        #other required config...
    )
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文