Create Multi-VM LAMP Stack on Oracle Virtualization

1
0
Send lab feedback

Create Multi-VM LAMP Stack on Oracle Virtualization

Introduction

At the core of the Oracle Virtualization solution is the Oracle Linux KVM host. On top of this hypervisor you can run many virtual machines - each running one of the supported operating systems including Oracle Linux, RHEL, Windows, Ubuntu, Solaris and others. This virtualization solution is managed through the Oracle Linux Virtualization Manager which offers a single interface to keep things organized. In addition to multi-host/multi-VM management, Oracle Linux Virtualization Manager enables key features of the Oracle Virtualization solution. For example, Oracle Linux Virtualization Manager provides automatic high availability with VM failover, live migration of VMs between hosts with zero downtime, role-based access control and audit logging, and simplified backup and disaster recovery workflows. These enterprise features transform Oracle Linux KVM from individual hosts into a true enterprise virtualization platform.

In this tutorial, we'll explore the core hypervisor technology that powers Oracle Virtualization. Kernel-based Virtual Machine (KVM) is an open-source type-1 (bare-metal) hypervisor. This functionality permits a host system, such as Oracle Linux, to host multiple virtual machines (VMs) or guests when running on supported hardware. You will deploy Oracle Linux KVM to create virtual machines configured with LAMP stack applications, enabling you to host complete web development environments with Linux, Apache, MySQL, and PHP components across multiple VMs.

Important: The two application codes in this lab are for educational purposes only. They are designed to help developers learn and practice application development skills with the LAMP stack. These codes are not intended for production environments and should not be used as-is in a live setting.

Objectives

  • Setup OCI, Luna, or On-premise server
  • Deploy Oracle Linux KVM
  • Create Virtual Machine using Oracle Cloud Images
  • Install MySQL server
  • Install Web server with Apache and PHP

Prerequisites for non Luna Access

Any Oracle Linux system with the following configurations:

  • a non-root user with sudo permissions

Step 1: Deploy Oracle Linux

Note: If not using Luna and running in your own tenancy, read the linux-virt-labs GitHub project README.md and complete the prerequisites before deploying the lab environment.

Quick Tip: Mac users use Command+C/Command+V for copy/paste, or two-finger click for right-click. Windows and Linux users, your usual Ctrl+C/Ctrl+V shortcuts work.

  1. Open a terminal from the Luna Desktop. Click the Applications menu in the bottom-left corner and select Terminal Emulator.

  2. Clone the linux-virt-labs GitHub project. This contains all the Ansible automation that will build the KVM environment. You'll see about 1,500 files downloading - watch for the 'done' messages.

    git clone https://github.com/oracle-devrel/linux-virt-labs.git
  3. Change into the working directory. Notice your prompt changes to show you're now inside 'linux-virt-labs/ol'. This is where all the deployment scripts live.

    cd linux-virt-labs/ol
  4. Install the required collections. This command installs Ansible collections - think of these as plugin packages that extend Ansible's capabilities.

    ansible-galaxy collection install -r requirements.yml
  5. Deploy the lab environment.

    ansible-playbook create_instance.yml -e localhost_python_interpreter="/usr/bin/python3.6" -e instance_ocpus="4" -e instance_memory="64"

    The free lab environment requires the extra variable local_python_interpreter, which sets ansible_python_interpreter for plays running on localhost. This variable is needed because the environment installs the RPM package for the Oracle Cloud Infrastructure SDK for Python, located under the python3.6 modules.

    The default deployment shape uses the AMD CPU and Oracle Linux 8. To use an Intel CPU add -e instance_shape="VM.Standard3.Flex"


⚠️ CRITICAL - Terminal Management Instructions

Wait for the playbook to run successfully and reach the pause task. You'll see multiple tasks executing as it creates your infrastructure. Each completed task shows "changed" or "ok" in the output.

When the playbook pauses, it will display your instance connection details:

  • name: ol-node-01
  • public ip: [Your unique IP address]
  • private ip: [Internal OCI IP]

IMPORTANT: Do NOT press Enter in this terminal yet!

Required Actions:

  1. Write down your Public IP address - You'll need this throughout the lab
  2. LEAVE this terminal window open and untouched - Do not press Enter or close it
  3. Open a NEW terminal window for Step 2 and all remaining lab work
  4. Return to this terminal ONLY after completing Step 12 to clean up resources

Why? This paused playbook is keeping your OCI infrastructure alive. If you press Enter now, it will immediately delete all resources. You'll press Enter at the very end of the lab (after Step 12) to properly clean up.

Next Step: Open a second terminal window and proceed to Step 2.


Step 2: Validate Environment Supports Virtualization

Reminder: Use the NEW terminal window (Terminal #2) you just opened. Terminal #1 from Step 1 should remain paused and untouched.

  1. In this new terminal, connect via SSH to the ol-node-01 instance using the Public IP you recorded in Step 1.

    ssh opc@<YOUR ol-node-01 PUBLIC_IP>

    Example:

    ssh

    opc@132.145.198
    https://luna.oracle.com/api/v1/labs/80324f86-de4d-4cf5-b35b-9de5e8bdc30c/gitlab/javascript:void(0)
    ....

    When prompted about the host fingerprint, type yes and press Enter.

  2. Run the following command to determine your CPU type.

    grep -e 'vendor_id' /proc/cpuinfo | uniq

    The vendor_id reports either AuthenticAMD for an AMD CPU or GenuinIntel for an Intel CPU.

  3. Check if the hardware supports virtualization.

    Run the command that matches your CPU type.

    For AMD CPUs - Verify the AMD-V CPU extensions exist:

    grep -w -o 'svm' /proc/cpuinfo | uniq

    For Intel CPUs - Verify the Intel VT CPU extensions exist:

    grep -w -o 'vmx' /proc/cpuinfo | uniq

    The existence of one of these flags (svm or vmx) in the command output indicates this system supports virtualization. You can also use the lscpu command and look for the Virtualization entry in the output.

  4. Check for the loaded KVM modules.

    lsmod | grep kvm

    The output shows the KVM kernel modules. On OCI, these modules may already be loaded when the instance is created in Step 1, so they can appear before package installation or starting libvirtd.


Step 3: Install and Start KVM

  1. Check the running version of Oracle Linux.

    hostnamectl | grep 'Operating System'

    You should see Oracle Linux Server 8.10 or similar.

  2. Install the associated software packages for Oracle Linux virtualization.

    This command installs the virtualization packages, which include libvirt and other dependencies on Oracle Linux. The libvirt package provides a software library and API to manage KVM virtual machines and includes the libvirtd daemon that runs in the background to handle the actual VM management.

    sudo dnf module install -y virt

    This install will take several minutes. Watch for "Complete!" at the end.

  3. Install virt-install - tool for creating and configuring virtual machines (VMs) using KVM (Kernel-based Virtual Machine) hypervisor.

    sudo dnf install -y virt-install

    You'll use this tool later to build your virtual machines.

  4. Validate the host machine is ready and set up to run libvirt VMs.

    virt-host-validate

    If all checks pass, the system is prepared for creating VMs. You should see mostly "PASS" in green. If any checks fail, follow the instructions to correct the problem. If any check returns "WARN", consider following the instructions to improve the virtualization capabilities, but warnings won't prevent the lab from working.

  5. Start the systemd (Linux service manager) service and enable it to start automatically on each boot.

    sudo systemctl enable --now libvirtd.service

    The --now flag starts the service immediately, and enable makes it start automatically on every boot.

  6. Check the services status to confirm they are up and running.

    sudo systemctl status libvirtd.service

    The output shows the service as enabled and running. Look for "active (running)" in green. Press q to exit the status view.


Step 4: Create two Virtual Machines using Oracle Cloud Images

  1. Change to the KVM image storage location.

    cd /var/lib/libvirt/images
  2. Download the Oracle Linux VM template.

    sudo curl -O https://yum.oracle.com/templates/OracleLinux/OL8/u10/x86_64/OL8U10_x86_64-kvm-b237.qcow2

    This downloads a pre-configured Oracle Linux 8.10 cloud image optimized for KVM.

    Create VM-01 (Database)

  3. Create a meta-data file for cloud-init configuration.

    cat << 'EOF' | sudo tee ~/meta-data > /dev/null
    instance-id: iid-local01
    local-hostname: vm-01
    EOF

    Cloud-init uses this metadata to configure the VM on first boot, setting the hostname and instance ID.

  4. Create a user-data file.

    cat << 'EOF' | sudo tee ~/user-data > /dev/null
    #cloud-config
    
    system_info:
      default_user:
        name: opc
    
    ssh_authorized_keys:
      - <paste_public_key_here>
    EOF
  5. Generate an SSH key pair for secure connection to the VM

    ssh-keygen -t rsa -b 4096

    When prompted, press Enter three times to accept all defaults (no passphrase needed for this lab). The command writes the key pair to the .ssh directory in the user's home.

  6. Extract the generated public SSH key

    SSHKEY=$(cat ~/.ssh/id_rsa.pub)

    This stores your public key in a variable for the next step. No output means success.

  7. Update the user-data file with the generated public SSH key

    sed -i "s|<paste_public_key_here>|${SSHKEY}|g" ~/user-data

    This automatically inserts your public SSH key into the user-data file, replacing the placeholder.

  8. Generate an ISO image containing the user-data and meta-data files

    sudo genisoimage -output /var/lib/libvirt/images/vm-01.iso -volid cidata -joliet -rock ~/user-data ~/meta-data

    This packages the configuration files into an ISO that the VM will mount on first boot to configure itself.

  9. Find the OS variant that matches the downloaded image

    osinfo-query os | grep ol8

    You should see ol8.10 in the output - we need this identifier for the VM creation command.

  10. Copy the Oracle Linux image to a new disk image for vm-01

    sudo cp /var/lib/libvirt/images/OL8U10_x86_64-kvm-b237.qcow2 /var/lib/libvirt/images/vm-01.qcow

    This creates an independent disk for VM-01 so our two VMs don't share the same disk file.

  11. Create a new virtual machine named vm-01 with specified resources and configuration

    sudo virt-install --name vm-01 \
    --memory 2048 \
    --vcpus 2 \
    --disk /var/lib/libvirt/images/vm-01.qcow,device=disk,bus=virtio \
    --disk /var/lib/libvirt/images/vm-01.iso,device=cdrom \
    --os-type linux --os-variant ol8.10 \
    --virt-type kvm --graphics none \
    --network network=default,model=virtio \
    --noautoconsole \
    --import

    This creates VM-01 with 2 CPUs and 2 GB RAM, attaches both the disk and cloud-init ISO, then boots the VM. You'll see "Domain is still running" confirming successful creation.

  12. List all running virtual machines

    sudo virsh list

    You should see vm-01 listed with State "running". If it does not show, wait a minute and reenter the command.

  13. Retrieve the IP address of the vm-01 virtual machine.

    sudo virsh net-dhcp-leases --network default

    Look for vm-01's IP address - usually something like 192.168.122.xxx. Write this down!

  14. Verify VM-01 works by connecting with SSH.

    ssh opc@<ip_address_of_vm-01>

    Example:

    ssh

    opc@192.168.122
    https://luna.oracle.com/api/v1/labs/80324f86-de4d-4cf5-b35b-9de5e8bdc30c/gitlab/javascript:void(0)
    ....

    When prompted about the fingerprint, type yes and press Enter. You're now inside your first virtual machine!

  15. Get details about the virtual machine.

    hostnamectl

    Verify the hostname is vm-01, OS is Oracle Linux 8.10, and under "Virtualization" it says kvm.

  16. Exit the vm-01 server to continue to the next step

    exit

    Your prompt changes back - you're now on ol-node-01 again, not inside the VM.

    Create VM-02 (Web Server)

  17. Create a meta-data file for vm-02

    cat << 'EOF' | sudo tee ~/meta-data > /dev/null
    instance-id: iid-local02
    local-hostname: vm-02
    EOF

    Same process as VM-01, but with a different hostname and instance ID.

  18. Generate an ISO image for vm-02

    sudo genisoimage -output /var/lib/libvirt/images/vm-02.iso -volid cidata -joliet -rock ~/user-data ~/meta-data

    We're reusing the same user-data file with our SSH key, just changing the metadata.

  19. Copy the Oracle Linux image to a new disk image for vm-02

    sudo cp /var/lib/libvirt/images/OL8U10_x86_64-kvm-b237.qcow2 /var/lib/libvirt/images/vm-02.qcow

    Each VM gets its own independent disk image.

  20. Create a new virtual machine named vm-02 with specified resources and configuration

    sudo virt-install --name vm-02 \
    --memory 2048 \
    --vcpus 2 \
    --disk /var/lib/libvirt/images/vm-02.qcow,device=disk,bus=virtio \
    --disk /var/lib/libvirt/images/vm-02.iso,device=cdrom \
    --os-type linux --os-variant ol8.10 \
    --virt-type kvm --graphics none \
    --network network=default,model=virtio \
    --noautoconsole \
    --import

    Creating VM-02 - our web server. Same specs as VM-01: 2 CPUs, 2 GB RAM.

  21. List all running virtual machines

    sudo virsh list

    Now you should see BOTH VMs running! VM-01 and VM-02 both listed with State "running". If VM-02 does not show, wait a minute and reenter the command.

  22. Retrieve the IP address of the vm-02 virtual machine.

    sudo virsh net-dhcp-leases --network default

    Find VM-02's IP address and write it down next to VM-01's IP.

  23. Verify the virtual machine works by connecting with ssh.

    ssh opc@<ip_address_of_vm-02>

    Example:

    ssh

    opc@192.168.122
    https://luna.oracle.com/api/v1/labs/80324f86-de4d-4cf5-b35b-9de5e8bdc30c/gitlab/javascript:void(0)
    ....

    Type yes for the fingerprint. You're now inside your second virtual machine!

  24. Get details about the virtual machine by running hostnamectl.

    hostnamectl

    Verify hostname is vm-02, same OS version, same virtualization type.

  25. Exit the vm-02 server to continue to the next step

    exit

    Back on ol-node-01 again.

  26. Extract and save VM-01's IP address to a variable.

    VM01_IP=$(sudo virsh net-dhcp-leases --network default | grep vm-01 | tail -1 | awk '{print $5}' | cut -d'/' -f1)

    No output means it worked - the IP is stored in the variable.

  27. Extract and save VM-02's IP address to a variable.

    VM02_IP=$(sudo virsh net-dhcp-leases --network default | grep vm-02 | tail -1 | awk '{print $5}' | cut -d'/' -f1)

    Both IPs are now saved for easy reuse throughout the rest of the lab.

  28. Verify VM-01 IP variable is set correctly.

    echo "VM-01 (Web): $VM01_IP"

    You should see VM-01's IP printed out.

  29. Verify VM-02 IP variable is set correctly.

    echo "VM-02 (Web): $VM02_IP"

    You should see VM-02's IP printed out. Make sure both IPs display correctly!

  30. Test SSH connection to VM-01 using the variable.

    ssh -o ConnectTimeout=10 opc@$VM01_IP "echo 'VM-01 OK'"

    You should see "VM-01 OK" printed, confirming the variable works and SSH is accessible.

  31. Test SSH connection to VM-02 using the variable.

    ssh -o ConnectTimeout=10 opc@$VM02_IP "echo 'VM-02 OK'"

    You should see "VM-02 OK" printed. Both VMs are ready for the LAMP stack installation!


Congratulations! You now have two fully functional virtual machines running on your Oracle Linux KVM host. VM-01 will become your database server, and VM-02 will become your web server.


Step 5: Setup MySQL on VM-01

Install MySQL

  1. From ol-node-01, establish an SSH connection to VM-01.

    ssh opc@$VM01_IP

    Notice your prompt changes to show vm-01 - you're now inside the database VM.

  2. Download MySQL repository configuration

    sudo yum -y install https://dev.mysql.com/get/mysql84-community-release-el8-1.noarch.rpm

    This installs the MySQL 8.4 repository configuration.

  3. Disable default MySQL module to avoid conflicts

    sudo yum -y module disable mysql
  4. Install MySQL server and client

    sudo yum install -y mysql mysql-server

    This is a large package with many dependencies. Watch for "Complete!" at the end.

  5. Start MySQL service

    sudo systemctl start mysqld

    On first start, MySQL initializes the data directory and generates a temporary root password.

  6. Enable MySQL service to start at boot

    sudo systemctl enable mysqld

    This ensures MySQL starts automatically when VM-01 reboots.

  7. Allow incoming MySQL traffic through the firewall

    sudo firewall-cmd --permanent --add-service=mysql

    This opens port 3306 so VM-02 can connect to this database. The --permanent flag makes this persist across reboots.

  8. Reload firewall configuration to apply changes

    sudo firewall-cmd --reload

    MySQL is now accessible from other VMs on the network.

Configure MySQL

  1. Extract temporary root password from MySQL log

    TEMP_PASS=$(sudo grep 'temporary password' /var/log/mysqld.log | awk '{print $NF}')

    MySQL generates a random temporary password on first start. This command finds it in the log and saves it to a variable. No output means success.

  2. Login to MySQL with temporary root password

    mysql -uroot -p$TEMP_PASS --connect-expired-password

    You'll see the MySQL prompt mysql> appear. You're now inside the MySQL database!

  3. Change root password to a secure value

    ALTER USER 'root'@'localhost' IDENTIFIED BY 'Welcome#123';

    In production you'd use a more secure password, but for this lab we're using Welcome#123. Press Enter - you'll see "Query OK" confirming the change.

  4. Create 'admin' user with a secure password

    CREATE USER 'admin'@'%' IDENTIFIED BY 'Welcome#123';

    The % means this user can connect from any host, not just localhost.

  5. Grant all privileges to 'admin' user

    GRANT ALL PRIVILEGES ON *.* TO 'admin'@'%' WITH GRANT OPTION;

    This gives the admin user full administrative privileges.

  6. Create 'empuser' user with a secure password

    CREATE USER 'empuser'@'%' IDENTIFIED BY 'Welcome#123';

    This is the account our web application will use to access the employee database.

  7. Grant all privileges to 'empuser'

    GRANT ALL PRIVILEGES ON *.* TO 'empuser'@'%' WITH GRANT OPTION;

    In production you'd restrict this to specific databases, but for our lab we're keeping it simple.

  8. Reload privilege tables to apply changes

    FLUSH PRIVILEGES;

    This reloads the privilege tables, making all our changes take effect immediately. You'll see "Query OK".

  9. Quit MySQL shell

    \q

    Your prompt changes back from mysql> to the VM-01 shell prompt. MySQL is now fully configured!

  10. Exit SSH session from VM-01

    exit

    Your prompt changes back to ol-node-01. MySQL is installed, running, and ready to accept connections from VM-02.


MySQL Setup Complete! VM-01 is now configured as a database server with MySQL 8.4, firewall rules configured, and user accounts ready for the web application.


Step 6: Setup Apache/PHP on VM-02

  1. From ol-node-01, establish an SSH connection to VM-02.

    ssh opc@$VM02_IP

    Notice your prompt changes to show vm-02 - you're now inside the web server VM.

  2. Install Apache HTTP server

    sudo yum install -y httpd

    This installs the Apache web server - the "A" in our LAMP stack. Watch for "Complete!" at the end.

  3. Install PHP 8.2 and its dependencies

    sudo dnf install -y @php:8.2

    This is the "P" in LAMP. The @php:8.2 syntax installs the entire PHP module group.

  4. Install PHP MySQL and JSON extensions

    sudo yum install -y php-mysqlnd php-json

    These extensions let our PHP code communicate with the MySQL database on VM-01 and handle JSON data.

  5. Enable and start Apache HTTP server

    sudo systemctl enable --now httpd

    The --now flag starts Apache immediately, and enable makes it start automatically on boot. Apache is now running and serving web pages!

  6. Allow incoming HTTP traffic on port 80

    sudo firewall-cmd --permanent --add-port=80/tcp

    This opens port 80 for HTTP traffic so web browsers can reach our Apache server.

  7. Reload firewall configuration to apply changes

    sudo firewall-cmd --reload

    HTTP port 80 is now accessible.

  8. Allow Apache to connect to network resources

    sudo setsebool -P httpd_can_network_connect 1

    This configures SELinux to allow Apache to make outbound network connections. Without this, Apache couldn't connect to the MySQL database on VM-01. The -P makes this permanent across reboots.

  9. Exit SSH session from VM-02

    exit

    Your prompt changes back to ol-node-01. VM-02 now has a complete LAMP stack - Linux, Apache, MySQL client libraries, and PHP. We're ready to test!


Apache/PHP Setup Complete! VM-02 is now configured as a web server with Apache, PHP 8.2, firewall rules, and SELinux permissions ready for the LAMP stack applications.


Step 7: Test Apache

  1. Display the IP address of VM-02.

    echo "VM-02 (Web): $VM02_IP"

    Copy this IP address - you'll need it for the SSH tunnel command.

  2. Open a NEW terminal window from the Luna Desktop.

    • Click the Applications menu (bottom-left corner)
    • Select Terminal Emulator
    • A new terminal window will open

    Important: This creates a third terminal window:

    • Terminal #1 - Paused Ansible playbook from Step 1 ⚠️ Still open, don't touch
    • Terminal #2 - Your main working terminal (where you just started step 2)
    • Terminal #3 - SSH tunnel (new terminal you just opened)
  3. In this NEW terminal (Terminal #3), create an SSH tunnel to access VM-02's web server.

    ssh -L 8081:<YOUR VM_02 IP>:80 opc@<YOUR ol-node-01 PUBLIC_IP>

    Example:

    ssh -L 8081:192.168.122...:80

    opc@132.145.198
    https://luna.oracle.com/api/v1/labs/80324f86-de4d-4cf5-b35b-9de5e8bdc30c/gitlab/javascript:void(0)
    ....

    Replace both placeholders:

    • <YOUR VM_02 IP> - The IP you just displayed in step 4 (like 192.168.122.xxx)
    • <YOUR ol-node-01 PUBLIC_IP> - The public IP from Step 1 (like 132.145.198.xxx)

    What this does: The tunnel forwards traffic from your Luna Desktop port 8081 to VM-02's port 80. When you browse to localhost:8081, traffic flows through the tunnel to VM-02's web server.

    ⚠️ Critical: This terminal must stay open while you browse! Don't close it or the tunnel will disconnect.

  4. Open Firefox inside Luna Desktop and test Apache.

    • Click Applications menu
    • Select Web Browser (Firefox)
    • Navigate to: http://localhost:8081

    You should see the Oracle Linux Apache test page (purple/white page). This confirms Apache is running and the tunnel works!

    Important: Leave the SSH tunnel (Terminal #3) and Firefox open for later use throughout Steps 8-12.


Apache Test Complete! Your web server is accessible through the SSH tunnel. You now have three terminal windows open and Firefox ready for testing web applications.


Step 8: Create Test Application

  1. Return to your main working terminal (Terminal #2) on ol-node-01.

    Make sure you're working in Terminal #2, not the tunnel terminal (Terminal #3).

  2. Retrieve and save VM-01's IP address.

    VM01_IP=$(sudo virsh net-dhcp-leases --network default | grep vm-01 | tail -1 | awk '{print $5}' | cut -d'/' -f1)

    This refreshes the VM-01 IP variable in case it was lost. No output means success.

  3. Retrieve and save VM-02's IP address.

    VM02_IP=$(sudo virsh net-dhcp-leases --network default | grep vm-02 | tail -1 | awk '{print $5}' | cut -d'/' -f1)

    Both VM IPs are now saved and ready to use.

  4. From ol-node-01, establish an SSH connection to VM-02.

    ssh opc@$VM02_IP

    Your prompt changes to vm-02 - you're now on the web server.

  5. Create a PHP info page to display PHP configuration

    sudo tee /var/www/html/info.php > /dev/null << 'EOF'
    <?php phpinfo(); ?>
    EOF

    This creates a simple page that displays all PHP configuration details - useful for verifying PHP is working correctly.

  6. Create database connection test application

    sudo tee /var/www/html/dbtest.php > /dev/null << 'EOF'
    <?php
    echo "<h1>Multi-VM LAMP Stack Test</h1>";
    
    // Database connection details
    define('DB_SERVER', '$VM01_IP');
    define('DB_USERNAME', 'admin');
    define('DB_PASSWORD', 'Welcome#123');
    define('DB_NAME', 'mysql');
    
    echo "<p>Testing connection to MySQL at: " . DB_SERVER . "</p>";
    
    // Test network connectivity
    $fp = @fsockopen(DB_SERVER, 3306, $errno, $errstr, 5);
    if (!$fp) {
        echo "<p style='color: red;'>ERROR: Cannot reach MySQL server</p>";
        echo "<p>Error: $errstr ($errno)</p>";
    } else {
        echo "<p style='color: green;'>✓ Network connection successful</p>";
        fclose($fp);
        
        // Test MySQL connection
        $link = mysqli_connect(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
        if($link === false){
            echo "<p style='color: red;'>ERROR: Could not connect to MySQL</p>";
            echo "<p>Error: " . mysqli_connect_error() . "</p>";
        } else {
            echo "<p style='color: green;'>✓ Successfully Connected to MySQL!</p>";
            echo "<p>MySQL Version: " . mysqli_get_server_info($link) . "</p>";
            echo "<p>Host Info: " . mysqli_get_host_info($link) . "</p>";
            mysqli_close($link);
        }
    }
    ?>
    EOF

    This PHP script tests both network connectivity and MySQL authentication between VM-02 and VM-01. Notice the placeholder $VM01_IP - we'll replace that next.

  7. Set proper file permissions for Apache.

    sudo chown apache:apache /var/www/html/*.php

    Apache needs to own these files to read and execute them.

  8. Edit dbtest.php to replace the placeholder with VM-01's actual IP address.

    sudo vi /var/www/html/dbtest.php

    In the vi editor:

    1. Type /DB_SERVER and press Enter (searches for the line)
    2. Press i to enter insert mode
    3. Navigate to $VM01_IP and replace it with your actual VM-01 IP (like 192.168.122.100)
    4. Press Esc
    5. Type :wq and press Enter (saves and quits)

    Before: define('DB_SERVER', '$VM01_IP');

    After: define('DB_SERVER', '192.168.122.xxx');

  9. Exit VM-02

    exit

    Your prompt changes back to ol-node-01. The test applications are now created and ready to access!


Test Applications Created! You now have two PHP applications on VM-02:

  • info.php - Displays PHP configuration
  • dbtest.php - Tests database connectivity to VM-01

Step 9: Access Your Webserver and Database Test application

  1. Verify your SSH tunnel from Step 7 is still open.

    Check Terminal #3 - it should still be connected and showing the ol-node-01 prompt. If you accidentally closed it, recreate the tunnel:

    ssh -L 8081:$VM02_IP:80 opc@<YOUR ol-node-01 PUBLIC_IP>
  2. Test PHP configuration - In Firefox, navigate to:

     http://localhost:8081/info.php

    You should see a detailed PHP configuration page with purple headers showing "PHP Version 8.2.x". This confirms PHP is installed and working correctly. Scroll down to see all loaded modules and settings.

  3. Test database connectivity - In Firefox, navigate to:

    http://localhost:8081/dbtest.php

    Expected Results:

    You should see:

    • Heading: "Multi-VM LAMP Stack Test"
    • Green checkmark: "✓ Network connection successful"
    • Green checkmark: "✓ Successfully Connected to MySQL!"
    • MySQL version information
    • Host info showing the connection to VM-01

    If you see all green messages, congratulations! Your two VMs are successfully communicating. The web tier on VM-02 is connecting to the database tier on VM-01. This proves your multi-VM architecture is working!


Database Connectivity Verified! Your LAMP stack is fully functional with VM-02 (web server) successfully connecting to VM-01 (database server) across the virtual network.


Step 10: Create and Load Employee Database

  1. Verify you're in your main working terminal (Terminal #2) on ol-node-01.

    Make sure you're not in the tunnel terminal (Terminal #3) or inside a VM.

  2. Retrieve and save VM-01's IP address.

    VM01_IP=$(sudo virsh net-dhcp-leases --network default | grep vm-01 | tail -1 | awk '{print $5}' | cut -d'/' -f1)

    Refreshing the variable. No output means success.

  3. Retrieve and save VM-02's IP address.

    VM02_IP=$(sudo virsh net-dhcp-leases --network default | grep vm-02 | tail -1 | awk '{print $5}' | cut -d'/' -f1)

    Both IPs are now saved.

  4. From ol-node-01, establish an SSH connection to VM-01.

    ssh opc@$VM01_IP

    Your prompt changes to vm-01 - you're now on the database server where we'll load the employee data.

  5. Change to the /tmp directory.

    cd /tmp

    We'll download the database files here temporarily.

  6. Download MySQL employee sample database

    sudo curl -L -o employees_db_full.zip "https://objectstorage.us-ashburn-1.oraclecloud.com/p/5UYZYk1vh241OqeHp_J0xnzBpzUOxZtTgaqCH16OP7HpOjC71W207gAY9EY1rW2U/n/idazzjlcjqzj/b/mysql-ee-downloads/o/employees_db_full.zip"

    This downloads Oracle's official MySQL test dataset with realistic employee data. You'll see a progress bar.

  7. Install the unzip utility.

    sudo dnf install -y unzip

    Quick install to extract the database archive.

  8. Extract the database files.

    sudo unzip employees_db_full.zip

    You'll see several SQL files being extracted.

  9. Change to the extracted database directory.

    cd employees_db_full

    This directory contains all the SQL scripts to create and populate the employee database.

  10. Load the employee database into MySQL.

    mysql -u admin -pWelcome#123 < employee.sql

    This creates tables and inserts over 300,000 employee records. You'll see table creation messages scrolling by. This is the real data our web application will display!

  11. Verify the database loaded successfully.

    mysql -u admin -pWelcome#123 -e "USE employee; SHOW TABLES; SELECT COUNT(*) FROM employee;"

    Expected Output:

    You should see a list of tables including:

    • employee
    • department
    • salary
    • title

    And the employee count should show: 300,024 records

  12. Exit database VM

    exit

    Your prompt changes back to ol-node-01. VM-01 now has a production-ready employee database with 300,000+ records!


Employee Database Loaded! VM-01 now contains:

  • 300,024 employee records
  • 9 departments
  • 400,000+ salary records
  • Complete organizational data ready for the web application

Step 11: Create Employee Database Web Application

  1. From ol-node-01, establish an SSH connection to VM-02.

    ssh opc@$VM02_IP

    Your prompt changes to vm-02 - you're now on the web server where we'll create the employee application.

  2. Create a professional employee database application with modern styling.

    sudo tee /var/www/html/employee.php > /dev/null << 'EOF'
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Employee Database - Multi-VM LAMP Demo</title>
        <style>
            body { 
                font-family: Arial, sans-serif; 
                max-width: 1200px; 
                margin: 0 auto; 
                padding: 20px;
                background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
            }
            .header { 
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white; 
                text-align: center; 
                padding: 30px;
                border-radius: 15px;
                margin-bottom: 20px;
                box-shadow: 0 8px 16px rgba(0,0,0,0.1);
            }
            .info-box { 
                background: rgba(255,255,255,0.9);
                padding: 25px; 
                border-radius: 12px;
                box-shadow: 0 4px 8px rgba(0,0,0,0.1);
                margin-bottom: 20px;
                backdrop-filter: blur(10px);
            }
            .success { color: #28a745; font-weight: bold; }
            .error { color: #dc3545; font-weight: bold; }
            table { 
                width: 100%; 
                border-collapse: collapse; 
                margin: 20px 0;
                background: rgba(255,255,255,0.9);
                border-radius: 12px;
                overflow: hidden;
                box-shadow: 0 4px 8px rgba(0,0,0,0.1);
            }
            th, td { 
                padding: 15px; 
                text-align: left; 
                border-bottom: 1px solid #ddd;
            }
            th { 
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white;
                font-weight: bold;
            }
            tr:hover { background-color: rgba(102, 126, 234, 0.1); }
            .stats { 
                display: grid; 
                grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); 
                gap: 20px; 
                margin: 20px 0; 
            }
            .stat-card { 
                background: white; 
                padding: 20px; 
                border-radius: 10px; 
                text-align: center; 
                box-shadow: 0 4px 8px rgba(0,0,0,0.1);
            }
            .stat-number { font-size: 2em; font-weight: bold; color: #667eea; }
        </style>
    </head>
    <body>
        <div class="header">
            <h1>🏢 Employee Database Demo</h1>
            <p>MySQL Employee Sample Database on Multi-VM Architecture</p>
        </div>
    
        <?php
        // Database connection details
        define('DB_SERVER', '$VM01_IP');
        define('DB_USERNAME', 'empuser');
        define('DB_PASSWORD', 'Welcome#123');
        define('DB_NAME', 'employee');
    
        try {
            // Connect to MySQL database on separate VM
            $pdo = new PDO("mysql:host=" . DB_SERVER . ";dbname=" . DB_NAME, DB_USERNAME, DB_PASSWORD);
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            
            echo '<div class="info-box success">';
            echo '<h2>✅ Connected to Employee Database</h2>';
            echo '<p><strong>Database Server:</strong> ' . DB_SERVER . ' (vm-01)</p>';
            echo '<p><strong>Web Server:</strong> ' . gethostname() . ' (vm-02)</p>';
            echo '</div>';
            
            // Get database statistics
            $stats = [];
            
            $stmt = $pdo->query("SELECT COUNT(*) as count FROM employee");
            $stats['employee'] = $stmt->fetch()['count'];
            
            $stmt = $pdo->query("SELECT COUNT(*) as count FROM department");
            $stats['departments'] = $stmt->fetch()['count'];
            
            $stmt = $pdo->query("SELECT COUNT(*) as count FROM salary");
            $stats['salaries'] = $stmt->fetch()['count'];
            
            $stmt = $pdo->query("SELECT COUNT(*) as count FROM title");
            $stats['titles'] = $stmt->fetch()['count'];
            
            echo '<div class="info-box">';
            echo '<h2>📊 Database Statistics</h2>';
            echo '<div class="stats">';
            echo '<div class="stat-card"><div class="stat-number">' . number_format($stats['employee']) . '</div><div>Employee</div></div>';
            echo '<div class="stat-card"><div class="stat-number">' . number_format($stats['departments']) . '</div><div>Departments</div></div>';
            echo '<div class="stat-card"><div class="stat-number">' . number_format($stats['salaries']) . '</div><div>Salary Records</div></div>';
            echo '<div class="stat-card"><div class="stat-number">' . number_format($stats['titles']) . '</div><div>Job Titles</div></div>';
            echo '</div>';
            echo '</div>';
            
            // Show recent employee
            echo '<div class="info-box">';
            echo '<h2>👥 Sample Employee Data</h2>';
            $stmt = $pdo->query("SELECT emp_no, first_name, last_name, gender, hire_date FROM employee ORDER BY hire_date DESC LIMIT 20");
            $employee = $stmt->fetchAll();
            
            echo '<table>';
            echo '<thead><tr><th>Employee #</th><th>First Name</th><th>Last Name</th><th>Gender</th><th>Hire Date</th></tr></thead>';
            echo '<tbody>';
            
            foreach ($employee as $emp) {
                echo '<tr>';
                echo '<td>' . htmlspecialchars($emp['emp_no']) . '</td>';
                echo '<td>' . htmlspecialchars($emp['first_name']) . '</td>';
                echo '<td>' . htmlspecialchars($emp['last_name']) . '</td>';
                echo '<td>' . htmlspecialchars($emp['gender']) . '</td>';
                echo '<td>' . htmlspecialchars($emp['hire_date']) . '</td>';
                echo '</tr>';
            }
            echo '</tbody></table>';
            echo '</div>';
            
            // Show departments
            echo '<div class="info-box">';
            echo '<h2>🏬 Departments</h2>';
            $stmt = $pdo->query("SELECT dept_no, dept_name FROM department ORDER BY dept_name");
            $departments = $stmt->fetchAll();
            
            echo '<table>';
            echo '<thead><tr><th>Department Code</th><th>Department Name</th></tr></thead>';
            echo '<tbody>';
            
            foreach ($departments as $dept) {
                echo '<tr>';
                echo '<td>' . htmlspecialchars($dept['dept_no']) . '</td>';
                echo '<td>' . htmlspecialchars($dept['dept_name']) . '</td>';
                echo '</tr>';
            }
            echo '</tbody></table>';
            echo '</div>';
            
        } catch (PDOException $e) {
            echo '<div class="info-box error">';
            echo '<h2>❌ Database Connection Error</h2>';
            echo '<p>Error: ' . htmlspecialchars($e->getMessage()) . '</p>';
            echo '</div>';
        }
        ?>
    
        <div class="info-box">
            <h2>🏗️ Multi-VM Architecture</h2>
            <p><strong>Database VM (vm-01):</strong> MySQL Server with Employee Database</p>
            <p><strong>Web VM (vm-02):</strong> Apache + PHP Web Application</p>
            <p><strong>Data Source:</strong> MySQL Sample Employee Database</p>
            <p><strong>Records:</strong> 300,000+ employee, 400,000+ salary records</p>
        </div>
    </body>
    </html>
    EOF

    This creates a production-ready web application with:

    • Modern gradient design and responsive layout
    • Database connection verification
    • Real-time statistics from 300,000+ employee records
    • Sample employee data display (20 most recent hires)
    • Complete department listing
  3. Set proper file permissions for Apache.

    sudo chown apache:apache /var/www/html/employee.php

    Apache needs to own this file to read and execute it.

  4. Edit employee.php to replace the placeholder with VM-01's actual IP address.

    sudo vi /var/www/html/employee.php

    In the vi editor:

    1. Type /DB_SERVER and press Enter (searches for the line)
    2. Press i to enter insert mode
    3. Navigate to $VM01_IP and replace it with your actual VM-01 IP (like 192.168.122.100)
    4. Press Esc
    5. Type :wq and press Enter (saves and quits)

    Before: define('DB_SERVER', '$VM01_IP');

    After: define('DB_SERVER', '192.168.122.xxx');

  5. Exit VM-02 back to the host.

    exit

    Your prompt changes back to ol-node-01. The professional employee application is now ready to display 300,000+ employee records!


Employee Application Created! A production-ready web application is now deployed on VM-02, ready to display comprehensive employee data from VM-01's MySQL database with modern styling and real-time statistics.


Step 12: Access Employee Application

  1. Verify your SSH tunnel from Step 7 is still open.

    Check Terminal #3 - it should still be connected and showing the ol-node-01 prompt. If you accidentally closed it, recreate the tunnel

    ssh -L 8081:$VM02_IP:80 opc@<YOUR ol-node-01 PUBLIC_IP>

    Example:

    ssh -L 8081:192.168.122...:80

    opc@132.145.198
    https://luna.oracle.com/api/v1/labs/80324f86-de4d-4cf5-b35b-9de5e8bdc30c/gitlab/javascript:void(0)
    ....

  2. In Firefox, navigate to the employee application:

    http://localhost:8081/employee.php

    What You'll See:

    • Purple gradient header: "Employee Database Demo"
    • Green success message: "Connected to Employee Database"
    • Database Statistics cards: Showing 300,024 employees, 9 departments, 400,000+ salary records
    • Sample Employee Data table: 20 most recently hired employees with names, employee numbers, and hire dates
    • Departments table: All 9 company departments listed
    • Architecture summary: Confirming the multi-VM setup

    If you see all this beautiful data displayed with modern styling - congratulations! You've successfully built a complete multi-tier LAMP stack application running across two virtual machines!


Lab Complete! Congratulations!

You have successfully:

  • Deployed Oracle Linux KVM infrastructure in the cloud
  • Created and configured two virtual machines from scratch
  • Built a complete multi-tier LAMP stack (Linux, Apache, MySQL, PHP)
  • Separated database and web tiers for security and scalability
  • Loaded a production-ready database with 300,000+ employee records
  • Deployed a professional web application with modern UI
  • Verified full connectivity across your multi-VM architecture

Your Architecture:

  • VM-01: MySQL 8.4 database server with employee database
  • VM-02: Apache + PHP 8.2 web application server
  • Networking: Virtual bridge connecting both VMs
  • Access: Secure SSH tunnel for web application access

What's Next? This KVM foundation you've built is exactly what Oracle Linux Virtualization Manager (OLVM) manages at enterprise scale. OLVM adds centralized management, high availability, live migration, and automated failover on top of the KVM infrastructure you mastered today.


Essential Management Commands

VM Management

  1. List running VMs and their status

    sudo virsh list
  2. Start VM-01

    sudo virsh start vm-01
  3. Shutdown VM-01

    sudo virsh shutdown vm-01
  4. Display DHCP leases for VMs on the default network

    sudo virsh net-dhcp-leases --network default

Setup For running this lab on Oracle Cloud Infrastructure

  1. Virtual Cloud Network Setup

    Navigate: Networking → Virtual Cloud Networks → Start VCN Wizard
    - Name: ol-vcn-01
    - Create VCN
    Navigate: Networking → Virtual Cloud Networks → kvm-network → Security Lists
    Add these ingress rules:
    - SSH: Source 0.0.0.0/0, TCP Port 22
    - HTTP: Source 0.0.0.0/0, TCP Port 80
  2. Compute Instance Setup:

        Navigation: Hamburger Menu  Compute  Instances  Create Instance
    
        Configuration:
        - Name: ol-node-01
        - Image: Oracle Linux 8 (Latest)
        - Shape: VM.Standard.E4.Flex (4 OCPUs, 16GB RAM)
        - Boot Volume: 100 GB
        - VCN: ol-vcn-01 
        - Subnet: Public subnet ol-vcn-01
        - Assign Public IP: Yes
        - Add SSH Key: Upload your public key
  3. Test Compute public IP with SSH

    ssh opc@<ol-vcn-01_PUBLIC_IP>
  4. Continue to Step 2: Validate Environment Supports Virtualization


Next Steps

Learn to manage hosts and vms with Oracle Linux Virtual Manager (OLVM)

SSR