Table of Contents

Apache CloudStack 4.21 is here, bringing a sharper UI and a stack of new features—there’s no better time to upgrade or roll out a fresh install. In this guide, we’ll walk step-by-step through installing the latest 4.21 release with TLS end-to-end, along with the required and strongly recommended tooling to run it confidently in production. I have an intro to CloudStack in the previous post, Feel free to read before proceeding. So, Lets dive right in.

We’ll do everything manually and we’ll use MariaDB for performance, so that you can understand how to do everything yourself.

Requirements:

  • Support OS: Ubuntu 24.04
  • Architecture: amd64, aarch64
  • Public Network block – min /28 (Important for Public Access only)
  • Timeshift package or a backup alternative Installed (Crucial)

Getting Started

Note: Installing iptables-save, netfilter-persistent will mess up with CloudStack Agent connecting the host, I will explain in another post how to secure the setup using iptables

Backup: Timeshift

First, Lets SSH into our server and we need to Install timeshift.

Bash
sudo su
passwd root # Change root password if you haven't already
apt update && apt upgrade -y
apt install -y timeshift
timeshift --rsync --create --comments "Initial snapshot" --tags D

Great, Now we can always go back to our backup in case we mess up badly without the need for system reload.

Disable Sleep

A server should not be put to sleep to ensure constant availability, prevent slow startup times, maintain performance, and avoid potential hardware issues caused by frequent power cycling.

Bash
systemctl status suspend.target

sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target suspend-then-hibernate.target

sudo mkdir -p /etc/systemd/sleep.conf.d
sudo tee /etc/systemd/sleep.conf.d/no-sleep.conf >/dev/null <<'EOF'
[Sleep]
AllowSuspend=no
AllowHibernation=no
AllowHybridSleep=no
AllowSuspendThenHibernate=no
EOF
Bash
sudo mkdir -p /etc/systemd/logind.conf.d
sudo tee /etc/systemd/logind.conf.d/no-sleep.conf >/dev/null <<'EOF'
[Login]
HandleLidSwitch=ignore
HandleLidSwitchExternalPower=ignore
HandleLidSwitchDocked=ignore
HandleSuspendKey=ignore
HandleHibernateKey=ignore
IdleAction=ignore
EOF

sudo systemctl restart systemd-logind

Networking

1: Let’s configure the hostname
Bash
hostname --fqdn
hostnamectl set-hostname node1.example.com
2: Install important tools before getting started
Bash
apt install -y vim nano wget curl openssh-server sudo htop tar iotop \
  bridge-utils net-tools ipset python3-pip software-properties-common net-tools \
  gnupg2 lsb-release openntpd cpu-checker openjdk-17-jre
3: Configure ssh to be CloudStack friendly
Bash
nano /etc/ssh/sshd_config
# Uncomment and Tweak these lines
PermitRootLogin prohibit-password
StrictModes yes
MaxAuthTries 3
PubkeyAuthentication yes

# Expect .ssh/authorized_keys2 to be disregarded by default in future
AuthorizedKeysFile      .ssh/authorized_keys .ssh/authorized_keys2
# Do not disable PasswordAuthentication before adding SSH Keys
PasswordAuthentication no
PermitEmptyPasswords no

X11Forwarding no
UsePAM yes
Bash
# Add at the end of the file
KexAlgorithms=+diffie-hellman-group-exchange-sha1
PubkeyAcceptedKeyTypes=+ssh-dss
HostKeyAlgorithms=+ssh-dss
KexAlgorithms=+diffie-hellman-group1-sha1
4: Create a bridge for CloudStack

This is a tricky part which requires some basic knowledge of networking.
What we need to understand:
A: If you have multiple Subnets: Is your Subnet Routed or Natted? most are routed.
B: Is your network port trunked or not?
It’s important that the switch/router allows trunking (802.1Q) so that we can set up vlans, if you are planning on using multiple Subnets with public IP’s.
We can get around this by:
4.1: (For Public IP Access) Setting up 2 Ethernet ports, because we can only have 1 active bridge linked to a port at a time and keep the bridge untagged.
4.2: (For Public IP Access) Have all traffic under 1 bridge (For Personal use) untagged.
Both option will be untagged, meaning we cannot use vlans but we can use security groups for isolation.

Now let’s configure netplan:

Nano
ifconfig
cd /etc/netplan
# Now rename all the files and add .bak to th end of the file names.
# Disable netplan regeneartion.
sudo mkdir -p /etc/cloud/cloud.cfg.d
echo 'network: {config: disabled}' | sudo tee /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg

# Create a new neplan conf
nano /etc/netplan/01-network-manager-all.yaml

Now you may copy and paste one of these configs from my Github into the file and save.

Bash
netplan try
# if everything is good, apply
netplan generate && netplan apply 
sudo update-initramfs -u && sudo reboot

Load Bridge modules

Bash
sudo modprobe bridge 
# Load netfilter modules 
sudo modprobe br_netfilter 
sudo modprobe nf_conntrack 
# Load required modules 
modprobe br_netfilter 2>/dev/null 
modprobe nf_conntrack 2>/dev/null echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf 
echo "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.conf 
echo "net.bridge.bridge-nf-call-iptables=1" >> /etc/sysctl.conf 
echo "net.bridge.bridge-nf-call-ip6tables=1" >> /etc/sysctl.conf 
echo "vm.swappiness=10" >> /etc/sysctl.conf sysctl -p modprobe kvm modprobe kvm-intel 
# or kvm-amd for AMD processors 
echo "kvm" >> /etc/modules 
echo "kvm-intel" >> /etc/modules # or kvm-amd

Configure NFS Server for storage

Bash
apt install nfs-kernel-server nfs-common -y

mkdir /export
mkdir -m 755 /export/primary
mkdir -m 755 /export/secondary
mkdir -m 755 /mnt/primary # If you have a secondary storage device you wanna use.
mkdir -m 755 /mnt/secondary # If you have a secondary storage device you wanna use.
Bash
# Configure exports
cat > /etc/exports << EOF
/export/primary  10.7.0.0/24(rw,async,no_root_squash,no_subtree_check) 127.0.0.1/24(rw,async,no_root_squash,no_subtree_check)
/export/secondary  10.7.0.0/24(rw,async,no_root_squash,no_subtree_check) 127.0.0.1(rw,async,no_root_squash,no_subtree_check)
EOF

# Now lets modify fstab
nano /etc/fstab 
# Add the following and save for permanent mount.
10.7.0.0:/export/primary    /mnt/primary   nfs defaults,noauto 0 0
10.7.0.0:/export/secondary    /mnt/secondary   nfs defaults,noauto 0 0

Configure the NFS kernel server settings by editing the configuration file with the following command:

Bash
nano /etc/default/nfs-kernel-server
# Add the following and save.
LOCKD_TCPPORT=32803
LOCKD_UDPPORT=32769
MOUNTD_PORT=892
RQUOTAD_PORT=875
STATD_PORT=662
STATD_OUTGOING_PORT=2020

Save and exit. Then enable and restart nfs-server

Bash
systemctl enable nfs-kernel-server
systemctl restart nfs-kernel-server
# Mount NFS shares and Fstab
systemctl daemon-reload
exportfs -a
mount -a

Installing MariaDB Database

CloudStack usually uses MySQL but MariaDB is preferable for production due to security and performance.

Bash
sudo apt install -y mariadb-server
Bash
cat | sudo tee /etc/mysql/conf.d/cloudstack.cnf >/dev/null <<'CFG'
[mysqld]
innodb_rollback_on_timeout=1
innodb_lock_wait_timeout=600
max_connections=350
log_bin=mysql-bin
binlog_format=ROW
bind-address=0.0.0.0 
# I would recommend binding it to 127.0.0.1
# If you plan replication, set a unique server_id
server_id=1
character-set-server = utf8mb4
collation-server     = utf8mb4_unicode_ci
CFG

sudo systemctl restart mariadb
mysql_secure_installation
  • Switch to unix_socket authentication N
  • Would you like to setup VALIDATE PASSWORD component? N
  • Change the password for root? N # Your choice
  • Remove anonymous users? Y
  • Disallow root login remotely? Y
  • Remove test database and access to it? Y
  • Reload privilege tables now? Y

Change MySQL password

Bash
mysql -u root
CREATE USER IF NOT EXISTS 'root'@'localhost'   IDENTIFIED BY '$mysqlRootPassword';
CREATE USER IF NOT EXISTS 'root'@'127.0.0.1'   IDENTIFIED BY '$mysqlRootPassword';
CREATE USER IF NOT EXISTS 'root'@'%'           IDENTIFIED BY '$mysqlRootPassword';
# Explicitly use mysql_native_password for each
ALTER USER 'root'@'localhost'  IDENTIFIED VIA mysql_native_password USING PASSWORD('$mysqlRootPassword');
ALTER USER 'root'@'127.0.0.1'  IDENTIFIED VIA mysql_native_password USING PASSWORD('$mysqlRootPassword');
ALTER USER 'root'@'%'          IDENTIFIED VIA mysql_native_password USING PASSWORD('$mysqlRootPassword');
FLUSH PRIVILEGES;
EXIT;
Bash
# Create DB/User for CloudStack
mysql -u root -p << EOF
CREATE USER 'cloud'@'localhost' IDENTIFIED BY 'StrongPassword!';
GRANT ALL PRIVILEGES ON *.* TO 'cloud'@'localhost' WITH GRANT OPTION;
CREATE USER 'cloud'@'%' IDENTIFIED BY 'StrongPassword!';
GRANT ALL PRIVILEGES ON *.* TO 'cloud'@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;
EOF

Add Apt Repo & Install CloudStack

Bash
mkdir -p /etc/apt/keyrings
wget -O- http://packages.shapeblue.com/release.asc | gpg --dearmor | sudo tee /etc/apt/keyrings/cloudstack.gpg > /dev/null

echo deb [signed-by=/etc/apt/keyrings/cloudstack.gpg] http://packages.shapeblue.com/cloudstack/upstream/debian/4.21 / > /etc/apt/sources.list.d/cloudstack.list
Bash
apt update
apt install -y cloudstack-management cloudstack-usage

apt install -y qemu-kvm libvirt-daemon-system libvirt-clients \
qemu-utils genisoimage iproute2 uuid-runtime virtinst cloudstack-agent

usermod -aG libvirt,libvirt-qemu,kvm "$USER"
newgrp libvirt

Next is to setup Database for CloudStack

Bash
cloudstack-setup-databases cloud:'StrongPassword!'@localhost \
  --deploy-as=root:'$mysqlRootPassword' \
  -e file \
  -m 'Fill_In_Random_MGMT_SERVER_KEY' \
  -k 'Fill_In_Random_DB_KEY' \
  -i "127.0.0.1"

cloudstack-setup-management

Download System VM’s templates for CloudStack (Important)

Bash
/usr/share/cloudstack-common/scripts/storage/secondary/cloud-install-sys-tmplt \
  -m /export/secondary \
  -u https://download.cloudstack.org/systemvm/4.20/systemvmtemplate-4.20.2-x86_64-kvm.qcow2.bz2 \
  -h kvm -F

Set sudoers to make sure everything works

Bash
nano /etc/sudoers
# Append the following line to the end of the file
Defaults:cloud !requiretty

Now the UI should be accessible at http://public_ip:8080/client, Credentials: (admin:password). But we are not done yet.

Configure CloudStack Agent, QEMU and services

Bash
systemctl enable cloudstack-agent.service
Bash
nano /etc/libvirt/libvirtd.conf
# Find the identifier and uncomment, change or append
listen_tls = 0
listen_tcp = 1
tcp_port = "16509"
auth_tcp = "none"
mdns_adv = 0
Bash
nano /etc/default/libvirtd
# Find the identifier and uncomment, change or append
LIBVIRTD_ARGS="--listen"
Bash
# Mask libvirt for listening
systemctl mask libvirtd.socket libvirtd-ro.socket \
libvirtd-admin.socket libvirtd-tls.socket libvirtd-tcp.socketd
systemctl restart libvirtd

Disable AppArmor

Bash
ln -s /etc/apparmor.d/usr.sbin.libvirtd /etc/apparmor.d/disable/
ln -s /etc/apparmor.d/usr.lib.libvirt.virt-aa-helper /etc/apparmor.d/disable/
apparmor_parser -R /etc/apparmor.d/usr.sbin.libvirtd
apparmor_parser -R /etc/apparmor.d/usr.lib.libvirt.virt-aa-helper

Now the basic setup is done and you are ready to launch the UI at http://public_ip:8080/client, Credentials: (admin:password).

Next Steps:

To avoid making the post longer than it should be, Please view the next post for the next steps and how to secure your setup.

Categorized in:

Cloud, Guides,