Skip to content

Infrastructure as Code (IaC)#

Original Author Provodin Grigory
Last checked by
Status Done
Last Updated 19.06.2025

Requirements#

General#

  1. Install Terraform: Link

  2. Install Python, if not installed: Link

  3. Create CSC account, if not created and remember the password: Link

  4. Create the cPouta account, if not created: Link

    Note: Follow only the first five steps, stopping before the SSH Key Pairs section

  5. It is recommended to follow this guide after you have already tried deploying cPouta VMs manually

  6. Read an intro to IaC: Link

CSC#

  • Floating IPs: Your environment should not have any floating IPs assigned to machines or allocated at all.

    The Overview section on the cPouta dashboard should show zero IPs allocated:

  • Volumes: You should have at least 2 free volume spaces available. Verify in the Overview section of your dashboard:

  • Virtual Resources: You need sufficient virtual resources to accommodate two VMs with standard.medium flavor.

    Required total: 6 VCPUs and 7.8 GB RAM. Verify in the Overview section of your dashboard:

Prerequisites#

Commands vs Script#

For almost every action in this guide, you have two options:

  1. Run individual commands - Choose this option if you want to learn more about each step.

  2. Use the provided script - Choose this for a faster and easier deployment.

Note: The script is written in Bash and works well with Linux and Mac but not with Windows unless you have Windows Subsystem for Linux (WSL) installed.

To make the script executable, run:

chmod +x infra.sh

Cloning the Repository and Installing Dependencies#

  1. Clone the repository and navigate to the project directory (from this point on all of the commands in this guide will be executed from the root directory e.g. microk8s-automation):

    git clone https://gitlab.labranet.jamk.fi/presta-shop-development-release-x/microk8s-automation.git
    cd microk8s-automation
    
  2. Create a Python virtual environment:

    python3 -m venv .env
    source .env/bin/activate
    
  3. Install dependencies:

    pip3 install -r requirements.txt
    

    Note: When you're done using this virtual environment, deactivate it with the command deactivate.

    For instructions for Windows and more information, refer to this Python venv guide.

Setting Up cPouta Access#

  1. Open the cPouta dashboard.

  2. Navigate to API Access in the dashboard.

    API Access Menu

  3. Click on the Download OpenStack RC File dropdown menu, then select the OpenStack RC File button.

    Important: Keep your RC file secure as it contains credentials for your project. Download RC File

  4. Source the RC file. You'll be prompted for the password you obtained during CSC account registration.

    source project_xxxxxxx-openrc.sh
    

    Note: Authentication will only work in the terminal where you sourced the file. If you want to use it in a new terminal session, you will need to source it again.

  5. Test your connection by running OpenStack commands:

    You can run this command:

    openstack router list
    

    * When successfully connected:

      ```
      (.env) ➜  openstack router list
      +--------------------------------------+------------------------+--------+-------+----------------------------------+
      | ID                                   | Name                   | Status | State | Project                          |
      +--------------------------------------+------------------------+--------+-------+----------------------------------+
      | 4ba41af4-a69d-440e-90f7-543688c8633a | project_xxxxxxx-router | ACTIVE | UP    | 4d8ca737497f4a50bc7098b1df465dbf |
      +--------------------------------------+------------------------+--------+-------+----------------------------------+
      ```
    

    * If you're not connected, you'll see:

      ```
      (.env)   openstack router list
      Missing value auth-url required for auth plugin password
      Could not clean up: 'ClientManager' object has no attribute 'session'
      ```
    

Importing the Rocky Linux Image#

This guide requires a virtual machine image that is not available by default on CSC. Choose one of the following methods to import it:

Option 1: Manual#

Follow this guide until the section Installing PrestaShop on Rocky Linux 9.

Important: Use the image name Rocky-9.5 instead of Rocky Linux 9 as this naming convention is used in the Terraform and Heat configurations.

Option 2: Manual (CLI)#

  1. Download the image from the website and save it in the rocky-image directory:

    wget https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2 -P rocky-image
    
  2. Import the image to CSC:

    openstack image create --progress --disk-format qcow2 --file rocky-image/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2 \
    --private Rocky-9.5
    

Option 3: Script#

Run the script from the project's root directory:

bash infra.sh image

Generating SSH Key Pairs#

You need to generate two key pairs for accessing your virtual machines.

Note: By default, SSH private keys are typically given permissions of 400 (read-only by owner). In this guide, private keys are given read+write permissions (600) to allow overwriting the same file for multiple deployments. If you plan on doing only one deployment, you can change the permissions to 400.

The keys will be stored in the keys/ directory of this project rather than the standard /home/user/.ssh location for simplicity.

Option 1: Manual (CLI)#

  1. Create keys directory:

    mkdir keys
    
  2. Generate two key pairs without passphrases:

    ssh-keygen -t rsa -f keys/ubuntu_key -N "" -C "Ubuntu VM Key"
    ssh-keygen -t rsa -f keys/rocky_key -N "" -C "Rocky VM Key"    
    
  3. Set appropriate permissions for private keys:

    chmod 600 keys/ubuntu_key
    chmod 600 keys/rocky_key
    
  4. Set appropriate permissions for public keys:

    chmod 644 keys/ubuntu_key.pub
    chmod 644 keys/rocky_key.pub
    
  5. Set appropriate permissions for the directory storing keys:

    chmod 700 keys
    

Option 2: Script#

Run the script from the project's root directory:

bash infra.sh keygen

Customizing Key Locations (Optional)#

If you want to store your keys in a different location or use different names, you need to update the following files:

  1. Ansible configuration (Private keys) - Edit ansible/inventory.yml lines 5 and 11:

    ansible_ssh_private_key_file: ./keys/ubuntu_key
    ansible_ssh_private_key_file: ./keys/rocky_key
    

  2. Heat configuration (Public keys) - Edit heat/main.yaml lines 99 and 105:

    public_key: { get_file: ../keys/ubuntu_key.pub }
    public_key: { get_file: ../keys/rocky_key.pub }
    

  3. Terraform configuration (Public keys) - Edit terraform/main.tf lines 41 and 46:

    public_key = file("../keys/ubuntu_key.pub")
    public_key = file("../keys/rocky_key.pub")
    

Retrieving Network IDs#

You need to retrieve your cPouta project's public and private network IDs as they will be used by both Terraform and Heat to configure network resources.

Option 1: Manual (CLI)#

  1. Get the public network ID:

    openstack network list --external --format value --column ID
    
  2. Get the private network ID:

    openstack network list --internal --format value --column ID
    

Option 2: Script#

Run the script from the project's root directory:

bash infra.sh heat --network

Updating Terraform and Heat Variable Files#

Update the following files with the network IDs you retrieved:

Note: Terraform uses only the private ID, while Heat requires both private and public.

  1. Terraform - Edit terraform/default-vars.tfvars:

    private_network_id = "3c608e83-0ffc-44bc-834c-1586a6a5eb41"  # Replace with your private network ID
    
  2. Heat - Edit heat/default-vars.yaml:

    private_network_id: "3c608e83-0ffc-44bc-834c-1586a6a5eb41"  # Replace with your private network ID
    public_network_id: "98bec751-702a-48d1-bf8e-a839185e4b0e"    # Replace with your public network ID
    

How to navigate this guide?#

Once you've completed the prerequisites, you have flexibility in how you follow this guide:

  • Infrastructure Only: You can deploy just the infrastructure using either Terraform or Heat without running Ansible. This creates the virtual machines and network resources.
  • Complete Deployment: Deploy the infrastructure using either Terraform or Heat, then install PrestaShop with Ansible. For more practice, you can try deploying with one method (e.g., Terraform), destroy the infrastructure, then redeploy using the alternative method (e.g., Heat) then deploy PrestaShop with Ansible.

Important: Regardless of which deployment method you choose, ensure that your infrastructure is properly set up before running the Ansible playbook for PrestaShop installation.

Terraform#

Terraform is one of the most popular IaC tools on the market, adopted by many organizations for infrastructure deployment. Its features include:

  • Cloud Compatibility: Works with all major cloud platforms including AWS, Azure, GCP, and others.
  • CI/CD Integration: Easily integrates with continuous integration and delivery pipelines like GitLab CI/CD.
  • Configuration Language: Uses HashiCorp Configuration Language (HCL), which has a flexible syntax. Modern IDEs like VS Code provide intellisense and code completion for HCL, offering a better configuration experience compared to many other IaC tools.
  • Declarative Approach: You specify the desired end state, and Terraform determines how to achieve it.

Understanding Terraform State and File Structure#

Terraform maintains a record of your managed infrastructure and configurations in state files. These files are crucial for Terraform's operation:

  • State files track what infrastructure exists and its current configuration.
  • State files are generated with the first apply in the project and updated during operations like apply and destroy.
  • Do not modify or delete these files while having your infrastructure controlled by Terraform as you will lose control.

Terraform file structure (after running terraform apply):

terraform/
├── .terraform # Directory containing plugins and modules (e.g. openstack module)
│   └── ---stripped---
├── .terraform.lock.hcl # Dependency lock file
├── default-vars.tfvars # Variables (default values, can be also declared in variables.tf)
├── main.tf # Main file with all resource declaration (e.g VMs and security groups)
├── output.tf # Output that will be shown after deployment (e.g floating ips)
├── provider.tf # Declaration of provider (e.g. openstack)
├── terraform.tfstate # Current state file
├── terraform.tfstate.backup # Backup state file
└── variables.tf # Declaration of variables (name and type)

Deploying Infrastructure with Terraform#

To deploy your infrastructure with Terraform, do:

Option 1: Manual (CLI)#

terraform -chdir=terraform/ init
terraform -chdir=terraform/ plan -var-file="default-vars.tfvars" -out=infra  
terraform -chdir=terraform/ apply infra  
  • The init command initializes the Terraform working directory and downloads providers (OpenStack in case of this configuration)
  • The plan command creates an execution plan
  • The apply command executes the plan file to provision the infrastructure

Option 2: Script#

Run the script from the project's root directory:

bash infra.sh terraform --deploy

If the Terraform deployment is successful, you'll receive an output with two floating IPs for your Ubuntu and Rocky VMs. Save these IPs as you'll need them later for Ansible configuration.

Outputs:

rocky_floating_ip = "86.50.229.170"
ubuntu_floating_ip = "86.50.168.197"

Deploy PrestaShop (Optional)#

At this point, you can deploy PrestaShop with Ansible.

Updating Infrastructure with Terraform (Optional)#

You can modify your existing infrastructure by updating the Terraform configuration files.

Note: It is not recommended to use machines with standard.small flavor as they may not handle Ansible, MicroK8s, and PrestaShop.

To update your infrastructure:

  1. Edit the terraform/default-vars.tfvars file to make your desired changes.

    For example, to add new security group ports and change instance flavors:

    Before:

    ubuntu_security_group_ports = [22, 80, 443]
    rocky_security_group_ports  = [22, 80, 443]
    ubuntu_instance             = "standard.medium"
    rocky_instance              = "standard.medium"
    

    After:

    ubuntu_security_group_ports = [22, 80, 443, 111]
    rocky_security_group_ports  = [22, 80, 443, 111]
    ubuntu_instance             = "standard.medium"
    rocky_instance              = "standard.small"
    
  2. Apply your changes using the same commands as deployment:

    Option 1: Manual (CLI)

    terraform -chdir=terraform/ plan -var-file="default-vars.tfvars" -out=infra
    terraform -chdir=terraform/ apply infra
    

    Option 2: Script

    Run the script from the project's root directory:

    bash infra.sh terraform --deploy
    

Destroying Infrastructure with Terraform#

When you no longer need your infrastructure, you can destroy all resources created by Terraform:

Option 1: Manual (CLI)#

terraform -chdir=terraform/ destroy -var-file="default-vars.tfvars"

Option 2: Script#

Run the script from the project's root directory:

bash infra.sh terraform --destroy

You will be prompted to confirm the destruction. Type "yes" if you want to proceed.

Warning: This will permanently delete all resources managed by Terraform in this project.

Heat#

Heat is a native OpenStack IaC tool, the platform that CSC cloud is built on. Its features include:

  • Template-based: Uses YAML-based HOT (Heat Orchestration Templates) to define infrastructure resources.
  • OpenStack Integration: As a native OpenStack service, it provides more control over the OpenStack resources than other IaC tools.
  • Stack Management: Organizes resources into "stacks" that can be created, updated, and deleted as a single unit. The stack is server-side, meaning all orchestration logic runs on the OpenStack platform rather than your local machine. Moreover, the stack can be managed via a dashboard.
  • Declarative Approach: You specify the desired end state, and Heat orchestration engine determines how to achieve it.

Deploying Infrastructure with Heat#

Option 1: Manual (CLI)#

To deploy your infrastructure using Heat:

  1. First, validate your template to ensure it's correctly formatted:

    openstack orchestration template validate -f json -t heat/main.yaml -e heat/default-vars.yaml
    
  2. Create the stack using your template and environment file:

    openstack stack create -t heat/main.yaml -e heat/default-vars.yaml vm-stack
    
  3. Wait for the stack to be deployed (this typically takes a minute or two).

  4. Verify that your stack has been successfully created:

    openstack stack show vm-stack -f yaml -c "stack_status"
    

    A status of CREATE_COMPLETE indicates that your stack was deployed successfully.

  5. Retrieve the floating IPs assigned to your virtual machines:

    openstack stack output show vm-stack ubuntu_floating_ip -f value -c output_value
    
    openstack stack output show vm-stack rocky_floating_ip -f value -c output_value
    

Option 2: Script#

Run the script from the project's root directory:

bash infra.sh heat --deploy

Heat Dashboard#

When you deploy with Heat you get to use the dashboard for managing your deployment.

  1. On the cPouta dashboard, go to Orchestration --> Stacks. You will see a vm-stack, click on it.

  2. On this dashboard you will see some generic information about your deployment. The network topology, stack parameters, the resources that were created and their states, the template that was used to create this stack. If the stack creation fails, you will see an error message describing what went wrong. Finally, you can delete the stack from this dashboard if you wish.

Deploy PrestaShop (Optional)#

At this point, you can deploy PrestaShop with Ansible.

Updating Infrastructure with Heat (Optional)#

You can modify your existing infrastructure by updating the Heat configuration files.

Note: It is not recommended to use machines with standard.small flavor as they may not handle Ansible, MicroK8s, and PrestaShop.

  1. Edit the heat/default-vars.yaml file to make your desired changes.

    For example, to add new security group ports and change instance flavors:

    Before:

    parameter_defaults:
        ubuntu_security_group_ports: "22,80,443"
        rocky_security_group_ports: "22,80,443"
        ubuntu_instance: standard.medium
        rocky_instance: standard.medium
    

    After:

    parameter_defaults:
        ubuntu_security_group_ports: "22,80,443,111"
        rocky_security_group_ports: "22,80,443,111"
        ubuntu_instance: standard.small
        rocky_instance: standard.small
    
  2. Apply the changes to your existing stack:

    #### Option 1: Manual (CLI)

    openstack stack update -t heat/main.yaml -e heat/default-vars.yaml vm-stack
    

    #### Option 2: Script

    Run the script from the project's root directory:

    bash infra.sh heat --update
    
  3. Monitor the update progress:

    openstack stack show vm-stack -f yaml -c "stack_status"
    

    A status of UPDATE_COMPLETE indicates that your stack has been successfully updated.

Destroying Infrastructure with Heat#

When you no longer need your infrastructure, you can delete the entire stack:

Option 1: Manual (CLI)#

openstack stack delete vm-stack

Option 2: Script#

Run the script from the project's root directory:

bash infra.sh heat --destroy

This will remove all resources that were created as part of the stack. You can confirm that the stack has been deleted by running:

openstack stack list

Your vm-stack should no longer appear in the list once the deletion is complete.

Warning: Deleting a stack removes all its resources permanently.

Ansible#

Ansible is a popular IaC tool used for cloud and server configurations. It automates many of the manual tasks, think of it as a fancier way of running scripts on a server. If you have done Data Networks course, think that most of the stuff you did there could be automated with Ansible. Its features include:

  • Agentless Architecture: Requires no software installation on managed nodes (devices being managed by Ansible e.g. VMs), using only SSH connections and Python to deploy configurations.
  • Idempotent Operations: When configured correctly, Ansible ensures that applying the same configuration multiple times produces consistent results without side effects.
  • Parallel Execution: Can run the same or different configurations on many servers simultaneously, making large-scale deployments efficient.
  • YAML-Based Playbooks: Uses human-readable YAML syntax to define automation tasks.

Customizing PrestaShop Admin Settings (Optional)#

By default, this PrestaShop deployment uses generic admin credentials and folder name. You can customize these settings by modifying the Ansible playbook at ansible/microk8s-deployment.yml.

To set your own admin folder name, email, and password, add these three parameters to the helm command:

    --set prestashop.env.psFolderAdmin="<your-folder>" \
    --set prestashop.env.adminMail="<student@jamk.fi>" \
    --set prestashop.env.adminPasswd="<your-password>"

Example Modification:

Before:

    - name: Deploy PrestaShop using helm chart
      ansible.builtin.shell: |
        microk8s helm install presta-helm prestashop \
          --set prestashop.env.psDomain="{{ domain_result.stdout }}" \
          --set prestashop.ingress.enabled=true \
          --set prestashop.ingress.letsencrypt.isProd=true \
          --set common.persistence.enabled=true

After:

    - name: Deploy PrestaShop using helm chart
      ansible.builtin.shell: |
        microk8s helm install presta-helm prestashop \
          --set prestashop.env.psDomain="{{ domain_result.stdout }}" \
          --set prestashop.ingress.enabled=true \
          --set prestashop.ingress.letsencrypt.isProd=true \
          --set common.persistence.enabled=true \
          --set prestashop.env.psFolderAdmin="adminfolder" \
          --set prestashop.env.adminMail="student@jamk.fi" \
          --set prestashop.env.adminPasswd="superstrongpassword***"

Deploy PrestaShop#

If you want to learn more about how PrestaShop was deployed on the MicroK8s, look at this guide:

  1. Setting up an inventory file

    Before running the playbook, you will need to modify the inventory file located at ansible/inventory.yml by adding floating IPs you retrieved from either Heat or Terraform deployment.

    Example Modification:

    Before:

    dev:
    hosts: 
        ubuntu:
        ansible_host: 
        ansible_ssh_private_key_file: ./keys/ubuntu_key
        ansible_user: ubuntu
        ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o ConnectionAttempts=20 -o ConnectTimeout=60'
    
        rocky: 
        ansible_host: 
        ansible_ssh_private_key_file: ./keys/rocky_key
        ansible_user: rocky
        ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o ConnectionAttempts=20 -o ConnectTimeout=60'
    

    After:

    dev:
    hosts: 
        ubuntu:
        ansible_host: 89.96.183.55
        ansible_ssh_private_key_file: ./keys/ubuntu_key
        ansible_user: ubuntu
        ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o ConnectionAttempts=20 -o ConnectTimeout=60'
    
        rocky: 
        ansible_host: 250.102.192.186
        ansible_ssh_private_key_file: ./keys/rocky_key
        ansible_user: rocky
        ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o ConnectionAttempts=20 -o ConnectTimeout=60'
    
  2. Executing the playbook:

    Option 1: Manual (CLI)

    ansible-playbook -i ansible/inventory.yml ansible/microk8s-deployment.yml
    

    Option 2: Script

    Run the script from the project's root directory:

    bash infra.sh ansible --deploy
    

    Note: The deployment process can take several minutes to complete.

Accessing the PrestaShop Website#

After the deployment completes successfully, you can access the PrestaShop website using domains provided in the Ansible playbook output.

Default credentials:

  • Link to the website: https://fip-AAA-BBB-CCC-DDD.kaj.poutavm.fi/
  • Link to the admin dashboard: https://fip-AAA-BBB-CCC-DDD.kaj.poutavm.fi/admin228/
  • Admin email: testuser@mail.com
  • Admin password: ReAlPaSsWoRd759***

Accessing Your Virtual Machines#

You can SSH into your virtual machines to check the status of the deployment:

SSH to Ubuntu VM#

ssh -i keys/ubuntu_key ubuntu@86.50.168.197  # Replace with your Ubuntu VM's floating IP

SSH to Rocky VM#

ssh -i keys/rocky_key rocky@86.50.229.170  # Replace with your Rocky VM's floating IP

Resolving SSH Host Key Issues#

If you've deployed multiple times with the same floating IP, you might encounter a "REMOTE HOST IDENTIFICATION HAS CHANGED" error:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

To resolve this, run the command suggested in the error message:

ssh-keygen -f '/home/user/.ssh/known_hosts' -R '86.50.229.170'  # Replace with the IP address and path in your error message

Checking PrestaShop Deployment Status#

Once connected to either VM, you can check the status of the PrestaShop deployment:

microk8s kubectl logs deployments/presta-helm-prestashop

The PrestaShop application is being deployed from scratch, so it will take some time to become accessible. When the deployment is complete, you should see logs indicating that the application is ready.

Cleaning Up Resources#

When you're done with your infrastructure, delete all of your resources:

Terraform#

Destroying Infrastructure with Terraform

Heat#

Destroying Infrastructure with Heat

Warning: These commands will permanently delete all resources created in your project.