64

我有一个 EC2 实例运行基于 Amazon Linux AMI 的 AMI。与所有此类 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 的设计者会考虑的。

4

8 回答 8

64

在 11.10、12.04 及更高版本中,您可以通过使“脚本用户”“始终”运行来实现此目的。在 /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的 cloud-config 文档中有更多关于此的信息

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

于 2012-05-04T19:31:06.583 回答
21

/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

祝你好运 !

于 2011-12-04T18:40:27.600 回答
12

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

“运行命令”:

#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 ]

“引导命令”:

#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
于 2017-07-06T10:03:36.323 回答
8

一种可能性,虽然有点骇人听闻,是删除 cloud-init 用来确定用户脚本是否已经运行的锁定文件。在我的例子中(Amazon Linux AMI),这个锁定文件位于/var/lib/cloud/sem/并被命名user-scripts.i-7f3f1d11(最后的哈希部分每次启动都会改变)。因此,添加到包含文件末尾的以下用户数据脚本可以解决问题:

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

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

于 2011-06-25T19:55:51.033 回答
3

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

示例:这里 m 将 hello world 打印到我的文件中

在添加到用户数据之前停止实例

脚本

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
--//
于 2018-12-20T10:13:44.123 回答
2

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

对于 Windows,用户数据有一个特殊的persist标签,但对于 Linux,您需要先使用MultiPart User 数据来设置 cloud-init。这个 Linux 示例使用cloud-config(参见ref blog)部件类型,而不是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...
    )

窗口示例:

    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...
    )
于 2021-11-03T06:25:12.453 回答
2

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

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 跨区域复制过程的设置。

于 2018-01-09T14:22:33.347 回答
1

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

云展位

  • 以 #cloud-boothook 或 Content-Type: text/cloud-boothook 开头。
  • 此内容为boothook 数据。它存储在 /var/lib/cloud 下的文件中,然后立即执行。
  • 这是最早可用的“钩子”。没有提供只运行一次的机制。展台必须自己解决这个问题。它与环境变量 INSTANCE_ID 中的实例 ID 一起提供。使用此变量可提供一组每个实例一次的boothook 数据。
于 2019-07-25T21:19:03.730 回答