Merge pull request #116 from nathanleclaire/bake_ami

[WIP] Add structure to enable baking Moby Linux AMI
This commit is contained in:
Justin Cormack 2016-05-07 10:17:02 +01:00
commit 9cd184e420
8 changed files with 299 additions and 0 deletions

14
alpine/Dockerfile.ami Normal file
View File

@ -0,0 +1,14 @@
FROM alpine
RUN apk add --update \
python \
py-pip \
bash \
curl \
e2fsprogs \
jq \
syslinux
RUN pip install -U awscli
COPY aws/bake-ami.sh .
ENTRYPOINT ["./bake-ami.sh"]

View File

@ -48,6 +48,13 @@ initrd-arm.img: Dockerfile.armhf
docker-compose build arm
docker-compose run --rm -T arm /bin/mkinitrd.sh > $@
ami: initrd.img
$(MAKE) -C kernel
$(MAKE) -C packages
docker-compose build ami
docker-compose run --rm -T ami clean
docker-compose run --rm -T ami bake
clean:
rm -f initrd.img initrd.img.gz initrd-arm.img Dockerfile.armhf etc/inittab
rm -f mobylinux-bios.iso mobylinux-efi.iso mobylinux.efi

1
alpine/aws/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.out

220
alpine/aws/bake-ami.sh Executable file
View File

@ -0,0 +1,220 @@
#!/bin/bash
# Script to automate creation and snapshotting of a Moby AMI. Currently, it's
# intended to be invoked from an instance running in the same region as the
# target AMI will be in, since it directly mounts the created EBS volume as a
# device on this running instance.
set -e
TAG_KEY=moby-bake
INSTANCE_METADATA_API_ENDPOINT=http://169.254.169.254/latest/meta-data/
# TODO(nathanleclaire): This could be calculated dynamically to avoid conflicts.
EBS_DEVICE=/dev/xvdb
function arrowecho () {
echo " --->" "$@"
}
function current_instance_az () {
curl -s ${INSTANCE_METADATA_API_ENDPOINT}/placement/availability-zone
}
function current_instance_id () {
curl -s ${INSTANCE_METADATA_API_ENDPOINT}/instance-id
}
# We tag resources created as part of the build to ensure that they can be
# cleaned up later.
function tag () {
arrowecho "Tagging $1"
aws ec2 create-tags --resources "$1" --tags Key=${TAG_KEY},Value= >/dev/null
}
function format_device () {
arrowecho "Waiting for EBS device to appear in build container"
while [[ ! -e ${EBS_DEVICE} ]]; do
sleep 1
done
# This heredoc might be confusing at first glance, so here is a detailed
# summary of what each line does:
#
# n - create new partition
# p - make it a primary partition
# 1 - it should be partition #1
# \n - use default first cylinder
# \n - use default last cylinder
# a - toggle a partition as bootable
# 1 - do the 1st partition specifically
# w - write changes and exit
arrowecho "Formatting boot partition"
fdisk ${EBS_DEVICE} >/dev/null << EOF
n
p
1
a
1
w
EOF
# To ensure everything went smoothly, print the resulting partition table.
echo
arrowecho "Printing device partition contents"
fdisk -l ${EBS_DEVICE}
ROOT_PARTITION="${EBS_DEVICE}1"
ROOT_PARTITION_MOUNT="/mnt/moby"
# Mount created root partition, format it as ext4, and copy over the needed
# files for boot (syslinux configuration, kernel binary, and initrd.img)
arrowecho "Making filesystem on partition 1"
mke2fs -t ext4 ${ROOT_PARTITION}
arrowecho "Mounting partition filesystem"
mkdir ${ROOT_PARTITION_MOUNT}
mount -t ext4 ${ROOT_PARTITION} ${ROOT_PARTITION_MOUNT}
arrowecho "Copying image and kernel binary to partition"
# Get files needed to boot in place.
cp /mnt/syslinux.cfg ${ROOT_PARTITION_MOUNT}
cp /mnt/kernel/vmlinuz64 ${ROOT_PARTITION_MOUNT}
cp /mnt/initrd.img ${ROOT_PARTITION_MOUNT}
# From http://www.syslinux.org/wiki/index.php?title=EXTLINUX:
#
# "Note that EXTLINUX installs in the filesystem partition like a
# well-behaved bootloader :). Thus, it needs a master boot record in the
# partition table; the mbr.bin shipped with SYSLINUX should work well."
# Thus, this step installs syslinux on the mounted filesystem (partition
# 1).
arrowecho "Installing syslinux to partition"
extlinux --install ${ROOT_PARTITION_MOUNT}
# Format master boot record in partition table on target device.
arrowecho "Copying MBR to partition table in target device"
dd if=/usr/share/syslinux/mbr.bin of=${EBS_DEVICE} bs=440 count=1
umount ${ROOT_PARTITION_MOUNT}
arrowecho "Checking device/partition sanity"
fdisk -l ${EBS_DEVICE}
}
function bake_image () {
# Create a new EBS volume. We will format this volume to boot into Moby
# initrd via syslinux in MBR. That formatted drive can then be snapshotted
# and turned into an AMI.
VOLUME_ID=$(aws ec2 create-volume \
--size 1 \
--availability-zone $(current_instance_az) | jq -r .VolumeId)
tag ${VOLUME_ID}
aws ec2 wait volume-available --volume-ids ${VOLUME_ID}
arrowecho "Attaching volume"
aws ec2 attach-volume \
--volume-id ${VOLUME_ID} \
--device ${EBS_DEVICE} \
--instance-id $(current_instance_id) >/dev/null
aws ec2 wait volume-in-use --volume-ids ${VOLUME_ID}
format_device
arrowecho "Taking snapshot!"
# Take a snapshot of the volume we wrote to.
SNAPSHOT_ID=$(aws ec2 create-snapshot \
--volume-id ${VOLUME_ID} \
--description "Snapshot of Moby device for AMI baking" | jq -r .SnapshotId)
tag ${SNAPSHOT_ID}
arrowecho "Waiting for snapshot completion"
aws ec2 wait snapshot-completed --snapshot-ids ${SNAPSHOT_ID}
# Convert that snapshot into an AMI as the root device.
IMAGE_ID=$(aws ec2 register-image \
--name "Moby Linux" \
--description "The best OS for running Docker" \
--architecture x86_64 \
--root-device-name "${EBS_DEVICE}" \
--virtualization-type "hvm" \
--block-device-mappings "[
{
\"DeviceName\": \"${EBS_DEVICE}\",
\"Ebs\": {
\"SnapshotId\": \"${SNAPSHOT_ID}\"
}
}
]" | jq -r .ImageId)
tag ${IMAGE_ID}
# Boom, now you (should) have a Moby AMI.
arrowecho "Created AMI: ${IMAGE_ID}"
echo ${IMAGE_ID} >/mnt/aws/ami_id.out
}
function clean_tagged_resources () {
if [[ -d /mnt/moby ]]; then
rm -rf /mnt/moby
fi
VOLUME_ID=$(aws ec2 describe-volumes --filters "Name=tag-key,Values=$TAG_KEY" | jq -r .Volumes[0].VolumeId)
if [[ ${VOLUME_ID} == "null" ]]; then
arrowecho "No volume found, skipping"
else
arrowecho "Detaching volume"
aws ec2 detach-volume --volume-id ${VOLUME_ID} >/dev/null
aws ec2 wait volume-available --volume-ids ${VOLUME_ID}
arrowecho "Deleting volume"
aws ec2 delete-volume --volume-id ${VOLUME_ID} >/dev/null
fi
IMAGE_ID=$(aws ec2 describe-images --filters "Name=tag-key,Values=$TAG_KEY" | jq -r .Images[0].ImageId)
if [[ ${IMAGE_ID} == "null" ]]; then
arrowecho "No image found, skipping"
else
arrowecho "Deregistering previously baked AMI"
# Sometimes describe-images does not return null even if the found
# image cannot be deregistered
#
# TODO(nathanleclaire): More elegant solution?
aws ec2 deregister-image --image-id ${IMAGE_ID} >/dev/null || true
fi
SNAPSHOT_ID=$(aws ec2 describe-snapshots --filters "Name=tag-key,Values=$TAG_KEY" | jq -r .Snapshots[0].SnapshotId)
if [[ ${SNAPSHOT_ID} == "null" ]]; then
arrowecho "No snapshot found, skipping"
else
arrowecho "Deleting volume snapshot"
aws ec2 delete-snapshot --snapshot-id ${SNAPSHOT_ID}
fi
}
case "$1" in
bake)
bake_image
;;
clean)
clean_tagged_resources
;;
regions)
# TODO(nathanleclaire): Propogate created image (if existing) to every
# possible region / AZ
;;
*)
arrowecho "Command $1 not found. Usage: ./bake-ami.sh [bake|clean|regions]"
esac

35
alpine/aws/run-instance.sh Executable file
View File

@ -0,0 +1,35 @@
#!/bin/bash
# Quick script to boot an instance from generated AMI. Intended to be invoked
# from "alpine" directory.
set -e
INSTANCE_ID=$(cat ./aws/instance_id.out)
aws ec2 terminate-instances --instance-id ${INSTANCE_ID} || true
if [[ ! -f ./aws/ami_id.out ]]; then
echo "AMI ID to launch instance from not found"
exit 1
fi
AMI_ID=$(cat ./aws/ami_id.out)
echo "Running instance from ${AMI_ID}"
INSTANCE_ID=$(aws ec2 run-instances --image-id ${AMI_ID} --instance-type t2.nano | jq -r .Instances[0].InstanceId)
aws ec2 create-tags --resources ${INSTANCE_ID} --tags Key=Name,Value=moby-boot-from-ami
echo "Running instance ${INSTANCE_ID}"
echo ${INSTANCE_ID} >./aws/instance_id.out
echo "Waiting for instance boot log to become available"
INSTANCE_BOOT_LOG="null"
while [[ ${INSTANCE_BOOT_LOG} == "null" ]]; do
INSTANCE_BOOT_LOG=$(aws ec2 get-console-output --instance-id ${INSTANCE_ID} | jq -r .Output)
sleep 5
done
aws ec2 get-console-output --instance-id ${INSTANCE_ID} | jq -r .Output

View File

@ -26,3 +26,13 @@ services:
context: .
dockerfile: Dockerfile.armhf
network_mode: bridge
ami:
privileged: true
build:
context: .
dockerfile: Dockerfile.ami
network_mode: bridge
volumes:
- .:/mnt
- $HOME/.aws:/root/.aws:ro
- /dev:/dev

View File

@ -26,7 +26,10 @@ mknod -m 666 zero c 1 5
mknod -m 666 tty c 5 0
mknod -m 600 console c 5 1
mknod -m 600 tty0 c 4 11
mknod -m 600 tty1 c 4 12
mknod -m 600 ttyS0 c 4 64
mknod -m 600 hvc0 c 229 2
mknod -m 600 fuse c 10 229
# we are using sata emulation at present
@ -44,6 +47,8 @@ mknod -m 600 sdb3 b 8 19
mknod -m 600 sdb4 b 8 20
mknod -m 600 sdb5 b 8 21
mknod -m 600 sdb6 b 8 22
mknod -m 600 xvdf b 8 23
mknod -m 600 xvdf1 b 8 24
# mount points in /dev
mkdir pts mqueue shm

7
alpine/syslinux.cfg Normal file
View File

@ -0,0 +1,7 @@
DEFAULT linux
TIMEOUT 0
PROMPT 0
LABEL linux
KERNEL /vmlinuz64
INITRD /initrd.img
APPEND root=/dev/xvdb1 console=hvc0 console=tty0 console=tty1 console=ttyS0