How to setup a media server for Prestashop on Google Cloud Platform

Prestashop is one of the most popular platform for e-commerce, it’s open-source and has more then 300,000 online stores according their website. www.prestashop.com.

This platform has a very useful feature called “media server” which allows you to configure another domain or subdomain to deliver the static content of the website and this should improve the online store performance as: (1) avoids unnecessary cookie network traffic; (2) allows static content to be cached by a CDN (Content Delivery Network).

In this blog post I will show how use Google Global Load Balancer and Google Cloud CDN as a media server for Prestashop to boost your online store speed.

Architecture

GCP Diagram

Components:

  • Prestashop site
  • Google Global Load Balancer, with Cloud CDN enabled
  • Backend VM (nginx)

When you setup a media server, Prestashop will update all the static content urls to use the media server domain. For example all the images will have the url like http://cdn.example.com/img/test_image.png instead of http://www.example.com/img/test_image.png. The domain cdn.example.com will point to the our GCP Load Balancer with Cloud CDN enabled.

Next the load balancer will proxy the requests to the Backend VM and the Backend VM will proxy the request back to the original Prestashop site.

By routing the traffic through the GCP Load Balancer will allow the Cloud CDN to cache the static content at the edges. After the static content has been cached it will be delivered from the cache for all the subsequent requests. You can find out more about how the Cloud CDN works from the official documentation Google Cloud CDN.

A response is cached by the Cloud CDN only if ALL of the following are true:

  • It was served by a backend service or backend bucket with Cloud CDN enabled.
  • It is a response to a GET request.
  • Its status code is 200, 203, 206, 300, 301, 302, 307, or 410.
  • It has a Cache-Control: public header.
  • It has a Cache-Control: s-maxage, Cache-Control: max-age, or Expires header.
  • It has a Content-Length, Content-Range, or Transfer-Encoding header.
  • Its size is less than or equal to the maximum size.

Setup

Prerequisites:

  • GCP project with billing enabled. If you don’t have one then sign-in to Google Cloud Platform Console and create a new project
  • Access to a standard internet browser

Let’s start!

First sign-in to your GCP Console and activate the Cloud Shell.

Setup the environment variables:

INSTANCE_NAME=ps-media
ZONE=europe-west3-c
PRESTASHOP_WEBSITE=http://www.example.com
MEDIA_SERVER_NAME=cdn.example.com

Create the backend virtual machine that will proxy our the CDN request back to the original site.

gcloud compute instances create-with-container $INSTANCE_NAME-vm \
    --container-image gcr.io/cloud-marketplace/google/nginx1:1.15 \
    --machine-type f1-micro \
    --tags $INSTANCE_NAME-http \
    --zone $ZONE

Nginx 1.15 is used here as the reverse proxy for this setup.

Because this VM has only one purpose, and that is to proxy all the requests back to the current Prestashop site, we will use the “Containers on VM” feature on GCP. This option allows you to deploy a docker container on a virtual machine without having to worry about the os, runtime, docker, etc. You can find more info about how to deploy container on virtual machines from the official documentation. Deploying containers on VMs

Add the cloud-init script that creates the nginx configuration file (nginx.conf).

# download vm-cloud-init.yaml
curl https://gist.githubusercontent.com/gabihodoroaga/02b74add2b72d1942df13ad8ddd529ac/raw \
    --output vm-cloud-init.yaml
# replace the variables names in vm-cloud-init.yaml
sed -i 's/PRESTASHOP_WEBSITE/'"$PRESTASHOP_WEBSITE"'/g' vm-cloud-init.yaml
sed -i 's/MEDIA_SERVER_NAME/'"$MEDIA_SERVER_NAME"'/g' vm-cloud-init.yaml
# add the file to vm metadata
gcloud compute instances add-metadata $INSTANCE_NAME-vm \
    --metadata-from-file user-data=vm-cloud-init.yaml \
    --zone $ZONE

Update the VM to map the nginx.conf file from the host to the container.

gcloud compute instances update-container $INSTANCE_NAME-vm \
    --container-mount-host-path host-path=/var/nginx.conf,mount-path=/etc/nginx/nginx.conf,mode=ro \
    --zone $ZONE

Add the firewall rule to allow the load balancer to access the vm.

gcloud compute firewall-rules create $INSTANCE_NAME-allow-health-check \
    --allow tcp:80 \
    --source-ranges 130.211.0.0/22,35.191.0.0/16 \
    --target-tags $INSTANCE_NAME-http

Create an unmanaged instance group

gcloud compute instance-groups unmanaged create $INSTANCE_NAME-instance-group \
    --zone $ZONE

Add the compute instance to this unmanaged instance group

gcloud compute instance-groups unmanaged add-instances $INSTANCE_NAME-instance-group \
      --instances $INSTANCE_NAME-vm \
      --zone $ZONE

Create a health check for the backend vm

gcloud compute health-checks create http $INSTANCE_NAME-health-check \
  --request-path / \
  --port 80 \
  --check-interval 60 \
  --unhealthy-threshold 3 \
  --healthy-threshold 2 \
  --timeout 5 \
  --host $MEDIA_SERVER_NAME

Heath checks are used by the load balancer to determine if a backend service is alive.

Create a backend service

gcloud compute backend-services create $INSTANCE_NAME-lb-backend \
  --http-health-checks $INSTANCE_NAME-health-check \
  --port-name http \
  --global \
  --enable-cdn \
  --connection-draining-timeout 300

Add the instance group to this backend service

gcloud compute backend-services add-backend $INSTANCE_NAME-lb-backend \
  --instance-group $INSTANCE_NAME-instance-group \
  --instance-group-zone $ZONE \
  --balancing-mode UTILIZATION \
  --max-rate-per-instance 100 \
  --capacity-scaler 1.0 \
  --global

Create the url map

gcloud compute url-maps create $INSTANCE_NAME-url-map \
  --default-service $INSTANCE_NAME-lb-backend

Create the http proxy

gcloud compute target-http-proxies create $INSTANCE_NAME-http-proxy \
  --url-map $INSTANCE_NAME-url-map

Create the global forwarding rule, a.k.a. frontend

gcloud compute forwarding-rules create $INSTANCE_NAME-forwarding-rule \
  --global \
  --ports 80 \
  --target-http-proxy $INSTANCE_NAME-http-proxy

If you want to learn more about the Google Load Balancer check the official documentation Setting Up HTTP(S) Load Balancing

And this is it. Our load balancer is fully functional is is ready to receive traffic.
To check if the load balancer and proxy works as expected run the following commands

# get the public ip address
IP_ADDRESS=$(gcloud compute forwarding-rules describe $INSTANCE_NAME-forwarding-rule --global --format="value(IPAddress)")
# print the public ip address
echo $IP_ADDRESS
# make a request to the service
curl -s -I http://$IP_ADDRESS/

and the output should be similar to this

HTTP/1.1 200 OK
Server: nginx/1.15.12
.....

Next you need to configure Prestashop to use the new media server.

Login to your Prestashop Admin page and navigate to

ADVANCED PARAMETERS -> PERFORMANCE -> MEDIA SERVERS

add there the ip address or subdomain name of the load balancer.

PS Media Servers

You have now all your static content served from the media server. Open up your browser developer tools and check the urls of your images, javascript and css files.

Testing

You need to do the following checks in order to make sure that all static content is actually cached:

  • verify the cache hit ratio Network Service -> Cloud CDN;
  • verify how much of your traffic is hitting the backend service.Network Service -> Load balancing -> Backends;
  • consult the logs. All request served by the load balancer are logged and the jsonPayload.statusDetails should be either “response_sent_by_backend” or “response_from_cache”;

If your content is not served from the cache then try to troubleshoot by following the next instructions.

In order for the Cloud CDN to cache the content header Cache-Control: public must be present and by default Apache and Prestashop does not set this header.

Add the following settings to your Prestashop Apache configuration file .htaccess.

<IfModule mod_headers.c>
    <filesMatch ".(css|jpg|jpeg|png|gif|js|ico|svg|woff|woff2|eot|ttf|otf)$">
        Header add Cache-Control "public"
    </filesMatch>
</IfModule>

If you receive errors in the browser related to CORS the add this settings also to your .htaccess file

<FilesMatch "\.(ttf|otf|eot|woff|woff2)$">
  <IfModule mod_headers.c>
    Header set Access-Control-Allow-Origin "*"
  </IfModule>
</FilesMatch>

Cached content can be also invalidated. Check out the documentation Cache Invalidation Overview.

Clean-up

To remove all the resources you can either delete the project or download and run the clean-up script.

# download the file
curl https://gist.githubusercontent.com/gabihodoroaga/8b3461de6d989069d17ae29c7f01da1d/raw \
    --output cleanup.sh
# adjust the values for INSTANCE_NAME and ZONE variables
# make the script executable
chmod +x cleanup.sh
# run the script
./cleanup.sh

Conclusion

Using the Google Cloud CDN you can boost your online shop performance as:

  • releases your server from the overhead of serving static content
  • reduce the network traffic sent to your server
  • high speed delivery of static content using the google network.

In my next blog post I plan to write about how to create a scalable Prestashop deployment on GCP that can handle millions of users.