Intro

Meanwhile I have update my OpenBSD Server to version 6.6, so some commands look different from before (vmctl uses a different ordering of arguments). All in all the whole vmd interactions feel a lot more stable and mature than before.

Create an Alpine VM

Create the disk and start the installation

The procedure is similar to what I described in my other post about creating virtual machines, though as mentioned before the vmctl has a slightly different syntax now.

Note also that the last command in the code-block below uses the virtual network called localnet which I set up for the VM network in a previous post.

The vmctl start command in the end means the following:

  1. -c: Connect to a virtual console after starting
  2. -B cdrom: Boot from a supplied cdrom iso if exists
  3. -r alpine-virt-3.11.5-x86_64.iso: Use the supplied iso as cdrom data
  4. -d alpine.drive: Use the raw disk drive we created
  5. -m 2G: With 2 Gigabyte of virtual ram
  6. -n localnet: Connecting to the virtual bridge network called localnet
  7. alpine-vm: Name the VM "alpine-vm"

# replace v3.11 with the latest version of alpine

doas ftp https://nl.alpinelinux.org/alpine/v3.11/releases/x86_64/alpine-virt-3.11.5-x86_64.iso
doas ftp https://nl.alpinelinux.org/alpine/v3.11/releases/x86_64/alpine-virt-3.11.5-x86_64.iso.sha256

# verify correct signature
diff <(sha256 -q alpine-virt-3.11.5-x86_64.iso) <(cat alpine-virt-3.11.5-x86_64.iso.sha256)

# create the raw disk file for alpine to install on
doas vmctl create -s 8G alpine.drive

# start the vm with boot rom
doas vmctl start -c -B cdrom -r alpine-virt-3.11.5-x86_64.iso -d alpine.drive -m 2G -n localnet alpine-vm

Installation of the alpine os inside the Alpine-VM

After the booting-process I logged into the VM as root user (without any password), and set up the network interfaces, for accessing online package resources during the installation.

The setting of IP-Addresses, gateways, nameserver all is oriented at a previous post, in which I set up the VM-network that I use on my server.


# set up the network interfaces itself
localhost:~# setup-interfaces

Available interfaces are: eth0.
Enter '?' for help on bridges, bonding and vlans.
Which one do you want to initialize? (or '?' or 'done') [eth0]

Ip address for eth0? (or 'dhcp', 'none', '?') [dhcp] 192.168.30.6

Netmask? [255.255.255.0]

Gateway? (or 'none') [none] 192.168.30.1

Configuration for eth0:
  type=static
  address=192.168.30.6
  netmask=255.255.255.0
  gateway=192.168.30.1

Do you want to do any manual network configuration? [no]
# set up the nameserver
localhost:~# setup-dns

DNS domain name? (e.g 'bar.com') [] hermes-technology.de

DNS nameserver(s)? [] 192.168.30.1

After I was making sure that my networking was working (via a ping google.com), I just started the installation choosing the commands shown below.


localhost:~# setup-alpine
Available keyboard layouts:
af     be     cn     fi     hu     it     lk     mm     pl     sy     uz
al     bg     cz     fo     id     jp     lt     mt     pt     th     vn
am     br     de     fr     ie     ke     lv     my     ro     tj
ara    brai   dk     gb     il     kg     ma     ng     rs     tm
at     by     dz     ge     in     kr     md     nl     ru     tr
az     ca     ee     gh     iq     kz     me     no     se     tw
ba     ch     epo    gr     ir     la     mk     ph     si     ua
bd     cm     es     hr     is     latam  ml     pk     sk     us
Select keyboard layout [none]: de
Available variants: de-T3 de-deadacute de-deadgraveacute de-deadtilde de-dsb de-dsb_qwertz de-dvorak de-mac de-mac_nodeadkeys de-neo de-nodeadkeys de-qwerty de-ro de-ro_nodeadkeys de-ru de-sundeadkeys de-tr de
Select variant []: de
 * Caching service dependencies ...
 [ ok ]
 * Setting keymap ...
 [ ok ]
Enter system hostname (short form, e.g. 'foo') [localhost]: services
Available interfaces are: eth0.
Enter '?' for help on bridges, bonding and vlans.
Which one do you want to initialize? (or '?' or 'done') [eth0]
Ip address for eth0? (or 'dhcp', 'none', '?') [192.168.30.6]
Netmask? [255.255.255.0]
Gateway? (or 'none') [192.168.30.1]
Configuration for eth0:
  type=static
  address=192.168.30.6
  netmask=255.255.255.0
  gateway=192.168.30.1
Do you want to do any manual network configuration? [no]
ifup: interface eth0 already configured
DNS domain name? (e.g 'bar.com') [] hermes-technology.de
DNS nameserver(s)? [192.168.30.1 ]
Changing password for root
New password:
Retype password:
passwd: password for root changed by root
Which timezone are you in? ('?' for list) [UTC] Europe/Berlin
 * Starting busybox acpid ...
 [ ok ]
 * Starting busybox crond ...
 [ ok ]
HTTP/FTP proxy URL? (e.g. 'http://proxy:8080', or 'none') [none]
Which NTP client to run? ('busybox', 'openntpd', 'chrony' or 'none') [chrony]
 * service chronyd added to runlevel default
 * Caching service dependencies ...
 [ ok ]
 * Starting chronyd ...
 [ ok ]

Available mirrors:
1) dl-cdn.alpinelinux.org
2) uk.alpinelinux.org
3) dl-2.alpinelinux.org
4) dl-4.alpinelinux.org
5) dl-5.alpinelinux.org
6) dl-8.alpinelinux.org
7) mirror.yandex.ru
8) mirrors.gigenet.com
9) mirror1.hs-esslingen.de
10) mirror.leaseweb.com
11) mirror.fit.cvut.cz
12) alpine.mirror.far.fi
13) alpine.mirror.wearetriple.com
14) mirror.clarkson.edu
15) linorg.usp.br
16) ftp.yzu.edu.tw
17) mirror.aarnet.edu.au
18) speglar.siminn.is
19) mirrors.dotsrc.org
20) ftp.halifax.rwth-aachen.de
21) mirrors.tuna.tsinghua.edu.cn
22) mirrors.ustc.edu.cn
23) mirrors.xjtu.edu.cn
24) mirrors.nju.edu.cn
25) mirror.lzu.edu.cn
26) ftp.acc.umu.se
27) mirror.xtom.com.hk
28) mirror.csclub.uwaterloo.ca
29) alpinelinux.mirror.iweb.com
30) mirror.neostrada.nl
31) pkg.adfinis-sygroup.ch
32) mirror.ps.kz
33) mirror.rise.ph
34) mirror.operationtulip.com
35) mirrors.ircam.fr
36) alpine.42.fr
37) mirror.math.princeton.edu
38) mirrors.sjtug.sjtu.edu.cn
39) alpine.mirror.didstopia.com
40) ftp.icm.edu.pl
41) mirror.ungleich.ch
42) alpine.mirror.vexxhost.ca
43) sjc.edge.kernel.org
44) ewr.edge.kernel.org
45) ams.edge.kernel.org
46) download.nus.edu.sg

r) Add random from the above list
f) Detect and add fastest mirror from above list
e) Edit /etc/apk/repositories with text editor

Enter mirror number (1-46) or URL to add (or r/f/e/done) [1]:
Added mirror dl-cdn.alpinelinux.org
Updating repository indexes... done.
Which SSH server? ('openssh', 'dropbear' or 'none') [openssh]
 * service sshd added to runlevel default
 * Caching service dependencies ...
 [ ok ]
ssh-keygen: generating new host keys: RSA DSA ECDSA ED25519
 * Starting sshd ...
 [ ok ]
Available disks are:
  vda   (8.6 GB 0x0b5d )
Which disk(s) would you like to use? (or '?' for help or 'none') [none] vda
The following disk is selected:
  vda   (8.6 GB 0x0b5d )
How would you like to use it? ('sys', 'data', 'lvm' or '?' for help) [?] sys
WARNING: The following disk(s) will be erased:
  vda   (8.6 GB 0x0b5d )
WARNING: Erase the above disk(s) and continue? [y/N]: y
Creating file systems...
Installing system on /dev/vda3:
/mnt/boot is device /dev/vda1
100% ############################################==> initramfs: creating /boot/initramfs-virt
/boot is device /dev/vda1

Installation is complete. Please reboot.

Running poweroff in the terminal should shutdown the vm and end the connection to the virtual terminal.

As an alternative you can run exit and hit ~^D to exit the virtual terminal, afterwards shutting down the vm via doas vmctl stop alpine-vm.

Create separate data disk and Modify vm.conf and restart vmd

At first I created a 20 Gigabyte raw disk drive for system-independent data storage of the virtual machine.

doas vmctl create -s 20G /mnt/data/vm-drives/alpine-data.img

Afterwards I modified the /etc/vm.conf to include the new virtual machine and feed the correct disk data to the alpine-vm.

files=  /home/web-spider/vm/

data=   /mnt/data/vm-drives/

vm host-vm {
    memory 2g
    disk $files host.drive
    interface tap { lladdr 00:00:00:00:00:02 switch localnet }
}

vm alpine-vm {
    memory 2g
    interface tap { lladdr 00:00:00:00:00:06 switch localnet }
    disk "/mnt/data/vm-drives/alpine.drive" format raw
    disk "/mnt/data/vm-drives/alpine-data.img" format raw
}


switch localnet {
    interface bridge0
}

Restart vmd for the changes to be effective.

doas rcctl restart vmd

Kernel booting modifications

For the virtual machine to work more stable and quick with vmd, I did some recommended changes to the booting parameters.

  1. Connect to alpine-vm via doas vmctl console alpine-vm
  2. Update config file /boot/extlinux.conf
  3. Reboot and reconnect to vm
Important
<uuid> and <dist> must be substituted by your specific values, in mycase <dist> was virt and <uuid> was 001c5e4f-af70-4f6f-bdbc-ceff9d23c6f3
SERIAL 0 115200
DEFAULT <dist>
PROMPT 0
LABEL <dist>
  MENU LABEL Linux <dist>
  LINUX vmlinuz-<dist>
  INITRD initramfs-<dist>
  APPEND root=UUID=<uuid> modules=sd-mod,usb-storage,ext4 rootfstype=ext4 console=ttyS0,115200

My configuration looked like this in the end:

SERIAL 0 115200
DEFAULT virt
PROMPT 0
LABEL virt
  MENU LABEL Linux virt
  LINUX vmlinuz-virt
  INITRD initramfs-virt
  APPEND root=UUID=001c5e4f-af70-4f6f-bdbc-ceff9d23c6f3 modules=sd-mod,usb-storage,ext4 rootfstype=ext4 console=ttyS0,115200

Enable SSH-Access to the alpine-vm

For a more comfortable and generally better user experience, I enabled ssh access to the VM. To enable ssh access with the possibility to execute root commands, I installed sudo, created a user and configured openssh accordingly.

The procedure was as follows

  1. Add user and groups, configure sudo for group wheel
  2. Modify /etc/ssh/sshd_config
  3. Add the desired public-key from the workstation
  4. Restart sshd or reboot the VM
  5. Connect to the vm via ssh
# add some user e.g. named "general"
adduser general

# add a groups that will be gained ssh access privileges
addgroup ssh-user

# install sudo package
apk add sudo

# add user to groups
for u in $(ls /home); do for g in ssh-user disk lp floppy audio cdrom dialout video netdev games users wheel; do addgroup $u $g; done;done

# edit sudoers file to allow users of group wheel
sed -i 's/# %wheel ALL=(ALL) ALL/%wheel ALL=(ALL) ALL/g' /etc
/sudoers

My configuration file for the open ssh daemon looks like the one below, for a better security I also referred an earlier post on how to make ssh configuration more secure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# /etc/ssh/sshd_config

# Make sure that ssh protocol version 2 is used only!
Protocol 2

# Set the hostkeys that will be used for server authentication at the client
# first preferred one:
HostKey /etc/ssh/ssh_host_ed25519_key
# fallback:
HostKey /etc/ssh/ssh_host_rsa_key

# Chose a port that is not "standard" so default port scanning on low ports fail
# The port number can be a 16bit Integer. So the maximum is 65535
Port 13423

# Tell sshd which key exchange algorithms it is allowed to use. The resource by stribka at
# https://stribika.github.io/2015/01/04/secure-secure-shell.html is explaining why these two (curve25519 and diffie-hellman sha256)
# are the only chosen ones
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256

# Only allow strong symmetric cyphers (see stribka's resource for detailed information)
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr

# Only allow strong hmacs for message integrity (see stribka's resource for detailed information)
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-ripemd160-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,hmac-ripemd160,umac-128@openssh.com

# I do not want to permit root login for ssh in general
# If I would like to do that later I would choose to do it in a Match rule for specific addresses for example
PermitRootLogin no

# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
# but this is overridden so installations will only check .ssh/authorized_keys
AuthorizedKeysFile      .ssh/authorized_keys

# Generally I want to disalow any kind of access
# and handle it in user or address specific match rules
# so disable pubkey authentication at first
PubkeyAuthentication no

# To disable tunneled clear text passwords:
PasswordAuthentication no

# We do not want to permit empty passwords in any case:
PermitEmptyPasswords no

# Generally disallow tcp forwarding
AllowTcpForwarding no

# override default of no subsystems
Subsystem   sftp    /usr/lib/ssh/sftp-server

# only allow ssh sessions for members of the ssh-user group
AllowGroups ssh-user

# then when its a member of the group ssh-user (like my user jan-server will be) to be logged in, do some specific actions:
Match Group ssh-user 
        
        # And allow pubkey authentication, so my generated pubkey can be used
    PubkeyAuthentication yes

        AuthenticationMethods publickey

add authorized keys from your workstation (e.g. copy and paste it)

echo "ssh-ed25519 SD323f928392i31sadfklsjdf37824 someone@some-workstation" > authorized_keys

# restart the sshd service
rc-service sshd restart

I logged in to the alpine machine from my workstation via ssh which was more comfortable. I did this by using my server as a jumphost into the vm. I covered the configuration of such an entry in a previous post.

Create a new filesystem and mount the data disk

Modify the repositories in /etc/apk/repositories to include the community packages and update them afterwards via apk update. I used comminity to check the available disks via lsblk command.

#/etc/apk/repositories

#/media/cdrom/apks
http://dl-cdn.alpinelinux.org/alpine/v3.11/main
http://dl-cdn.alpinelinux.org/alpine/v3.11/community
#http://dl-cdn.alpinelinux.org/alpine/edge/main
#http://dl-cdn.alpinelinux.org/alpine/edge/community
#http://dl-cdn.alpinelinux.org/alpine/edge/testing
  1. update repos
# change to root
sudo -s

# install linux utils (for lsblk)
apk add util-linux

# check the disks
lsblk

# use parted, so install it
apk add parted

# execute parted (in my case with device vdb)
parted vdb

## now inside the parted-shell:
# configure new gpt label
(parted) mklabel gpt

# set unit size
(parted) unit MiB

# make new ext4 partition
(parted) mkpart 1 ext4 1 20479

# check optimal alignment
(parted) align-check opt

# name partition e.g. "data"
(parted) name 1 data

# quit
(parted) quit

# make new filesystem
mkfs.ext4 /dev/vdb1

# not the partition UUID that mkfs reports, copy it
DATA_UUID=<your partition uuid that you copied>

# create mount point directory
mkdir /data

# add mounting into fstab
echo "UUID=$DATA_UUID /data ext4 rw,relatime,user 0 0" >> /etc/fstab 

# mount it
mount /data

Install Docker and make it use the data-disk

Install Docker

sudo apk add docker

sudo adduser general docker

Modify DOCKER_OPTS in /etc/conf.d/docker


# any other random options you want to pass to docker
DOCKER_OPTS="--data-root /data/docker"

Restart Docker via rc-service docker restart