在 AWS 中,user_data 是否在 cloud-init 之前执行?

in AWS, is user_data executed before cloud-init?

我使用 terraform 创建一个 EC2 实例,我使用 user_data/var/lib/cloud/scripts/per-once 中放置一个文件。这没有执行 - 我现在的问题是:cloud-init 运行 在 user_data 之前吗?

===编辑===

对 Dude0001 非常有帮助的答案的更长回复:

我已经尝试了以下方法,现在 - 这是我的 user_data:

#!/bin/bash
cat >/var/lib/cloud/scripts/per-once/install_mysql <<!
#cloud-config

package_update: true

packages:
  - mysql-server

!

cat >>/root/.bashrc <<!
set -o vi
unalias -a
alias ll='ls -lp'
!

cat >>/home/admin/.bashrc <<!
set -o vi
unalias -a
alias ll='ls -lp'
!

cat /root/.vimrc <<!
set t_ti= t_te=
set compatible
set expandtab ts=2 sw=2 ai
!

cat >/home/admin/.vimrc <<!
set t_ti= t_te=
set compatible
set expandtab ts=2 sw=2 ai
!

这将按预期创建所有文件(我真的很守旧,不喜欢 vim 的大部分新功能)。创建实例后我尝试重新启动:没有 mysqld。我更改了权限,chmod 755 /var/lib/cloud/scripts/per-once/install_mysql,然后重新启动:也没有结果(我更改权限的原因是 python 代码显示云- init 仅查找可执行文件)。

===编辑===

对我上面的user_data的一些解释:

这种结构可能会让一些人感到困惑,因为它不太常见:

cat >/some/path/to/a/file <<!
...
!

cat 是一个简单地从标准输入读取并写入标准输出的命令,它通常与重定向 <> 一起使用。在上面的构造中,我将任何输出定向到一个文件 /some/path/to/a/file。另一部分,涉及 <<!! 被称为 here 文档 ,我怀疑它起源于大型机上使用的 JCL 语言,但是这真的很有用。意思是 阅读以下行,直到结束标记 (此处:!,但它可以是任何字符串)。所以,总而言之,它说 创建一个包含以下内容的文件:....

第一个文件 /var/lib/cloud/scripts/per-once/install_mysql 包含:

#cloud-config

package_update: true

packages:
  - mysql-server

我希望这应该告诉 cloud-init 更新包存储库并安装 mysql-server - 这不会发生。

接下来的 4 个文件只是 rootadmin 用户环境中的一些设置;基本上,我创建了一个 .vimrc 并向 .bashrc 添加了几行以确保某些设置符合我的喜好。

文件都创建好了,但是#cloud-config的那个好像根本就没动过。我昨天做了一些实验,将这个文件放在 /var/lib/cloud/scripts/ 下的不同目录中,但看起来很像当 cloud-init 读取目录时这些文件不存在。通读cloud-init的源码,好像是运行经历了10个阶段——user_data是在第5阶段抓取的,应该是第7阶段读取的。我也可以看到它似乎需要设置执行权限位;然而,这是重启后日志中的内容:

2019-10-02 08:06:52,884 - handlers.py[DEBUG]: start: modules-final/config-scripts-per-boot: running config-scripts-per-boot with frequency always
2019-10-02 08:06:52,884 - helpers.py[DEBUG]: Running config-scripts-per-boot using lock (<cloudinit.helpers.DummyLock object at 0x7f677362acc0>)
2019-10-02 08:06:52,885 - util.py[DEBUG]: Running command ['/var/lib/cloud/scripts/per-boot/install_mysql'] with allowed return codes [0] (shell=False, capture=False)
2019-10-02 08:06:52,887 - util.py[WARNING]: Failed running /var/lib/cloud/scripts/per-boot/install_mysql [-]
2019-10-02 08:06:52,887 - util.py[DEBUG]: Failed running /var/lib/cloud/scripts/per-boot/install_mysql [-]
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/cloudinit/util.py", line 1992, in subp
    env=env, shell=shell)
  File "/usr/lib/python3.7/subprocess.py", line 775, in __init__
    restore_signals, start_new_session)
  File "/usr/lib/python3.7/subprocess.py", line 1522, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
OSError: [Errno 8] Exec format error: b'/var/lib/cloud/scripts/per-boot/install_mysql'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/cloudinit/util.py", line 835, in runparts
    subp(prefix + [exe_path], capture=False)
  File "/usr/lib/python3/dist-packages/cloudinit/util.py", line 2000, in subp
    stderr="-" if decode else b"-")
cloudinit.util.ProcessExecutionError: Exec format error. Missing #! in script?
Command: ['/var/lib/cloud/scripts/per-boot/install_mysql']
Exit code: -
Reason: [Errno 8] Exec format error: b'/var/lib/cloud/scripts/per-boot/install_mysql'
Stdout: -
Stderr: -
2019-10-02 08:06:52,897 - cc_scripts_per_boot.py[WARNING]: Failed to run module scripts-per-boot (per-boot in /var/lib/cloud/scripts/per-boot)
2019-10-02 08:06:52,898 - handlers.py[DEBUG]: finish: modules-final/config-scripts-per-boot: FAIL: running config-scripts-per-boot with frequency always
2019-10-02 08:06:52,898 - util.py[WARNING]: Running module scripts-per-boot (<module 'cloudinit.config.cc_scripts_per_boot' from '/usr/lib/python3/dist-packages/cloudinit/config/cc_scripts_per_boot.py'>) failed
2019-10-02 08:06:52,898 - util.py[DEBUG]: Running module scripts-per-boot (<module 'cloudinit.config.cc_scripts_per_boot' from '/usr/lib/python3/dist-packages/cloudinit/config/cc_scripts_per_boot.py'>) failed
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/cloudinit/stages.py", line 800, in _run_modules
    freq=freq)
  File "/usr/lib/python3/dist-packages/cloudinit/cloud.py", line 54, in run
    return self._runners.run(name, functor, args, freq, clear_on_fail)
  File "/usr/lib/python3/dist-packages/cloudinit/helpers.py", line 187, in run
    results = functor(*args)
  File "/usr/lib/python3/dist-packages/cloudinit/config/cc_scripts_per_boot.py", line 41, in handle
    util.runparts(runparts_path)
  File "/usr/lib/python3/dist-packages/cloudinit/util.py", line 842, in runparts
    % (len(failed), len(attempted)))
RuntimeError: Runparts: 1 failures in 1 attempted commands

因此,它绝对不喜欢文件的格式 - 它想要查看 #!... 或者可能是二进制可执行文件。

我现在将更详细地尝试 Dude0001 的建议。

===编辑===

最后,按照 Dude0001 的建议,使用 multipart/mixed 格式是有效的:

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
package_update: yes
package_upgrade: all

packages:
 - mariadb-server
 - apt-file

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

#!/bin/bash
cat >>/root/.bashrc <<!
set -o vi
unalias -a
alias ll='ls -lp'
!

cat >>/home/admin/.bashrc <<!
set -o vi
unalias -a
alias ll='ls -lp'
!

cat /root/.vimrc <<!
set t_ti= t_te=
set compatible
set expandtab ts=2 sw=2 ai
!

cat >/home/admin/.vimrc <<!
set t_ti= t_te=
set compatible
set expandtab ts=2 sw=2 ai
!

--//

仅指定 #cloud-config 似乎行不通,但这种方式行之有效。至少对我来说。在当下。

简答:

A user_data 值设置为 shell 脚本将导致给定的 shell 脚本在 cloud-init 的最后阶段成为 运行(我相信在您引用的一次性文件夹中的 cloud-init 指令之后。

如果您想在 EC2 user_data 属性 中使用自定义 cloud-init 指令和 shell 脚本,您需要使用 multipart/mixed mime 格式 https://aws.amazon.com/premiumsupport/knowledge-center/execute-user-data-ec2/

长答案:

user_data 可以保存要通过 EC2 元数据、脚本或 cloud-init 指令读取的原始数据。此外,您可以将其设置为 multipart/mixed mime 类型并提供其中的每一个。

如果user_data是原始数据,可以在EC2实例中用curl命令获取。由调用命令来解释数据,可以是用户选择的任何内容。

[ec2-user ~]$ curl http://169.254.169.254/latest/user-data

如果user_data是一个脚本(比如第一行的#!/bin/bash),那么在cloud-init的最后阶段运行作为cloud-init的一个步骤https://cloudinit.readthedocs.io/en/latest/topics/boot.html#final.

如果user_data是一个cloud-init指令(例如第一行的#cloud-config),它是运行作为指定的cloud-init指令。

来自 https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html#user-data-cloud-init

"To pass cloud-init directives to an instance with user_data... enter your cloud-init directive text in the user_data text."

像这样

#cloud-config
repo_update: true
repo_upgrade: all

packages:
 - httpd
 - mariadb-server

runcmd:
 - [ sh, -c, "amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2" ]
 - systemctl start httpd
 - sudo systemctl enable httpd
 - [ sh, -c, "usermod -a -G apache ec2-user" ]
 - [ sh, -c, "chown -R ec2-user:apache /var/www" ]
 - chmod 2775 /var/www
 - [ find, /var/www, -type, d, -exec, chmod, 2775, {}, \; ]
 - [ find, /var/www, -type, f, -exec, chmod, 0664, {}, \; ]
 - [ sh, -c, 'echo "<?php phpinfo(); ?>" > /var/www/html/phpinfo.php' ]

此处描述了 multipart/mixed mime 格式 https://aws.amazon.com/premiumsupport/knowledge-center/execute-user-data-ec2/,示例

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" >> /tmp/testfile.txt
--//