Based in Milan, Italy, Diary of a Digital Engineer is a blog by Stefano Sandrini. Here I post about my journey as a software engineer and solution architect, new ideas, thoughts and quotes by influential architects, engineers, and digital innovators.

Deploy Parse-Server and Parse-Dashboard on GoogleCloudPlatform with Container Engine

Hi all,
in this post I'll show you my journey to deploy parse-server and the great parse-dashboard web app on Google Cloud Platform.
As you probably already know, the Parse service was switched off on January 2017. A great open source version came out in 2016, named "Parse Server". You can find info, docs and links to several GitHub repos at http://parseplatform.org/.

Ok, Parse Server is the open source version of the Parse backend, but what about Parse Dashboard? 
Parse Dashboard is a standalone dashboard for managing Parse apps that are hosted with your own version of Parse Server. 

Both projects are based on Node.js, also Parse Server needs MongoDB as database. 
My idea is to host my Parse app on Google Cloud Platform via Container Engine, so I'll be able to manage scalability, automatic deployment and orchestration with Kubernetes.

Prerequisites

To follow me through all the steps you'll need:

Also, I assume you are familiar with basic Docker and Kubernetes concepts, Git and GitHub, Node.js and you have a basic knowledge of the Parse platform.

Getting Started

First thing to do is to get parse-server and parse-dashboard source code from the Parse Community GitHub repos. You can find those repositories at https://github.com/parse-community. You can checkout, download or fork those repos.  
I put the source code in two separated folders: "parse-server" and "parse-dashboard" so I'll refer to them from now on.

Let's move to the parse-server directory. We need to create two folders, under the root project folder "parse-server": one is named "config", the other one named "cloud".

The cloud folder is the folder that will host our own Cloud Functions, a great feature of parse that enables the run of custom code on the cloud based on webhooks, triggers, etc. You can find more info at the official doc page. We'll be able to add cloud functions from the dashboard at the end of our installation process, but so far the only thing we need is to create an empty file named main.js inside the cloud folder.

Now that we took care of the cloud folder, let's move to the config folder and create a new JSON file with name "config.json". This file will contain all the configurations required to run our parse-server instance. Here's the JSON:

{
    "databaseURI": "mongodb://mongo:27017/my-db",
    "appId": "parse_app_id_starter",
    "masterKey": "1234567890",
    "serverURL": "http://localhost:1337/parse",
    "cloud": "./cloud/main.js",
    "mountPath": "/parse",
    "port": 1337
}

As you can see, we define several properties:

  1. databaseURI : parse-server use MongoDB to persist the data so the databaseURI is the connection to our mongoDB. We are using the default port where MongoDB is running (27017), and we set the host as "mongo". As i’ve mentioned above, we'll use Kubernetes (also written as k8s) to orchestrate and manage our containers. As we will see later on, k8s will take care of resolving "mongo" host.
  2. appId : is the application ID managed by parse-server.
  3. masterKey : the masterKey is a typical API-Key that allows you to run api calls as an admin.
  4. serverURL : this entry defines the endpoint for the parse-server api. You can run api calls in front of this endpoint. Also, the dashboard will interact with parse-server via this endpoint.
  5. cloud : this parameter contains the path to the source code of cloud functions.
  6. mountPath : mount path for parse-server.
  7. port :  contains the port where parse-server runs on. The default value for parse-server is 1337, and I decided to let it as it is.

Containerize parse-server

Now we can create a Docker image that we will use on Google Container Engine. 
Let's create a new file, under parse-server folder, named Dockerfile.

FROM node:boron

RUN mkdir -p /parse-server
COPY ./ /parse-server/

RUN mkdir -p /parse-server/config
VOLUME /parse-server/config

RUN mkdir -p /parse-server/cloud
VOLUME /parse-server/cloud

WORKDIR /parse-server

RUN npm install && \
npm run build

ENV PORT=1337

EXPOSE $PORT

ENTRYPOINT ["npm", "start", "--"]

The file is pretty straightforward:

  • we start from the node:boron image 
  • we copy the whole parse-server folder
  • we mount the config and the cloud 
  • we run npm-install to install all the dependencies defined in the package.json file 
  • we run npm-build, defined in package.json
  • we define the parse server port as ENV variable and we expose it so it will be accessible

Now you can build the image: open the terminal, navigate to the parse-server folder and type:

docker build -t <my-parse-server-image-name> .

please note that you should substitute <my-parse-server-image-name> with the name you want for your image. I choose "ss-parse-app-plain", since i usually use "ss" as namespace for all my projects.

Once the image build process is completed you can run 

docker images

to check the image metadata. Image will be probably tagged as "latest".

Containerize parse-dashboard

Now we can create a Docker image for the dashboard, but before you need to edit the file parse-dashboard-config.json that you'll find under the ParseDashboard folder.
This file contains configurations for the Dashboard. We'll overwrite this config trough k8s ConfigMap later, but let's edit it so our image has a default config.

{
"apps": [
{
"serverURL": "http://ss-parse-app-service/parse",
"appId": "parse_app_id_starter",
"masterKey": "1234567890",
"appName": "Parse Start app",
"iconName": ""
}
],
"users": [
 {
 "user":"myUser",
 "pass":"myPassword",
 "apps": [{"appId": "parse_app_id_starter"}]
 } ],
"iconsFolder": "icons"
}

 

I have defined the endpoint for the parse-server, so our dashboard can run API calls in front of it. Also I set appId and masterKey, so the Dashboard can use those parameters to run APIs. In the "users" section, I defined an array of users that are enabled to access specific app. 

As you can see, the dashboard can manage more than one parse-server app. In my case, I have only one app to manage, but you can modify your settings accordingly to your needs.

Now, let's create the docker image.
Navigate to the parse-dashboard folder and create a new Dockerfile.

FROM node:boron

RUN mkdir -p /parse-dashboard
COPY ./ /parse-dashboard/

RUN mkdir -p /parse-dashboard/Parse-Dashboard
VOLUME /parse-dashboard/Parse-Dashboard

WORKDIR /parse-dashboard

RUN npm install && \
npm run build

ENV PORT=4040

EXPOSE $PORT

ENV PARSE_DASHBOARD_ALLOW_INSECURE_HTTP=true

RUN ["echo", "$PARSE_DASHBOARD_ALLOW_INSECURE_HTTP"]

ENTRYPOINT ["npm", "run", "start", "--allowInsecureHTTP"]

Please take a look at the PARSE_DASHBOARD_ALLOW_INSECURE_HTTP variable. Basically, we are telling the dashboard to run in http. The default behaviour is to run only in HTTPS, but I don't want to deal with HTTPS at the moment, since I'm not running a production app.

Ok, let's build the image with the same command we launched for the parse-server image:

docker build -t <my-parse-dashboard-image-name> .

Create a cluster on GCP

Now let's move on and create our cluster on GCP. As mentioned before, you should have installed the GCP sdk (follow the link at the beginning of this post).

gcloud container clusters create ss-parse-server --num-nodes=3

I created my cluster with name "ss-parse-server", following my personal convention of using the "ss" namespace for my projects. Change it with the name for your cluster. Also, i set number of nodes in my cluster as 3.
After cluster creation you can check your cluster properties with

gcloud container clusters list

Now, we need to tag our images and push them to the Google Container Registry so they will be available.

docker tag ss-parse-dashboard-app-plain gcr.io/${PROJECT_ID}/ss-parse-dashboard-app-plain:v1.0

docker tag ss-parse-app-plain gcr.io/${PROJECT_ID}/ss-parse-app-plain:v1.0

$PROJECT_ID is a variable that contains your gcloud project (see more here).

Now, let's push images to the container registry:

gcloud docker push gcr.io/YOUR_PROJECT_ID/ss-parse-dashboard-app-plain:v1.0
gcloud docker push gcr.io/YOUR_PROJECT_ID/ss-parse-app-plain:v1.0

 

Deploy and manage with Kubernetes

We are now ready to start deploying our container-based Parse app. First, let's start with MongoDB and let's create the deployment and the service description files.

mongo-deployment.yml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: mongo-deployment
spec:
replicas: 1
template:
metadata:
labels:
name: mongo
app: mongo
tier: backend
role: master
spec:
containers:
- name: mongo
image: mongo:latest
ports:
- name: mongo
containerPort: 27017
volumeMounts:
- name: mongo-persistent-storage
mountPath: /data/db
volumes:
- name: mongo-persistent-storage
gcePersistentDisk:
pdName: ss-parse-app-db-disk
fsType: ext4

mongo-service.yml

apiVersion: v1
kind: Service
metadata:
name: mongo
labels:
app: mongo
tier: backend
role: master

spec:
ports:
- port: 27017
targetPort: 27017
selector:
name: mongo
app: mongo
tier: backend
role: master

The mongo-deployment mounts a volume on the container, named "mongo-persistent-storage". The mount path is /data/db. This volume will be used as data storage for the database, so we need to use a persistent storage since we don't want to lose our data if the container is switched off. That's why we need to create another component before creating the deployment and the service for MongoDB: the PersistenDisk

PersistentDisk

We can create the peristentDisk with this command:

gcloud compute disks create ss-parse-app-db-disk --zone europe-west1-b --size=10GB

The name for the persistent disk is "ss-parse-app-db-disk". As usual, change it accordingly with your name policy. Also I specified the zone params, but you can use the default by omitting the parameter. The persistent disk name is referenced by the mondo-deployment to select the persistent disk to mount on the container.

Create mongo deployment and service

We can now create our mongo deployment and the mongo service.

kubectl create -f mongo-deployment.yml

You can check the status for pods created by the deployment with the command

kubectl get pods
NAME                                      READY     STATUS    RESTARTS   AGE
mongo-deployment-67899791-c3bdp           1/1       Running   0          31sec

Then, create the service

kubectl create -f mongo-deployment.yml

and you can check the status with 

kubectl get svc

 

ConfigMap

ConfigMap can be mounted as volume and let us to define configuration params and other stuff. We need to create 2 maps, one for the server and one for the dashboard. We will use those configMaps to launch containers with a configuration that is defined by the configMap instead of the configuration installed in the docker image. By proceeding in this way, we can change and overwrite the config without changing the docker image for parse-server or parse-dashboard. You can create the config map for parse-server with this command:

kubectl create configmap ss-parse-app-config --from-file=PATH_TO_PARSE_config.json

The config map for parse server can be the same as the docker image:

{
	"databaseURI": "mongodb://mongo:27017/my-db",
	"appId": "parse_app_id_starter",
	"masterKey": "1234567890",
	"serverURL": "http://localhost:1337/parse",
	"cloud": "./cloud/main.js",
	"mountPath": "/parse",
	"port": 1337
}

 

Now, we can create:

  • deployment for the parse server
  • service for the parse server
  • deployment for the parse dashboard
  • service for the parse dashboard

parse-server-deployment.yml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: ss-parse-app
spec:
replicas: 1 
template:
metadata:
labels:
name: ss-parse-app
app: ss-parse-app
tier: backend
role: application-server
spec:
volumes: 
- name: ss-parse-app-config
configMap:
name: ss-parse-app-config 
containers:
- image: gcr.io/YOUR_PROJECT_ID/ss-parse-app-plain:v1.0.1 
imagePullPolicy: Always 
name: ss-parse-app
command: ["npm","start"]
volumeMounts: 
- name: ss-parse-app-config
mountPath: "/etc/config"
readOnly: true
ports:
- containerPort: 1337 
readinessProbe:# important for load balancer helath check 
httpGet:
path: /parse/health
port: 1337
initialDelaySeconds: 5
timeoutSeconds: 1

parse-server-service.deployment

apiVersion: v1
kind: Service
metadata:
name: ss-parse-app-service
spec:
ports:
- port: 80
targetPort: 1337 
protocol: TCP
name: ss-parse-app-service
type: LoadBalancer 
selector:
app: ss-parse-app

Let's create deployment and service:

kubectl create -f parse-server-deployment.yml
kubectl create -f parse-server-service.yml

Before creating deployment and service for the parse-dashboard, we need to create another config map for the dashboard, with this JSON:

{
"apps": [
	{
	"serverURL": "http://PARSE_SERVER_IP/parse",
	"appId": "parse_app_id_starter",
	"masterKey": "1234567890",
	"appName": "Parse Start app",
	"iconName": ""
	}
],
"users": [
 {
 "user":"myUser",
 "pass":"myPassword",
 "apps": [{"appId": "parse_app_id_starter"}]
 } ],
"iconsFolder": "icons"
}

IMPORTANT: if you look carefully, I made a slight change on the config map. The serverURL is set with parse service public IP. You could thing... why !? Kubernetes will resolve the parse-server service name with its internal DNS, so you don't need to set the IP. Correct... BUT... Parse Dashboard calls the parse-server URL directly from the browser in a couple of pages. Since your browser will not be able to resolve the parse-server service name, those dashboard pages will not work. That's why I had to set the parse-server URL to the IP of the service.

To create the config map, use this command:

kubectl create configmap ss-parse-dashboard-app-config --from-file=PATH_TO_PARSE_DASHBOARD_config.json

Once we have created the configMap, we can proceed with deployment and service for the dashboard

parse-dashboard-deployment.yml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: ss-parse-dahsboard-app
spec:
replicas: 1
template:
metadata:
labels:
name: ss-parse-dahsboard-app
app: ss-parse-dahsboard-app
tier: frontend
role: application-server
spec:
volumes: 
- name: ss-parse-dashboard-app-config
configMap:
name: ss-parse-dashboard-app-config
containers:
- image: gcr.io/YOUR_PROJECT_ID/ss-parse-dashboard-app-plain:v1.0
imagePullPolicy: Always
name: ss-parse-dashboard-app
command: ["npm","start",--,--config] 
args: [/etc/config/ss-parse-dashboard-app-config.json]
volumeMounts: 
- name: ss-parse-dashboard-app-config
mountPath: "/etc/config"
readOnly: true
ports:
- containerPort: 4040 
readinessProbe:# important for load balancer health check
httpGet:
path: /apps
port: 4040
initialDelaySeconds: 15
timeoutSeconds: 1

parse-dashboard-service.yml

apiVersion: v1
kind: Service
metadata:
labels:
app: ss-parse-dashboard
name: ss-parse-dashboard
spec:
ports:
- port: 80
targetPort: 4040 
protocol: TCP
name: ss-parse-dashboard
type: LoadBalancer
selector:
app: ss-parse-dahsboard-app

Now, let's create deployment first and then the service.

Once you have created both, and checked the service is up and running you can access the parse-dashboard with the public IP assigned by GCE (you can retrieved it with kubectl get svc). 

You should see the login page, and you can login with the username and password you set in the configMap.

 

 Parse Dashboard login page

Parse Dashboard login page

Once logged in, you'll see a landing page with the list of all Parse Server apps managed by your dashboard (in my case, only one app).

parse-dashboard-app-list.jpg

By selecting the parse app, you enter into the data browser for that specific app. This is the place where you can do CRUD operation in objects, create new classes and so on.

parse-dashboard-browser.jpg

 

Also, you can invoke directly the parse server public API with a REST API client like Postman, or Paw, or via curl in the terminal.  For example: 

GET /parse/classes/_Product HTTP/1.1
X-Parse-Application-Id: parse_app_id_starter
content-type: application/json
Host: <YOUR_PARSE_SERVER_PUBLIC_IP>
Connection: close
User-Agent: Paw/3.1.2 (Macintosh; OS X/10.12.6) GCDHTTPRequest

and you'll receive a response like

{"results":[{"objectId":"CklKM74duU","order":1,"subtitle":"MySubtitle","downloadName":"MyDownloadProduct","title":"MyTitle","productIdentifier":"Product01","createdAt":"2017-08-22T13:39:15.293Z","updatedAt":"2017-08-22T13:41:13.650Z"}]}

 

Latest considerations

As you could see, we created a cluster of 3 nodes (which, by the way, is the default value) but we set a replica count of 1 for mongo, parse-server, parse-dashboard. Of course, the interesting thing should be to change the replica count and play with the "scale" factor. To do this, you can simply change the replica parameter and use the kubectl apply command to apply changes. You'll se the number of pods change accordingly.

Okay, I think that's all. I hope this is a useful basic guide on how to run parse server and parse dashboard on GCP via Container Engine.

Bye, see you next time!

Run shell commands on a EC2 from a Lambda function