1
0
mirror of https://gitlab.alpinelinux.org/alpine/cloud/tiny-cloud.git synced 2025-12-16 11:52:43 +03:00

* initial round of nocloud should be just about complete, pending tests

* update README, example config
* nocloud and aws share IMDS layout, move vars and funcs from aws imds lib to main imds script
* IMDS_USERDATA value is no longer configurable
* note that azure and oci are missing some network bits yet
This commit is contained in:
Jake Buchholz Göktürk 2022-07-10 20:22:59 -07:00
parent ca264b7387
commit 64a6c4ea4f
12 changed files with 162 additions and 110 deletions

View File

@ -1,6 +1,6 @@
PREFIX?=/ PREFIX?=/
SUBPACKAGES = core network openrc aws azure gcp oci SUBPACKAGES = core network openrc aws azure gcp oci nocloud
.PHONY: install $(SUBPACKAGES) .PHONY: install $(SUBPACKAGES)
@ -59,5 +59,11 @@ oci: conf_dir
sed -Ee 's/^#?CLOUD=.*/CLOUD=oci/' \ sed -Ee 's/^#?CLOUD=.*/CLOUD=oci/' \
etc/conf.d/tiny-cloud.example > "$(PREFIX)"/etc/conf.d/tiny-cloud etc/conf.d/tiny-cloud.example > "$(PREFIX)"/etc/conf.d/tiny-cloud
nocloud: conf_dir
install -Dm644 -t $(PREFIX)/lib/tiny-cloud/nocloud \
lib/tiny-cloud/nocloud/*
sed -Ee 's/^#?CLOUD=.*/CLOUD=nocloud/' \
etc/conf.d/tiny-cloud.example > "$(PREFIX)"/etc/conf.d/tiny-cloud
conf_dir: conf_dir:
mkdir -p "$(PREFIX)"/etc/conf.d mkdir -p "$(PREFIX)"/etc/conf.d

View File

@ -8,10 +8,13 @@ do just what is necessary with a small footprint and minimal dependencies.
A direct descendant of [tiny-ec2-bootstrap]( A direct descendant of [tiny-ec2-bootstrap](
https://gitlab.alpinelinux.org/alpine/cloud/tiny-ec2-bootstrap), Tiny Cloud https://gitlab.alpinelinux.org/alpine/cloud/tiny-ec2-bootstrap), Tiny Cloud
works with multiple cloud providers. Currently, the following are supported: works with multiple cloud providers. Currently, the following are supported:
* AWS (Amazon Web Services) * [AWS](https://aws.amazon.com) (Amazon Web Services)
* Azure (Microsoft Azure) * [Azure](https://azure.microsoft.com) (Microsoft Azure)
* GCP (Google Cloud Platform) * [GCP](https://cloud.google.com) (Google Cloud Platform)
* OCI (Oracle Cloud Infrastructure) * [OCI](https://cloud.oracle.com) (Oracle Cloud Infrastructure)
* [NoCloud](
https://cloudinit.readthedocs.io/en/latest/topics/datasources/nocloud.html)
(cloud-init's NoCloud AWS-compatible data source)
## Features ## Features
@ -44,9 +47,12 @@ As Tiny Cloud is meant to be tiny, it has very few dependencies:
* `partx` * `partx`
* `resize2fs` * `resize2fs`
* `sfdisk` * `sfdisk`
* [`yx`](https://gitlab.com/tomalok/yx)
(optional, allows NoCloud to extract metadata from YAML files)
Tiny Cloud has been developed specifically for use with the Tiny Cloud has been developed specifically for use with the
[Alpine Cloud Images](https://gitlab.alpinelinux.org/alpine/cloud/alpine-cloud-images) [Alpine Cloud Images](
https://gitlab.alpinelinux.org/alpine/cloud/alpine-cloud-images)
project, and as such, it is currently tailored for use with [Alpine Linux]( project, and as such, it is currently tailored for use with [Alpine Linux](
https://alpinelinux.org), the [OpenRC](https://github.com/OpenRC/openrc) init https://alpinelinux.org), the [OpenRC](https://github.com/OpenRC/openrc) init
system, and the `ext4` root filesystem. If you would like to see Tiny Cloud system, and the `ext4` root filesystem. If you would like to see Tiny Cloud
@ -57,12 +63,12 @@ open an issue with your request -- or better yet, submit a merge request!
Typically, Tiny Cloud is installed and configured when building a cloud image, Typically, Tiny Cloud is installed and configured when building a cloud image,
and is available on Alpine Linux as the [`tiny-cloud`]( and is available on Alpine Linux as the [`tiny-cloud`](
https://pkgs.alpinelinux.org/packages?name=tiny-cloud) APK... https://pkgs.alpinelinux.org/packages?name=tiny-cloud*) APKs...
``` ```
apk install tiny-cloud apk install tiny-cloud-<cloud>
``` ```
This will install the necessary init scripts, libraries, etc. plus any missing This will install the necessary init scripts, libraries, etc. plus any missing
dependencies. dependencies for Tiny Cloud to support _`<cloud>`_.
Alternately, you can download a release tarball, and use `make` to install it. Alternately, you can download a release tarball, and use `make` to install it.
@ -79,9 +85,9 @@ By default, Tiny Cloud expects configuration at `/etc/conf.d/tiny-cloud`,
The stock [`etc/conf.d/tiny-cloud`](etc/conf.d/tiny-cloud) file contains The stock [`etc/conf.d/tiny-cloud`](etc/conf.d/tiny-cloud) file contains
details of all tuneable settings. details of all tuneable settings.
*Because Tiny Cloud does not currently do auto-detection, you **MUST** set a _Because Tiny Cloud does not currently do auto-detection, you **MUST** set a
configuration value for `CLOUD` indicating which cloud provider will be used. configuration value for `CLOUD` indicating which cloud provider will be used.
Current valid values are `aws`, `azure`, `gcp`, and `oci`.* Current valid values are `aws`, `azure`, `gcp`, `oci`, and `nocloud`._
## Operation ## Operation
@ -106,13 +112,18 @@ data, and sets up instance's hostname and the cloud user's SSH keys before
`tiny-cloud-final` should be the very last init script to run in the `tiny-cloud-final` should be the very last init script to run in the
**default** runlevel. By default, it saves the instance's user data to **default** runlevel. By default, it saves the instance's user data to
`/var/lib/cloud/user-data`, which is overrideable via the `TINY_CLOUD_VAR` `/var/lib/cloud/user-data`; the directory overrideable via the `TINY_CLOUD_VAR`
andr `CLOUD_USERDATA` config settings. config setting.
If the user data is a script starting with `#!/`, it will be executed; its If the user data is compressed, Tiny Cloud will decompress it. Currently
supported compression algorithms are `gzip`, `bzip2`, `unxz`, `lzma`, `lzop`,
`lz4`, and `zstd`. _(Note that `lz4` and `zstd` are not installed in Alpine
by default, and would need to be added to the image.)_
If the user data is a script starting with `#!`, it will be executed; its
output (combined STDOUT and STDERR) and exit code are saved to output (combined STDOUT and STDERR) and exit code are saved to
`/var/log/user-data.log` and `/var/log/user-data.exit`, respectively -- unless `/var/log/user-data.log` and `/var/log/user-data.exit`, respectively; the
overriden with `TINY_CLOUD_LOGS` and `CLOUD_USERDATA` config settings. directory is overrideable via the `TINY_CLOUD_LOGS` config setting.
If all went well, the very last thing `tiny-cloud-final` does is touch If all went well, the very last thing `tiny-cloud-final` does is touch
a `.bootstrap-complete` file into existence in `/var/lib/cloud` or another a `.bootstrap-complete` file into existence in `/var/lib/cloud` or another

View File

@ -12,18 +12,7 @@
unset \ unset \
IMDS_HEADER \ IMDS_HEADER \
IMDS_URI \ IMDS_URI \
IMDS_QUERY \ IMDS_QUERY
IMDS_HOSTNAME \
IMDS_SSH_KEYS \
IMDS_USERDATA \
IMDS_NICS \
IMDS_MAC \
IMDS_IPV4 \
IMDS_IPV6 \
IMDS_IPV4_NET \
IMDS_IPV6_NET \
IMDS_IPV4_PREFIX \
IMDS_IPV6_PREFIX
unset -f \ unset -f \
_imds_token \ _imds_token \
_imds_header \ _imds_header \
@ -35,15 +24,36 @@ unset -f \
CLOUD="${CLOUD:-unknown}" CLOUD="${CLOUD:-unknown}"
IMDS_ENDPOINT="169.254.169.254" IMDS_ENDPOINT="169.254.169.254"
# Common to AWS and NoCloud(ish)
IMDS_HOSTNAME="meta-data/hostname"
IMDS_SSH_KEYS="meta-data/public-keys"
IMDS_USERDATA="user-data"
IMDS_NICS="meta-data/network/interfaces/macs"
IMDS_MAC="mac"
IMDS_IPV4="local-ipv4s"
IMDS_IPV6="ipv6s"
IMDS_IPV4_NET="subnet-ipv4-cidr-block"
IMDS_IPV6_NET="subnet-ipv6-cidr-blocks"
IMDS_IPV4_PREFIX="ipv4-prefix"
IMDS_IPV6_PREFIX="ipv6-prefix"
_imds() { _imds() {
wget --quiet --timeout 1 --output-document - \ wget --quiet --timeout 1 --output-document - \
--header "$(_imds_header)" \ --header "$(_imds_header)" \
"http://$IMDS_ENDPOINT/$IMDS_URI/$1$IMDS_QUERY" "http://$IMDS_ENDPOINT/$IMDS_URI/$1$IMDS_QUERY"
} }
_imds_ssh_keys() { _imds "$IMDS_SSH_KEYS"; }
_imds_userdata() { _imds "$IMDS_USERDATA"; } _imds_userdata() { _imds "$IMDS_USERDATA"; }
_imds_ssh_keys() {
local key
for key in $(imds "$IMDS_SSH_KEYS"); do
imds "$IMDS_SSH_KEYS/${key%=*}/openssh-key"
done | sort -u
}
_imds_nic_index() { cat "/sys/class/net/$1/address"; }
### load cloud-specific variables and functions ### load cloud-specific variables and functions
if [ ! -d /lib/tiny-cloud/"$CLOUD" ]; then if [ ! -d /lib/tiny-cloud/"$CLOUD" ]; then

View File

@ -1,15 +1,12 @@
# Tiny Cloud configuration # Tiny Cloud configuration
# REQUIRED: The instance's cloud provider (valid: aws, azure, gcp, oci) # REQUIRED: The instance's cloud provider
# valid: aws, azure, gcp, oci # valid: aws, azure, gcp, oci, nocloud
#CLOUD= #CLOUD=
# User account where instance SSH keys are installed # User account where instance SSH keys are installed
#CLOUD_USER=alpine #CLOUD_USER=alpine
# Filename of userdata file (in TINY_CLOUD_VAR directory)
#CLOUD_USERDATA=user-data
# IMDS token validity, in seconds (AWS only) # IMDS token validity, in seconds (AWS only)
#IMDS_TOKEN_TTL=5 #IMDS_TOKEN_TTL=5
@ -28,5 +25,5 @@
# Explicitly skip these (whitespace delimited) things during init # Explicitly skip these (whitespace delimited) things during init
# valid: expand_root install_hotplugs set_hostname set_ssh_keys # valid: expand_root install_hotplugs set_hostname set_ssh_keys
# save_userdata run_userdata # save_userdata decompress_userdata run_userdata
#SKIP_INIT_ACTIONS= #SKIP_INIT_ACTIONS=

View File

@ -6,19 +6,6 @@ IMDS_TOKEN_TTL_HEADER="X-aws-ec2-metadata-token-ttl-seconds"
IMDS_TOKEN_TTL=${IMDS_TOKEN_TTL:-5} IMDS_TOKEN_TTL=${IMDS_TOKEN_TTL:-5}
IMDS_URI="latest" IMDS_URI="latest"
IMDS_HOSTNAME="meta-data/hostname"
IMDS_SSH_KEYS="meta-data/public-keys"
IMDS_USERDATA="user-data"
IMDS_NICS="meta-data/network/interfaces/macs"
IMDS_MAC="mac"
IMDS_IPV4="local-ipv4s"
IMDS_IPV6="ipv6s"
IMDS_IPV4_NET="subnet-ipv4-cidr-block"
IMDS_IPV6_NET="subnet-ipv6-cidr-blocks"
IMDS_IPV4_PREFIX="ipv4-prefix"
IMDS_IPV6_PREFIX="ipv6-prefix"
_imds_token() { _imds_token() {
echo -ne "PUT /latest/api/token" \ echo -ne "PUT /latest/api/token" \
"HTTP/1.0\r\n$IMDS_TOKEN_TTL_HEADER: $IMDS_TOKEN_TTL\r\n\r\n" | "HTTP/1.0\r\n$IMDS_TOKEN_TTL_HEADER: $IMDS_TOKEN_TTL\r\n\r\n" |
@ -28,13 +15,3 @@ _imds_token() {
_imds_header() { _imds_header() {
echo "$IMDS_HEADER: $(_imds_token)" echo "$IMDS_HEADER: $(_imds_token)"
} }
# digs deeper than the default
_imds_ssh_keys() {
local key
for key in $(imds "$IMDS_SSH_KEYS"); do
imds "$IMDS_SSH_KEYS/${key%=*}/openssh-key"
done | sort -u
}
_imds_nic_index() { cat "/sys/class/net/$1/address"; }

View File

@ -8,8 +8,17 @@ IMDS_URI="metadata/instance"
IMDS_HOSTNAME="compute/name" IMDS_HOSTNAME="compute/name"
IMDS_SSH_KEYS="compute/publicKeys" IMDS_SSH_KEYS="compute/publicKeys"
IMDS_USERDATA="compute/userData" IMDS_USERDATA="compute/userData"
IMDS_NICS="network/interface"
IMDS_NICS="" # TODO: flesh out networking
unset \
IMDS_MAC \
IMDS_IPV4 \
IMDS_IPV6 \
IMDS_IPV4_NET \
IMDS_IPV6_NET \
IMDS_IPV4_PREFIX \
IMDS_IPV6_PREFIX
_imds_header() { _imds_header() {
echo "$IMDS_HEADER: true" echo "$IMDS_HEADER: true"

View File

@ -2,7 +2,7 @@
# vim: ts=4 et ft=sh: # vim: ts=4 et ft=sh:
log() { log() {
local facility=kern local facility="kern"
local stderr local stderr
local tag=$(basename "$0") local tag=$(basename "$0")
while [ "${1:0:1}" = '-' ]; do while [ "${1:0:1}" = '-' ]; do

View File

@ -5,7 +5,6 @@
# set defaults # set defaults
CLOUD_USER=${CLOUD_USER:-alpine} CLOUD_USER=${CLOUD_USER:-alpine}
CLOUD_USERDATA=${CLOUD_USERDATA:-user-data}
TINY_CLOUD_LOGS=${TINY_CLOUD_LOGS:-/var/log} TINY_CLOUD_LOGS=${TINY_CLOUD_LOGS:-/var/log}
TINY_CLOUD_VAR=${TINY_CLOUD_VAR:-/var/lib/cloud} TINY_CLOUD_VAR=${TINY_CLOUD_VAR:-/var/lib/cloud}
SKIP_INIT_ACTIONS=${SKIP_INIT_ACTIONS:-} SKIP_INIT_ACTIONS=${SKIP_INIT_ACTIONS:-}

View File

@ -9,7 +9,6 @@ match_header() {
[ "$bytes" = $(dd bs=1 count=${#bytes} if="$2" 2>/dev/null) ] [ "$bytes" = $(dd bs=1 count=${#bytes} if="$2" 2>/dev/null) ]
} }
# TODO: also do this for nocloud meta-data and vendor-data?
save_userdata() { save_userdata() {
skip_action save_userdata && return skip_action save_userdata && return
@ -18,29 +17,30 @@ save_userdata() {
local cmd local cmd
imds -e @userdata > "$tmpfile" imds -e @userdata > "$tmpfile"
cmd="cat"
if ! skip_action decompress_userdata; then
if match_header '\037\213\010' "$tmpfile"; then if match_header '\037\213\010' "$tmpfile"; then
cmd='gzip -dc' cmd="gzip -dc"
elif match_header 'BZh' "$tmpfile"; then elif match_header 'BZh' "$tmpfile"; then
cmd='bzip2 -dc' cmd="bzip2 -dc"
elif match_header '\3757zXZ\000' "$tmpfile"; then elif match_header '\3757zXZ\000' "$tmpfile"; then
cmd='xz -dc' cmd="unxz -c"
elif match_header '\135\0\0\0' "$tmpfile"; then elif match_header '\135\0\0\0' "$tmpfile"; then
cmd='lzma -dc' cmd="lzma -dc"
elif match_header '\211\114\132' "$tmpfile"; then elif match_header '\211\114\132' "$tmpfile"; then
cmd='lzop -dc' cmd="lzop -dc"
elif match_header '\002!L\030' "$tmpfile"; then elif match_header '\002!L\030' "$tmpfile"; then
cmd='lz4 -dc' cmd="lz4 -dc"
elif match_header '(\265/\375' "$tmpfile"; then elif match_header '(\265/\375' "$tmpfile"; then
cmd='zstd -dc' cmd="zstd -dc"
else fi
cmd='cat'
fi fi
$cmd "$tmpfile" > "$userdata" $cmd "$tmpfile" > "$userdata"
rm "$tmpfile" rm "$tmpfile"
} }
is_userdata_script() { is_userdata_script() {
head -n1 "$TINY_CLOUD_VAR/$CLOUD_USERDATA" | grep -q '#!/' head -n1 "$TINY_CLOUD_VAR/$CLOUD_USERDATA" | grep -q "#!/"
} }
run_userdata() { run_userdata() {

View File

@ -15,7 +15,7 @@ install_before() {
line="-$line" line="-$line"
else else
# no rule exists, put it before the catch-all fallback # no rule exists, put it before the catch-all fallback
before='^# fallback' before="^# fallback"
line="$line\n" line="$line\n"
fi fi
sed -i -Ee "s|($before.*)|$line\n\1|" /etc/mdev.conf sed -i -Ee "s|($before.*)|$line\n\1|" /etc/mdev.conf
@ -25,8 +25,8 @@ install_before() {
mod__vnic_eth_hotplug() { mod__vnic_eth_hotplug() {
[ -f /lib/mdev/vnic-eth-hotplug ] || return 1 [ -f /lib/mdev/vnic-eth-hotplug ] || return 1
install_before '^eth' \ install_before "^eth" \
'eth[0-9] root:root 0644 */lib/mdev/vnic-eth-hotplug' "eth[0-9] root:root 0644 */lib/mdev/vnic-eth-hotplug"
# NICs attached at launch don't get added with mdev -s # NICs attached at launch don't get added with mdev -s
assemble-interfaces assemble-interfaces

View File

@ -1,46 +1,76 @@
# NoCloud Instance Metadata # NoCloud Instance Metadata
# vim: ts=4 et ft=sh: # vim: ts=4 et ft=sh:
IMDS_HOSTNAME=meta-data/hostname NOCLOUD_FILES="meta-data user-data vendor-data network-config"
IMDS_SSH_KEYS=meta-data/ssh-keys
IMDS_USERDATA=user-data
# have we loaded the nocloud meta/user/vendor data?
is_nocloud_loaded() { [ -f "$TINY_CLOUD_VAR/.nocloud_loaded" ]; } is_nocloud_loaded() { [ -f "$TINY_CLOUD_VAR/.nocloud_loaded" ]; }
# from location specified in kernel cmdline
_load_nocloud_cmdline() { _load_nocloud_cmdline() {
# TODO local kopt kv k v data
for kopt in $(cat /proc/cmdline); do
echo "$kopt" | grep -qE '(^|=)ds=nocloud(-net)?(;|$)' || continue
for kv in $(echo "$kopt" | cut -d\; -f2-); do
k=$(echo "$kv" | cut -d= -f1)
v=$(echo "$kv" | cut -d= -f2- | sed -Ee s'|^file://|/|')
case "$k" in
h|hostname)
echo -e "\nhostname: $hostname" >> "$TINY_CLOUD_VAR/meta-data"
;;
i|instance-id)
echo -e "\ninstance-id: $hostname" >> "$TINY_CLOUD_VAR/meta-data"
;;
s|seedfrom)
for data in $NOCLOUD_FILES; do
case "$v" in
/*)
cat "$v" >> "$TINY_CLOUD_VAR/$data" || continue
echo >> "$TINY_CLOUD_VAR/$data"
;;
http://*|https://*)
wget -qO - "$v" >> "$TINY_CLOUD_VAR/$data" || continue
echo >> "$TINY_CLOUD_VAR/$data"
;;
*) echo "WARNING: Ignoring unknown seedfrom value '$v'" >&2
;;
esac
;;
done
;;
*) echo "WARNING: Ignoring unknown nocloud kernel cmdline key '$k'" >&2
;;
esac
done
return
done
} }
# from volume labeled cidata|CIDATA
_load_nocloud_volume() { _load_nocloud_volume() {
local mntdir='/mnt/cidata' local mntdir=$(mktmemp /mnt/cidata-XXXXXX)
local data
mkdir "$mntdir" mkdir -p "$mntdir"
mount -L cidata "$mntdir" || mount -L CIDATA "$mntdir" || return 1 # TODO: are lables case insensitive?
for data in meta user vendor; do mount LABEL=cidata "$mntdir" || mount LABEL=CIDATA "$mntdir" || return 1
if ! cp "$mntdir/$data-data" "$TINY_CLOUD_VAR/$data-data"; then for data in $NOCLOUD_FILES; do
[ "$data" = vendor ] && continue cp "$mntdir/$data" "$TINY_CLOUD_VAR/$data" 2>/dev/null
echo "ERROR: Required $data-data not found on CIDATA volume"
umount "$mntdir"
return 1
fi
done done
umount "$mntdir" umount "$mntdir"
rmdir "$mntdir"
} }
load_nocloud() { load_nocloud() {
is_nocloud_loaded && return is_nocloud_loaded && return
if grep -qE ' ds=nocloud(-net)?[; ]' /proc/cmdline; then
if ! _load_nocloud_cmdline; then # start with a clean slate
echo 'ERROR: Unable to load nocloud data specified in kernel cmdline' >&2 rm -f $NOCLOUD_FILES
return 1
fi if ! _load_nocloud_cmdline || _load_nocloud_volume; then
elif ! _load_nocloud_volume; then echo "ERROR: Unable to load NoCloud data" >&2
echo 'ERROR: Unable to load nocloud data from CIDATA volume' >&2
return 1 return 1
fi fi
# at the very minimum, we expect something in meta-data
touch "$TINY_CLOUD_VAR/.nocloud_loaded" touch "$TINY_CLOUD_VAR/.nocloud_loaded"
} }

View File

@ -7,11 +7,24 @@ IMDS_URI="opc/v2"
IMDS_HOSTNAME="instance/hostname" IMDS_HOSTNAME="instance/hostname"
IMDS_SSH_KEYS="instance/metadata/ssh_authorized_keys" IMDS_SSH_KEYS="instance/metadata/ssh_authorized_keys"
IMDS_USERDATA="instance/metadata/userdata" IMDS_USERDATA="instance/metadata/userdata"
IMDS_NICS="nics"
# TODO: flesh out networking
unset \
IMDS_MAC \
IMDS_IPV4 \
IMDS_IPV6 \
IMDS_IPV4_NET \
IMDS_IPV6_NET \
IMDS_IPV4_PREFIX \
IMDS_IPV6_PREFIX
_imds_header() { _imds_header() {
echo "$IMDS_HEADER: Bearer Oracle" echo "$IMDS_HEADER: Bearer Oracle"
} }
_imds_ssh_keys() { _imds "$IMDS_SSH_KEYS"; }
_imds_nic_index() { _imds_nic_index() {
local m n=0 local m n=0
local mac=$(cat /sys/class/net/$1/mac) local mac=$(cat /sys/class/net/$1/mac)