在亚马逊 AWS 上重新启动 EC2 实例后,读取脚本执行自动配置的问题。

今天在重启了无数次 instance 之后,我最后一次刷新页面,看到终于跳出来的网站页面,全身一阵抖动,顿觉索然无味。

花了三天时间完成了项目的部署,我觉得有必要记录一下这个巨坑。

先说一下项目的大致配置:前端使用的是 Angular6 框架,中间层使用 Node.js 搭配 Express 框架来负责路由,后端采用 Python 编写,基于 Flask 框架,数据库使用 MongoDB 配合 Redis 缓存。 相对应的,启动网站时,我需要开启 Node 服务、Flask 服务、Redis 服务,打包前端代码,并且开启 Nginx 将用户请求传递到 Node。所以,编写一个自动开启以上服务们的脚本义不容辞。而 AWS 的 EC2(Elastic Comput Cloud,用多少,花多少的云服务器)提供了启动 instance 实例时运行脚本命令的功能:

AWS 提供了一个叫做 cloud-init 的工具,通过它可以在启动 instance 的时候执行自动配置任务。用户需要做的就是在 用户数据(user data) 字段中复制 shell 脚本。cloud-init 支持两种格式:Shell 脚本和 cloud-init 指令。

下面是一个 AWS 官方指导提供的脚本实例:

#!/bin/bash
yum update -y
amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2
yum install -y httpd mariadb-server
systemctl start httpd
systemctl enable httpd
usermod -a -G apache ec2-user
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 {} \;
echo "" > /var/www/html/phpinfo.php

对于 Shell 脚本,必须以 #! 字符以及指向要读取脚本的解释器的路径(通常为 /bin/bash))开头。另外,作为用户数据输入的脚本是作为 root 用户加以执行的,因此在脚本中不使用 sudo 命令。

这时,需要注意的地方是:cloud-init 默认只会在 instance 第一次启动的时候运行 user data 脚本!此后的任何重启都不会再次运行。这不禁让调试和部署变得困难,所以我们需要通过修改脚本,让 instance 每次重启的时候都运行一遍脚本。AWS 作为一个云服务集大成之作,自然也考虑到了这个用户的需求,提供了官网指导:用户可以通过修改脚本,将脚本配置成一个 mime multi-part file(MIME多部分文件) 来配置脚本的执行频率。

所谓的 mime multi-part file,实质上就是在脚本中声明 text/cloud-configtext/x-shellscript 两种内容类型。前者是 cloud-init 的调试信息,我们可以将 SCRIPTS-USER 参数设为 ALWAYS 来达到每次重启都运行一遍脚本的目的,而接下来我们只需要将之前的自动配置脚本填到 text/x-shellscript 类型下即可。

下面是 AWS 提供的 mime multi-part file 脚本文件实例:

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

可以看到,相比较普通的 shell 脚本,这段脚本只是更加格式化了,新增了控制语段。

在埋了一整篇文章的伏笔之后,巨坑出现了:按照上述格式修改脚本并重启之后,新的脚本并没有覆盖掉之前的,以前的脚本文件以 part-001, part-002 的名字继续保存在 /var/lib/cloud/instance/scripts 这个 cloud-init 用来保存脚本的文件夹下!导致的结果是,每次重新启动 instance 的时候,原先的脚本由于并没有被删除,所有的脚本都会被运行一遍! 以上的结果是,会有一系列不可预知的冲突出现,还可能的情况是由于开启了多个重复的服务,机器的内存和CPU爆满,调试的时候所有的指令都无法执行,更有甚者,有时根本无法登入服务器!

这个 AWS 官网没有指出的问题一度让我百思不得其解。最终还是在查看了多次的 log 文件之后(cloud-init 默认的控制台输出位于 /var/log/cloud-init-output.log)醒悟。我在 AWS 的论坛上也证实了上述这一问题:根据 AWS 员工在 2013 年的评论:“The cause of the bug is that user-data scripts are run only once per instance, but they are put into that directory when user-data is processed at instance start and there is nothing to delete them at any point in the future.” 看来这个2013年就存在的 bug 直到2019年还存在着。

最后,解决方法变得异常简单:到 /var/lib/cloud/instance/scripts 目录下删除所有的脚本,再重新把用于自动配置的脚本输入到 user data 字段,重启运行,instance 总算正常跑起来了。

附上 AWS 官方的参考链接:EC2实例自动配置任务,EC2实例重启后再次执行配置脚本。
以及 AWS 论坛上关于重启脚本覆盖问题的讨论:cloudinit user-data script runs more than once。

你可能感兴趣的:(网站开发,AWS,EC2,部署)