Skip to content

Instantly share code, notes, and snippets.

@george-hawkins
Last active August 5, 2024 07:02
Show Gist options
  • Save george-hawkins/16ee37063213f348a17717a7007d2c79 to your computer and use it in GitHub Desktop.
Save george-hawkins/16ee37063213f348a17717a7007d2c79 to your computer and use it in GitHub Desktop.
Running virtualized x86_64 and emulated arm64 Ubuntu cloud images using QEMU

QEMU arm64 cloud server emulation

This is basically a rehash of an original post on CNXSoft - all credit (particularly for the Virtio device arguments used below) belongs to the author of that piece.

Download the latest uefi1.img image. E.g. ubuntu-16.04-server-cloudimg-arm64-uefi1.img from https://cloud-images.ubuntu.com/releases/16.04/release/

Download the UEFI firmware image QEMU_EFI.fd from https://releases.linaro.org/components/kernel/uefi-linaro/latest/release/qemu64/

Determine your current username and get your current ssh public key:

$ whoami
ghawkins
$ cat ~/.ssh/id_rsa.pub 
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC...

Use these values to create a cloud.txt file replacing the username, here shown as ghawkins, and the ssh-rsa value with the values appropriate for you:

$ cat > cloud.txt << EOF
#cloud-config
users:
  - name: ghawkins
    ssh-authorized-keys:
      - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC...
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    groups: sudo
    shell: /bin/bash
EOF

Important: the #cloud-config line above is not a comment and things will fail silently without it.

Cread a cloud-config disk image:

$ cloud-localds --disk-format qcow2 cloud.img cloud.txt

Note: by default cloud-localds creates a raw image and QEMU now complains at having to guess about such an image so use --disk-format qcow2 to specify a well defined format that QEMU can easily consume.

Backup your image:

$ cp ubuntu-16.04-server-cloudimg-arm64-disk1.img ubuntu-16.04-server-cloudimg-arm64-disk1.img.orig

The QEMU launch command is somewhat more complex than for e.g. a fully virtualized, rather than emulated, setup with an x86_64 guest running on an x86_64 host.

Here is the command first:

$ qemu-system-aarch64 \
    -smp 2 \
    -m 1024 \
    -M virt \
    -cpu cortex-a57 \
    -bios QEMU_EFI.fd \
    -nographic \
    -device virtio-blk-device,drive=image \
    -drive if=none,id=image,file=ubuntu-16.04-server-cloudimg-arm64-uefi1.img \
    -device virtio-blk-device,drive=cloud \
    -drive if=none,id=cloud,file=cloud.img \
    -device virtio-net-device,netdev=user0 \
    -netdev user,id=user0 \
    -redir tcp:2222::22

You'll have to change ubuntu-16.04-server-cloudimg-arm64-uefi1.img if you downloaded a later image with a different name.

Now let's look at the arguments that configure our system:

  • -smp 2 - 2 (virtual) cores.
  • -m 1024 - 1024MB of system memory.
  • -M virt - emulate a generic QEMU ARM machine.
  • -cpu cortex-a57 - the CPU model to emulate.
  • -bios QEMU_EFI.fd - the BIOS firmware file to use.
  • -nographic - output goes to the terminal (rather than opening a graphics capable window).
  • -device virtio-blk-device,drive=image - create a Virtio block device called "image".
  • -drive if=none,id=image,file=ubuntu-16.04-server-cloudimg-arm64-uefi1.img - create a drive using the "image" device and our cloud server disk image.
  • -device virtio-blk-device,drive=cloud - create another Virtio block device called "cloud".
  • -drive if=none,id=cloud,file=cloud.img - create a drive using the "cloud" device and our cloud-config disk image.
  • -device virtio-net-device,netdev=user0 - create a Virtio network device called "user0"
  • -netdev user,id=user0 - create a user mode network stack using device "user0"
  • -redir tcp:2222::22 - map port 2222 on the host to port 22 (the standard ssh port) on the guest.

Here we create a generic QEMU ARM machine. You can see a complete list of possible ARM machines like so:

$ qemu-system-aarch64 -M help
akita                Sharp SL-C1000 (Akita) PDA (PXA270)
...
z2                   Zipit Z2 (PXA27x)

This list seems to include all ARM machines, not just 64-bit ones. The latest versions of QEMU (but not the one that currently comes with Ubuntu 16.04 LTS) include the well know Raspberry Pi 2 (but not the 3).

For a given machine you can then see the supported processors:

$ qemu-system-aarch64 -M virt -cpu help
 arm1026
 ...
 ti925t

Once you run the command up above to launch an emulated ARM64 machine it will take a few minutes to boot and will output something like the following:

error: no suitable video mode found.
EFI stub: Booting Linux Kernel...
EFI stub: Using DTB from configuration table
EFI stub: Exiting boot services and installing virtual address map...
[    0.000000] Booting Linux on physical CPU 0x0
[    0.000000] Initializing cgroup subsys cpuset

The initial error, about "no suitable video mode found", can be ignored - we specifically set -nographic.

Eventually a login prompt will appear - which cannot be used as in our cloud-config file we only specified key based ssh login.

Depending on how fast various jobs (kicked off during the boot process) run further output will appear after the login prompt appears.

The first time you launch a given system you should see output confirming that the ssh key specified up above has been installed.

And eventually you should see something like:

[  220.784509] cloud-init[1358]: Cloud-init v. 0.7.8 finished at ...

Now in another terminal you can log in to the newly launched cloud server:

$ ssh -p 2222 ghawkins@localhost

If all goes well you'll log straight in without any username or password.

If you've started previous QEMU images in a similar manner then ssh may issue a dire warning like so (and refuse to login):

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!

To resolve this and remove previous details:

$ ssh-keygen -f ~/.ssh/known_hosts -R '[localhost]:2222'

When logged into the cloud server you can...

  • Confirm that it's an aarch64 system:
$ uname -a
Linux ubuntu 4.4.0-59-generic #80-Ubuntu SMP Fri Jan 6 17:37:14 UTC 2017 aarch64 aarch64 aarch64 GNU/Linux
  • Has two cores:
$ cat /proc/cpuinfo
processor   : 0
...

processor   : 1
...
  • Shut it down:
$ sudo shutdown now

In the original terminal (where you launched qemu-system-aarch64) you can follow the shutdown process.

Note: when running sudo shutdown now the shutdown succeeds but the following error appears:

sudo: unable to resolve host ubuntu

You'll see this anytime you run sudo - to resolve it (as per Ask Ubuntu) just edit /etc/hosts and add ubuntu at the end of the existing line for the address 127.0.0.1 so you end up with something like:

127.0.0.1 localhost ubuntu

QEMU x86_64 cloud server virtualization

Get a cloud image from:

https://cloud-images.ubuntu.com/releases/

E.g. https://cloud-images.ubuntu.com/releases/16.10/release/ubuntu-16.10-server-cloudimg-amd64.img

Create a cloud-config called cloud.txt, which defines who can login etc. to the virtual cloud server, and create a disk image from it. For this you need your login name on your current system, along with the public part of your current ssh key:

$ whoami
ghawkins
$ cat ~/.ssh/id_rsa.pub 
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC...
$ cat > cloud.txt << EOF
#cloud-config
users:
  - name: ghawkins
    ssh-authorized-keys:
      - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC...
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    groups: sudo
    shell: /bin/bash
EOF
$ cloud-localds cloud.img cloud.txt

Copy the line contained in id_rsa.pub into the ssh-authorized-keys section and replace the username specified by name with your username.

Important: I thought #cloud-config was a comment and left it out - but without it no error is reported but you cannot login later.

Backup your image:

$ cp ubuntu-16.10-server-cloudimg-amd64.img ubuntu-16.10-server-cloudimg-amd64.img.orig

Note: this is a compressed qcow2 image - while it's about 320MB the running guest will see it as 2GB (as we'll confirm later).

Now start the cloud guest:

$ qemu-system-x86_64 \
    -enable-kvm \
    -smp 2 \
    -m 1024 \
    -nographic \
    -hda ubuntu-16.10-server-cloudimg-amd64.img \
    -hdb cloud.img \
    -redir tcp:2222::22

The command line arguments:

  • -enable-kvm - full virtualization (rather than emulation).
  • -smp 2 - two (virtual) processors (as we'll confirm later).
  • -m 1024 - 1024MB of system memory.
  • -nographic - output goes to the terminal (rather than opening a graphics capable window).
  • -hda ubuntu-16.10-server-cloudimg-amd64.img - use our Ubuntu cloud image as the primary drive.
  • -hdb cloud.img - use the image we created from cloud.txt as the secondary drive.
  • -redir tcp:2222::22 - map port 2222 on the host to port 22 (the standard ssh port) on the guest.

Once booted you eventually get to the console getty login prompt. No one can login here - so you need to switch to another terminal tab.

Now let's logon to the guest using the redirected port and check out a few things and then shut down the guest:

$ ssh -p 2222 ghawkins@localhost
Welcome to Ubuntu 16.10 (GNU/Linux 4.8.0-26-generic x86_64)
...
$ df -h
Filesystem      Size  Used Avail Use% Mounted on
udev            491M     0  491M   0% /dev
tmpfs           100M  3.2M   97M   4% /run
/dev/sda1       2.0G  979M 1016M  50% /
tmpfs           496M     0  496M   0% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs           496M     0  496M   0% /sys/fs/cgroup
/dev/sda15      105M  4.8M  100M   5% /boot/efi
tmpfs           100M     0  100M   0% /run/user/1000

$ cat /proc/cpuinfo 
processor   : 0
vendor_id   : GenuineIntel
...

processor   : 1
vendor_id   : GenuineIntel
$ sudo shutdown now
Connection to localhost closed by remote host.

So above using df -h we can see that the disk appears to be 2GB and with cat /proc/cpuinfo we can see that we appear to have two processors. Finally using shutdown we can get back to the command prompt in the terminal where the guest was started.

TODO: see how changing the number of virtual CPUs affects the performance of the guest.

If you redo everything from scratch again with a copy of the original disk image then the guest will generate a new key to identify itself which will cause ssh to refuse to allow you to reconnect due to the change in key. To remove the old key from known_hosts do:

$ ssh-keygen -f ~/.ssh/known_hosts -R '[localhost]:2222'

Working out how to get this far was down to:

The Ubuntu cloud images page wasn't as helpful as it should be:

But it does cover uncompressing the qcow2 disk image and increasing its size (2GB isn't much) and fancier stuff like creating a delta image to keep your initial disk image in a pristine condition.

@mbs0221
Copy link

mbs0221 commented Aug 5, 2024

How to launch above scripts with arm-trusted-firmware or optee?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment