我启动m5.large
(硝基类) 从 Ubuntu AMI 启动 EC2 实例并附加 EBS 卷。有systemd
一个默认的初始化系统。如 AWS 文档所述“使 Amazon EBS 卷可在 Linux 上使用”站立,我安装 EBS 卷用户数据:
#!/bin/bash
# Sleep gives the SSD drive a chance to mount before the user data script completes.
sleep 15
mkdir /application
mount /dev/nvme1n1 /application
我需要 Nginx 并在 EBS 卷上为其提供站点配置。对于nginx
带有 systemd 单元文件的默认包,我使用以下命令声明对挂载的依赖关系:RequiresMountsFor
插入式指令:
# /lib/systemd/system/nginx.service
[Unit]
Description=A high performance web server and a reverse proxy server
After=network.target
[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload
ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid
TimeoutStopSec=5
KillMode=mixed
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/nginx.service.d/override.conf
[Unit]
RequiresMountsFor=/application
[Service]
Restart=always
但出于某种原因,这无助于仅在挂载完成后(在用户数据中)运行 Nginx。我可以看到/application
路径的挂载单元,但我看不到Required=application.mount
我期望的内容:
$ sudo systemctl show -p After,Requires nginx
Requires=system.slice sysinit.target -.mount
After=sysinit.target -.mount systemd-journald.socket basic.target application.mount system.slice network.target
Nginx 服务在完成用户数据执行之前仍尝试运行cloud-init
,用尽所有运行服务的尝试并失败:
Apr 08 15:34:32 hostname nginx[1303]: nginx: [emerg] open() "/application/libexec/etc/nginx/nginx.site.conf" failed (2: No such file or directory) in /etc/nginx/sites-e
Apr 08 15:34:32 hostname nginx[1303]: nginx: configuration file /etc/nginx/nginx.conf test failed
Apr 08 15:34:32 hostname systemd[1]: nginx.service: Control process exited, code=exited status=1
Apr 08 15:34:32 hostname systemd[1]: Failed to start A high performance web server and a reverse proxy server.
我假设该服务应由 systemd 在指定路径的挂载通知时启动/application
。我遗漏了什么?
在 Ubuntu + systemd 上安装 EBS 卷最灵活和正确的方法是什么?
答案1
以下是我对解决方案的看法,该解决方案试图满足https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nvme-ebs-volumes.html#identify-nvme-ebs-device。目前未在生产中使用,但这就是目的……
NVMe 设备名称遵循模式 /dev/nvmen,其中是枚举顺序,对于 EBS,为 1。有时,设备可以在后续实例启动时以不同的顺序响应发现,这会导致设备名称发生变化。
基本思想是Requires=foo.mount
通过将 EBS 挂载过程包装在 systemd 中来模仿 systemd 行为。依赖于 EBS 挂载的其他服务可以简单地指定它们必须在挂载可用时service
启动。After
service
该功能基于将物理设备与卷连接时指定的请求设备进行符号链接的模块构建udev
。例如/dev/sdf
链接到/dev/nvme...
)。请参阅https://github.com/oogali/ebs-automatic-nvme-mapping并参阅上述指南以了解更多详情。
在睡眠循环中等待data-mount.service
符号链接出现(允许您有时间连接卷),然后在定义的挂载点挂载卷,并在必要时对其进行格式化。
挂载 EBS 卷的服务
/sbin/ec2-boot-mount-ebs-volume
#!/bin/bash
#
# Copyright 2019 - binx.io B.V.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Based on https://github.com/binxio/ec2-boot-mount-ebs-volume.
#
# Requires the udev rule that automatically creates a sym-link to the actual device. See the note on udev on https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nvme-ebs-volumes.html#identify-nvme-ebs-device
set -e -o pipefail
exec 1> >(logger -s -t $(basename $0)) 2>&1
function wait_for_device {
while [[ ! -b $(readlink -f $1) ]]; do
echo "waiting for device $1" >&2
sleep 5;
done
}
function label_device {
label=$(blkid $1 | sed -e 's/.*LABEL="\([^"]*\)".*/\1/')
if [[ -z $label ]]; then
echo "INFO: labeling $1 with $2">&2
e2label $1 $2;
elif [[ $label != $2 ]]; then
echo "ERROR: device $1 already has label $label, expected $2">&2; exit 1
fi
}
function main {
local device mount_point fstype options
if [[ $# -ne 4 ]] ;then
echo "Usage: $(basename $0) device-name mount-point fstype options" >&2
echo " waits for the volume, formats it if unformatted and mounts it." >&2
exit 1
fi
device="$1"
mount_point="$2"
fstype="$3"
options="$4"
wait_for_device $device
real_device=$(readlink -f $device)
if [[ $real_device != $device ]] ;then
echo "INFO: $device appeared as $real_device" >&2
fi
if grep -q "^$real_device $mount_point " /proc/mounts; then
echo "INFO: $real_device already mounted on $mount_point" >&2
return 0
fi
if [[ -z $(blkid $real_device) ]] ; then
echo "INFO: formatting $real_device" >&2
mkfs -L $mount_point -t $fstype $real_device
else
echo "INFO: $real_device already formatted" >&2
label_device $real_device $mount_point
fi
echo "INFO: mounting $real_device on $mount_point" >&2
mkdir -p $mount_point
mount -t $fstype -o "$options" $real_device $mount_point
}
main "$@"
/etc/systemd/system/data-mount.service
[Unit]
Description=Mount Data Volume
After=cloud-init-local.service
[Service]
Type=oneshot
RemainAfterExit=yes
# /dev/sdf should be replaced with the device name you requested when attaching the volume
ExecStart=/sbin/ec2-boot-mount-ebs-volume /dev/sdf /mnt/data ext4 "defaults"
ExecStop=umount -v /mnt/data
[Install]
其他服务
/etc/systemd/system/other-service.service.d/override.conf
[Unit]
# if the data-mount.service stops then this service also needs to stop
BindsTo=data-mount.service
# ensure this service starts after the mount is available
After=data-mount.service